mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-17 21:29:54 +01:00
Refactor ContactListAdapter to be reusable for other use cases.
This commit introduces an abstract `BaseContactListAdapter` which provides most of the adapter logic. The original `ContactListAdapter` extends it to show date and online status of the contacts. The new `ContactChooserAdapter` which is used for introductions extends the `ContactListAdapter` and adds logic for graying out contacts from different identities than the currently used one. A new `ContactSelectorAdapter` extends the `BaseContactListAdapter` and allows to select multiple contacts. It offers a method to return a collection of all selected `ContactId`s. This commit also sneaks in an animation when the 'Share Forum' button is clicked. Closes #292
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v7.util.SortedList;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.identity.Author;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import im.delight.android.identicons.IdenticonDrawable;
|
||||
|
||||
import static android.support.v7.util.SortedList.INVALID_POSITION;
|
||||
|
||||
public abstract class BaseContactListAdapter<VH extends BaseContactListAdapter.BaseContactHolder>
|
||||
extends RecyclerView.Adapter<VH> {
|
||||
|
||||
protected SortedList<ContactListItem> contacts;
|
||||
protected final OnItemClickListener listener;
|
||||
protected Context ctx;
|
||||
|
||||
public BaseContactListAdapter(Context context, OnItemClickListener listener) {
|
||||
this.ctx = context;
|
||||
this.listener = listener;
|
||||
this.contacts = new SortedList<ContactListItem>(ContactListItem.class,
|
||||
new SortedListCallBacks());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final VH ui, final int position) {
|
||||
final ContactListItem item = getItem(position);
|
||||
|
||||
Author author = item.getContact().getAuthor();
|
||||
ui.avatar.setImageDrawable(
|
||||
new IdenticonDrawable(author.getId().getBytes()));
|
||||
String contactName = author.getName();
|
||||
ui.name.setText(contactName);
|
||||
|
||||
ui.layout.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (listener != null) listener.onItemClick(ui.avatar, item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return contacts.size();
|
||||
}
|
||||
|
||||
public ContactListItem getItem(int position) {
|
||||
if (position == INVALID_POSITION || contacts.size() <= position) {
|
||||
return null; // Not found
|
||||
}
|
||||
return contacts.get(position);
|
||||
}
|
||||
|
||||
public void updateItem(int position, ContactListItem item) {
|
||||
contacts.updateItemAt(position, item);
|
||||
}
|
||||
|
||||
public int findItemPosition(ContactListItem c) {
|
||||
return contacts.indexOf(c);
|
||||
}
|
||||
|
||||
public int findItemPosition(ContactId c) {
|
||||
int count = getItemCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
ContactListItem item = getItem(i);
|
||||
if (item.getContact().getId().equals(c)) return i;
|
||||
}
|
||||
return INVALID_POSITION; // Not found
|
||||
}
|
||||
|
||||
public void addAll(List<ContactListItem> contacts) {
|
||||
this.contacts.addAll(contacts);
|
||||
}
|
||||
|
||||
public void add(ContactListItem contact) {
|
||||
contacts.add(contact);
|
||||
}
|
||||
|
||||
public void remove(ContactListItem contact) {
|
||||
contacts.remove(contact);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
contacts.clear();
|
||||
}
|
||||
|
||||
public static class BaseContactHolder extends RecyclerView.ViewHolder {
|
||||
public final ViewGroup layout;
|
||||
public final ImageView avatar;
|
||||
public final TextView name;
|
||||
|
||||
public BaseContactHolder(View v) {
|
||||
super(v);
|
||||
|
||||
layout = (ViewGroup) v;
|
||||
avatar = (ImageView) v.findViewById(R.id.avatarView);
|
||||
name = (TextView) v.findViewById(R.id.nameView);
|
||||
}
|
||||
}
|
||||
|
||||
public int compareContactListItems(ContactListItem c1, ContactListItem c2) {
|
||||
return compareByName(c1, c2);
|
||||
}
|
||||
|
||||
protected int compareByName(ContactListItem c1, ContactListItem c2) {
|
||||
int authorCompare = c1.getLocalAuthor().getName()
|
||||
.compareTo(c2.getLocalAuthor().getName());
|
||||
if (authorCompare == 0) {
|
||||
// if names are equal, compare by time instead
|
||||
return compareByTime(c1, c2);
|
||||
} else {
|
||||
return authorCompare;
|
||||
}
|
||||
}
|
||||
|
||||
protected int compareByTime(ContactListItem c1, ContactListItem c2) {
|
||||
long time1 = c1.getTimestamp();
|
||||
long time2 = c2.getTimestamp();
|
||||
if (time1 < time2) return 1;
|
||||
if (time1 > time2) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected class SortedListCallBacks extends SortedList.Callback<ContactListItem> {
|
||||
|
||||
@Override
|
||||
public void onInserted(int position, int count) {
|
||||
notifyItemRangeInserted(position, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(int position, int count) {
|
||||
notifyItemRangeChanged(position, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoved(int fromPosition, int toPosition) {
|
||||
notifyItemMoved(fromPosition, toPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoved(int position, int count) {
|
||||
notifyItemRangeRemoved(position, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(ContactListItem c1, ContactListItem c2) {
|
||||
return compareContactListItems(c1, c2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(ContactListItem c1, ContactListItem c2) {
|
||||
return c1.getContact().getId().equals(c2.getContact().getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(ContactListItem c1,
|
||||
ContactListItem c2) {
|
||||
// check for all properties that influence visual
|
||||
// representation of contact
|
||||
if (c1.isConnected() != c2.isConnected()) {
|
||||
return false;
|
||||
}
|
||||
if (c1.getUnreadCount() != c2.getUnreadCount()) {
|
||||
return false;
|
||||
}
|
||||
if (c1.getTimestamp() != c2.getTimestamp()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnItemClickListener {
|
||||
void onItemClick(View view, ContactListItem item);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +1,7 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.os.Build;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.util.SortedList;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -17,98 +10,12 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.api.identity.AuthorId;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import im.delight.android.identicons.IdenticonDrawable;
|
||||
|
||||
import static android.support.v7.util.SortedList.INVALID_POSITION;
|
||||
|
||||
public class ContactListAdapter
|
||||
extends RecyclerView.Adapter<ContactListAdapter.ContactHolder> {
|
||||
extends BaseContactListAdapter<ContactListAdapter.ContactHolder> {
|
||||
|
||||
private final SortedList<ContactListItem> contacts =
|
||||
new SortedList<ContactListItem>(ContactListItem.class,
|
||||
new SortedList.Callback<ContactListItem>() {
|
||||
@Override
|
||||
public void onInserted(int position, int count) {
|
||||
notifyItemRangeInserted(position, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(int position, int count) {
|
||||
notifyItemRangeChanged(position, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoved(int fromPosition, int toPosition) {
|
||||
notifyItemMoved(fromPosition, toPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoved(int position, int count) {
|
||||
notifyItemRangeRemoved(position, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(ContactListItem c1,
|
||||
ContactListItem c2) {
|
||||
int authorCompare = 0;
|
||||
if (chooser) {
|
||||
authorCompare = c1.getLocalAuthor().getName()
|
||||
.compareTo(
|
||||
c2.getLocalAuthor().getName());
|
||||
}
|
||||
if (authorCompare == 0) {
|
||||
// sort items by time
|
||||
// and do not take unread messages into account
|
||||
long time1 = c1.getTimestamp();
|
||||
long time2 = c2.getTimestamp();
|
||||
if (time1 < time2) return 1;
|
||||
if (time1 > time2) return -1;
|
||||
return 0;
|
||||
} else {
|
||||
return authorCompare;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(ContactListItem c1,
|
||||
ContactListItem c2) {
|
||||
return c1.getContact().getId()
|
||||
.equals(c2.getContact().getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(ContactListItem c1,
|
||||
ContactListItem c2) {
|
||||
// check for all properties that influence visual
|
||||
// representation of contact
|
||||
if (c1.isConnected() != c2.isConnected()) {
|
||||
return false;
|
||||
}
|
||||
if (c1.getUnreadCount() != c2.getUnreadCount()) {
|
||||
return false;
|
||||
}
|
||||
if (c1.getTimestamp() != c2.getTimestamp()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
private final OnItemClickListener listener;
|
||||
private final boolean chooser;
|
||||
private Context ctx;
|
||||
private AuthorId localAuthorId;
|
||||
|
||||
public ContactListAdapter(Context context, OnItemClickListener listener,
|
||||
boolean chooser) {
|
||||
ctx = context;
|
||||
this.listener = listener;
|
||||
this.chooser = chooser;
|
||||
public ContactListAdapter(Context context, OnItemClickListener listener) {
|
||||
super(context, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -121,38 +28,25 @@ public class ContactListAdapter
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final ContactHolder ui, final int position) {
|
||||
super.onBindViewHolder(ui, position);
|
||||
|
||||
final ContactListItem item = getItem(position);
|
||||
|
||||
// name and unread count
|
||||
String contactName = item.getContact().getAuthor().getName();
|
||||
int unread = item.getUnreadCount();
|
||||
if (!chooser && unread > 0) {
|
||||
ui.layout.setBackgroundColor(
|
||||
ContextCompat.getColor(ctx, R.color.unread_background));
|
||||
}
|
||||
|
||||
if (item.isConnected()) {
|
||||
ui.bulb.setImageResource(R.drawable.contact_connected);
|
||||
} else {
|
||||
ui.bulb.setImageResource(R.drawable.contact_disconnected);
|
||||
}
|
||||
|
||||
Author author = item.getContact().getAuthor();
|
||||
ui.avatar.setImageDrawable(
|
||||
new IdenticonDrawable(author.getId().getBytes()));
|
||||
String contactName = author.getName();
|
||||
|
||||
if (!chooser && unread > 0) {
|
||||
if (unread > 0) {
|
||||
// TODO show these in a bubble on top of the avatar
|
||||
ui.name.setText(contactName + " (" + unread + ")");
|
||||
|
||||
// different background for contacts with unread messages
|
||||
ui.layout.setBackgroundColor(
|
||||
ContextCompat.getColor(ctx, R.color.unread_background));
|
||||
} else {
|
||||
ui.name.setText(contactName);
|
||||
}
|
||||
|
||||
if (chooser) {
|
||||
ui.identity.setText(item.getLocalAuthor().getName());
|
||||
} else {
|
||||
ui.identity.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// date of last message
|
||||
if (item.isEmpty()) {
|
||||
ui.date.setText(R.string.no_private_messages);
|
||||
} else {
|
||||
@@ -162,115 +56,33 @@ public class ContactListAdapter
|
||||
DateUtils.getRelativeTimeSpanString(ctx, timestamp));
|
||||
}
|
||||
|
||||
if (chooser && !item.getLocalAuthor().getId().equals(localAuthorId)) {
|
||||
grayOutItem(ui);
|
||||
}
|
||||
|
||||
ui.layout.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onItemClick(ui.avatar, item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return contacts.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the identity from whose perspective the contact shall be chosen.
|
||||
* This is only used if chooser is true.
|
||||
* @param authorId The ID of the local Author
|
||||
*/
|
||||
public void setLocalAuthor(AuthorId authorId) {
|
||||
localAuthorId = authorId;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void grayOutItem(final ContactHolder ui) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
float alpha = 0.25f;
|
||||
ui.bulb.setAlpha(alpha);
|
||||
ui.avatar.setAlpha(alpha);
|
||||
ui.name.setAlpha(alpha);
|
||||
ui.date.setAlpha(alpha);
|
||||
ui.identity.setAlpha(alpha);
|
||||
// online/offline
|
||||
if (item.isConnected()) {
|
||||
ui.bulb.setImageResource(R.drawable.contact_connected);
|
||||
} else {
|
||||
ColorFilter colorFilter = new PorterDuffColorFilter(Color.GRAY,
|
||||
PorterDuff.Mode.MULTIPLY);
|
||||
ui.bulb.setColorFilter(colorFilter);
|
||||
ui.avatar.setColorFilter(colorFilter);
|
||||
ui.name.setEnabled(false);
|
||||
ui.date.setEnabled(false);
|
||||
ui.bulb.setImageResource(R.drawable.contact_disconnected);
|
||||
}
|
||||
}
|
||||
|
||||
public ContactListItem getItem(int position) {
|
||||
if (position == INVALID_POSITION || contacts.size() <= position) {
|
||||
return null; // Not found
|
||||
}
|
||||
return contacts.get(position);
|
||||
}
|
||||
protected static class ContactHolder
|
||||
extends BaseContactListAdapter.BaseContactHolder {
|
||||
|
||||
public void updateItem(int position, ContactListItem item) {
|
||||
contacts.updateItemAt(position, item);
|
||||
}
|
||||
|
||||
public int findItemPosition(ContactId c) {
|
||||
int count = getItemCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
ContactListItem item = getItem(i);
|
||||
if (item.getContact().getId().equals(c)) return i;
|
||||
}
|
||||
return INVALID_POSITION; // Not found
|
||||
}
|
||||
|
||||
public void addAll(List<ContactListItem> contacts) {
|
||||
this.contacts.addAll(contacts);
|
||||
}
|
||||
|
||||
public void add(ContactListItem contact) {
|
||||
contacts.add(contact);
|
||||
}
|
||||
|
||||
public void remove(ContactListItem contact) {
|
||||
contacts.remove(contact);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
contacts.beginBatchedUpdates();
|
||||
|
||||
while(contacts.size() != 0) {
|
||||
contacts.removeItemAt(0);
|
||||
}
|
||||
|
||||
contacts.endBatchedUpdates();
|
||||
}
|
||||
|
||||
public static class ContactHolder extends RecyclerView.ViewHolder {
|
||||
public ViewGroup layout;
|
||||
public ImageView bulb;
|
||||
public ImageView avatar;
|
||||
public TextView name;
|
||||
public TextView identity;
|
||||
public TextView date;
|
||||
public final ImageView bulb;
|
||||
public final TextView date;
|
||||
public final TextView identity;
|
||||
|
||||
public ContactHolder(View v) {
|
||||
super(v);
|
||||
|
||||
layout = (ViewGroup) v;
|
||||
bulb = (ImageView) v.findViewById(R.id.bulbView);
|
||||
avatar = (ImageView) v.findViewById(R.id.avatarView);
|
||||
name = (TextView) v.findViewById(R.id.nameView);
|
||||
identity = (TextView) v.findViewById(R.id.identityView);
|
||||
date = (TextView) v.findViewById(R.id.dateView);
|
||||
identity = (TextView) v.findViewById(R.id.identityView);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnItemClickListener {
|
||||
void onItemClick(View view, ContactListItem item);
|
||||
@Override
|
||||
public int compareContactListItems(ContactListItem c1, ContactListItem c2) {
|
||||
return compareByTime(c1, c2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ public class ContactListFragment extends BaseEventFragment {
|
||||
inflater.inflate(R.layout.activity_contact_list, container,
|
||||
false);
|
||||
|
||||
ContactListAdapter.OnItemClickListener onItemClickListener =
|
||||
BaseContactListAdapter.OnItemClickListener onItemClickListener =
|
||||
new ContactListAdapter.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(View view, ContactListItem item) {
|
||||
@@ -124,8 +124,7 @@ public class ContactListFragment extends BaseEventFragment {
|
||||
}
|
||||
};
|
||||
|
||||
adapter = new ContactListAdapter(getContext(), onItemClickListener,
|
||||
false);
|
||||
adapter = new ContactListAdapter(getContext(), onItemClickListener);
|
||||
list = (BriarRecyclerView) contentView.findViewById(R.id.contactList);
|
||||
list.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
list.setAdapter(adapter);
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnMultiChoiceClickListener;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class SelectContactsDialog implements OnMultiChoiceClickListener {
|
||||
|
||||
private Listener listener = null;
|
||||
private List<Contact> contacts = null;
|
||||
private Set<ContactId> selected = null;
|
||||
|
||||
public void setListener(Listener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void setContacts(Collection<Contact> contacts) {
|
||||
this.contacts = new ArrayList<Contact>(contacts);
|
||||
}
|
||||
|
||||
public void setSelected(Collection<ContactId> selected) {
|
||||
this.selected = new HashSet<ContactId>(selected);
|
||||
}
|
||||
|
||||
public Dialog build(Context ctx) {
|
||||
if (listener == null || contacts == null || selected == null)
|
||||
throw new IllegalStateException();
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(ctx,
|
||||
R.style.BriarDialogTheme);
|
||||
int size = contacts.size();
|
||||
String[] names = new String[size];
|
||||
boolean[] checked = new boolean[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
Contact c = contacts.get(i);
|
||||
names[i] = c.getAuthor().getName();
|
||||
checked[i] = selected.contains(c.getId());
|
||||
}
|
||||
builder.setMultiChoiceItems(names, checked, this);
|
||||
builder.setPositiveButton(R.string.done_button,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
listener.contactsSelected(selected);
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(R.string.cancel_button,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
listener.contactSelectionCancelled();
|
||||
}
|
||||
});
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
|
||||
if (isChecked) selected.add(contacts.get(which).getId());
|
||||
else selected.remove(contacts.get(which).getId());
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
|
||||
void contactsSelected(Collection<ContactId> selected);
|
||||
|
||||
void contactSelectionCancelled();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user