Switch edit UI to tabs, TYPE_ASSERT for version, cleanup.
Integrated with the tabs and header widget built by
emillar, kept separate from BaseContactCardActivity since
I'm handling EDIT/INSERT intents differently. Added hooks
to pick primary photo/name from header.
Cleaned up the colorful testing UI, added padding to match
mocks, and initial pass at collapsed secondary area. Added
back menu items and confirmation toast.
Changed to new TYPE_ASSERT ContentProviderOperation for
asserting RawContacts.VERSION number during updates.
diff --git a/src/com/android/contacts/BaseContactCardActivity.java b/src/com/android/contacts/BaseContactCardActivity.java
index 4378c45..7d4bb19 100644
--- a/src/com/android/contacts/BaseContactCardActivity.java
+++ b/src/com/android/contacts/BaseContactCardActivity.java
@@ -39,6 +39,7 @@
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.view.Window;
import android.widget.CheckBox;
import android.widget.ImageView;
@@ -157,7 +158,7 @@
* @param icon An icon to display in the tab indicator.
*/
protected void addTab(long contactId, String label, Drawable icon) {
- addTab(contactId, createTabIndicatorView(label, icon));
+ addTab(contactId, createTabIndicatorView(mTabWidget, label, icon));
}
/**
@@ -201,8 +202,10 @@
* @param icon The icon to display. If null, no icon will be displayed.
* @return The tab indicator View.
*/
- protected View createTabIndicatorView(String label, Drawable icon) {
- View tabIndicator = mInflater.inflate(R.layout.tab_indicator, mTabWidget, false);
+ public static View createTabIndicatorView(ViewGroup parent, CharSequence label, Drawable icon) {
+ final LayoutInflater inflater = (LayoutInflater)parent.getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ final View tabIndicator = inflater.inflate(R.layout.tab_indicator, parent, false);
final TextView tv = (TextView) tabIndicator.findViewById(R.id.tab_title);
tv.setText(label);
diff --git a/src/com/android/contacts/ScrollingTabWidget.java b/src/com/android/contacts/ScrollingTabWidget.java
index 9f4aee8..982d661 100644
--- a/src/com/android/contacts/ScrollingTabWidget.java
+++ b/src/com/android/contacts/ScrollingTabWidget.java
@@ -273,7 +273,7 @@
* Provides a way for ViewContactActivity and EditContactActivity to be notified that the
* user clicked on a tab indicator.
*/
- void setTabSelectionListener(OnTabSelectionChangedListener listener) {
+ public void setTabSelectionListener(OnTabSelectionChangedListener listener) {
mSelectionChangedListener = listener;
}
@@ -345,7 +345,7 @@
}
}
- static interface OnTabSelectionChangedListener {
+ public interface OnTabSelectionChangedListener {
/**
* Informs the tab widget host which tab was selected. It also indicates
* if the tab was clicked/pressed or just focused into.
diff --git a/src/com/android/contacts/ViewContactActivity.java b/src/com/android/contacts/ViewContactActivity.java
index 80fb7fc..9dfa551 100644
--- a/src/com/android/contacts/ViewContactActivity.java
+++ b/src/com/android/contacts/ViewContactActivity.java
@@ -568,7 +568,7 @@
protected void setAggregationException(long contactId, int exceptionType) {
ContentValues values = new ContentValues(3);
values.put(AggregationExceptions.CONTACT_ID, ContentUris.parseId(mUri));
- values.put(AggregationExceptions.CONTACT_ID, contactId);
+ values.put(AggregationExceptions.RAW_CONTACT_ID, contactId);
values.put(AggregationExceptions.TYPE, exceptionType);
mResolver.update(AggregationExceptions.CONTENT_URI, values, null, null);
}
diff --git a/src/com/android/contacts/model/ContactsSource.java b/src/com/android/contacts/model/ContactsSource.java
index d975d64..078b46c 100644
--- a/src/com/android/contacts/model/ContactsSource.java
+++ b/src/com/android/contacts/model/ContactsSource.java
@@ -84,6 +84,9 @@
*/
public String resPackageName;
+ public int titleRes;
+ public int iconRes;
+
/**
* Set of {@link DataKind} supported by this source.
*/
diff --git a/src/com/android/contacts/model/EntityDelta.java b/src/com/android/contacts/model/EntityDelta.java
index f313399..8526427 100644
--- a/src/com/android/contacts/model/EntityDelta.java
+++ b/src/com/android/contacts/model/EntityDelta.java
@@ -242,7 +242,12 @@
return builder.toString();
}
- private void possibleAdd(ArrayList<ContentProviderOperation> diff, ContentProviderOperation.Builder builder) {
+ /**
+ * Consider building the given {@link ContentProviderOperation.Builder} and
+ * appending it to the given list, which only happens if builder is valid.
+ */
+ private void possibleAdd(ArrayList<ContentProviderOperation> diff,
+ ContentProviderOperation.Builder builder) {
if (builder != null) {
diff.add(builder.build());
}
@@ -291,13 +296,11 @@
// If any operations, assert that version is identical so we bail if changed
if (diff.size() > 0 && beforeVersion != null && beforeId != null) {
- // TODO: re-enable version enforcement once we have COUNT(*) or ASSERT
-// builder = ContentProviderOperation.newCountQuery(RawContacts.CONTENT_URI);
-// builder.withSelection(RawContacts._ID + "=" + beforeId + " AND " + RawContacts.VERSION
-// + "=" + beforeVersion, null);
-// builder.withExpectedCount(1);
-// // Sneak version check at beginning of list
-// diff.add(0, builder.build());
+ builder = ContentProviderOperation.newAssertQuery(RawContacts.CONTENT_URI);
+ builder.withSelection(RawContacts._ID + "=" + beforeId, null);
+ builder.withValue(RawContacts.VERSION, beforeVersion);
+ // Sneak version check at beginning of list
+ diff.add(0, builder.build());
}
return diff;
diff --git a/src/com/android/contacts/model/EntityDiff.java b/src/com/android/contacts/model/EntityDiff.java
index ce1c1e6..ea46567 100644
--- a/src/com/android/contacts/model/EntityDiff.java
+++ b/src/com/android/contacts/model/EntityDiff.java
@@ -32,6 +32,7 @@
* Describes a set of {@link ContentProviderOperation} that need to be
* executed to transform a database from one {@link Entity} to another.
*/
+@Deprecated
public class EntityDiff extends ArrayList<ContentProviderOperation> {
private EntityDiff() {
}
diff --git a/src/com/android/contacts/model/Sources.java b/src/com/android/contacts/model/Sources.java
index 303d15d..ee13b82 100644
--- a/src/com/android/contacts/model/Sources.java
+++ b/src/com/android/contacts/model/Sources.java
@@ -503,7 +503,7 @@
public CharSequence inflateUsing(Context context, Cursor cursor) {
final EditType type = EntityModifier.getCurrentType(cursor, mKind);
- final boolean validString = type.actionRes > 0;
+ final boolean validString = (type != null && type.actionRes > 0);
return validString ? context.getPackageManager().getText(mPackageName, type.actionRes,
null) : null;
}
diff --git a/src/com/android/contacts/ui/EditContactActivity.java b/src/com/android/contacts/ui/EditContactActivity.java
index d56e771..720882f 100644
--- a/src/com/android/contacts/ui/EditContactActivity.java
+++ b/src/com/android/contacts/ui/EditContactActivity.java
@@ -17,19 +17,24 @@
package com.android.contacts.ui;
import com.android.contacts.R;
+import com.android.contacts.ScrollingTabWidget;
import com.android.contacts.model.ContactsSource;
import com.android.contacts.model.EntityDelta;
import com.android.contacts.model.Sources;
import com.android.contacts.model.EntityDelta.ValuesDelta;
import com.android.contacts.ui.widget.ContactEditorView;
+import com.android.internal.widget.ContactHeaderWidget;
import android.app.Activity;
+import android.app.AlertDialog;
import android.app.Dialog;
+import android.content.ActivityNotFoundException;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Entity;
import android.content.EntityIterator;
import android.content.Intent;
@@ -43,8 +48,13 @@
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
+import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
import java.util.ArrayList;
@@ -52,20 +62,16 @@
* Activity for editing or inserting a contact.
*/
public final class EditContactActivity extends Activity implements View.OnClickListener,
- View.OnFocusChangeListener {
-
+ ScrollingTabWidget.OnTabSelectionChangedListener, ContactHeaderWidget.ContactHeaderListener {
private static final String TAG = "EditContactActivity";
/** The launch code when picking a photo and the raw data is returned */
private static final int PHOTO_PICKED_WITH_DATA = 3021;
- // Dialog IDs
- final static int DELETE_CONFIRMATION_DIALOG = 2;
+ private static final String KEY_DELTAS = "deltas";
- // Menu item IDs
- public static final int MENU_ITEM_DONE = 1;
- public static final int MENU_ITEM_REVERT = 2;
- public static final int MENU_ITEM_PHOTO = 6;
+ private ScrollingTabWidget mTabWidget;
+ private ContactHeaderWidget mHeader;
private View mTabContent;
private ContactEditorView mEditor;
@@ -75,8 +81,6 @@
private ArrayList<EntityDelta> mEntities = new ArrayList<EntityDelta>();
-
-
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -84,8 +88,22 @@
final Context context = this;
final LayoutInflater inflater = this.getLayoutInflater();
+ final Intent intent = getIntent();
+ final String action = intent.getAction();
+ final Bundle extras = intent.getExtras();
+
+ mUri = intent.getData();
+ mSources = Sources.getInstance(this);
+
setContentView(R.layout.act_edit);
+ mHeader = (ContactHeaderWidget)this.findViewById(R.id.contact_header_widget);
+ mHeader.setContactHeaderListener(this);
+ mHeader.showStar(true);
+
+ mTabWidget = (ScrollingTabWidget)this.findViewById(R.id.tab_widget);
+ mTabWidget.setTabSelectionListener(this);
+
mTabContent = this.findViewById(android.R.id.tabcontent);
mEditor = new ContactEditorView(context);
@@ -94,22 +112,19 @@
findViewById(R.id.btn_done).setOnClickListener(this);
findViewById(R.id.btn_discard).setOnClickListener(this);
- final Intent intent = getIntent();
- final String action = intent.getAction();
- final Bundle extras = intent.getExtras();
-
- mUri = intent.getData();
- mSources = Sources.getInstance(this);
-
if (Intent.ACTION_EDIT.equals(action) && icicle == null) {
// Read initial state from database
readEntities();
rebuildTabs();
+
+ final long contactId = ContentUris.parseId(mUri);
+ mHeader.bindFromContactId(contactId);
+ } else if (Intent.ACTION_INSERT.equals(action)) {
+ // TODO: handle insert case for header
+
}
}
- private static final String KEY_DELTAS = "deltas";
-
@Override
protected void onSaveInstanceState(Bundle outState) {
// Store entities with modifications
@@ -168,59 +183,95 @@
}
protected void rebuildTabs() {
- // TODO: hook up to tabs
- showEntity(0);
+ mTabWidget.removeAllTabs();
+ for (EntityDelta entity : mEntities) {
+ final String accountType = entity.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
+ final ContactsSource source = getSourceForEntity(entity);
+
+ final View tabView = createTabView(mTabWidget, source);
+ mTabWidget.addTab(tabView);
+ }
+ mTabWidget.setCurrentTab(0);
}
- protected void showEntity(int index) {
- // Find entity and source for selected tab
- final EntityDelta entity = mEntities.get(index);
- final String accountType = entity.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
+ /**
+ * Create the {@link View} to represent the given {@link ContactsSource}.
+ */
+ public static View createTabView(ViewGroup parent, ContactsSource source) {
+ final Context context = parent.getContext();
+ final LayoutInflater inflater = (LayoutInflater)context
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ final View tabIndicator = inflater.inflate(R.layout.tab_indicator, parent, false);
+ final TextView titleView = (TextView)tabIndicator.findViewById(R.id.tab_title);
+ final ImageView iconView = (ImageView) tabIndicator.findViewById(R.id.tab_icon);
+
+ if (source.titleRes > 0) {
+ titleView.setText(source.titleRes);
+ }
+ if (source.iconRes > 0) {
+ iconView.setImageResource(source.iconRes);
+ }
+
+ return tabIndicator;
+ }
+
+ /**
+ * Find the {@link ContactsSource} that describes the structure for the
+ * given {@link EntityDelta}, or null if no matching source found.
+ */
+ private ContactsSource getSourceForEntity(EntityDelta entity) {
+ final String accountType = entity.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
ContactsSource source = mSources.getSourceForType(accountType);
if (source == null) {
// TODO: remove and place "read only" placard when missing
source = mSources.getSourceForType(Sources.ACCOUNT_TYPE_GOOGLE);
}
+ return source;
+ }
+
+
+ /** {@inheritDoc} */
+ public void onTabSelectionChanged(int tabIndex, boolean clicked) {
+ // Find entity and source for selected tab
+ final EntityDelta entity = mEntities.get(tabIndex);
+ final ContactsSource source = getSourceForEntity(entity);
// Assign editor state based on entity and source
mEditor.setState(entity, source);
}
- public void onTabChanged(String tabId) {
- // Tag is really an array index
- final int index = Integer.parseInt(tabId);
- showEntity(index);
+ /** {@inheritDoc} */
+ public void onDisplayNameLongClick(View view) {
+ // TODO: show dialog to pick primary display name
}
-
- public View createTabContent(String tag) {
- // Content is identical for all tabs
- return mEditor.getView();
+ /** {@inheritDoc} */
+ public void onPhotoLongClick(View view) {
+ // TODO: show dialog to pick primary photo
}
+
+ /** {@inheritDoc} */
public void onClick(View view) {
switch (view.getId()) {
- case R.id.btn_done: {
+ case R.id.btn_done:
doSaveAction();
break;
- }
- case R.id.btn_discard: {
+ case R.id.btn_discard:
doRevertAction();
break;
- }
}
}
+
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
- case KeyEvent.KEYCODE_BACK: {
- doSaveAction();
- return true;
- }
+ case KeyEvent.KEYCODE_BACK:
+ return doSaveAction();
}
return super.onKeyDown(keyCode, event);
}
@@ -230,9 +281,8 @@
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (resultCode != RESULT_OK) {
- return;
- }
+ // Ignore failed requests
+ if (resultCode != RESULT_OK) return;
switch (requestCode) {
case PHOTO_PICKED_WITH_DATA: {
@@ -254,20 +304,16 @@
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
-// menu.add(0, MENU_ITEM_SAVE, 0, R.string.menu_done)
-// .setIcon(android.R.drawable.ic_menu_save)
-// .setAlphabeticShortcut('\n');
-// menu.add(0, MENU_ITEM_DONT_SAVE, 0, R.string.menu_doNotSave)
-// .setIcon(android.R.drawable.ic_menu_close_clear_cancel)
-// .setAlphabeticShortcut('q');
-// if (!mInsert) {
-// menu.add(0, MENU_ITEM_DELETE, 0, R.string.menu_deleteContact)
-// .setIcon(android.R.drawable.ic_menu_delete);
-// }
-//
-// mPhotoMenuItem = menu.add(0, MENU_ITEM_PHOTO, 0, null);
-// // Updates the state of the menu item
-// setPhotoPresent(mPhotoPresent);
+
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.edit, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ // show or hide photo item based on current tab
+ // hide entirely if on read-only source
return true;
}
@@ -275,67 +321,32 @@
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
-// case MENU_ITEM_SAVE:
-// doSaveAction();
-// return true;
-//
-// case MENU_ITEM_DONT_SAVE:
-// doRevertAction();
-// return true;
-//
-// case MENU_ITEM_DELETE:
-// // Get confirmation
-// showDialog(DELETE_CONFIRMATION_DIALOG);
-// return true;
-//
-// case MENU_ITEM_PHOTO:
-// if (!mPhotoPresent) {
-// doPickPhotoAction();
-// } else {
-// doRemovePhotoAction();
-// }
-// return true;
+ case R.id.menu_done:
+ return doSaveAction();
+ case R.id.menu_discard:
+ return doRevertAction();
+ case R.id.menu_delete:
+ return doDeleteAction();
+ case R.id.menu_photo_add:
+ return doPickPhotoAction();
+ case R.id.menu_photo_remove:
+ return doRemovePhotoAction();
}
-
return false;
}
-
-
- private void doRevertAction() {
- finish();
- }
-
-
- @Override
- protected Dialog onCreateDialog(int id) {
- switch (id) {
- case DELETE_CONFIRMATION_DIALOG:
-// return new AlertDialog.Builder(EditContactActivity.this)
-// .setTitle(R.string.deleteConfirmation_title)
-// .setIcon(android.R.drawable.ic_dialog_alert)
-// .setMessage(R.string.deleteConfirmation)
-// .setNegativeButton(android.R.string.cancel, null)
-// .setPositiveButton(android.R.string.ok, mDeleteContactDialogListener)
-// .setCancelable(false)
-// .create();
- }
- return super.onCreateDialog(id);
- }
-
-
/**
- * Saves or creates the contact based on the mode, and if sucessful finishes the activity.
+ * Saves or creates the contact based on the mode, and if successful
+ * finishes the activity.
*/
- private void doSaveAction() {
-
+ private boolean doSaveAction() {
final ContentResolver resolver = this.getContentResolver();
-
+ boolean savedChanges = false;
for (EntityDelta entity : mEntities) {
-
Log.d(TAG, "about to persist " + entity.toString());
final ArrayList<ContentProviderOperation> diff = entity.buildDiff();
+ savedChanges |= diff.size() > 0;
// TODO: handle failed operations by re-reading entity
// may also need backoff algorithm to give failed msg after n tries
@@ -347,21 +358,125 @@
} catch (OperationApplicationException e) {
Log.w(TAG, "problem writing rawcontact diff", e);
}
+ }
+ if (savedChanges) {
+ Toast.makeText(this, R.string.contactSavedToast, Toast.LENGTH_SHORT).show();
}
this.finish();
+ return true;
+ }
+
+ /**
+ * Revert any changes the user has made, and finish the activity.
+ */
+ private boolean doRevertAction() {
+ finish();
+ return true;
+ }
+
+ /**
+ * Delete the entire contact currently being edited, which usually asks for
+ * user confirmation before continuing.
+ */
+ private boolean doDeleteAction() {
+ showDialog(R.id.dialog_delete);
+ return true;
+ }
+
+
+ /**
+ * Delete the entire contact currently being edited.
+ */
+ private void onDeleteActionConfirmed() {
+ // TODO: delete entire contact
+ }
+
+
+ /**
+ * Pick a specific photo to be added under this contact.
+ */
+ private boolean doPickPhotoAction() {
+ try {
+ final Intent intent = getPhotoPickIntent();
+ startActivityForResult(intent, PHOTO_PICKED_WITH_DATA);
+ } catch (ActivityNotFoundException e) {
+ new AlertDialog.Builder(EditContactActivity.this).setTitle(R.string.errorDialogTitle)
+ .setMessage(R.string.photoPickerNotFoundText).setPositiveButton(
+ android.R.string.ok, null).show();
+ }
+ return true;
+ }
+
+ public static Intent getPhotoPickIntent() {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
+ intent.setType("image/*");
+ intent.putExtra("crop", "true");
+ intent.putExtra("aspectX", 1);
+ intent.putExtra("aspectY", 1);
+ intent.putExtra("outputX", 96);
+ intent.putExtra("outputY", 96);
+ intent.putExtra("return-data", true);
+ return intent;
+ }
+
+ public boolean doRemovePhotoAction() {
+ // TODO: remove photo from current contact
+ return true;
}
- public void onFocusChange(View v, boolean hasFocus) {
- // Because we're emulating a ListView, we need to setSelected() for
- // views as they are focused.
- v.setSelected(hasFocus);
+
+
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ switch (id) {
+ case R.id.dialog_delete:
+ return createDeleteDialog();
+ case R.id.dialog_photo:
+ return createPhotoDialog();
+ case R.id.dialog_name:
+ return createNameDialog();
+ }
+ return super.onCreateDialog(id);
}
+
+ private Dialog createDeleteDialog() {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.deleteConfirmation_title);
+ builder.setIcon(android.R.drawable.ic_dialog_alert);
+ builder.setMessage(R.string.deleteConfirmation);
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ onDeleteActionConfirmed();
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, null);
+ builder.setCancelable(false);
+ return builder.create();
+ }
+
+ private Dialog createPhotoDialog() {
+ // TODO: build dialog for picking primary photo
+ return null;
+ }
+
+ private Dialog createNameDialog() {
+ // TODO: build dialog for picking primary name
+ return null;
+ }
+
+
+
+
+
+
+
}
diff --git a/src/com/android/contacts/ui/widget/ContactEditorView.java b/src/com/android/contacts/ui/widget/ContactEditorView.java
index 6e67dd6..751e037 100644
--- a/src/com/android/contacts/ui/widget/ContactEditorView.java
+++ b/src/com/android/contacts/ui/widget/ContactEditorView.java
@@ -26,15 +26,19 @@
import com.android.contacts.model.ContactsSource.EditType;
import android.app.AlertDialog;
+import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Entity;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.text.Editable;
import android.text.TextWatcher;
+import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
@@ -59,7 +63,7 @@
* adding {@link Data} rows or changing {@link EditType}, are performed through
* {@link EntityModifier} to ensure that {@link ContactsSource} are enforced.
*/
-public class ContactEditorView extends ViewHolder {
+public class ContactEditorView extends ViewHolder implements OnClickListener {
private static final int RES_CONTENT = R.layout.act_edit_contact;
private PhotoEditor mPhoto;
@@ -68,17 +72,47 @@
private ViewGroup mGeneral;
private ViewGroup mSecondary;
+ private TextView mSecondaryHeader;
+
+ private Drawable mSecondaryOpen;
+ private Drawable mSecondaryClosed;
+
public ContactEditorView(Context context) {
super(context, RES_CONTENT);
mGeneral = (ViewGroup)mContent.findViewById(R.id.sect_general);
mSecondary = (ViewGroup)mContent.findViewById(R.id.sect_secondary);
+ mSecondaryHeader = (TextView)mContent.findViewById(R.id.head_secondary);
+ mSecondaryHeader.setOnClickListener(this);
+
+ final Resources res = context.getResources();
+ mSecondaryOpen = res.getDrawable(com.android.internal.R.drawable.expander_ic_maximized);
+ mSecondaryClosed = res.getDrawable(com.android.internal.R.drawable.expander_ic_minimized);
+
+ this.setSecondaryVisible(false);
+
mPhoto = new PhotoEditor(context);
- mPhoto.swapWith(mContent, R.id.hook_photo);
+ mPhoto.swapInto((ViewGroup)mContent.findViewById(R.id.hook_photo));
mDisplayName = new DisplayNameEditor(context);
- mDisplayName.swapWith(mContent, R.id.hook_displayname);
+ mDisplayName.swapInto((ViewGroup)mContent.findViewById(R.id.hook_displayname));
+ }
+
+ /** {@inheritDoc} */
+ public void onClick(View v) {
+ // Toggle visibility of secondary kinds
+ final boolean makeVisible = mSecondary.getVisibility() != View.VISIBLE;
+ this.setSecondaryVisible(makeVisible);
+ }
+
+ /**
+ * Set the visibility of secondary sections, along with header icon.
+ */
+ private void setSecondaryVisible(boolean makeVisible) {
+ mSecondary.setVisibility(makeVisible ? View.VISIBLE : View.GONE);
+ mSecondaryHeader.setCompoundDrawablesWithIntrinsicBounds(makeVisible ? mSecondaryOpen
+ : mSecondaryClosed, null, null, null);
}
/**
@@ -139,6 +173,9 @@
public KindSection(Context context, DataKind kind, EntityDelta state) {
super(context, RES_SECTION);
+ mContent.setDrawingCacheEnabled(true);
+ ((ViewGroup)mContent).setAlwaysDrawnWithCacheEnabled(true);
+
mKind = kind;
mState = state;
@@ -152,10 +189,12 @@
this.rebuildFromState();
this.updateAddEnabled();
+ this.updateEditorsVisible();
}
public void onDeleted(Editor editor) {
this.updateAddEnabled();
+ this.updateEditorsVisible();
}
/**
@@ -180,6 +219,11 @@
}
}
+ protected void updateEditorsVisible() {
+ final boolean hasChildren = mEditors.getChildCount() > 0;
+ mEditors.setVisibility(hasChildren ? View.VISIBLE : View.GONE);
+ }
+
protected void updateAddEnabled() {
// Set enabled state on the "add" view
final boolean canInsert = EntityModifier.canInsert(mState, mKind);
@@ -191,6 +235,7 @@
EntityModifier.insertChild(mState, mKind);
this.rebuildFromState();
this.updateAddEnabled();
+ this.updateEditorsVisible();
}
}
@@ -227,11 +272,14 @@
* the entry. Uses {@link ValuesDelta} to read any existing
* {@link Entity} values, and to correctly write any changes values.
*/
- protected static class GenericEditor extends ViewHolder implements Editor, OnClickListener {
+ protected static class GenericEditor extends ViewHolder implements Editor, View.OnClickListener {
private static final int RES_EDITOR = R.layout.item_editor;
private static final int RES_FIELD = R.layout.item_editor_field;
private static final int RES_LABEL_ITEM = android.R.layout.simple_list_item_1;
+ private static final int INPUT_TYPE_CUSTOM = EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
+
private TextView mLabel;
private ViewGroup mFields;
private View mDelete;
@@ -343,38 +391,38 @@
}
}
- private static final int INPUT_TYPE_CUSTOM = EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
-
/**
- * Show dialog for entering a custom label.
+ * Prepare dialog for entering a custom label.
*/
- private void showCustomDialog() {
+ public Dialog createCustomDialog() {
final EditText customType = new EditText(mContext);
customType.setInputType(INPUT_TYPE_CUSTOM);
customType.requestFocus();
- final DialogInterface.OnClickListener clickPositive = new DialogInterface.OnClickListener() {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+ builder.setTitle(R.string.customLabelPickerTitle);
+ builder.setView(customType);
+
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
final String customText = customType.getText().toString();
mEntry.put(mType.customColumn, customText);
rebuildLabel();
}
- };
+ });
// TODO: handle canceled case by reverting to previous type?
+ builder.setNegativeButton(android.R.string.cancel, null);
- new AlertDialog.Builder(mContext).setView(customType).setTitle(
- R.string.customLabelPickerTitle).setPositiveButton(android.R.string.ok,
- clickPositive).setNegativeButton(android.R.string.cancel, null).show();
+ return builder.create();
}
/**
- * Show dialog for picking a new {@link EditType} or entering a custom
- * label. This dialog is limited to the valid types as determined by
- * {@link EntityModifier}.
+ * Prepare dialog for picking a new {@link EditType} or entering a
+ * custom label. This dialog is limited to the valid types as determined
+ * by {@link EntityModifier}.
*/
- private void showTypeDialog() {
+ public Dialog createLabelDialog() {
// Build list of valid types, including the current value
final List<EditType> validTypes = EntityModifier.getValidTypes(mState, mKind, mType);
@@ -408,28 +456,33 @@
if (mType.customColumn != null) {
// Show custom label dialog if requested by type
- showCustomDialog();
+ createCustomDialog().show();
} else {
rebuildLabel();
}
}
};
- new AlertDialog.Builder(mContext).setSingleChoiceItems(typeAdapter, 0, clickListener)
- .setTitle(R.string.selectLabel).show();
+ final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+ builder.setTitle(R.string.selectLabel);
+ builder.setSingleChoiceItems(typeAdapter, 0, clickListener);
+ return builder.create();
}
-
+ /** {@inheritDoc} */
public void onClick(View v) {
switch (v.getId()) {
case R.id.edit_label: {
- showTypeDialog();
+ createLabelDialog().show();
break;
}
case R.id.edit_delete: {
- // Mark as deleted and hide this editor
+ // Keep around in model, but mark as deleted
mEntry.markDeleted();
- mContent.setVisibility(View.GONE);
+
+ // Remove editor from parent view
+ final ViewGroup parent = (ViewGroup)mContent.getParent();
+ parent.removeView(mContent);
if (mListener != null) {
// Notify listener when present
@@ -474,33 +527,6 @@
// }
// }
-// private void doPickPhotoAction() {
-// Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
-// // TODO: get these values from constants somewhere
-// intent.setType("image/*");
-// intent.putExtra("crop", "true");
-// intent.putExtra("aspectX", 1);
-// intent.putExtra("aspectY", 1);
-// intent.putExtra("outputX", 96);
-// intent.putExtra("outputY", 96);
-// try {
-// intent.putExtra("return-data", true);
-// startActivityForResult(intent, PHOTO_PICKED_WITH_DATA);
-// } catch (ActivityNotFoundException e) {
-// new AlertDialog.Builder(EditContactActivity.this)
-// .setTitle(R.string.errorDialogTitle)
-// .setMessage(R.string.photoPickerNotFoundText)
-// .setPositiveButton(android.R.string.ok, null)
-// .show();
-// }
-// }
-
-// private void doRemovePhotoAction() {
-// mPhoto = null;
-// mPhotoChanged = true;
-// setPhotoPresent(false);
-// }
-
public void setValues(DataKind kind, ValuesDelta values, EntityDelta state) {
}
@@ -524,5 +550,4 @@
public void setEditorListener(EditorListener listener) {
}
}
-
}
diff --git a/src/com/android/contacts/ui/widget/ViewHolder.java b/src/com/android/contacts/ui/widget/ViewHolder.java
index 223dbb1..241dddd 100644
--- a/src/com/android/contacts/ui/widget/ViewHolder.java
+++ b/src/com/android/contacts/ui/widget/ViewHolder.java
@@ -37,6 +37,11 @@
mContent = mInflater.inflate(layoutRes, null);
}
+ public void swapInto(ViewGroup target) {
+ target.removeAllViews();
+ target.addView(mContent);
+ }
+
public void swapWith(View target) {
// Borrow layout params and id for ourselves
this.mContent.setLayoutParams(target.getLayoutParams());