Merge branch '82-identicons' into 'master'

Identicons

Closes #120. Part of #82.

I pulled in the identicon code because there is no published library, and also because when I used it for Bote I ended up modifying the code somewhat for my purposes; I expect the same to happen here.

I am not sure what information should be used to seed the identicon. I originally thought of using the public key, but that seemed to change the identicon every single page load (obviously the representation wasn't consistent). Then I tried using the `ContactId`, which was fine until I got to adding an identicon to `AuthorView`: activities that use it are only passed (via Intent) a name, not anything else. Hence `AuthorView` identicons are currently inconsistent with the contact list and conversations.

See merge request !51
This commit is contained in:
akwizgran
2016-01-27 17:49:52 +00:00
22 changed files with 835 additions and 110 deletions

View File

@@ -14,10 +14,14 @@ import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.identity.Author;
import org.briarproject.api.sync.GroupId;
import java.util.List;
import im.delight.android.identicons.IdenticonDrawable;
import static android.support.v7.util.SortedList.INVALID_POSITION;
public class ContactListAdapter
@@ -83,9 +87,11 @@ public class ContactListAdapter
}
});
private Context ctx;
private CryptoComponent crypto;
public ContactListAdapter(Context context) {
public ContactListAdapter(Context context, CryptoComponent cryptoComponent) {
ctx = context;
crypto = cryptoComponent;
}
@Override
@@ -113,7 +119,10 @@ public class ContactListAdapter
ui.bulb.setImageResource(R.drawable.contact_disconnected);
}
String contactName = item.getContact().getAuthor().getName();
Author author = item.getContact().getAuthor();
ui.avatar.setImageDrawable(
new IdenticonDrawable(crypto, author.getId().getBytes()));
String contactName = author.getName();
if (unread > 0) {
ui.name.setText(contactName + " (" + unread + ")");
} else {
@@ -193,6 +202,7 @@ public class ContactListAdapter
public static class ContactHolder extends RecyclerView.ViewHolder {
public ViewGroup layout;
public ImageView bulb;
public ImageView avatar;
public TextView name;
public TextView date;
@@ -201,6 +211,7 @@ public class ContactListAdapter
layout = (ViewGroup) v;
bulb = (ImageView) v.findViewById(R.id.bulbView);
avatar = (ImageView) v.findViewById(R.id.avatarView);
name = (TextView) v.findViewById(R.id.nameView);
date = (TextView) v.findViewById(R.id.dateView);
}

View File

@@ -16,6 +16,7 @@ import org.briarproject.android.util.BriarRecyclerView;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchContactException;
import org.briarproject.api.event.ContactAddedEvent;
@@ -62,6 +63,8 @@ public class ContactListFragment extends BaseEventFragment {
return TAG;
}
@Inject
private CryptoComponent crypto;
@Inject
private ConnectionRegistry connectionRegistry;
private ContactListAdapter adapter = null;
@@ -83,7 +86,7 @@ public class ContactListFragment extends BaseEventFragment {
inflater.inflate(R.layout.activity_contact_list, container,
false);
adapter = new ContactListAdapter(getContext());
adapter = new ContactListAdapter(getContext(), crypto);
list = (BriarRecyclerView) contentView.findViewById(R.id.contactList);
list.setLayoutManager(new LinearLayoutManager(getContext()));
list.setAdapter(adapter);

View File

@@ -23,6 +23,7 @@ import org.briarproject.api.android.AndroidNotificationManager;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.CryptoExecutor;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchContactException;
@@ -71,6 +72,7 @@ public class ConversationActivity extends BriarActivity
private static final Logger LOG =
Logger.getLogger(ConversationActivity.class.getName());
@Inject private CryptoComponent crypto;
@Inject private AndroidNotificationManager notificationManager;
@Inject private ConnectionRegistry connectionRegistry;
@Inject @CryptoExecutor private Executor cryptoExecutor;
@@ -88,6 +90,7 @@ public class ConversationActivity extends BriarActivity
private volatile GroupId groupId = null;
private volatile ContactId contactId = null;
private volatile String contactName = null;
private volatile byte[] contactIdenticonKey = null;
private volatile boolean connected = false;
@Override
@@ -101,7 +104,7 @@ public class ConversationActivity extends BriarActivity
setContentView(R.layout.activity_conversation);
adapter = new ConversationAdapter(this);
adapter = new ConversationAdapter(this, crypto);
list = (BriarRecyclerView) findViewById(R.id.conversationView);
list.setLayoutManager(new LinearLayoutManager(this));
list.setAdapter(adapter);
@@ -165,6 +168,7 @@ public class ConversationActivity extends BriarActivity
contactId = messagingManager.getContactId(groupId);
Contact contact = contactManager.getContact(contactId);
contactName = contact.getAuthor().getName();
contactIdenticonKey = contact.getAuthor().getId().getBytes();
connected = connectionRegistry.isConnected(contactId);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
@@ -192,6 +196,7 @@ public class ConversationActivity extends BriarActivity
actionBar.setSubtitle(getString(R.string.offline));
}
}
adapter.setIdenticonKey(contactIdenticonKey);
}
});
}

View File

@@ -11,9 +11,12 @@ import android.widget.ImageView;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.messaging.PrivateMessageHeader;
import org.briarproject.util.StringUtils;
import im.delight.android.identicons.IdenticonDrawable;
import static android.support.v7.util.SortedList.INVALID_POSITION;
class ConversationAdapter extends
@@ -70,9 +73,17 @@ class ConversationAdapter extends
}
});
private Context ctx;
private CryptoComponent crypto;
private byte[] identiconKey;
public ConversationAdapter(Context context) {
public ConversationAdapter(Context context, CryptoComponent cryptoComponent) {
ctx = context;
crypto = cryptoComponent;
}
public void setIdenticonKey(byte[] key) {
this.identiconKey = key;
notifyDataSetChanged();
}
@Override
@@ -119,18 +130,23 @@ class ConversationAdapter extends
} else {
ui.status.setImageResource(R.drawable.message_stored);
}
} else if (!header.isRead()) {
int left = ui.layout.getPaddingLeft();
int top = ui.layout.getPaddingTop();
int right = ui.layout.getPaddingRight();
int bottom = ui.layout.getPaddingBottom();
} else {
if (identiconKey != null)
ui.avatar.setImageDrawable(
new IdenticonDrawable(crypto, identiconKey));
if (!header.isRead()) {
int left = ui.layout.getPaddingLeft();
int top = ui.layout.getPaddingTop();
int right = ui.layout.getPaddingRight();
int bottom = ui.layout.getPaddingBottom();
// show unread messages in different color to not miss them
ui.layout.setBackgroundResource(R.drawable.msg_in_unread);
// show unread messages in different color to not miss them
ui.layout.setBackgroundResource(R.drawable.msg_in_unread);
// re-apply the previous padding due to bug in some Android versions
// see: https://code.google.com/p/android/issues/detail?id=17885
ui.layout.setPadding(left, top, right, bottom);
// re-apply the previous padding due to bug in some Android versions
// see: https://code.google.com/p/android/issues/detail?id=17885
ui.layout.setPadding(left, top, right, bottom);
}
}
if (item.getBody() == null) {
@@ -186,6 +202,7 @@ class ConversationAdapter extends
public TextView body;
public TextView date;
public ImageView status;
public ImageView avatar;
public MessageHolder(View v, int type) {
super(v);
@@ -197,6 +214,8 @@ class ConversationAdapter extends
// outgoing message (local)
if (type == MSG_OUT) {
status = (ImageView) v.findViewById(R.id.msgStatus);
} else {
avatar = (ImageView) v.findViewById(R.id.msgAvatar);
}
}
}

View File

@@ -18,6 +18,7 @@ import org.briarproject.android.util.ElasticHorizontalSpace;
import org.briarproject.android.util.HorizontalBorder;
import org.briarproject.android.util.ListLoadingProgressBar;
import org.briarproject.api.android.AndroidNotificationManager;
import org.briarproject.api.android.ReferenceManager;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchMessageException;
import org.briarproject.api.db.NoSuchSubscriptionException;
@@ -70,6 +71,8 @@ public class ForumActivity extends BriarActivity implements EventListener,
private ListLoadingProgressBar loading = null;
private ImageButton composeButton = null, shareButton = null;
@Inject private ReferenceManager referenceManager;
// Fields that are accessed from background threads must be volatile
@Inject private volatile ForumManager forumManager;
@Inject private volatile EventBus eventBus;
@@ -366,7 +369,8 @@ public class ForumActivity extends BriarActivity implements EventListener,
i.putExtra("briar.FORUM_NAME", forum.getName());
i.putExtra("briar.MESSAGE_ID", header.getId().getBytes());
Author author = header.getAuthor();
if (author != null) i.putExtra("briar.AUTHOR_NAME", author.getName());
if (author != null) i.putExtra("briar.AUTHOR_HANDLE",
referenceManager.putReference(author, Author.class));
i.putExtra("briar.AUTHOR_STATUS", header.getAuthorStatus().name());
i.putExtra("briar.CONTENT_TYPE", header.getContentType());
i.putExtra("briar.TIMESTAMP", header.getTimestamp());

View File

@@ -55,8 +55,7 @@ class ForumAdapter extends ArrayAdapter<ForumItem> {
AuthorView authorView = new AuthorView(ctx);
authorView.setLayoutParams(WRAP_WRAP_1);
Author author = header.getAuthor();
if (author == null) authorView.init(null, header.getAuthorStatus());
else authorView.init(author.getName(), header.getAuthorStatus());
authorView.init(author, header.getAuthorStatus());
headerLayout.addView(authorView);
TextView date = new TextView(ctx);

View File

@@ -17,6 +17,7 @@ import org.briarproject.android.util.AuthorView;
import org.briarproject.android.util.ElasticHorizontalSpace;
import org.briarproject.android.util.HorizontalBorder;
import org.briarproject.android.util.LayoutUtils;
import org.briarproject.api.android.ReferenceManager;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchMessageException;
import org.briarproject.api.forum.ForumManager;
@@ -56,6 +57,8 @@ implements OnClickListener {
private TextView content = null;
private int position = -1;
@Inject private ReferenceManager referenceManager;
// Fields that are accessed from background threads must be volatile
@Inject private volatile ForumManager forumManager;
private volatile MessageId messageId = null;
@@ -82,7 +85,9 @@ implements OnClickListener {
if (minTimestamp == -1) throw new IllegalStateException();
position = i.getIntExtra("briar.POSITION", -1);
if (position == -1) throw new IllegalStateException();
String authorName = i.getStringExtra("briar.AUTHOR_NAME");
long authorHandle = i.getLongExtra("briar.AUTHOR_HANDLE", -1);
if (authorHandle == -1) throw new IllegalStateException();
Author author = referenceManager.removeReference(authorHandle, Author.class);
String s = i.getStringExtra("briar.AUTHOR_STATUS");
if (s == null) throw new IllegalStateException();
Author.Status authorStatus = Author.Status.valueOf(s);
@@ -102,10 +107,10 @@ implements OnClickListener {
header.setOrientation(HORIZONTAL);
header.setGravity(CENTER_VERTICAL);
AuthorView author = new AuthorView(this);
author.setLayoutParams(WRAP_WRAP_1);
author.init(authorName, authorStatus);
header.addView(author);
AuthorView authorView = new AuthorView(this);
authorView.setLayoutParams(WRAP_WRAP_1);
authorView.init(author, authorStatus);
header.addView(authorView);
int pad = LayoutUtils.getPadding(this);

View File

@@ -123,7 +123,7 @@ implements OnItemSelectedListener, OnClickListener {
left.addRule(CENTER_VERTICAL);
header.addView(from, left);
adapter = new LocalAuthorSpinnerAdapter(this, true);
adapter = new LocalAuthorSpinnerAdapter(this, crypto, true);
spinner = new Spinner(this);
spinner.setId(2);
spinner.setAdapter(adapter);

View File

@@ -1,21 +1,24 @@
package org.briarproject.android.identity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.android.util.LayoutUtils;
import org.briarproject.api.crypto.CryptoComponent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import static android.text.TextUtils.TruncateAt.END;
import im.delight.android.identicons.IdenticonDrawable;
import static org.briarproject.android.identity.LocalAuthorItem.ANONYMOUS;
import static org.briarproject.android.identity.LocalAuthorItem.NEW;
@@ -23,11 +26,14 @@ public class LocalAuthorSpinnerAdapter extends BaseAdapter
implements SpinnerAdapter {
private final Context ctx;
private final CryptoComponent crypto;
private final boolean includeAnonymous;
private final List<LocalAuthorItem> list = new ArrayList<LocalAuthorItem>();
public LocalAuthorSpinnerAdapter(Context ctx, boolean includeAnonymous) {
public LocalAuthorSpinnerAdapter(Context ctx,
CryptoComponent crypto, boolean includeAnonymous) {
this.ctx = ctx;
this.crypto = crypto;
this.includeAnonymous = includeAnonymous;
}
@@ -49,17 +55,33 @@ implements SpinnerAdapter {
@Override
public View getDropDownView(int position, View convertView,
ViewGroup parent) {
TextView name = new TextView(ctx);
name.setTextSize(18);
name.setSingleLine();
name.setEllipsize(END);
int pad = LayoutUtils.getPadding(ctx);
name.setPadding(pad, pad, pad, pad);
View view;
if (convertView == null) {
LayoutInflater inflater =
(LayoutInflater) ctx
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.dropdown_author, parent, false);
} else
view = convertView;
TextView name = (TextView) view.findViewById(R.id.nameView);
ImageView avatar =
(ImageView) view.findViewById(R.id.avatarView);
LocalAuthorItem item = getItem(position);
if (item == ANONYMOUS) name.setText(R.string.anonymous);
else if (item == NEW) name.setText(R.string.new_identity_item);
else name.setText(item.getLocalAuthor().getName());
return name;
if (item == ANONYMOUS) {
name.setText(R.string.anonymous);
avatar.setVisibility(View.INVISIBLE);
} else if (item == NEW) {
name.setText(R.string.new_identity_item);
avatar.setVisibility(View.INVISIBLE);
} else {
name.setText(item.getLocalAuthor().getName());
avatar.setVisibility(View.VISIBLE);
avatar.setImageDrawable(new IdenticonDrawable(crypto,
item.getLocalAuthor().getId().getBytes()));
}
return view;
}
public LocalAuthorItem getItem(int position) {
@@ -78,15 +100,7 @@ implements SpinnerAdapter {
}
public View getView(int position, View convertView, ViewGroup parent) {
TextView name = new TextView(ctx);
name.setTextSize(18);
name.setSingleLine();
name.setEllipsize(END);
LocalAuthorItem item = getItem(position);
if (item == ANONYMOUS) name.setText(R.string.anonymous);
else if (item == NEW) name.setText(R.string.new_identity_item);
else name.setText(item.getLocalAuthor().getName());
return name;
return getDropDownView(position, convertView, parent);
}
@Override

View File

@@ -16,11 +16,16 @@ import org.briarproject.android.identity.CreateIdentityActivity;
import org.briarproject.android.identity.LocalAuthorItem;
import org.briarproject.android.identity.LocalAuthorItemComparator;
import org.briarproject.android.identity.LocalAuthorSpinnerAdapter;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.identity.LocalAuthor;
import java.util.Collection;
import javax.inject.Inject;
import roboguice.RoboGuice;
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
import static org.briarproject.android.identity.LocalAuthorItem.NEW;
@@ -30,6 +35,7 @@ import static org.briarproject.android.invitation.AddContactActivity.REQUEST_CRE
class ChooseIdentityView extends AddContactView
implements OnItemSelectedListener, OnClickListener {
@Inject private CryptoComponent crypto;
private LocalAuthorSpinnerAdapter adapter = null;
private Spinner spinner = null;
@@ -40,6 +46,7 @@ implements OnItemSelectedListener, OnClickListener {
void populate() {
removeAllViews();
Context ctx = getContext();
RoboGuice.injectMembers(ctx, this);
LayoutInflater inflater = (LayoutInflater) ctx.getSystemService
(Context.LAYOUT_INFLATER_SERVICE);
@@ -50,7 +57,7 @@ implements OnItemSelectedListener, OnClickListener {
TextView step = (TextView) view.findViewById(R.id.stepView);
step.setText(String.format(ctx.getString(R.string.step), 1, 3));
adapter = new LocalAuthorSpinnerAdapter(ctx, false);
adapter = new LocalAuthorSpinnerAdapter(ctx, crypto, false);
spinner = (Spinner) view.findViewById(R.id.spinner);
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(this);

View File

@@ -1,42 +1,69 @@
package org.briarproject.android.util;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.identity.Author;
import static android.text.TextUtils.TruncateAt.END;
import javax.inject.Inject;
public class AuthorView extends RelativeLayout {
import im.delight.android.identicons.IdenticonDrawable;
import roboguice.RoboGuice;
public class AuthorView extends FrameLayout {
@Inject private CryptoComponent crypto;
private ImageView avatarView;
private TextView nameView;
private ImageView statusView;
public AuthorView(Context ctx) {
super(ctx);
initViews();
}
public void init(String name, Author.Status status) {
Context ctx = getContext();
int pad = LayoutUtils.getPadding(ctx);
public AuthorView(Context context, AttributeSet attrs) {
super(context, attrs);
TextView nameView = new TextView(ctx);
nameView.setId(1);
nameView.setTextSize(18);
nameView.setSingleLine();
nameView.setEllipsize(END);
nameView.setPadding(pad, pad, pad, pad);
if (name == null) nameView.setText(R.string.anonymous);
else nameView.setText(name);
LayoutParams leftOf = CommonLayoutParams.relative();
leftOf.addRule(ALIGN_PARENT_LEFT);
leftOf.addRule(CENTER_VERTICAL);
leftOf.addRule(LEFT_OF, 2);
addView(nameView, leftOf);
initViews();
}
public AuthorView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
initViews();
}
private void initViews() {
RoboGuice.injectMembers(getContext(), this);
if (isInEditMode())
return;
View v = LayoutInflater.from(getContext()).inflate(
R.layout.author_view, this, true);
avatarView = (ImageView) v.findViewById(R.id.avatarView);
nameView = (TextView) v.findViewById(R.id.nameView);
statusView = (ImageView) v.findViewById(R.id.statusView);
}
public void init(Author author, Author.Status status) {
if (author == null) nameView.setText(R.string.anonymous);
else {
avatarView.setImageDrawable(
new IdenticonDrawable(crypto, author.getId().getBytes()));
nameView.setText(author.getName());
}
ImageView statusView = new ImageView(ctx);
statusView.setId(2);
statusView.setPadding(0, pad, pad, pad);
switch(status) {
case ANONYMOUS:
statusView.setImageResource(R.drawable.identity_anonymous);
@@ -51,9 +78,5 @@ public class AuthorView extends RelativeLayout {
statusView.setImageResource(R.drawable.identity_verified);
break;
}
LayoutParams right = CommonLayoutParams.relative();
right.addRule(ALIGN_PARENT_RIGHT);
right.addRule(CENTER_VERTICAL);
addView(statusView, right);
}
}