Add a multi account KindSectionView (E4)
* This allows us to treat all kinds (i.e. mime types) the
same (except for name and photo).
* We can also set the icons and add empty editors naturally
(w/o checking if the kind section is the last on in the list)
* Finally, we can also expand to the full editor in place now
Bug 23589603
Change-Id: Ibdecd8a9ef33cd1db70ffd9fd17e7883555d1fa4
diff --git a/src/com/android/contacts/editor/CompactContactEditorFragment.java b/src/com/android/contacts/editor/CompactContactEditorFragment.java
index 287d814..df47fe4 100644
--- a/src/com/android/contacts/editor/CompactContactEditorFragment.java
+++ b/src/com/android/contacts/editor/CompactContactEditorFragment.java
@@ -159,7 +159,7 @@
final View view = inflater.inflate(
R.layout.compact_contact_editor_fragment, container, false);
- mContent = (LinearLayout) view.findViewById(R.id.editors);
+ mContent = (LinearLayout) view.findViewById(R.id.raw_contacts_editor_view);
return view;
}
@@ -350,30 +350,6 @@
}
@Override
- public void onExpandEditor() {
- // Determine if this is an insert (new contact) or edit
- final boolean isInsert = isInsert(getActivity().getIntent());
-
- if (isInsert) {
- // For inserts, prevent any changes from being saved when the base fragment is destroyed
- mStatus = Status.CLOSING;
- } else if (hasPendingRawContactChanges()) {
- // Save whatever is in the form
- save(SaveMode.CLOSE, /* backPressed =*/ false);
- }
-
- // Prepare an Intent to start the expanded editor
- final Intent intent = isInsert
- ? EditorIntents.createInsertContactIntent(mState, getDisplayName(),
- getPhoneticName(), mUpdatedPhotos, mNewLocalProfile)
- : EditorIntents.createEditContactIntent(mLookupUri, getMaterialPalette(),
- mPhotoId, mNameId);
- ImplicitIntentsUtil.startActivityInApp(getActivity(), intent);
-
- getActivity().finish();
- }
-
- @Override
public void onNameFieldChanged(long rawContactId, ValuesDelta valuesDelta) {
final Activity activity = getActivity();
if (activity == null || activity.isFinishing()) {
diff --git a/src/com/android/contacts/editor/CompactKindSectionView.java b/src/com/android/contacts/editor/CompactKindSectionView.java
new file mode 100644
index 0000000..827eb58
--- /dev/null
+++ b/src/com/android/contacts/editor/CompactKindSectionView.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+package com.android.contacts.editor;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import com.android.contacts.R;
+import com.android.contacts.common.model.RawContactDelta;
+import com.android.contacts.common.model.RawContactModifier;
+import com.android.contacts.common.model.ValuesDelta;
+import com.android.contacts.common.model.dataitem.DataKind;
+import com.android.contacts.editor.Editor.EditorListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Version of {@link KindSectionView} that supports multiple RawContactDeltas.
+ */
+public class CompactKindSectionView extends LinearLayout implements EditorListener {
+
+ private ViewGroup mEditors;
+ private ImageView mIcon;
+
+ private List<KindSectionData> mKindSectionDatas;
+ private boolean mReadOnly;
+ private boolean mShowOneEmptyEditor = false;
+ private boolean mHideIfEmpty = true;
+
+ private ViewIdGenerator mViewIdGenerator;
+ private LayoutInflater mInflater;
+
+ public CompactKindSectionView(Context context) {
+ this(context, /** attrs =*/ null);
+ }
+
+ public CompactKindSectionView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ if (mEditors != null) {
+ int childCount = mEditors.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ mEditors.getChildAt(i).setEnabled(enabled);
+ }
+ }
+ // TODO: why is this necessary?
+ updateEmptyEditors(/* shouldAnimate = */ true);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void onFinishInflate() {
+ setDrawingCacheEnabled(true);
+ setAlwaysDrawnWithCacheEnabled(true);
+
+ mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ mEditors = (ViewGroup) findViewById(R.id.kind_editors);
+ mIcon = (ImageView) findViewById(R.id.kind_icon);
+ }
+
+ @Override
+ public void onDeleteRequested(Editor editor) {
+ if (mShowOneEmptyEditor && getEditorCount() == 1) {
+ // If there is only 1 editor in the section, then don't allow the user to delete it.
+ // Just clear the fields in the editor.
+ editor.clearAllFields();
+ } else {
+ editor.deleteEditor();
+ }
+ }
+
+ @Override
+ public void onRequest(int request) {
+ // If a field has become empty or non-empty, then check if another row
+ // can be added dynamically.
+ if (request == FIELD_TURNED_EMPTY || request == FIELD_TURNED_NON_EMPTY) {
+ updateEmptyEditors(/* shouldAnimate = */ true);
+ }
+ }
+
+ /**
+ * @param showOneEmptyEditor If true, we will always show one empty, otherwise an empty editor
+ * will not be shown until the user enters a value.
+ */
+ public void setShowOneEmptyEditor(boolean showOneEmptyEditor) {
+ mShowOneEmptyEditor = showOneEmptyEditor;
+ }
+
+ /**
+ * @param hideWhenEmpty If true, the entire section will be hidden if all inputs are empty,
+ * otherwise one empty input will always be displayed.
+ */
+ public void setHideWhenEmpty(boolean hideWhenEmpty) {
+ mHideIfEmpty = hideWhenEmpty;
+ }
+
+ public void setState(List<KindSectionData> kindSectionDatas, boolean readOnly,
+ ViewIdGenerator viewIdGenerator) {
+ mKindSectionDatas = kindSectionDatas;
+ mReadOnly = readOnly;
+ mViewIdGenerator = viewIdGenerator;
+
+ // Set the icon using the first DataKind (all DataKinds should be the same type)
+ final DataKind dataKind = mKindSectionDatas.isEmpty()
+ ? null : mKindSectionDatas.get(0).getDataKind();
+ if (dataKind != null) {
+ mIcon.setContentDescription(dataKind.titleRes == -1 || dataKind.titleRes == 0
+ ? "" : getResources().getString(dataKind.titleRes));
+ mIcon.setImageDrawable(EditorUiUtils.getMimeTypeDrawable(getContext(),
+ dataKind.mimeType));
+ if (mIcon.getDrawable() == null) mIcon.setContentDescription(null);
+ }
+
+ rebuildFromState();
+ updateEmptyEditors(/* shouldAnimate = */ false);
+ }
+
+ /**
+ * Build editors for all current rows.
+ */
+ private void rebuildFromState() {
+ // Remove any existing editors
+ mEditors.removeAllViews();
+
+ // Check if we are displaying anything here
+ boolean hasValuesDeltas = false;
+ for (KindSectionData kindSectionData : mKindSectionDatas) {
+ if (kindSectionData.hasValuesDeltas()) {
+ hasValuesDeltas = true;
+ break;
+ }
+ }
+
+ if (hasValuesDeltas) {
+ for (KindSectionData kindSectionData : mKindSectionDatas) {
+ for (ValuesDelta valuesDelta : kindSectionData.getValuesDeltas()) {
+ // Skip entries that aren't visible
+ if (!valuesDelta.isVisible()) continue;
+ if (isUnchanged(kindSectionData.getDataKind(), valuesDelta)) continue;
+ createEditorView(kindSectionData.getRawContactDelta(),
+ kindSectionData.getDataKind(), valuesDelta);
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates an EditorView for the given values delta. This function must be used while
+ * constructing the views corresponding to the the object-model. The resulting EditorView is
+ * also added to the end of mEditors
+ */
+ private View createEditorView(RawContactDelta rawContactDelta, DataKind dataKind,
+ ValuesDelta entry) {
+ // Inflate the layout
+ final View view;
+ final int layoutResId = EditorUiUtils.getLayoutResourceId(dataKind.mimeType);
+ try {
+ view = mInflater.inflate(layoutResId, mEditors, false);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to allocate editor with layout resource ID " +
+ layoutResId + " for mime type " + dataKind.mimeType + ": " + e.toString());
+ }
+
+ // Hide the types drop downs until the associated edit field is focused
+ if (view instanceof LabeledEditorView) {
+ ((LabeledEditorView) view).setHideTypeInitially(true);
+ }
+
+ // Fix the start margin for phonetic name views
+ if (view instanceof PhoneticNameEditorView) {
+ final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ layoutParams.setMargins(0, 0, 0, 0);
+ view.setLayoutParams(layoutParams);
+ }
+
+ // Set whether the editor is enabled
+ view.setEnabled(isEnabled());
+
+ if (view instanceof Editor) {
+ Editor editor = (Editor) view;
+ editor.setDeletable(true);
+ editor.setValues(dataKind, entry, rawContactDelta, !dataKind.editable, mViewIdGenerator);
+ editor.setEditorListener(this);
+ }
+ mEditors.addView(view);
+ return view;
+ }
+
+ /**
+ * Whether the given values delta has no changes (i.e. it exists in the database but is empty).
+ */
+ private static boolean isUnchanged(DataKind dataKind, ValuesDelta item) {
+ if (!item.isNoop()) return false;
+ final int fieldCount = dataKind.fieldList == null ? 0 : dataKind.fieldList.size();
+ for (int i = 0; i < fieldCount; i++) {
+ final String column = dataKind.fieldList.get(i).column;
+ final String value = item.getAsString(column);
+ if (!TextUtils.isEmpty(value)) return false;
+ }
+ return true;
+ }
+
+ /**
+ * Updates the editors being displayed to the user removing extra empty
+ * {@link Editor}s, so there is only max 1 empty {@link Editor} view at a time.
+ * If there is only 1 empty editor and {@link #setHideWhenEmpty} was set to true,
+ * then the entire section is hidden.
+ */
+ public void updateEmptyEditors(boolean shouldAnimate) {
+ if (mKindSectionDatas.isEmpty()) return;
+ final DataKind dataKind = mKindSectionDatas.get(0).getDataKind();
+ final RawContactDelta rawContactDelta = mKindSectionDatas.get(0).getRawContactDelta();
+
+ // Update whether the entire section is visible or not
+ final int editorCount = getEditorCount();
+ final List<View> emptyEditors = getEmptyEditors();
+ if (editorCount == emptyEditors.size() && mHideIfEmpty) {
+ setVisibility(GONE);
+ return;
+ }
+ setVisibility(VISIBLE);
+
+ // Update the number of empty editors
+ if (emptyEditors.size() > 1) {
+ // If there is more than 1 empty editor, then remove it from the list of editors.
+ int deleted = 0;
+ for (final View emptyEditorView : emptyEditors) {
+ // If no child {@link View}s are being focused on within this {@link View}, then
+ // remove this empty editor. We can assume that at least one empty editor has focus.
+ // One way to get two empty editors is by deleting characters from a non-empty
+ // editor, in which case this editor has focus. Another way is if there is more
+ // values delta so we must also count number of editors deleted.
+
+ // TODO: we must not delete the editor for the "primary" account. It's working
+ // because the primary account is always the last one when the account is changed
+ // in the editor but it is a bit brittle to rely on that (though that is what is
+ // happening in LMP).
+ if (emptyEditorView.findFocus() == null) {
+ final Editor editor = (Editor) emptyEditorView;
+ if (shouldAnimate) {
+ editor.deleteEditor();
+ } else {
+ mEditors.removeView(emptyEditorView);
+ }
+ deleted++;
+ if (deleted == emptyEditors.size() -1) break;
+ }
+ }
+ return;
+ }
+ if (dataKind == null // There is nothing we can do.
+ || mReadOnly // We don't show empty editors for read only data kinds.
+ // We have already reached the maximum number of editors, don't add any more.
+ || (dataKind.typeOverallMax == editorCount && dataKind.typeOverallMax != 0)
+ // We have already reached the maximum number of empty editors, don't add any more.
+ || emptyEditors.size() == 1) {
+ return;
+ }
+ // Add a new empty editor
+ if (mShowOneEmptyEditor) {
+ final ValuesDelta values = RawContactModifier.insertChild(rawContactDelta, dataKind);
+ final View editorView = createEditorView(rawContactDelta, dataKind, values);
+ if (shouldAnimate) {
+ editorView.setVisibility(View.GONE);
+ EditorAnimator.getInstance().showFieldFooter(editorView);
+ }
+ }
+ }
+
+ /**
+ * Returns a list of empty editor views in this section.
+ */
+ private List<View> getEmptyEditors() {
+ List<View> emptyEditorViews = new ArrayList<View>();
+ for (int i = 0; i < mEditors.getChildCount(); i++) {
+ View view = mEditors.getChildAt(i);
+ if (((Editor) view).isEmpty()) {
+ emptyEditorViews.add(view);
+ }
+ }
+ return emptyEditorViews;
+ }
+
+ private int getEditorCount() {
+ return mEditors.getChildCount();
+ }
+
+ /**
+ * Returns the editor View at the given index.
+ */
+ public View getEditorView(int index) {
+ return mEditors == null || mEditors.getChildCount() == 0 || mEditors.getChildCount() > index
+ ? null : mEditors.getChildAt(index);
+ }
+}
diff --git a/src/com/android/contacts/editor/CompactRawContactsEditorView.java b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
index 4aabf0e..c51e47a 100644
--- a/src/com/android/contacts/editor/CompactRawContactsEditorView.java
+++ b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
@@ -23,7 +23,6 @@
import com.android.contacts.common.model.RawContactModifier;
import com.android.contacts.common.model.ValuesDelta;
import com.android.contacts.common.model.account.AccountType;
-import com.android.contacts.common.model.account.AccountType.EditField;
import com.android.contacts.common.model.account.AccountWithDataSet;
import com.android.contacts.common.model.dataitem.DataKind;
import com.android.contacts.common.util.AccountsListAdapter;
@@ -35,11 +34,19 @@
import android.graphics.Bitmap;
import android.net.Uri;
import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.Relation;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
@@ -53,30 +60,29 @@
import android.widget.TextView;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.TreeSet;
/**
- * View to display information from multiple {@link RawContactDelta}s grouped together
- * (e.g. all the phone numbers from a {@link com.android.contacts.common.model.Contact} together.
+ * View to display information from multiple {@link RawContactDelta}s grouped together.
*/
public class CompactRawContactsEditorView extends LinearLayout implements View.OnClickListener {
private static final String TAG = "CompactEditorView";
+ private static final MimeTypeComparator MIME_TYPE_COMPARATOR = new MimeTypeComparator();
+
/**
* Callbacks for hosts of {@link CompactRawContactsEditorView}s.
*/
public interface Listener {
/**
- * Invoked when the compact editor should be expanded to show all fields.
- */
- public void onExpandEditor();
-
- /**
* Invoked when the structured name editor field has changed.
*
* @param rawContactId The raw contact ID from the underlying {@link RawContactDelta}.
@@ -135,6 +141,55 @@
}
}
+ /** Used to sort kind sections. */
+ private static final class MimeTypeComparator implements
+ Comparator<Map.Entry<String,List<KindSectionData>>> {
+
+ // Order of kinds roughly matches quick contacts; we diverge in the following ways:
+ // 1) all names are together at the top (in quick contacts the nickname and phonetic name
+ // are in the about card)
+ // 2) IM is moved up after addresses
+ // 3) SIP addresses are moved to below phone numbers
+ private static final List<String> MIME_TYPE_ORDER = Arrays.asList(new String[] {
+ StructuredName.CONTENT_ITEM_TYPE,
+ DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME,
+ DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME,
+ Nickname.CONTENT_ITEM_TYPE,
+ Phone.CONTENT_ITEM_TYPE,
+ SipAddress.CONTENT_ITEM_TYPE,
+ Email.CONTENT_ITEM_TYPE,
+ StructuredPostal.CONTENT_ITEM_TYPE,
+ Im.CONTENT_ITEM_TYPE,
+ Website.CONTENT_ITEM_TYPE,
+ Organization.CONTENT_ITEM_TYPE,
+ Event.CONTENT_ITEM_TYPE,
+ Relation.CONTENT_ITEM_TYPE,
+ Note.CONTENT_ITEM_TYPE
+ });
+
+ @Override
+ public int compare(Map.Entry<String, List<KindSectionData>> entry1,
+ Map.Entry<String, List<KindSectionData>> entry2) {
+ if (entry1 == entry2) return 0;
+ if (entry1 == null) return -1;
+ if (entry2 == null) return 1;
+
+ final String mimeType1 = entry1.getKey();
+ final String mimeType2 = entry2.getKey();
+
+ int index1 = MIME_TYPE_ORDER.indexOf(mimeType1);
+ int index2 = MIME_TYPE_ORDER.indexOf(mimeType2);
+
+ // Fallback to alphabetical ordering of the mime type if both are not found
+ if (index1 < 0 && index2 < 0) return mimeType1.compareTo(mimeType2);
+ if (index1 < 0) return 1;
+ if (index2 < 0) return -1;
+
+ return index1 < index2 ? -1 : 1;
+ }
+ }
+
+
private Listener mListener;
private AccountTypeManager mAccountTypeManager;
@@ -164,12 +219,9 @@
private CompactPhotoEditorView mPhoto;
private ViewGroup mNames;
- private ViewGroup mPhoneticNames;
- private ViewGroup mNicknames;
- private ViewGroup mPhoneNumbers;
- private ViewGroup mEmails;
- private ViewGroup mOtherTypes;
- private Map<String,LinearLayout> mOtherTypesMap = new HashMap<>();
+ private ViewGroup mKindSectionViews;
+ // Map of mime types to KindSectionViews
+ private Map<String,LinearLayout> mKindSectionViewsMap = new HashMap<>();
private View mMoreFields;
// The ValuesDelta for the non super primary name that was displayed to the user.
@@ -216,19 +268,24 @@
mPhoto = (CompactPhotoEditorView) findViewById(R.id.photo_editor);
mNames = (LinearLayout) findViewById(R.id.names);
- mPhoneticNames = (LinearLayout) findViewById(R.id.phonetic_names);
- mNicknames = (LinearLayout) findViewById(R.id.nicknames);
- mPhoneNumbers = (LinearLayout) findViewById(R.id.phone_numbers);
- mEmails = (LinearLayout) findViewById(R.id.emails);
- mOtherTypes = (LinearLayout) findViewById(R.id.other);
+ mKindSectionViews = (LinearLayout) findViewById(R.id.kind_section_views);
mMoreFields = findViewById(R.id.more_fields);
mMoreFields.setOnClickListener(this);
}
@Override
public void onClick(View view) {
- if (view.getId() == R.id.more_fields && mListener != null ) {
- mListener.onExpandEditor();
+ if (view.getId() == R.id.more_fields) {
+ // Stop hiding empty editors and allow the user to enter values for all kinds now
+ for (int i = 0; i < mKindSectionViews.getChildCount(); i++) {
+ final CompactKindSectionView kindSectionView =
+ (CompactKindSectionView) mKindSectionViews.getChildAt(i);
+ kindSectionView.setHideWhenEmpty(false);
+ kindSectionView.setShowOneEmptyEditor(true);
+ kindSectionView.updateEmptyEditors(/* shouldAnimate =*/ false);
+ }
+
+ updateMoreFieldsButton();
}
}
@@ -236,13 +293,7 @@
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
setEnabled(enabled, mNames);
- setEnabled(enabled, mPhoneticNames);
- setEnabled(enabled, mNicknames);
- setEnabled(enabled, mPhoneNumbers);
- setEnabled(enabled, mEmails);
- for (Map.Entry<String,LinearLayout> otherType : mOtherTypesMap.entrySet()) {
- setEnabled(enabled, otherType.getValue());
- }
+ setEnabled(enabled, mKindSectionViews);
}
private void setEnabled(boolean enabled, ViewGroup viewGroup) {
@@ -300,9 +351,16 @@
}
public PhoneticNameEditorView getFirstPhoneticNameEditorView() {
- // There should only ever be one phonetic name
- return mPhoneticNames.getChildCount() == 0
- ? null : (PhoneticNameEditorView) mPhoneticNames.getChildAt(0);
+ // There should only ever be one phonetic name, it is enforced by dataKind.typeOverallMax
+ final CompactKindSectionView kindSectionView = (CompactKindSectionView)
+ mKindSectionViewsMap.get(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME);
+ if (kindSectionView != null && kindSectionView.getChildCount() > 0) {
+ final View editorView = kindSectionView.getEditorView(0);
+ if (editorView instanceof PhoneticNameEditorView) {
+ return ((PhoneticNameEditorView) editorView);
+ }
+ }
+ return null;
}
public View getAggregationAnchorView() {
@@ -325,12 +383,8 @@
mKindSectionDataMap.clear();
mNames.removeAllViews();
- mPhoneticNames.removeAllViews();
- mNicknames.removeAllViews();
- mPhoneNumbers.removeAllViews();
- mEmails.removeAllViews();
- mOtherTypes.removeAllViews();
- mOtherTypesMap.clear();
+ mKindSectionViews.removeAllViews();
+ mKindSectionViewsMap.clear();
mMoreFields.setVisibility(View.VISIBLE);
if (rawContactDeltas == null || rawContactDeltas.isEmpty()) {
@@ -352,19 +406,19 @@
}
vlog("state: primary " + mPrimaryAccount);
- vlog("state: setting compact editor state from " + rawContactDeltas);
+
+ vlog("state: setting editor state from " + rawContactDeltas.size() + " RawContactDelta(s)");
parseRawContactDeltas(rawContactDeltas);
+ if (mKindSectionDataMap == null || mKindSectionDataMap.isEmpty()) {
+ elog("No kind section data parsed from raw contact deltas");
+ return;
+ }
addAccountInfo();
addPhotoView();
- addStructuredNameView();
- addEditorViews(rawContactDeltas);
- updateKindEditorEmptyFields(mPhoneNumbers);
- updateKindEditorIcons(mPhoneNumbers);
- updateKindEditorEmptyFields(mEmails);
- updateKindEditorIcons(mEmails);
- for (Map.Entry<String,LinearLayout> otherTypes : mOtherTypesMap.entrySet()) {
- updateKindEditorIcons(otherTypes.getValue());
- }
+ addNameView();
+ addKindSectionViews();
+
+ updateMoreFieldsButton();
}
private void parseRawContactDeltas(RawContactDeltaList rawContactDeltas) {
@@ -563,8 +617,8 @@
mViewIdGenerator);
}
- private void addStructuredNameView() {
- // Get the kind section data and values delta that will back the name view
+ private void addNameView() {
+ // Get the kind section data and values delta that will back the photo view
final String mimeType = StructuredName.CONTENT_ITEM_TYPE;
Pair<KindSectionData,ValuesDelta> pair = getPrimaryKindSectionData(mimeType, mNameId);
if (pair == null) {
@@ -578,7 +632,6 @@
// read-only contact in the name editor backed by the new raw contact
// that was created. The new raw contact is the first writable one.
// See go/editing-read-only-contacts
- // TODO
if (!TextUtils.isEmpty(mReadOnlyDisplayName)) {
for (KindSectionData data : mKindSectionDataMap.get(mimeType)) {
if (data.getAccountType().areContactsWritable()) {
@@ -658,197 +711,37 @@
? new Pair<>(resultKindSectionData, resultValuesDelta) : null;
}
- private void addEditorViews(RawContactDeltaList rawContactDeltas) {
- for (RawContactDelta rawContactDelta : rawContactDeltas) {
- if (!rawContactDelta.isVisible()) continue;
- final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
+ private void addKindSectionViews() {
+ // Sort the kinds
+ final TreeSet<Map.Entry<String,List<KindSectionData>>> entries =
+ new TreeSet<>(MIME_TYPE_COMPARATOR);
+ entries.addAll(mKindSectionDataMap.entrySet());
- for (DataKind dataKind : accountType.getSortedDataKinds()) {
- if (!dataKind.editable) continue;
+ vlog("kind: " + entries.size() + " kindSection(s)");
+ int i = 0;
+ for (Map.Entry<String, List<KindSectionData>> entry : entries) {
+ final String mimeType = entry.getKey();
+ final List<KindSectionData> kindSectionDataList = entry.getValue();
+ vlog("kind: " + i++ + " " + mimeType + ": " + (kindSectionDataList == null ? 0
+ : kindSectionDataList.size()) + " kindSectionData(s)");
- final String mimeType = dataKind.mimeType;
- vlog(mimeType + " " + dataKind.fieldList.size() + " field(s)");
- if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)
- || StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)
- || GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
- // Photos and structured names are handled separately and
- // group membership is not supported
- continue;
- } else if (DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(mimeType)) {
- // Only add phonetic names if there is a non-empty one. Note the use of
- // StructuredName mimeType below, even though we matched a pseudo mime type.
- final ValuesDelta valuesDelta = rawContactDelta.getSuperPrimaryEntry(
- StructuredName.CONTENT_ITEM_TYPE, /* forceSelection =*/ true);
- if (hasNonEmptyValue(dataKind, valuesDelta)) {
- mPhoneticNames.addView(inflatePhoneticNameEditorView(
- mPhoneticNames, accountType, valuesDelta, rawContactDelta));
- }
- } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) {
- // Add all non-empty nicknames
- final List<ValuesDelta> valuesDeltas = getNonEmptyValuesDeltas(
- rawContactDelta, Nickname.CONTENT_ITEM_TYPE, dataKind);
- if (valuesDeltas != null && !valuesDeltas.isEmpty()) {
- for (ValuesDelta valuesDelta : valuesDeltas) {
- mNicknames.addView(inflateNicknameEditorView(
- mNicknames, dataKind, valuesDelta, rawContactDelta));
- }
- }
- } else if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
- final KindSectionView kindSectionView =
- inflateKindSectionView(mPhoneNumbers, dataKind, rawContactDelta);
- kindSectionView.setListener(new KindSectionView.Listener() {
- @Override
- public void onDeleteRequested(Editor editor) {
- if (kindSectionView.getEditorCount() == 1) {
- kindSectionView.markForRemoval();
- EditorAnimator.getInstance().removeEditorView(kindSectionView);
- } else {
- editor.deleteEditor();
- }
- updateKindEditorEmptyFields(mPhoneNumbers);
- updateKindEditorIcons(mPhoneNumbers);
- }
- });
- mPhoneNumbers.addView(kindSectionView);
- } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
- final KindSectionView kindSectionView =
- inflateKindSectionView(mEmails, dataKind, rawContactDelta);
- kindSectionView.setListener(new KindSectionView.Listener() {
- @Override
- public void onDeleteRequested(Editor editor) {
- if (kindSectionView.getEditorCount() == 1) {
- kindSectionView.markForRemoval();
- EditorAnimator.getInstance().removeEditorView(kindSectionView);
- } else {
- editor.deleteEditor();
- }
- updateKindEditorEmptyFields(mEmails);
- updateKindEditorIcons(mEmails);
- }
- });
- mEmails.addView(kindSectionView);
- } else if (hasNonEmptyValuesDelta(rawContactDelta, mimeType, dataKind)) {
- final LinearLayout otherTypeViewGroup;
- if (mOtherTypesMap.containsKey(mimeType)) {
- otherTypeViewGroup = mOtherTypesMap.get(mimeType);
- } else {
- otherTypeViewGroup = new LinearLayout(getContext());
- otherTypeViewGroup.setOrientation(LinearLayout.VERTICAL);
- mOtherTypes.addView(otherTypeViewGroup);
- mOtherTypesMap.put(mimeType, otherTypeViewGroup);
- }
- final KindSectionView kindSectionView =
- inflateKindSectionView(mOtherTypes, dataKind, rawContactDelta);
- kindSectionView.setListener(new KindSectionView.Listener() {
- @Override
- public void onDeleteRequested(Editor editor) {
- if (kindSectionView.getEditorCount() == 1) {
- kindSectionView.markForRemoval();
- EditorAnimator.getInstance().removeEditorView(kindSectionView);
- } else {
- editor.deleteEditor();
- }
- updateKindEditorIcons(otherTypeViewGroup);
- }
- });
- otherTypeViewGroup.addView(kindSectionView);
- }
+ // Ignore mime types that we've already handled
+ if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)
+ || StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)
+ || DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(mimeType)) continue;
+
+ // Ignore mime types that we don't handle
+ if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) continue;
+
+ if (kindSectionDataList != null) {
+ final CompactKindSectionView kindSectionView = inflateKindSectionView(
+ mKindSectionViews, kindSectionDataList, mimeType);
+ mKindSectionViewsMap.put(mimeType, kindSectionView);
+ mKindSectionViews.addView(kindSectionView);
}
}
}
- private static void updateKindEditorEmptyFields(ViewGroup viewGroup) {
- KindSectionView lastVisibleKindSectionView = null;
- for (int i = 0; i < viewGroup.getChildCount(); i++) {
- if (viewGroup.getChildAt(i).getVisibility() == View.VISIBLE) {
- lastVisibleKindSectionView = (KindSectionView) viewGroup.getChildAt(i);
- }
- }
- // Only the last editor should show an empty editor
- if (lastVisibleKindSectionView != null) {
- // Hide all empty kind sections except the last one
- for (int i = 0; i < viewGroup.getChildCount(); i++) {
- final KindSectionView kindSectionView = (KindSectionView) viewGroup.getChildAt(i);
- if (kindSectionView != lastVisibleKindSectionView
- && kindSectionView.areAllEditorsEmpty()) {
- kindSectionView.setVisibility(View.GONE);
- }
- }
- // Set the last editor to show empty editor fields
- lastVisibleKindSectionView.setShowOneEmptyEditor(true);
- lastVisibleKindSectionView.updateEmptyEditors(/* shouldAnimate =*/ false);
- }
- }
-
- private static void updateKindEditorIcons(ViewGroup viewGroup) {
- // Show the icon on the first visible kind editor
- boolean iconVisible = false;
- for (int i = 0; i < viewGroup.getChildCount(); i++) {
- final KindSectionView kindSectionView = (KindSectionView) viewGroup.getChildAt(i);
- if (kindSectionView.getVisibility() != View.VISIBLE
- || kindSectionView.isMarkedForRemoval()) {
- continue;
- }
- if (!iconVisible) {
- kindSectionView.setIconVisibility(true);
- iconVisible = true;
- } else {
- kindSectionView.setIconVisibility(false);
- }
- }
- }
-
- private static boolean hasNonEmptyValuesDelta(RawContactDelta rawContactDelta,
- String mimeType, DataKind dataKind) {
- return !getNonEmptyValuesDeltas(rawContactDelta, mimeType, dataKind).isEmpty();
- }
-
- private static ValuesDelta getNonEmptySuperPrimaryValuesDeltas(RawContactDelta rawContactDelta,
- String mimeType, DataKind dataKind) {
- for (ValuesDelta valuesDelta : getNonEmptyValuesDeltas(
- rawContactDelta, mimeType, dataKind)) {
- if (valuesDelta.isSuperPrimary()) {
- return valuesDelta;
- }
- }
- return null;
- }
-
- static List<ValuesDelta> getNonEmptyValuesDeltas(RawContactDelta rawContactDelta,
- String mimeType, DataKind dataKind) {
- final List<ValuesDelta> result = new ArrayList<>();
- if (rawContactDelta == null) {
- vlog("Null RawContactDelta");
- return result;
- }
- if (!rawContactDelta.hasMimeEntries(mimeType)) {
- vlog("No ValueDeltas");
- return result;
- }
- for (ValuesDelta valuesDelta : rawContactDelta.getMimeEntries(mimeType)) {
- if (hasNonEmptyValue(dataKind, valuesDelta)) {
- result.add(valuesDelta);
- }
- }
- return result;
- }
-
- private static boolean hasNonEmptyValue(DataKind dataKind, ValuesDelta valuesDelta) {
- if (valuesDelta == null) {
- vlog("Null valuesDelta");
- return false;
- }
- for (EditField editField : dataKind.fieldList) {
- final String column = editField.column;
- final String value = valuesDelta == null ? null : valuesDelta.getAsString(column);
- vlog("Field " + column + " empty=" + TextUtils.isEmpty(value) + " value=" + value);
- if (!TextUtils.isEmpty(value)) {
- return true;
- }
- }
- return false;
- }
-
private StructuredNameEditorView inflateStructuredNameEditorView(ViewGroup viewGroup,
AccountType accountType, ValuesDelta valuesDelta, RawContactDelta rawContactDelta,
NameEditorListener nameEditorListener, boolean readOnly) {
@@ -858,54 +751,45 @@
result.setEditorListener(nameEditorListener);
}
result.setDeletable(false);
- result.setValues(
- accountType.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME),
- valuesDelta,
- rawContactDelta,
- readOnly,
- mViewIdGenerator);
+ result.setValues(accountType.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME),
+ valuesDelta, rawContactDelta, readOnly, mViewIdGenerator);
return result;
}
- private PhoneticNameEditorView inflatePhoneticNameEditorView(ViewGroup viewGroup,
- AccountType accountType, ValuesDelta valuesDelta, RawContactDelta rawContactDelta) {
- final PhoneticNameEditorView result = (PhoneticNameEditorView) mLayoutInflater.inflate(
- R.layout.phonetic_name_editor_view, viewGroup, /* attachToRoot =*/ false);
- result.setDeletable(false);
- result.setValues(
- accountType.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME),
- valuesDelta,
- rawContactDelta,
- /* readOnly =*/ false,
+ private CompactKindSectionView inflateKindSectionView(ViewGroup viewGroup,
+ List<KindSectionData> kindSectionDataList, String mimeType) {
+ final CompactKindSectionView kindSectionView = (CompactKindSectionView)
+ mLayoutInflater.inflate(R.layout.compact_item_kind_section, viewGroup,
+ /* attachToRoot =*/ false);
+
+ if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)
+ || Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ // Phone numbers and emails address are always displayed and are
+ // the only types you add new values to initially
+ kindSectionView.setHideWhenEmpty(false);
+ }
+ // TODO: talk to Ricardo about when to allow adding new entries in compact mode
+ kindSectionView.setShowOneEmptyEditor(true);
+
+ kindSectionView.setState(kindSectionDataList, /* readOnly =*/ false,
mViewIdGenerator);
- return result;
+
+ return kindSectionView;
}
- private TextFieldsEditorView inflateNicknameEditorView(ViewGroup viewGroup, DataKind dataKind,
- ValuesDelta valuesDelta, RawContactDelta rawContactDelta) {
- final TextFieldsEditorView result = (TextFieldsEditorView) mLayoutInflater.inflate(
- R.layout.nick_name_editor_view, viewGroup, /* attachToRoot =*/ false);
- result.setDeletable(false);
- result.setValues(
- dataKind,
- valuesDelta,
- rawContactDelta,
- /* readOnly =*/ false,
- mViewIdGenerator);
- return result;
- }
-
-
- private KindSectionView inflateKindSectionView(ViewGroup viewGroup, DataKind dataKind,
- RawContactDelta rawContactDelta) {
- final KindSectionView result = (KindSectionView) mLayoutInflater.inflate(
- R.layout.item_kind_section, viewGroup, /* attachToRoot =*/ false);
- result.setState(
- dataKind,
- rawContactDelta,
- /* readOnly =*/ false,
- mViewIdGenerator);
- return result;
+ private void updateMoreFieldsButton() {
+ // If any kind section views are hidden then show the link
+ for (int i = 0; i < mKindSectionViews.getChildCount(); i++) {
+ final CompactKindSectionView kindSectionView =
+ (CompactKindSectionView) mKindSectionViews.getChildAt(i);
+ if (kindSectionView.getVisibility() == View.GONE) {
+ // Show the more fields button
+ mMoreFields.setVisibility(View.VISIBLE);
+ return;
+ }
+ }
+ // Hide the more fields button
+ mMoreFields.setVisibility(View.GONE);
}
private static void vlog(String message) {
diff --git a/src/com/android/contacts/editor/EditorUiUtils.java b/src/com/android/contacts/editor/EditorUiUtils.java
index 56be3c0..7a40b1d 100644
--- a/src/com/android/contacts/editor/EditorUiUtils.java
+++ b/src/com/android/contacts/editor/EditorUiUtils.java
@@ -16,12 +16,22 @@
package com.android.contacts.editor;
-import static android.provider.ContactsContract.CommonDataKinds.Event;
import static android.provider.ContactsContract.CommonDataKinds.GroupMembership;
-import static android.provider.ContactsContract.CommonDataKinds.Photo;
import static android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.content.Context;
+import android.graphics.drawable.Drawable;
+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.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+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.text.TextUtils;
import android.util.Pair;
import com.android.contacts.R;
@@ -142,4 +152,39 @@
}
return builder.toString();
}
+
+ /**
+ * Return an icon that represents {@param mimeType}.
+ */
+ public static Drawable getMimeTypeDrawable(Context context, String mimeType) {
+ switch (mimeType) {
+ case StructuredPostal.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(R.drawable.ic_place_24dp);
+ case SipAddress.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(R.drawable.ic_dialer_sip_black_24dp);
+ case Phone.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(R.drawable.ic_phone_24dp);
+ case Im.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(R.drawable.ic_message_24dp);
+ case Event.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(R.drawable.ic_event_24dp);
+ case Email.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(R.drawable.ic_email_24dp);
+ case Website.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(R.drawable.ic_public_black_24dp);
+ case Photo.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(R.drawable.ic_camera_alt_black_24dp);
+ case GroupMembership.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(R.drawable.ic_people_black_24dp);
+ case Organization.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(R.drawable.ic_business_black_24dp);
+ case Note.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(R.drawable.ic_insert_comment_black_24dp);
+ case Relation.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(
+ R.drawable.ic_circles_extended_black_24dp);
+ default:
+ return null;
+ }
+ }
}
diff --git a/src/com/android/contacts/editor/KindSectionView.java b/src/com/android/contacts/editor/KindSectionView.java
index 26ef058..a75c2e4 100644
--- a/src/com/android/contacts/editor/KindSectionView.java
+++ b/src/com/android/contacts/editor/KindSectionView.java
@@ -17,19 +17,6 @@
package com.android.contacts.editor;
import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.provider.Contacts.GroupMembership;
-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.Organization;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
-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;
@@ -200,7 +187,7 @@
: getResources().getString(kind.titleRes);
mIcon.setContentDescription(titleString);
- mIcon.setImageDrawable(getMimeTypeDrawable(kind.mimeType));
+ mIcon.setImageDrawable(EditorUiUtils.getMimeTypeDrawable(getContext(), kind.mimeType));
if (mIcon.getDrawable() == null) {
mIcon.setContentDescription(null);
}
@@ -355,38 +342,4 @@
public DataKind getKind() {
return mKind;
}
-
- /**
- * Return an icon that represents {@param mimeType}.
- */
- private Drawable getMimeTypeDrawable(String mimeType) {
- switch (mimeType) {
- case StructuredPostal.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_place_24dp);
- case SipAddress.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_dialer_sip_black_24dp);
- case Phone.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_phone_24dp);
- case Im.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_message_24dp);
- case Event.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_event_24dp);
- case Email.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_email_24dp);
- case Website.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_public_black_24dp);
- case Photo.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_camera_alt_black_24dp);
- case GroupMembership.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_people_black_24dp);
- case Organization.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_business_black_24dp);
- case Note.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_insert_comment_black_24dp);
- case Relation.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_circles_extended_black_24dp);
- default:
- return null;
- }
- }
}
diff --git a/src/com/android/contacts/editor/TextFieldsEditorView.java b/src/com/android/contacts/editor/TextFieldsEditorView.java
index fe476ed..51c9d94 100644
--- a/src/com/android/contacts/editor/TextFieldsEditorView.java
+++ b/src/com/android/contacts/editor/TextFieldsEditorView.java
@@ -213,7 +213,7 @@
}
boolean hidePossible = false;
- int fieldCount = kind.fieldList.size();
+ int fieldCount = kind.fieldList == null ? 0 : kind.fieldList.size();
mFieldEditTexts = new EditText[fieldCount];
for (int index = 0; index < fieldCount; index++) {
final EditField field = kind.fieldList.get(index);