Refactor ContactSelectionListAdapter and associated views.

Fixes #3181
Closes #3197

// FREEBIE
This commit is contained in:
Moxie Marlinspike 2015-05-19 14:00:54 -07:00
parent 3e890e11d9
commit 636b11abea
8 changed files with 227 additions and 382 deletions

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <org.thoughtcrime.securesms.contacts.ContactSelectionListItem
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight" android:layout_height="?android:attr/listPreferredItemHeight"
android:paddingRight="25dip"> android:paddingRight="25dip">
@ -16,23 +17,41 @@
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:contentDescription="@string/SingleContactSelectionActivity_contact_photo" /> android:contentDescription="@string/SingleContactSelectionActivity_contact_photo" />
<TextView android:id="@+id/number" <LinearLayout
android:id="@+id/number_container"
android:orientation="horizontal"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="8dip" android:layout_marginBottom="8dip"
android:layout_marginTop="-8dip"
android:layout_marginLeft="14dip" android:layout_marginLeft="14dip"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"
android:layout_toRightOf="@id/contact_photo_image">
<TextView android:id="@+id/number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true" android:singleLine="true"
android:ellipsize="marquee" android:ellipsize="marquee"
android:layout_toRightOf="@id/contact_photo_image"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"
android:fontFamily="sans-serif-light" /> android:fontFamily="sans-serif-light" />
<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dip"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?contact_selection_label_text"
android:fontFamily="sans-serif-light"/>
</LinearLayout>
<TextView android:id="@+id/name" <TextView android:id="@+id/name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_above="@id/number" android:layout_above="@id/number_container"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_marginBottom="1dip" android:layout_marginBottom="1dip"
android:layout_marginLeft="14dip" android:layout_marginLeft="14dip"
@ -51,4 +70,4 @@
android:focusable="false" android:focusable="false"
android:clickable="false" /> android:clickable="false" />
</RelativeLayout> </org.thoughtcrime.securesms.contacts.ContactSelectionListItem>

View File

@ -380,11 +380,9 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity {
switch (reqCode) { switch (reqCode) {
case PICK_CONTACT: case PICK_CONTACT:
List<ContactData> selected = data.getParcelableArrayListExtra("contacts"); List<String> selected = data.getStringArrayListExtra("contacts");
for (ContactData contact : selected) { for (String contact : selected) {
for (ContactAccessor.NumberData numberData : contact.numbers) { Recipient recipient = RecipientFactory.getRecipientsFromString(this, contact, false).getPrimaryRecipient();
Recipient recipient = RecipientFactory.getRecipientsFromString(this, numberData.number, false)
.getPrimaryRecipient();
if (!selectedContacts.contains(recipient) && if (!selectedContacts.contains(recipient) &&
(existingContacts == null || !existingContacts.contains(recipient)) && (existingContacts == null || !existingContacts.contains(recipient)) &&
@ -392,7 +390,6 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity {
addSelectedContact(recipient); addSelectedContact(recipient);
} }
} }
}
syncAdapterWithSelectedContacts(); syncAdapterWithSelectedContacts();
break; break;

View File

@ -24,25 +24,15 @@ import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import org.thoughtcrime.securesms.contacts.ContactAccessor; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.DirectoryHelper; import org.thoughtcrime.securesms.util.DirectoryHelper;
import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.NumberUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import static org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
/** /**
* Activity container for selecting a list of contacts. * Activity container for selecting a list of contacts.
@ -67,7 +57,6 @@ public class NewConversationActivity extends PassphraseRequiredActionBarActivity
@Override @Override
protected void onCreate(Bundle icicle, @NonNull MasterSecret masterSecret) { protected void onCreate(Bundle icicle, @NonNull MasterSecret masterSecret) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.new_conversation_activity); setContentView(R.layout.new_conversation_activity);
initializeResources(); initializeResources();
} }
@ -94,7 +83,6 @@ public class NewConversationActivity extends PassphraseRequiredActionBarActivity
super.onOptionsItemSelected(item); super.onOptionsItemSelected(item);
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.menu_refresh_directory: handleDirectoryRefresh(); return true; case R.id.menu_refresh_directory: handleDirectoryRefresh(); return true;
case R.id.menu_selection_finished: handleSelectionFinished(); return true;
case android.R.id.home: finish(); return true; case android.R.id.home: finish(); return true;
} }
return false; return false;
@ -104,24 +92,14 @@ public class NewConversationActivity extends PassphraseRequiredActionBarActivity
contactsFragment = (PushContactSelectionListFragment) getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment); contactsFragment = (PushContactSelectionListFragment) getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
contactsFragment.setOnContactSelectedListener(new PushContactSelectionListFragment.OnContactSelectedListener() { contactsFragment.setOnContactSelectedListener(new PushContactSelectionListFragment.OnContactSelectedListener() {
@Override @Override
public void onContactSelected(ContactData contactData) { public void onContactSelected(String number) {
Log.i(TAG, "Choosing contact from list."); Log.i(TAG, "Choosing contact from list.");
Recipients recipients = contactDataToRecipients(contactData); Recipients recipients = RecipientFactory.getRecipientsFromString(NewConversationActivity.this, number, true);
openNewConversation(recipients); openNewConversation(recipients);
} }
}); });
} }
private void handleSelectionFinished() {
final Intent resultIntent = getIntent();
final List<ContactData> selectedContacts = contactsFragment.getSelectedContacts();
if (selectedContacts != null) {
resultIntent.putParcelableArrayListExtra("contacts", new ArrayList<>(contactsFragment.getSelectedContacts()));
}
setResult(RESULT_OK, resultIntent);
finish();
}
private void handleDirectoryRefresh() { private void handleDirectoryRefresh() {
DirectoryHelper.refreshDirectoryWithProgressDialog(this, new DirectoryHelper.DirectoryUpdateFinishedListener() { DirectoryHelper.refreshDirectoryWithProgressDialog(this, new DirectoryHelper.DirectoryUpdateFinishedListener() {
@Override @Override
@ -131,20 +109,6 @@ public class NewConversationActivity extends PassphraseRequiredActionBarActivity
}); });
} }
private Recipients contactDataToRecipients(ContactData contactData) {
if (contactData == null || contactData.numbers == null) return null;
Recipients recipients = new Recipients(new LinkedList<Recipient>());
for (ContactAccessor.NumberData numberData : contactData.numbers) {
if (NumberUtil.isValidSmsOrEmailOrGroup(numberData.number)) {
Recipients recipientsForNumber = RecipientFactory.getRecipientsFromString(NewConversationActivity.this,
numberData.number,
false);
recipients.getRecipientsList().addAll(recipientsForNumber.getRecipientsList());
}
}
return recipients;
}
private void openNewConversation(Recipients recipients) { private void openNewConversation(Recipients recipients) {
if (recipients != null) { if (recipients != null) {
Intent intent = new Intent(this, ConversationActivity.class); Intent intent = new Intent(this, ConversationActivity.class);

View File

@ -19,7 +19,6 @@ package org.thoughtcrime.securesms;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
@ -30,12 +29,9 @@ import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
/** /**
* Activity container for selecting a list of contacts. * Activity container for selecting a list of contacts.
* *
@ -98,21 +94,16 @@ public class PushContactSelectionActivity extends PassphraseRequiredActionBarAct
private void initializeResources() { private void initializeResources() {
contactsFragment = (PushContactSelectionListFragment) getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment); contactsFragment = (PushContactSelectionListFragment) getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
contactsFragment.setMultiSelect(true); contactsFragment.setMultiSelect(true);
contactsFragment.setOnContactSelectedListener(new PushContactSelectionListFragment.OnContactSelectedListener() {
@Override
public void onContactSelected(ContactData contactData) {
Log.i(TAG, "Choosing contact from list.");
}
});
} }
private void handleSelectionFinished() { private void handleSelectionFinished() {
Intent resultIntent = getIntent();
List<String> selectedContacts = contactsFragment.getSelectedContacts();
final Intent resultIntent = getIntent();
final List<ContactData> selectedContacts = contactsFragment.getSelectedContacts();
if (selectedContacts != null) { if (selectedContacts != null) {
resultIntent.putParcelableArrayListExtra("contacts", new ArrayList<ContactData>(contactsFragment.getSelectedContacts())); resultIntent.putStringArrayListExtra("contacts", new ArrayList<>(selectedContacts));
} }
setResult(RESULT_OK, resultIntent); setResult(RESULT_OK, resultIntent);
finish(); finish();
} }

View File

@ -19,14 +19,12 @@ package org.thoughtcrime.securesms;
import android.database.Cursor; import android.database.Cursor;
import android.os.Bundle; import android.os.Bundle;
import android.provider.ContactsContract;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter; import android.support.v4.widget.CursorAdapter;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -35,11 +33,8 @@ import android.widget.EditText;
import android.widget.TextView; import android.widget.TextView;
import org.thoughtcrime.securesms.contacts.ContactAccessor; import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter; import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter;
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter.ViewHolder; import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter.DataHolder;
import org.thoughtcrime.securesms.contacts.ContactsDatabase;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -61,13 +56,12 @@ public class PushContactSelectionListFragment extends Fragment
private TextView emptyText; private TextView emptyText;
private Map<Long, ContactData> selectedContacts; private Map<Long, String> selectedContacts;
private OnContactSelectedListener onContactSelectedListener; private OnContactSelectedListener onContactSelectedListener;
private boolean multi = false;
private StickyListHeadersListView listView; private StickyListHeadersListView listView;
private EditText filterEditText;
private String cursorFilter; private String cursorFilter;
private boolean multi = false;
@Override @Override
public void onActivityCreated(Bundle icicle) { public void onActivityCreated(Bundle icicle) {
@ -91,10 +85,10 @@ public class PushContactSelectionListFragment extends Fragment
return inflater.inflate(R.layout.push_contact_selection_list_activity, container, false); return inflater.inflate(R.layout.push_contact_selection_list_activity, container, false);
} }
public List<ContactData> getSelectedContacts() { public List<String> getSelectedContacts() {
if (selectedContacts == null) return null; if (selectedContacts == null) return null;
List<ContactData> selected = new LinkedList<ContactData>(); List<String> selected = new LinkedList<>();
selected.addAll(selectedContacts.values()); selected.addAll(selectedContacts.values());
return selected; return selected;
@ -104,23 +98,6 @@ public class PushContactSelectionListFragment extends Fragment
this.multi = multi; this.multi = multi;
} }
private void addContact(DataHolder data) {
final ContactData contactData = new ContactData(data.id, data.name);
final CharSequence label = ContactsContract.CommonDataKinds.Phone.getTypeLabel(getResources(),
data.numberType, "");
contactData.numbers.add(new ContactAccessor.NumberData(label.toString(), data.number));
if (multi) {
selectedContacts.put(contactData.id, contactData);
}
if (onContactSelectedListener != null) {
onContactSelectedListener.onContactSelected(contactData);
}
}
private void removeContact(DataHolder contactData) {
selectedContacts.remove(contactData.id);
}
private void initializeCursor() { private void initializeCursor() {
ContactSelectionListAdapter adapter = new ContactSelectionListAdapter(getActivity(), null, multi); ContactSelectionListAdapter adapter = new ContactSelectionListAdapter(getActivity(), null, multi);
selectedContacts = adapter.getSelectedContacts(); selectedContacts = adapter.getSelectedContacts();
@ -135,7 +112,8 @@ public class PushContactSelectionListFragment extends Fragment
listView.setFastScrollEnabled(true); listView.setFastScrollEnabled(true);
listView.setDrawingListUnderStickyHeader(false); listView.setDrawingListUnderStickyHeader(false);
listView.setOnItemClickListener(new ListClickListener()); listView.setOnItemClickListener(new ListClickListener());
filterEditText = (EditText) getView().findViewById(R.id.filter);
EditText filterEditText = (EditText) getView().findViewById(R.id.filter);
filterEditText.addTextChangedListener(new TextWatcher() { filterEditText.addTextChangedListener(new TextWatcher() {
@Override @Override
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
@ -185,20 +163,15 @@ public class PushContactSelectionListFragment extends Fragment
private class ListClickListener implements AdapterView.OnItemClickListener { private class ListClickListener implements AdapterView.OnItemClickListener {
@Override @Override
public void onItemClick(AdapterView<?> l, View v, int position, long id) { public void onItemClick(AdapterView<?> l, View v, int position, long id) {
final DataHolder contactData = (DataHolder) v.getTag(R.id.contact_info_tag); ContactSelectionListItem contact = (ContactSelectionListItem)v;
final ViewHolder holder = (ViewHolder) v.getTag(R.id.holder_tag);
if (holder == null) { if (!multi || !selectedContacts.containsKey(contact.getContactId())) {
Log.w(TAG, "ViewHolder was null, can't proceed with click logic."); selectedContacts.put(contact.getContactId(), contact.getNumber());
return; contact.setChecked(true);
} if (onContactSelectedListener != null) onContactSelectedListener.onContactSelected(contact.getNumber());
} else {
if (multi) holder.checkBox.toggle(); selectedContacts.remove(contact.getContactId());
contact.setChecked(false);
if (!multi || holder.checkBox.isChecked()) {
addContact(contactData);
} else if (multi) {
removeContact(contactData);
} }
} }
} }
@ -208,6 +181,6 @@ public class PushContactSelectionListFragment extends Fragment
} }
public interface OnContactSelectedListener { public interface OnContactSelectedListener {
public void onContactSelected(ContactData contactData); public void onContactSelected(String number);
} }
} }

View File

@ -21,27 +21,15 @@ import android.content.res.TypedArray;
import android.database.Cursor; import android.database.Cursor;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import android.support.v4.widget.CursorAdapter; import android.support.v4.widget.CursorAdapter;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.BitmapWorkerRunnable;
import org.thoughtcrime.securesms.util.BitmapWorkerRunnable.AsyncDrawable;
import org.thoughtcrime.securesms.util.TaggedFutureTask;
import org.thoughtcrime.securesms.util.Util;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.FutureTask;
import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
@ -53,53 +41,22 @@ import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
public class ContactSelectionListAdapter extends CursorAdapter public class ContactSelectionListAdapter extends CursorAdapter
implements StickyListHeadersAdapter implements StickyListHeadersAdapter
{ {
private final static String TAG = "ContactListAdapter"; private final static String TAG = ContactSelectionListAdapter.class.getSimpleName();
private final static ExecutorService photoResolver = Util.newSingleThreadedLifoExecutor();
private final static int STYLE_ATTRIBUTES[] = new int[]{R.attr.contact_selection_push_user, private final static int STYLE_ATTRIBUTES[] = new int[]{R.attr.contact_selection_push_user,
R.attr.contact_selection_lay_user, R.attr.contact_selection_lay_user};
R.attr.contact_selection_label_text};
private int TYPE_COLUMN = -1;
private int NAME_COLUMN = -1;
private int NUMBER_COLUMN = -1;
private int NUMBER_TYPE_COLUMN = -1;
private int LABEL_COLUMN = -1;
private int ID_COLUMN = -1;
private final Context context;
private final boolean multiSelect; private final boolean multiSelect;
private final LayoutInflater li; private final LayoutInflater li;
private final TypedArray drawables; private final TypedArray drawables;
private final int scaledPhotoSize;
private final HashMap<Long, ContactAccessor.ContactData> selectedContacts = new HashMap<>(); private final HashMap<Long, String> selectedContacts = new HashMap<>();
public ContactSelectionListAdapter(Context context, Cursor cursor, boolean multiSelect) { public ContactSelectionListAdapter(Context context, Cursor cursor, boolean multiSelect) {
super(context, cursor, 0); super(context, cursor, 0);
this.context = context;
this.li = LayoutInflater.from(context); this.li = LayoutInflater.from(context);
this.drawables = context.obtainStyledAttributes(STYLE_ATTRIBUTES); this.drawables = context.obtainStyledAttributes(STYLE_ATTRIBUTES);
this.multiSelect = multiSelect; this.multiSelect = multiSelect;
this.scaledPhotoSize = context.getResources().getDimensionPixelSize(R.dimen.contact_selection_photo_size);
}
public static class ViewHolder {
public CheckBox checkBox;
public TextView name;
public TextView number;
public ImageView contactPhoto;
public int position;
}
public static class DataHolder {
public int type;
public String name;
public String number;
public int numberType;
public String label;
public long id;
} }
public static class HeaderViewHolder { public static class HeaderViewHolder {
@ -108,83 +65,35 @@ public class ContactSelectionListAdapter extends CursorAdapter
@Override @Override
public View newView(Context context, Cursor cursor, ViewGroup parent) { public View newView(Context context, Cursor cursor, ViewGroup parent) {
final View v = li.inflate(R.layout.push_contact_selection_list_item, parent, false); return li.inflate(R.layout.push_contact_selection_list_item, parent, false);
final ViewHolder holder = new ViewHolder();
if (v != null) {
holder.name = (TextView) v.findViewById(R.id.name);
holder.number = (TextView) v.findViewById(R.id.number);
holder.checkBox = (CheckBox) v.findViewById(R.id.check_box);
holder.contactPhoto = (ImageView) v.findViewById(R.id.contact_photo_image);
if (!multiSelect) holder.checkBox.setVisibility(View.GONE);
v.setTag(R.id.holder_tag, holder);
v.setTag(R.id.contact_info_tag, new DataHolder());
}
return v;
} }
@Override @Override
public void bindView(View view, Context context, Cursor cursor) { public void bindView(View view, Context context, Cursor cursor) {
final DataHolder contactData = (DataHolder) view.getTag(R.id.contact_info_tag); long id = cursor.getLong(cursor.getColumnIndexOrThrow(ContactsDatabase.ID_COLUMN));
final ViewHolder holder = (ViewHolder) view.getTag(R.id.holder_tag); int type = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.TYPE_COLUMN));
if (holder == null) { String name = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NAME_COLUMN));
Log.w(TAG, "ViewHolder was null. This should not happen."); String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_COLUMN));
return; int numberType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_TYPE_COLUMN));
} String label = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.LABEL_COLUMN));
if (contactData == null) { String labelText = ContactsContract.CommonDataKinds.Phone.getTypeLabel(context.getResources(),
Log.w(TAG, "DataHolder was null. This should not happen."); numberType, label).toString();
return;
}
if (ID_COLUMN < 0) {
populateColumnIndices(cursor);
}
contactData.type = cursor.getInt(TYPE_COLUMN); int color = (type == ContactsDatabase.PUSH_TYPE) ? drawables.getColor(0, 0xa0000000) :
contactData.name = cursor.getString(NAME_COLUMN); drawables.getColor(1, 0xff000000);
contactData.number = cursor.getString(NUMBER_COLUMN);
contactData.numberType = cursor.getInt(NUMBER_TYPE_COLUMN);
contactData.label = cursor.getString(LABEL_COLUMN);
contactData.id = cursor.getLong(ID_COLUMN);
if (contactData.type != ContactsDatabase.PUSH_TYPE) {
holder.name.setTextColor(drawables.getColor(1, 0xff000000));
holder.number.setTextColor(drawables.getColor(1, 0xff000000));
} else {
holder.name.setTextColor(drawables.getColor(0, 0xa0000000));
holder.number.setTextColor(drawables.getColor(0, 0xa0000000));
}
if (selectedContacts.containsKey(contactData.id)) { ((ContactSelectionListItem)view).unbind();
holder.checkBox.setChecked(true); ((ContactSelectionListItem)view).set(id, type, name, number, labelText, color, multiSelect);
} else { ((ContactSelectionListItem)view).setChecked(selectedContacts.containsKey(id));
holder.checkBox.setChecked(false);
}
holder.name.setText(contactData.name);
if (contactData.number == null || contactData.number.isEmpty()) {
holder.name.setEnabled(false);
holder.number.setText("");
} else if (contactData.type == ContactsDatabase.PUSH_TYPE) {
holder.number.setText(contactData.number);
} else {
final CharSequence label = ContactsContract.CommonDataKinds.Phone.getTypeLabel(context.getResources(),
contactData.numberType, contactData.label);
final CharSequence numberWithLabel = contactData.number + " " + label;
final Spannable numberLabelSpan = new SpannableString(numberWithLabel);
numberLabelSpan.setSpan(new ForegroundColorSpan(drawables.getColor(2, 0xff444444)), contactData.number.length(), numberWithLabel.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
holder.number.setText(numberLabelSpan);
}
holder.contactPhoto.setImageDrawable(ContactPhotoFactory.getLoadingPhoto(context));
if (contactData.id > -1) loadBitmap(contactData.number, holder.contactPhoto);
} }
@Override @Override
public View getHeaderView(int i, View convertView, ViewGroup viewGroup) { public View getHeaderView(int i, View convertView, ViewGroup viewGroup) {
final Cursor c = getCursor(); Cursor cursor = getCursor();
final HeaderViewHolder holder;
HeaderViewHolder holder;
if (convertView == null) { if (convertView == null) {
holder = new HeaderViewHolder(); holder = new HeaderViewHolder();
convertView = li.inflate(R.layout.push_contact_selection_list_header, viewGroup, false); convertView = li.inflate(R.layout.push_contact_selection_list_header, viewGroup, false);
@ -193,63 +102,26 @@ public class ContactSelectionListAdapter extends CursorAdapter
} else { } else {
holder = (HeaderViewHolder) convertView.getTag(); holder = (HeaderViewHolder) convertView.getTag();
} }
c.moveToPosition(i);
final int type = c.getInt(c.getColumnIndexOrThrow(ContactsDatabase.TYPE_COLUMN)); cursor.moveToPosition(i);
final int headerTextRes;
switch (type) { int type = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.TYPE_COLUMN));
case 1: headerTextRes = R.string.contact_selection_list__header_textsecure_users; break;
default: headerTextRes = R.string.contact_selection_list__header_other; break; if (type == ContactsDatabase.PUSH_TYPE) holder.text.setText(R.string.contact_selection_list__header_textsecure_users);
} else holder.text.setText(R.string.contact_selection_list__header_other);
holder.text.setText(headerTextRes);
return convertView; return convertView;
} }
@Override @Override
public long getHeaderId(int i) { public long getHeaderId(int i) {
final Cursor c = getCursor(); Cursor cursor = getCursor();
c.moveToPosition(i); cursor.moveToPosition(i);
return c.getInt(c.getColumnIndexOrThrow(ContactsDatabase.TYPE_COLUMN));
return cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.TYPE_COLUMN));
} }
public boolean cancelPotentialWork(String number, ImageView imageView) { public Map<Long, String> getSelectedContacts() {
final TaggedFutureTask<?> bitmapWorkerTask = AsyncDrawable.getBitmapWorkerTask(imageView);
if (bitmapWorkerTask != null) {
final Object tag = bitmapWorkerTask.getTag();
if (tag != null && !tag.equals(number)) {
bitmapWorkerTask.cancel(true);
} else {
return false;
}
}
return true;
}
// FIXME -- It should be unnecessary to duplicate the existing asynchronous resolution
// infrastructure we've built for Recipient objects here.
public void loadBitmap(String number, ImageView imageView) {
if (cancelPotentialWork(number, imageView)) {
final BitmapWorkerRunnable runnable = new BitmapWorkerRunnable(context, imageView, number, scaledPhotoSize);
final TaggedFutureTask<?> task = new TaggedFutureTask<Void>(runnable, null, number);
final AsyncDrawable asyncDrawable = new AsyncDrawable(task);
imageView.setImageDrawable(asyncDrawable);
if (!task.isCancelled()) photoResolver.execute(new FutureTask<Void>(task, null));
}
}
public Map<Long,ContactAccessor.ContactData> getSelectedContacts() {
return selectedContacts; return selectedContacts;
} }
private void populateColumnIndices(final Cursor cursor) {
this.TYPE_COLUMN = cursor.getColumnIndexOrThrow(ContactsDatabase.TYPE_COLUMN);
this.NAME_COLUMN = cursor.getColumnIndexOrThrow(ContactsDatabase.NAME_COLUMN);
this.NUMBER_COLUMN = cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_COLUMN);
this.NUMBER_TYPE_COLUMN = cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_TYPE_COLUMN);
this.LABEL_COLUMN = cursor.getColumnIndexOrThrow(ContactsDatabase.LABEL_COLUMN);
this.ID_COLUMN = cursor.getColumnIndexOrThrow(ContactsDatabase.ID_COLUMN);
}
} }

View File

@ -0,0 +1,126 @@
package org.thoughtcrime.securesms.contacts;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
public class ContactSelectionListItem extends RelativeLayout implements Recipient.RecipientModifiedListener {
private ImageView contactPhotoImage;
private TextView numberView;
private TextView nameView;
private TextView labelView;
private CheckBox checkBox;
private long id;
private String number;
private Recipient recipient;
public ContactSelectionListItem(Context context) {
super(context);
}
public ContactSelectionListItem(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ContactSelectionListItem(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onFinishInflate() {
this.contactPhotoImage = (ImageView) findViewById(R.id.contact_photo_image);
this.numberView = (TextView) findViewById(R.id.number);
this.labelView = (TextView) findViewById(R.id.label);
this.nameView = (TextView) findViewById(R.id.name);
this.checkBox = (CheckBox) findViewById(R.id.check_box);
}
public void set(long id, int type, String name, String number, String label, int color, boolean multiSelect) {
this.id = id;
this.number = number;
if (number != null) {
this.recipient = RecipientFactory.getRecipientsFromString(getContext(), number, true)
.getPrimaryRecipient();
}
this.nameView.setTextColor(color);
this.numberView.setTextColor(color);
setText(type, name, number, label);
setContactPhotoImage(recipient);
if (multiSelect) this.checkBox.setVisibility(View.VISIBLE);
else this.checkBox.setVisibility(View.GONE);
}
public void setChecked(boolean selected) {
this.checkBox.setChecked(selected);
}
public void unbind() {
if (recipient != null) {
recipient.removeListener(this);
recipient = null;
}
}
private void setText(int type, String name, String number, String label) {
if (number == null || number.isEmpty()) {
this.nameView.setEnabled(false);
this.numberView.setText("");
this.labelView.setVisibility(View.GONE);
} else if (type == ContactsDatabase.PUSH_TYPE) {
this.numberView.setText(number);
this.nameView.setEnabled(true);
this.labelView.setVisibility(View.GONE);
} else {
this.numberView.setText(number);
this.nameView.setEnabled(true);
this.labelView.setText(label);
this.labelView.setVisibility(View.VISIBLE);
}
this.nameView.setText(name);
}
private void setContactPhotoImage(@Nullable Recipient recipient) {
if (recipient!= null) {
contactPhotoImage.setImageDrawable(recipient.getContactPhoto());
recipient.addListener(this);
} else {
contactPhotoImage.setImageDrawable(null);
}
}
@Override
public void onModified(final Recipient recipient) {
if (this.recipient == recipient) {
this.contactPhotoImage.post(new Runnable() {
@Override
public void run() {
contactPhotoImage.setImageDrawable(recipient.getContactPhoto());
}
});
}
}
public long getContactId() {
return id;
}
public String getNumber() {
return number;
}
}

View File

@ -1,97 +0,0 @@
/**
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.util;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.DrawableContainer;
import android.widget.ImageView;
import com.makeramen.RoundedDrawable;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import java.lang.ref.WeakReference;
/**
* Runnable to load contact photos if they have them
*
* @author Jake McGinty
*/
public class BitmapWorkerRunnable implements Runnable {
private final static String TAG = BitmapWorkerRunnable.class.getSimpleName();
private final WeakReference<ImageView> imageViewReference;
private final Context context;
private final int size;
public final String number;
public BitmapWorkerRunnable(Context context, ImageView imageView, String number, int size) {
this.imageViewReference = new WeakReference<>(imageView);
this.context = context;
this.size = size;
this.number = number;
}
@Override
public void run() {
final Recipient recipient = RecipientFactory.getRecipientsFromString(context, number, false).getPrimaryRecipient();
final Drawable contactPhoto = recipient.getContactPhoto();
if (contactPhoto != null) {
final ImageView imageView = imageViewReference.get();
final TaggedFutureTask<?> bitmapWorkerTask = AsyncDrawable.getBitmapWorkerTask(imageView);
if (bitmapWorkerTask.getTag().equals(number) && imageView != null) {
imageView.post(new Runnable() {
@Override
public void run() {
imageView.setImageDrawable(contactPhoto);
}
});
}
}
}
public static class AsyncDrawable extends BitmapDrawable {
private final WeakReference<TaggedFutureTask<?>> bitmapWorkerTaskReference;
public AsyncDrawable(TaggedFutureTask<?> bitmapWorkerTask) {
bitmapWorkerTaskReference =
new WeakReference<TaggedFutureTask<?>>(bitmapWorkerTask);
}
public TaggedFutureTask<?> getBitmapWorkerTask() {
return bitmapWorkerTaskReference.get();
}
public static TaggedFutureTask<?> getBitmapWorkerTask(ImageView imageView) {
if (imageView != null) {
final Drawable drawable = imageView.getDrawable();
if (drawable instanceof AsyncDrawable) {
final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
return asyncDrawable.getBitmapWorkerTask();
}
}
return null;
}
}
}