"Add field" footer logic
- Instead of an explicit "+" button to add a new field (which
also allows adding multiple empty fields), only show a
"add field" footer to the KindSectionView if there are no
existing empty fields in the section.
- Make the whole footer a touch target and display a
custom message according to the DataKind mime type
Bug: 4363151
Change-Id: Ib7cef5ed5aaefa5b1269f95ed8fe89e7dd742f36
diff --git a/res/layout-xlarge/item_kind_section.xml b/res/layout-xlarge/item_kind_section.xml
index a4c874e..0a55282 100644
--- a/res/layout-xlarge/item_kind_section.xml
+++ b/res/layout-xlarge/item_kind_section.xml
@@ -20,31 +20,21 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="horizontal">
+ android:orientation="vertical">
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="15dip"
+ android:background="?android:attr/listDivider" />
<LinearLayout
android:id="@+id/kind_editors"
- android:layout_width="0dip"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_gravity="bottom"
android:orientation="vertical" />
- <FrameLayout
- android:id="@+id/kind_plus_container"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/editor_min_line_item_height">
- <ImageButton
- android:id="@+id/kind_plus"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:paddingLeft="@dimen/editor_round_button_padding_left"
- android:paddingRight="@dimen/editor_round_button_padding_right"
- android:paddingTop="@dimen/editor_round_button_padding_top"
- android:paddingBottom="@dimen/editor_round_button_padding_bottom"
- android:background="?android:attr/selectableItemBackground"
- android:src="@drawable/ic_menu_add_field_holo_light"
- android:contentDescription="@string/description_plus_button" />
- </FrameLayout>
+ <include
+ android:id="@+id/add_field_footer"
+ layout="@layout/edit_add_field" />
+
</com.android.contacts.editor.KindSectionView>
diff --git a/res/layout/edit_add_field.xml b/res/layout/edit_add_field.xml
new file mode 100644
index 0000000..68a482c
--- /dev/null
+++ b/res/layout/edit_add_field.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!-- Layout of "add field" row in contact editor -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="10dip"
+ android:background="?android:attr/selectableItemBackground">
+ <TextView
+ android:id="@+id/add_text"
+ android:layout_gravity="center_vertical"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="?android:attr/textColorTertiary" />
+ <ImageView
+ android:id="@+id/add_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_menu_add_field_holo_light"
+ android:paddingLeft="@dimen/editor_round_button_padding_left"
+ android:paddingRight="@dimen/editor_round_button_padding_right"
+ android:paddingTop="@dimen/editor_round_button_padding_top"
+ android:paddingBottom="@dimen/editor_round_button_padding_bottom"
+ android:contentDescription="@string/description_plus_button" />
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/item_kind_section.xml b/res/layout/item_kind_section.xml
index 2cfb2a5..ba14bb6 100644
--- a/res/layout/item_kind_section.xml
+++ b/res/layout/item_kind_section.xml
@@ -4,9 +4,9 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
-
+
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -29,56 +29,18 @@
android:background="?android:attr/listDivider" />
<LinearLayout
- android:id="@+id/kind_header"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_marginLeft="14dip"
- android:layout_marginTop="2dip"
- android:layout_marginBottom="2dip"
- android:layout_marginRight="?android:attr/scrollbarSize"
- android:orientation="horizontal"
- android:gravity="center_vertical"
- android:focusable="true"
- android:clickable="true"
- android:visibility="gone">
-
- <TextView
- android:id="@+id/kind_title"
- android:layout_width="0dip"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="@color/kind_title"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal" />
-
- <FrameLayout
- android:id="@+id/kind_plus_container"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/editor_min_line_item_height">
- <ImageButton
- android:id="@+id/kind_plus"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:duplicateParentState="true"
- android:background="?android:attr/selectableItemBackground"
- android:src="@drawable/ic_menu_add_field_holo_light"
- android:paddingLeft="@dimen/editor_round_button_padding_left"
- android:paddingRight="@dimen/editor_round_button_padding_right"
- android:paddingTop="@dimen/editor_round_button_padding_top"
- android:paddingBottom="@dimen/editor_round_button_padding_bottom"
- android:contentDescription="@string/description_plus_button" />
- </FrameLayout>
-
- </LinearLayout>
-
- <LinearLayout
android:id="@+id/kind_editors"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="6dip"
android:orientation="vertical" />
-</com.android.contacts.editor.KindSectionView>
+ <include
+ android:id="@+id/divider"
+ layout="@layout/edit_divider" />
+
+ <include
+ android:id="@+id/add_field_footer"
+ layout="@layout/edit_add_field" />
+
+</com.android.contacts.editor.KindSectionView>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c16a135..a57ac3a 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1319,6 +1319,33 @@
<!-- The add field button shown in the editor under each editable Raw Contact [CHAR LIMIT=30] -->
<string name="add_field">Add another field</string>
+ <!-- The editable field hint text to add a new phone number to a contact in the Raw Contact Editor [CHAR LIMIT=22] -->
+ <string name="add_phone">Add new phone number</string>
+
+ <!-- The editable field hint text to add a new email to a contact in the Raw Contact Editor [CHAR LIMIT=22] -->
+ <string name="add_email">Add new email</string>
+
+ <!-- The editable field hint text to add a new IM account to a contact in the Raw Contact Editor [CHAR LIMIT=22] -->
+ <string name="add_im">Add new IM account</string>
+
+ <!-- The editable field hint text to add a new postal address to a contact in the Raw Contact Editor [CHAR LIMIT=22] -->
+ <string name="add_address">Add new address</string>
+
+ <!-- The editable field hint text to add a new note to a contact in the Raw Contact Editor [CHAR LIMIT=22] -->
+ <string name="add_note">Add new note</string>
+
+ <!-- The editable field hint text to add a new website to a contact in the Raw Contact Editor [CHAR LIMIT=22] -->
+ <string name="add_website">Add new website</string>
+
+ <!-- The editable field hint text to add a new internet call method to a contact in the Raw Contact Editor [CHAR LIMIT=22] -->
+ <string name="add_internet_call">Add new internet call</string>
+
+ <!-- The editable field hint text to add a new event to a contact in the Raw Contact Editor [CHAR LIMIT=22] -->
+ <string name="add_event">Add new event</string>
+
+ <!-- The editable field hint text to add a relationship field to a contact in the Raw Contact Editor [CHAR LIMIT=22] -->
+ <string name="add_relationship">Add new relationship</string>
+
<!-- Attbution of a contact status update, when the time of update is unknown -->
<string name="contact_status_update_attribution">via <xliff:g id="source" example="Google Talk">%1$s</xliff:g></string>
diff --git a/src/com/android/contacts/editor/Editor.java b/src/com/android/contacts/editor/Editor.java
index d87aea4..ab13a4b 100644
--- a/src/com/android/contacts/editor/Editor.java
+++ b/src/com/android/contacts/editor/Editor.java
@@ -51,7 +51,13 @@
}
/**
- * Prepare this editor for the given {@link ValuesDelta}, which
+ * Returns whether or not there is at least one empty field (i.e. text
+ * fields) in this {@link Editor}.
+ */
+ public boolean hasEmptyField();
+
+ /**
+ * Prepares this editor for the given {@link ValuesDelta}, which
* builds any needed views. Any changes performed by the user will be
* written back to that same object.
*/
diff --git a/src/com/android/contacts/editor/EventFieldEditorView.java b/src/com/android/contacts/editor/EventFieldEditorView.java
index f757b54..a83a9c8 100644
--- a/src/com/android/contacts/editor/EventFieldEditorView.java
+++ b/src/com/android/contacts/editor/EventFieldEditorView.java
@@ -110,6 +110,10 @@
mDateView.setText(data);
}
+ public boolean hasEmptyField() {
+ return TextUtils.isEmpty(mDateView.getText());
+ }
+
@Override
public Dialog createDialog(Bundle bundle) {
if (bundle == null) throw new IllegalArgumentException("bundle must not be null");
diff --git a/src/com/android/contacts/editor/KindSectionView.java b/src/com/android/contacts/editor/KindSectionView.java
index ce5c96a..e24acff 100644
--- a/src/com/android/contacts/editor/KindSectionView.java
+++ b/src/com/android/contacts/editor/KindSectionView.java
@@ -24,17 +24,27 @@
import com.android.contacts.model.EntityModifier;
import android.content.Context;
-import android.os.Handler;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Relation;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
import android.provider.ContactsContract.Data;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageButton;
import android.widget.LinearLayout;
+import android.widget.TextView;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
/**
* Custom view for an entire section of data as segmented by
@@ -45,8 +55,8 @@
private static final String TAG = "KindSectionView";
private ViewGroup mEditors;
- private View mAddPlusButtonContainer;
- private ImageButton mAddPlusButton;
+ private View mAddFieldFooter;
+ private TextView mAddFieldText;
private String mTitleString;
private DataKind mKind;
@@ -57,6 +67,31 @@
private LayoutInflater mInflater;
+ /**
+ * Map of data MIME types to the "add field" footer text resource ID
+ * (that for example, maps to "Add new phone number").
+ */
+ private static final HashMap<String, Integer> sAddFieldFooterTextResourceIds =
+ new HashMap<String, Integer>();
+
+ static {
+ final HashMap<String, Integer> hashMap = sAddFieldFooterTextResourceIds;
+ hashMap.put(Phone.CONTENT_ITEM_TYPE, R.string.add_phone);
+ hashMap.put(Email.CONTENT_ITEM_TYPE, R.string.add_email);
+ hashMap.put(Im.CONTENT_ITEM_TYPE, R.string.add_im);
+ hashMap.put(StructuredPostal.CONTENT_ITEM_TYPE, R.string.add_address);
+ hashMap.put(Note.CONTENT_ITEM_TYPE, R.string.add_note);
+ hashMap.put(Website.CONTENT_ITEM_TYPE, R.string.add_website);
+ hashMap.put(SipAddress.CONTENT_ITEM_TYPE, R.string.add_internet_call);
+ hashMap.put(Event.CONTENT_ITEM_TYPE, R.string.add_event);
+ hashMap.put(Relation.CONTENT_ITEM_TYPE, R.string.add_relationship);
+ }
+
+ /**
+ * List of the empty editor views.
+ */
+ private List<View> mEmptyEditorViews = new ArrayList<View>();
+
public KindSectionView(Context context) {
this(context, null);
}
@@ -75,26 +110,13 @@
}
}
- if (mAddPlusButton != null) {
- mAddPlusButton.setEnabled(enabled && !mReadOnly);
+ if (enabled && !mReadOnly) {
+ mAddFieldFooter.setVisibility(View.VISIBLE);
+ } else {
+ mAddFieldFooter.setVisibility(View.GONE);
}
}
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
-
- if (mAddPlusButton == null || mEditors == null || mEditors.getChildCount() < 2) {
- return;
- }
-
- // Align the "+" button with the "-" button in the last editor
- View lastEditor = mEditors.getChildAt(mEditors.getChildCount() - 1);
- int top = lastEditor.getTop();
- mAddPlusButtonContainer.layout(mAddPlusButtonContainer.getLeft(), top,
- mAddPlusButtonContainer.getRight(), top + mAddPlusButtonContainer.getHeight());
- }
-
public boolean isReadOnly() {
return mReadOnly;
}
@@ -108,19 +130,14 @@
mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mEditors = (ViewGroup)findViewById(R.id.kind_editors);
-
- mAddPlusButtonContainer = findViewById(R.id.kind_plus_container);
- mAddPlusButton = (ImageButton) findViewById(R.id.kind_plus);
- mAddPlusButton.setOnClickListener(new OnClickListener() {
+ mAddFieldText = (TextView) findViewById(R.id.add_text);
+ mAddFieldFooter = findViewById(R.id.add_field_footer);
+ mAddFieldFooter.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- // defer action so that the pressed state of the button is visible shortly
- new Handler().post(new Runnable() {
- @Override
- public void run() {
- addItem();
- }
- });
+ // Setup click listener to add an empty field when the footer is clicked.
+ mAddFieldFooter.setVisibility(View.GONE);
+ addItem();
}
});
}
@@ -128,14 +145,17 @@
/** {@inheritDoc} */
@Override
public void onDeleted(Editor editor) {
- updateAddVisible();
- updateVisible();
+ updateAddFooterVisible();
+ updateSectionVisible();
}
/** {@inheritDoc} */
@Override
public void onRequest(int request) {
- // Ignore requests
+ // If a field has changed, then check if another row can be added dynamically.
+ if (request == FIELD_CHANGED) {
+ updateAddFooterVisible();
+ }
}
public void setState(DataKind kind, EntityDelta state, boolean readOnly, ViewIdGenerator vig) {
@@ -151,9 +171,17 @@
? ""
: getResources().getString(kind.titleRes);
+ // Set "add field" footer message according to MIME type. Some MIME types
+ // can only have max 1 field, so the map will return null if these sections
+ // should not have an "Add field" option.
+ Integer textResourceId = sAddFieldFooterTextResourceIds.get(kind.mimeType);
+ if (textResourceId != null) {
+ mAddFieldText.setText(textResourceId);
+ }
+
rebuildFromState();
- updateAddVisible();
- updateVisible();
+ updateAddFooterVisible();
+ updateSectionVisible();
}
public String getTitle() {
@@ -223,21 +251,58 @@
return true;
}
- private void updateVisible() {
+ private void updateSectionVisible() {
setVisibility(getEditorCount() != 0 ? VISIBLE : GONE);
}
-
- protected void updateAddVisible() {
- final boolean isVisible;
- if (!mKind.isList) {
- isVisible = false;
- } else {
- // Set enabled state on the "add" view
- final boolean canInsert = EntityModifier.canInsert(mState, mKind);
- isVisible = !mReadOnly && canInsert;
+ protected void updateAddFooterVisible() {
+ if (!mReadOnly && mKind.isList) {
+ // First determine whether there are any existing empty editors.
+ updateEmptyEditors();
+ // If there are no existing empty editors and it's possible to add
+ // another field, then make the "add footer" field visible.
+ if (!hasEmptyEditor() && EntityModifier.canInsert(mState, mKind)) {
+ mAddFieldFooter.setVisibility(View.VISIBLE);
+ return;
+ }
}
- mAddPlusButton.setVisibility(isVisible ? View.VISIBLE : View.INVISIBLE);
+ mAddFieldFooter.setVisibility(View.GONE);
+ }
+
+ /**
+ * Determines a list of {@link Editor} {@link View}s that have an empty
+ * field in them and removes extra ones, so there is max 1 empty
+ * {@link Editor} {@link View} at a time.
+ */
+ private void updateEmptyEditors() {
+ mEmptyEditorViews.clear();
+
+ // Construct a list of editors that have an empty field in them.
+ for (int i = 0; i < mEditors.getChildCount(); i++) {
+ View v = mEditors.getChildAt(i);
+ if (((Editor) v).hasEmptyField()) {
+ mEmptyEditorViews.add(v);
+ }
+ }
+
+ // If there is more than 1 empty editor, then remove it from the list of editors.
+ if (mEmptyEditorViews.size() > 1) {
+ for (View emptyEditorView : mEmptyEditorViews) {
+ // If no child {@link View}s are being focused on within
+ // this {@link View}, then remove this empty editor.
+ if (emptyEditorView.findFocus() == null) {
+ mEditors.removeView(emptyEditorView);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns true if one of the editors has an empty field, or false
+ * otherwise.
+ */
+ private boolean hasEmptyEditor() {
+ return mEmptyEditorViews.size() > 0;
}
public void addItem() {
@@ -269,12 +334,11 @@
}
});
- // For non-lists (e.g. Notes we can only have one field. in that case we need to disable
- // the add button
- updateAddVisible();
+ // Hide the "add field" footer because there is now a blank field.
+ mAddFieldFooter.setVisibility(View.GONE);
// Ensure we are visible
- updateVisible();
+ updateSectionVisible();
}
public int getEditorCount() {
diff --git a/src/com/android/contacts/editor/PhotoEditorView.java b/src/com/android/contacts/editor/PhotoEditorView.java
index 6942912..242e3b5 100644
--- a/src/com/android/contacts/editor/PhotoEditorView.java
+++ b/src/com/android/contacts/editor/PhotoEditorView.java
@@ -178,4 +178,8 @@
public void setDeletable(boolean deletable) {
// Photo is not deletable
}
+
+ public boolean hasEmptyField() {
+ return mHasSetPhoto;
+ }
}
diff --git a/src/com/android/contacts/editor/TextFieldsEditorView.java b/src/com/android/contacts/editor/TextFieldsEditorView.java
index a1b3476..39bfa80 100644
--- a/src/com/android/contacts/editor/TextFieldsEditorView.java
+++ b/src/com/android/contacts/editor/TextFieldsEditorView.java
@@ -31,6 +31,7 @@
import android.telephony.PhoneNumberFormattingTextWatcher;
import android.text.Editable;
import android.text.InputType;
+import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.Gravity;
@@ -233,6 +234,16 @@
mExpansionButton.setEnabled(!readOnly && isEnabled());
}
+ public boolean hasEmptyField() {
+ for (int i = 0; i < mFields.getChildCount(); i++) {
+ EditText editText = (EditText) mFields.getChildAt(i);
+ if (TextUtils.isEmpty(editText.getText())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Returns true if the editor is currently configured to show optional fields.
*/