From 97fb9c8c3ebe3d6e9580dad732ceea5a9092f945 Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Sat, 8 Feb 2014 17:36:17 +0000
Subject: [PATCH] Updated group conversation view to resemble private
 conversation view.

---
 .../android/contact/ConversationActivity.java |   6 +-
 .../android/contact/ConversationAdapter.java  |   1 +
 .../contact/ConversationItemComparator.java   |   5 +-
 .../android/groups/GroupActivity.java         | 104 +++++++++++++-----
 .../android/groups/GroupAdapter.java          |  55 ++++++---
 .../android/groups/GroupItem.java             |  37 +++++++
 .../android/groups/GroupItemComparator.java   |  17 +++
 7 files changed, 181 insertions(+), 44 deletions(-)
 create mode 100644 briar-android/src/org/briarproject/android/groups/GroupItem.java
 create mode 100644 briar-android/src/org/briarproject/android/groups/GroupItemComparator.java

diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
index 990e67efc8..0498107143 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
@@ -50,7 +50,7 @@ import android.widget.ListView;
 public class ConversationActivity extends BriarActivity
 implements EventListener, OnClickListener, OnItemClickListener {
 
-	private static final int REQUEST_READ_MESSAGE = 2;
+	private static final int REQUEST_READ = 2;
 	private static final Logger LOG =
 			Logger.getLogger(ConversationActivity.class.getName());
 
@@ -239,7 +239,7 @@ implements EventListener, OnClickListener, OnItemClickListener {
 	@Override
 	protected void onActivityResult(int request, int result, Intent data) {
 		super.onActivityResult(request, result, data);
-		if(request == REQUEST_READ_MESSAGE && result == RESULT_PREV_NEXT) {
+		if(request == REQUEST_READ && result == RESULT_PREV_NEXT) {
 			int position = data.getIntExtra("briar.POSITION", -1);
 			if(position >= 0 && position < adapter.getCount())
 				displayMessage(position);
@@ -296,6 +296,6 @@ implements EventListener, OnClickListener, OnItemClickListener {
 		i.putExtra("briar.CONTENT_TYPE", header.getContentType());
 		i.putExtra("briar.TIMESTAMP", header.getTimestamp());
 		i.putExtra("briar.POSITION", position);
-		startActivityForResult(i, REQUEST_READ_MESSAGE);
+		startActivityForResult(i, REQUEST_READ);
 	}
 }
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java
index 62f4eebc53..a48ed35b83 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java
@@ -56,6 +56,7 @@ class ConversationAdapter extends ArrayAdapter<ConversationItem> {
 		authorView.init(header.getAuthor().getName(), VERIFIED);
 		headerLayout.addView(authorView);
 
+		// FIXME: Factor this out into a TimestampView
 		TextView date = new TextView(ctx);
 		date.setTextSize(14);
 		date.setPadding(0, pad, pad, pad);
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationItemComparator.java b/briar-android/src/org/briarproject/android/contact/ConversationItemComparator.java
index 1bf7a39055..6b5f3ec77e 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationItemComparator.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationItemComparator.java
@@ -2,10 +2,9 @@ package org.briarproject.android.contact;
 
 import java.util.Comparator;
 
-public class ConversationItemComparator
-implements Comparator<ConversationItem> {
+class ConversationItemComparator implements Comparator<ConversationItem> {
 
-	public static final ConversationItemComparator INSTANCE =
+	static final ConversationItemComparator INSTANCE =
 			new ConversationItemComparator();
 
 	public int compare(ConversationItem a, ConversationItem b) {
diff --git a/briar-android/src/org/briarproject/android/groups/GroupActivity.java b/briar-android/src/org/briarproject/android/groups/GroupActivity.java
index ff9970b7c0..a6d124dcc4 100644
--- a/briar-android/src/org/briarproject/android/groups/GroupActivity.java
+++ b/briar-android/src/org/briarproject/android/groups/GroupActivity.java
@@ -17,7 +17,6 @@ import java.util.logging.Logger;
 import javax.inject.Inject;
 
 import org.briarproject.R;
-import org.briarproject.android.AscendingHeaderComparator;
 import org.briarproject.android.BriarActivity;
 import org.briarproject.android.util.HorizontalBorder;
 import org.briarproject.android.util.ListLoadingProgressBar;
@@ -26,6 +25,7 @@ import org.briarproject.api.android.DatabaseUiExecutor;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.db.MessageHeader;
+import org.briarproject.api.db.NoSuchMessageException;
 import org.briarproject.api.db.NoSuchSubscriptionException;
 import org.briarproject.api.event.Event;
 import org.briarproject.api.event.EventListener;
@@ -34,6 +34,7 @@ import org.briarproject.api.event.MessageExpiredEvent;
 import org.briarproject.api.event.SubscriptionRemovedEvent;
 import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.messaging.GroupId;
+import org.briarproject.api.messaging.MessageId;
 
 import android.content.Intent;
 import android.os.Bundle;
@@ -48,7 +49,7 @@ import android.widget.ListView;
 public class GroupActivity extends BriarActivity implements EventListener,
 OnClickListener, OnItemClickListener {
 
-	private static final int REQUEST_READ_POST = 2;
+	private static final int REQUEST_READ = 2;
 	private static final Logger LOG =
 			Logger.getLogger(GroupActivity.class.getName());
 
@@ -125,11 +126,7 @@ OnClickListener, OnItemClickListener {
 					displayHeaders(headers);
 				} catch(NoSuchSubscriptionException e) {
 					if(LOG.isLoggable(INFO)) LOG.info("Subscription removed");
-					runOnUiThread(new Runnable() {
-						public void run() {
-							finish();
-						}
-					});
+					finishOnUiThread();
 				} catch(DbException e) {
 					if(LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
@@ -148,30 +145,91 @@ OnClickListener, OnItemClickListener {
 				list.setVisibility(VISIBLE);
 				loading.setVisibility(GONE);
 				adapter.clear();
-				for(MessageHeader h : headers) adapter.add(h);
-				adapter.sort(AscendingHeaderComparator.INSTANCE);
+				for(MessageHeader h : headers) adapter.add(new GroupItem(h));
+				adapter.sort(GroupItemComparator.INSTANCE);
 				adapter.notifyDataSetChanged();
-				selectFirstUnread();
+				expandMessages();
 			}
 		});
 	}
 
-	private void selectFirstUnread() {
-		int firstUnread = -1, count = adapter.getCount();
+	private void expandMessages() {
+		// Expand unread messages and the last three messages
+		int firstExpanded = -1, count = adapter.getCount();
+		if(count == 0) return;
 		for(int i = 0; i < count; i++) {
-			if(!adapter.getItem(i).isRead()) {
-				firstUnread = i;
-				break;
+			GroupItem item = adapter.getItem(i);
+			if(!item.getHeader().isRead() || i >= count - 3) {
+				if(firstExpanded == -1) firstExpanded = i;
+				item.setExpanded(true);
+				loadMessage(item.getHeader());
 			}
 		}
-		if(firstUnread == -1) list.setSelection(count - 1);
-		else list.setSelection(firstUnread);
+		// Scroll to the first expanded message
+		list.setSelection(firstExpanded);
+	}
+
+	private void loadMessage(final MessageHeader h) {
+		dbUiExecutor.execute(new Runnable() {
+			public void run() {
+				try {
+					lifecycleManager.waitForDatabase();
+					long now = System.currentTimeMillis();
+					byte[] body = db.getMessageBody(h.getId());
+					long duration = System.currentTimeMillis() - now;
+					if(LOG.isLoggable(INFO))
+						LOG.info("Loading message took " + duration + " ms");
+					displayMessage(h.getId(), body);
+					if(!h.isRead()) {
+						now = System.currentTimeMillis();
+						db.setReadFlag(h.getId(), true);
+						duration = System.currentTimeMillis() - now;
+						if(LOG.isLoggable(INFO))
+							LOG.info("Setting read took " + duration + " ms");
+					}
+				} catch(NoSuchMessageException e) {
+					if(LOG.isLoggable(INFO)) LOG.info("Message expired");
+					// The item will be removed when we get the event
+				} catch(DbException e) {
+					if(LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+				} catch(InterruptedException e) {
+					if(LOG.isLoggable(INFO))
+						LOG.info("Interrupted while waiting for database");
+					Thread.currentThread().interrupt();
+				}
+			}
+		});
+	}
+
+	private void displayMessage(final MessageId m, final byte[] body) {
+		runOnUiThread(new Runnable() {
+			public void run() {
+				int count = adapter.getCount();
+				for(int i = 0; i < count; i++) {
+					GroupItem item = adapter.getItem(i);
+					if(item.getHeader().getId().equals(m)) {
+						item.setBody(body);
+						adapter.notifyDataSetChanged();
+						return;
+					}
+				}
+			}
+		});
+	}
+
+	private void finishOnUiThread() {
+		runOnUiThread(new Runnable() {
+			public void run() {
+				finish();
+			}
+		});
 	}
 
 	@Override
 	protected void onActivityResult(int request, int result, Intent data) {
 		super.onActivityResult(request, result, data);
-		if(request == REQUEST_READ_POST && result == RESULT_PREV_NEXT) {
+		if(request == REQUEST_READ && result == RESULT_PREV_NEXT) {
 			int position = data.getIntExtra("briar.POSITION", -1);
 			if(position >= 0 && position < adapter.getCount())
 				displayMessage(position);
@@ -197,11 +255,7 @@ OnClickListener, OnItemClickListener {
 			SubscriptionRemovedEvent s = (SubscriptionRemovedEvent) e;
 			if(s.getGroup().getId().equals(groupId)) {
 				if(LOG.isLoggable(INFO)) LOG.info("Subscription removed");
-				runOnUiThread(new Runnable() {
-					public void run() {
-						finish();
-					}
-				});
+				finishOnUiThread();
 			}
 		}
 	}
@@ -218,7 +272,7 @@ OnClickListener, OnItemClickListener {
 	}
 
 	private void displayMessage(int position) {
-		MessageHeader item = adapter.getItem(position);
+		MessageHeader item = adapter.getItem(position).getHeader();
 		Intent i = new Intent(this, ReadGroupPostActivity.class);
 		i.putExtra("briar.GROUP_ID", groupId.getBytes());
 		i.putExtra("briar.GROUP_NAME", groupName);
@@ -229,6 +283,6 @@ OnClickListener, OnItemClickListener {
 		i.putExtra("briar.CONTENT_TYPE", item.getContentType());
 		i.putExtra("briar.TIMESTAMP", item.getTimestamp());
 		i.putExtra("briar.POSITION", position);
-		startActivityForResult(i, REQUEST_READ_POST);
+		startActivityForResult(i, REQUEST_READ);
 	}
 }
diff --git a/briar-android/src/org/briarproject/android/groups/GroupAdapter.java b/briar-android/src/org/briarproject/android/groups/GroupAdapter.java
index 09c2ad732c..1953666774 100644
--- a/briar-android/src/org/briarproject/android/groups/GroupAdapter.java
+++ b/briar-android/src/org/briarproject/android/groups/GroupAdapter.java
@@ -1,7 +1,9 @@
 package org.briarproject.android.groups;
 
+import static android.view.Gravity.CENTER_HORIZONTAL;
 import static android.view.Gravity.CENTER_VERTICAL;
 import static android.widget.LinearLayout.HORIZONTAL;
+import static android.widget.LinearLayout.VERTICAL;
 import static java.text.DateFormat.SHORT;
 import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP_1;
 
@@ -12,6 +14,7 @@ import org.briarproject.android.util.AuthorView;
 import org.briarproject.android.util.LayoutUtils;
 import org.briarproject.api.Author;
 import org.briarproject.api.db.MessageHeader;
+import org.briarproject.util.StringUtils;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -20,45 +23,71 @@ import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ArrayAdapter;
 import android.widget.LinearLayout;
+import android.widget.ProgressBar;
 import android.widget.TextView;
 
-class GroupAdapter extends ArrayAdapter<MessageHeader> {
+class GroupAdapter extends ArrayAdapter<GroupItem> {
 
 	private final int pad;
 
 	GroupAdapter(Context ctx) {
 		super(ctx, android.R.layout.simple_expandable_list_item_1,
-				new ArrayList<MessageHeader>());
+				new ArrayList<GroupItem>());
 		pad = LayoutUtils.getPadding(ctx);
 	}
 
 	@Override
 	public View getView(int position, View convertView, ViewGroup parent) {
-		MessageHeader header = getItem(position);
+		GroupItem item = getItem(position);
+		MessageHeader header = item.getHeader();
 		Context ctx = getContext();
+		Resources res = ctx.getResources();
 
-		LinearLayout layout = new LinearLayout(ctx);
-		layout.setOrientation(HORIZONTAL);
-		layout.setGravity(CENTER_VERTICAL);
-		if(!header.isRead()) {
-			Resources res = ctx.getResources();
-			layout.setBackgroundColor(res.getColor(R.color.unread_background));
-		}
+		LinearLayout headerLayout = new LinearLayout(ctx);
+		headerLayout.setOrientation(HORIZONTAL);
+		headerLayout.setGravity(CENTER_VERTICAL);
+		int background;
+		if(header.isRead()) background = res.getColor(R.color.read_background);
+		else background = res.getColor(R.color.unread_background);
+		headerLayout.setBackgroundColor(background);
 
 		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());
-		layout.addView(authorView);
+		headerLayout.addView(authorView);
 
+		// FIXME: Factor this out into a TimestampView
 		TextView date = new TextView(ctx);
 		date.setTextSize(14);
 		date.setPadding(0, pad, pad, pad);
 		long then = header.getTimestamp(), now = System.currentTimeMillis();
 		date.setText(DateUtils.formatSameDayTime(then, now, SHORT, SHORT));
-		layout.addView(date);
+		headerLayout.addView(date);
+
+		if(!item.isExpanded()) return headerLayout;
+
+		LinearLayout expanded = new LinearLayout(ctx);
+		expanded.setOrientation(VERTICAL);
+		expanded.setGravity(CENTER_HORIZONTAL);
+		expanded.setBackgroundColor(background);
+		expanded.addView(headerLayout);
+
+		byte[] body = item.getBody();
+		if(body == null) {
+			ProgressBar progress = new ProgressBar(ctx);
+			progress.setPadding(pad, 0, pad, pad);
+			progress.setIndeterminate(true);
+			expanded.addView(progress);
+		} else if(header.getContentType().equals("text/plain")) {
+			TextView text = new TextView(ctx);
+			text.setPadding(pad, 0, pad, pad);
+			text.setBackgroundColor(background);
+			text.setText(StringUtils.fromUtf8(body));
+			expanded.addView(text);
+		}
 
-		return layout;
+		return expanded;
 	}
 }
diff --git a/briar-android/src/org/briarproject/android/groups/GroupItem.java b/briar-android/src/org/briarproject/android/groups/GroupItem.java
new file mode 100644
index 0000000000..d3ec3cf03b
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/groups/GroupItem.java
@@ -0,0 +1,37 @@
+package org.briarproject.android.groups;
+
+import org.briarproject.api.db.MessageHeader;
+
+// This class is not thread-safe
+class GroupItem {
+
+	private final MessageHeader header;
+	private boolean expanded;
+	private byte[] body;
+
+	GroupItem(MessageHeader header) {
+		this.header = header;
+		expanded = false;
+		body = null;
+	}
+
+	MessageHeader getHeader() {
+		return header;
+	}
+
+	boolean isExpanded() {
+		return expanded;
+	}
+
+	void setExpanded(boolean expanded) {
+		this.expanded = expanded;
+	}
+
+	byte[] getBody() {
+		return body;
+	}
+
+	void setBody(byte[] body) {
+		this.body = body;
+	}
+}
diff --git a/briar-android/src/org/briarproject/android/groups/GroupItemComparator.java b/briar-android/src/org/briarproject/android/groups/GroupItemComparator.java
new file mode 100644
index 0000000000..858c0b2c59
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/groups/GroupItemComparator.java
@@ -0,0 +1,17 @@
+package org.briarproject.android.groups;
+
+import java.util.Comparator;
+
+class GroupItemComparator implements Comparator<GroupItem> {
+
+	static final GroupItemComparator INSTANCE = new GroupItemComparator();
+
+	public int compare(GroupItem a, GroupItem b) {
+		// The oldest message comes first
+		long aTime = a.getHeader().getTimestamp();
+		long bTime = b.getHeader().getTimestamp();
+		if(aTime < bTime) return -1;
+		if(aTime > bTime) return 1;
+		return 0;
+	}
+}
-- 
GitLab