diff --git a/briar-android/build.gradle b/briar-android/build.gradle
index f982a86be..4ead68e29 100644
--- a/briar-android/build.gradle
+++ b/briar-android/build.gradle
@@ -17,6 +17,7 @@ dependencies {
compile "org.roboguice:roboguice:2.0"
compile "info.guardianproject.panic:panic:0.5"
compile "info.guardianproject.trustedintents:trustedintents:0.2"
+ compile "de.hdodenhof:circleimageview:2.0.0"
}
dependencyVerification {
diff --git a/briar-android/res/layout/author_view.xml b/briar-android/res/layout/author_view.xml
new file mode 100644
index 000000000..92c863c37
--- /dev/null
+++ b/briar-android/res/layout/author_view.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/briar-android/res/layout/dropdown_author.xml b/briar-android/res/layout/dropdown_author.xml
new file mode 100644
index 000000000..01a9b3f53
--- /dev/null
+++ b/briar-android/res/layout/dropdown_author.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/briar-android/res/layout/list_item_contact.xml b/briar-android/res/layout/list_item_contact.xml
index 69ec0383e..8259e1b3b 100644
--- a/briar-android/res/layout/list_item_contact.xml
+++ b/briar-android/res/layout/list_item_contact.xml
@@ -2,49 +2,71 @@
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
-
+ android:layout_height="@dimen/listitem_height_one_line_avatar"
+ android:background="?attr/selectableItemBackground">
-
+
-
+
-
+
-
+
+
+
+
+
+
+
diff --git a/briar-android/res/layout/list_item_msg_in.xml b/briar-android/res/layout/list_item_msg_in.xml
index d93d269e7..50ccd0077 100644
--- a/briar-android/res/layout/list_item_msg_in.xml
+++ b/briar-android/res/layout/list_item_msg_in.xml
@@ -4,12 +4,20 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical"
+ android:orientation="horizontal"
android:paddingRight="@dimen/margin_medium"
android:paddingEnd="@dimen/margin_medium"
android:paddingTop="@dimen/margin_small"
android:paddingBottom="@dimen/margin_small">
+
+
300dp
1dp
+ 16dp
+ 40dp
+ 72dp
+
+ 56dp
+
+ 32dp
+
diff --git a/briar-android/src/im/delight/android/identicons/AsymmetricIdenticon.java b/briar-android/src/im/delight/android/identicons/AsymmetricIdenticon.java
new file mode 100644
index 000000000..b1d98d9b3
--- /dev/null
+++ b/briar-android/src/im/delight/android/identicons/AsymmetricIdenticon.java
@@ -0,0 +1,84 @@
+package im.delight.android.identicons;
+
+/**
+ * Copyright 2014 www.delight.im
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.content.Context;
+import android.graphics.Color;
+import android.util.AttributeSet;
+
+import org.briarproject.api.crypto.CryptoComponent;
+
+import javax.inject.Inject;
+
+import roboguice.RoboGuice;
+
+public class AsymmetricIdenticon extends IdenticonView {
+
+ @Inject private CryptoComponent mCrypto;
+ private IdenticonBase mDelegate;
+
+ public AsymmetricIdenticon(Context context) {
+ super(context);
+ initDelegate();
+ }
+
+ public AsymmetricIdenticon(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initDelegate();
+ }
+
+ public AsymmetricIdenticon(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initDelegate();
+ }
+
+ @Override
+ protected IdenticonBase getDelegate() {
+ return mDelegate;
+ }
+
+ private void initDelegate() {
+ RoboGuice.injectMembers(getContext(), this);
+ mDelegate = new IdenticonBase() {
+ @Override
+ protected CryptoComponent getCrypto() {
+ return mCrypto;
+ }
+
+ @Override
+ protected int getRowCount() {
+ return 4;
+ }
+
+ @Override
+ protected int getColumnCount() {
+ return 4;
+ }
+
+ @Override
+ protected boolean isCellVisible(int row, int column) {
+ return getByte(3 + row * getColumnCount() + column) >= 0;
+ }
+
+ @Override
+ protected int getIconColor() {
+ return Color.rgb(getByte(0) + 128, getByte(1) + 128, getByte(2) + 128);
+ }
+ };
+ }
+
+}
diff --git a/briar-android/src/im/delight/android/identicons/IdenticonBase.java b/briar-android/src/im/delight/android/identicons/IdenticonBase.java
new file mode 100644
index 000000000..f5ccc90e4
--- /dev/null
+++ b/briar-android/src/im/delight/android/identicons/IdenticonBase.java
@@ -0,0 +1,127 @@
+package im.delight.android.identicons;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+
+import org.briarproject.api.crypto.CryptoComponent;
+
+/**
+ * Created by saiimons on 05/10/14.
+ */
+public abstract class IdenticonBase {
+ private final CryptoComponent mCrypto;
+ private final int mRowCount;
+ private final int mColumnCount;
+ private final Paint mPaint;
+ private volatile int mCellWidth;
+ private volatile int mCellHeight;
+ private volatile byte[] mHash;
+ private volatile int[][] mColors;
+ private volatile boolean mReady;
+
+ public IdenticonBase() {
+ mCrypto = getCrypto();
+ mRowCount = getRowCount();
+ mColumnCount = getColumnCount();
+ mPaint = new Paint();
+
+ mPaint.setStyle(Paint.Style.FILL);
+ mPaint.setAntiAlias(true);
+ mPaint.setDither(true);
+ }
+
+ public byte[] getHash(byte[] input) {
+ byte[] mHash;
+ // if the input was null
+ if (input == null) {
+ // we can't create a hash value and have nothing to show (draw to the view)
+ mHash = null;
+ } else {
+ // generate a hash from the input to get unique but deterministic byte values
+ try {
+ mHash = mCrypto.hash(input);
+ } catch (Exception e) {
+ mHash = null;
+ }
+ }
+ return mHash;
+ }
+
+ protected void setupColors() {
+ mColors = new int[mRowCount][mColumnCount];
+ int colorVisible = getIconColor();
+ int colorInvisible = getBackgroundColor();
+
+ for (int r = 0; r < mRowCount; r++) {
+ for (int c = 0; c < mColumnCount; c++) {
+ if (isCellVisible(r, c)) {
+ mColors[r][c] = colorVisible;
+ } else {
+ mColors[r][c] = colorInvisible;
+ }
+ }
+ }
+ }
+
+ public void show(byte[] input) {
+ if(input != null) {
+ mHash = getHash(input);
+ } else {
+ mHash = null;
+ }
+ // set up the cell colors according to the input that was provided via show(...)
+ setupColors();
+
+ // this view may now be drawn (and thus must be re-drawn)
+ mReady = true;
+ }
+
+ public byte getByte(int index) {
+ if (mHash == null) {
+ return -128;
+ } else {
+ return mHash[index % mHash.length];
+ }
+ }
+
+ abstract protected CryptoComponent getCrypto();
+
+ abstract protected int getRowCount();
+
+ abstract protected int getColumnCount();
+
+ abstract protected boolean isCellVisible(int row, int column);
+
+ abstract protected int getIconColor();
+
+ protected int getBackgroundColor() {
+ float[] hsv = new float[3];
+ Color.colorToHSV(getIconColor(), hsv);
+ if (hsv[2] < 0.5)
+ return Color.parseColor("#ffeeeeee"); // @color/background_material_light
+ else
+ return Color.parseColor("#ff303030"); // @color/background_material_dark
+ }
+
+ public void updateSize(int w, int h) {
+ mCellWidth = w / mColumnCount;
+ mCellHeight = h / mRowCount;
+ }
+
+ protected void draw(Canvas canvas) {
+ if (mReady) {
+ int x, y;
+ for (int r = 0; r < mRowCount; r++) {
+ for (int c = 0; c < mColumnCount; c++) {
+ x = mCellWidth * c;
+ y = mCellHeight * r;
+
+ mPaint.setColor(mColors[r][c]);
+
+ canvas.drawRect(x, y + mCellHeight, x + mCellWidth, y, mPaint);
+ }
+ }
+ }
+ }
+}
diff --git a/briar-android/src/im/delight/android/identicons/IdenticonDrawable.java b/briar-android/src/im/delight/android/identicons/IdenticonDrawable.java
new file mode 100644
index 000000000..6c78367f9
--- /dev/null
+++ b/briar-android/src/im/delight/android/identicons/IdenticonDrawable.java
@@ -0,0 +1,102 @@
+package im.delight.android.identicons;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+
+import org.briarproject.api.crypto.CryptoComponent;
+
+/**
+ * Created by saiimons on 05/10/14.
+ */
+public class IdenticonDrawable extends Drawable {
+ private IdenticonBase mDelegate;
+
+ private static final int CENTER_COLUMN_INDEX = 5;
+
+ public IdenticonDrawable(final CryptoComponent crypto, byte[] toShow) {
+ super();
+ mDelegate = new IdenticonBase() {
+ @Override
+ protected CryptoComponent getCrypto() {
+ return crypto;
+ }
+
+ @Override
+ protected int getRowCount() {
+ return 9;
+ }
+
+ @Override
+ protected int getColumnCount() {
+ return 9;
+ }
+
+ @Override
+ protected boolean isCellVisible(int row, int column) {
+ return getByte(3 + row * CENTER_COLUMN_INDEX + getSymmetricColumnIndex(column)) >= 0;
+ }
+
+ @Override
+ protected int getIconColor() {
+ return Color.rgb(getByte(0) + 128, getByte(1) + 128, getByte(2) + 128);
+ }
+ };
+ mDelegate.show(toShow);
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return 200;
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return 200;
+ }
+
+ @Override
+ public void setBounds(Rect bounds) {
+ super.setBounds(bounds);
+ Log.d("IDENTICON", "SIZE : " + (bounds.right - bounds.left) + " " + (bounds.bottom - bounds.top));
+ mDelegate.updateSize(bounds.right - bounds.left, bounds.bottom - bounds.top);
+ }
+
+ @Override
+ public void setBounds(int left, int top, int right, int bottom) {
+ super.setBounds(left, top, right, bottom);
+ mDelegate.updateSize(right - left, bottom - top);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ Log.d("IDENTICON", "DRAW IN PROGRESS");
+ mDelegate.draw(canvas);
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+
+ }
+
+ @Override
+ public int getOpacity() {
+ return 0;
+ }
+
+ protected int getSymmetricColumnIndex(int row) {
+ if (row < CENTER_COLUMN_INDEX) {
+ return row;
+ } else {
+ return mDelegate.getColumnCount() - row - 1;
+ }
+ }
+}
diff --git a/briar-android/src/im/delight/android/identicons/IdenticonView.java b/briar-android/src/im/delight/android/identicons/IdenticonView.java
new file mode 100644
index 000000000..32a4f863c
--- /dev/null
+++ b/briar-android/src/im/delight/android/identicons/IdenticonView.java
@@ -0,0 +1,122 @@
+package im.delight.android.identicons;
+
+/**
+ * Copyright 2014 www.delight.im
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.View;
+
+abstract public class IdenticonView extends View {
+
+
+ public IdenticonView(Context context) {
+ super(context);
+ init();
+ }
+
+ public IdenticonView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public IdenticonView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ @SuppressLint("NewApi")
+ protected void init() {
+ setWillNotDraw(false);
+ if (Build.VERSION.SDK_INT >= 11) {
+ setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+ }
+ }
+
+ public void show(byte[] input) {
+ getDelegate().show(input);
+ invalidate();
+ }
+
+ public void show(String input) {
+ show(input.getBytes());
+ }
+
+ public void show(int input) {
+ show(String.valueOf(input));
+ }
+
+ public void show(long input) {
+ show(String.valueOf(input));
+ }
+
+ public void show(float input) {
+ show(String.valueOf(input));
+ }
+
+ public void show(double input) {
+ show(String.valueOf(input));
+ }
+
+ public void show(byte input) {
+ show(new byte[] { input });
+ }
+
+ public void show(char input) {
+ show(String.valueOf(input));
+ }
+
+ public void show(boolean input) {
+ show(String.valueOf(input));
+ }
+
+ public void show(Object input) {
+ if (input == null) {
+ getDelegate().show(null);
+ } else {
+ show(String.valueOf(input));
+ }
+ }
+
+ protected byte getByte(int index) {
+ return getDelegate().getByte(index);
+ }
+
+ abstract protected IdenticonBase getDelegate();
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ getDelegate().updateSize(w, h);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ int size = Math.min(getMeasuredWidth(), getMeasuredHeight());
+ setMeasuredDimension(size, size);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ getDelegate().draw(canvas);
+ }
+
+}
diff --git a/briar-android/src/im/delight/android/identicons/SymmetricIdenticon.java b/briar-android/src/im/delight/android/identicons/SymmetricIdenticon.java
new file mode 100644
index 000000000..f102e45f4
--- /dev/null
+++ b/briar-android/src/im/delight/android/identicons/SymmetricIdenticon.java
@@ -0,0 +1,94 @@
+package im.delight.android.identicons;
+
+/**
+ * Copyright 2014 www.delight.im
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.content.Context;
+import android.graphics.Color;
+import android.util.AttributeSet;
+
+import org.briarproject.api.crypto.CryptoComponent;
+
+import javax.inject.Inject;
+
+import roboguice.RoboGuice;
+
+public class SymmetricIdenticon extends IdenticonView {
+
+ private static final int CENTER_COLUMN_INDEX = 5;
+
+ @Inject private CryptoComponent mCrypto;
+ private IdenticonBase mDelegate;
+
+ public SymmetricIdenticon(Context context) {
+ super(context);
+ initDelegate();
+ }
+
+ public SymmetricIdenticon(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initDelegate();
+ }
+
+ public SymmetricIdenticon(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initDelegate();
+ }
+
+ private void initDelegate() {
+ RoboGuice.injectMembers(getContext(), this);
+ mDelegate = new IdenticonBase() {
+ @Override
+ protected CryptoComponent getCrypto() {
+ return mCrypto;
+ }
+
+ @Override
+ protected int getRowCount() {
+ return 9;
+ }
+
+ @Override
+ protected int getColumnCount() {
+ return 9;
+ }
+
+ @Override
+ protected boolean isCellVisible(int row, int column) {
+ return getByte(3 + row * CENTER_COLUMN_INDEX + getSymmetricColumnIndex(column)) >= 0;
+ }
+
+ @Override
+ protected int getIconColor() {
+ return Color.rgb(getByte(0) + 128, getByte(1) + 128, getByte(2) + 128);
+ }
+ };
+ }
+
+ @Override
+ protected IdenticonBase getDelegate() {
+ return mDelegate;
+ }
+
+ protected int getSymmetricColumnIndex(int row) {
+ if (row < CENTER_COLUMN_INDEX) {
+ return row;
+ } else {
+ return getDelegate().getColumnCount() - row - 1;
+ }
+ }
+
+}
diff --git a/briar-android/src/org/briarproject/android/contact/ContactListAdapter.java b/briar-android/src/org/briarproject/android/contact/ContactListAdapter.java
index 9d040e87d..739912d95 100644
--- a/briar-android/src/org/briarproject/android/contact/ContactListAdapter.java
+++ b/briar-android/src/org/briarproject/android/contact/ContactListAdapter.java
@@ -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);
}
diff --git a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java
index d2bff01e5..bd52792c5 100644
--- a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java
+++ b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java
@@ -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);
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
index 6b96389c8..dc96bd47f 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
@@ -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);
}
});
}
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java
index 09509c0ee..a35db98bd 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java
@@ -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);
}
}
}
diff --git a/briar-android/src/org/briarproject/android/forum/ForumActivity.java b/briar-android/src/org/briarproject/android/forum/ForumActivity.java
index 7f725d40d..a67ebd12e 100644
--- a/briar-android/src/org/briarproject/android/forum/ForumActivity.java
+++ b/briar-android/src/org/briarproject/android/forum/ForumActivity.java
@@ -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());
diff --git a/briar-android/src/org/briarproject/android/forum/ForumAdapter.java b/briar-android/src/org/briarproject/android/forum/ForumAdapter.java
index ac1211447..30ff7d0bf 100644
--- a/briar-android/src/org/briarproject/android/forum/ForumAdapter.java
+++ b/briar-android/src/org/briarproject/android/forum/ForumAdapter.java
@@ -55,8 +55,7 @@ class ForumAdapter extends ArrayAdapter {
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);
diff --git a/briar-android/src/org/briarproject/android/forum/ReadForumPostActivity.java b/briar-android/src/org/briarproject/android/forum/ReadForumPostActivity.java
index 706cb31fe..5ffc8aaa8 100644
--- a/briar-android/src/org/briarproject/android/forum/ReadForumPostActivity.java
+++ b/briar-android/src/org/briarproject/android/forum/ReadForumPostActivity.java
@@ -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);
diff --git a/briar-android/src/org/briarproject/android/forum/WriteForumPostActivity.java b/briar-android/src/org/briarproject/android/forum/WriteForumPostActivity.java
index 40e72a148..a38433f31 100644
--- a/briar-android/src/org/briarproject/android/forum/WriteForumPostActivity.java
+++ b/briar-android/src/org/briarproject/android/forum/WriteForumPostActivity.java
@@ -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);
diff --git a/briar-android/src/org/briarproject/android/identity/LocalAuthorSpinnerAdapter.java b/briar-android/src/org/briarproject/android/identity/LocalAuthorSpinnerAdapter.java
index 2aa2358f4..a0e2d88de 100644
--- a/briar-android/src/org/briarproject/android/identity/LocalAuthorSpinnerAdapter.java
+++ b/briar-android/src/org/briarproject/android/identity/LocalAuthorSpinnerAdapter.java
@@ -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 list = new ArrayList();
- 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
diff --git a/briar-android/src/org/briarproject/android/invitation/ChooseIdentityView.java b/briar-android/src/org/briarproject/android/invitation/ChooseIdentityView.java
index 940de7e75..912d28cce 100644
--- a/briar-android/src/org/briarproject/android/invitation/ChooseIdentityView.java
+++ b/briar-android/src/org/briarproject/android/invitation/ChooseIdentityView.java
@@ -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);
diff --git a/briar-android/src/org/briarproject/android/util/AuthorView.java b/briar-android/src/org/briarproject/android/util/AuthorView.java
index 152a801b9..dd20a28e7 100644
--- a/briar-android/src/org/briarproject/android/util/AuthorView.java
+++ b/briar-android/src/org/briarproject/android/util/AuthorView.java
@@ -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);
}
}