diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml
index e5158b9ef..02a4e1d7f 100644
--- a/briar-android/AndroidManifest.xml
+++ b/briar-android/AndroidManifest.xml
@@ -82,19 +82,15 @@
android:label="@string/add_contact_title" >
-
-
diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml
index 00d884d35..004263092 100644
--- a/briar-android/res/values/strings.xml
+++ b/briar-android/res/values/strings.xml
@@ -12,7 +12,6 @@
Wrong password, try again:
This software has expired.\nPlease install a newer version.
Contacts
- Messages
Forums
Synchronize
Quit
diff --git a/briar-android/src/net/sf/briar/android/HomeScreenActivity.java b/briar-android/src/net/sf/briar/android/HomeScreenActivity.java
index b9374dd7d..6495889b6 100644
--- a/briar-android/src/net/sf/briar/android/HomeScreenActivity.java
+++ b/briar-android/src/net/sf/briar/android/HomeScreenActivity.java
@@ -26,7 +26,6 @@ import net.sf.briar.android.BriarService.BriarBinder;
import net.sf.briar.android.BriarService.BriarServiceConnection;
import net.sf.briar.android.contact.ContactListActivity;
import net.sf.briar.android.groups.GroupListActivity;
-import net.sf.briar.android.messages.ConversationListActivity;
import net.sf.briar.api.LocalAuthor;
import net.sf.briar.api.android.DatabaseUiExecutor;
import net.sf.briar.api.android.ReferenceManager;
@@ -315,20 +314,6 @@ public class HomeScreenActivity extends RoboActivity {
});
buttons.add(contactsButton);
- Button messagesButton = new Button(this);
- messagesButton.setLayoutParams(matchMatch);
- messagesButton.setBackgroundResource(0);
- messagesButton.setCompoundDrawablesWithIntrinsicBounds(0,
- R.drawable.content_email, 0, 0);
- messagesButton.setText(R.string.messages_button);
- messagesButton.setOnClickListener(new OnClickListener() {
- public void onClick(View view) {
- startActivity(new Intent(HomeScreenActivity.this,
- ConversationListActivity.class));
- }
- });
- buttons.add(messagesButton);
-
Button forumsButton = new Button(this);
forumsButton.setLayoutParams(matchMatch);
forumsButton.setBackgroundResource(0);
diff --git a/briar-android/src/net/sf/briar/android/contact/ContactListActivity.java b/briar-android/src/net/sf/briar/android/contact/ContactListActivity.java
index 834416420..44eb0aad3 100644
--- a/briar-android/src/net/sf/briar/android/contact/ContactListActivity.java
+++ b/briar-android/src/net/sf/briar/android/contact/ContactListActivity.java
@@ -24,6 +24,7 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import net.sf.briar.R;
+import net.sf.briar.android.groups.NoContactsDialog;
import net.sf.briar.android.invitation.AddContactActivity;
import net.sf.briar.android.util.HorizontalBorder;
import net.sf.briar.android.util.HorizontalSpace;
@@ -33,28 +34,32 @@ import net.sf.briar.api.ContactId;
import net.sf.briar.api.android.DatabaseUiExecutor;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.db.NoSuchContactException;
+import net.sf.briar.api.db.PrivateMessageHeader;
import net.sf.briar.api.db.event.ContactAddedEvent;
import net.sf.briar.api.db.event.ContactRemovedEvent;
import net.sf.briar.api.db.event.DatabaseEvent;
import net.sf.briar.api.db.event.DatabaseListener;
+import net.sf.briar.api.db.event.MessageExpiredEvent;
+import net.sf.briar.api.db.event.PrivateMessageAddedEvent;
import net.sf.briar.api.lifecycle.LifecycleManager;
import net.sf.briar.api.transport.ConnectionListener;
import net.sf.briar.api.transport.ConnectionRegistry;
-import roboguice.activity.RoboActivity;
+import roboguice.activity.RoboFragmentActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
import android.view.View;
import android.view.View.OnClickListener;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ListView;
-public class ContactListActivity extends RoboActivity
-implements OnClickListener, OnItemClickListener, DatabaseListener,
-ConnectionListener {
+public class ContactListActivity extends RoboFragmentActivity
+implements OnClickListener, DatabaseListener, ConnectionListener,
+NoContactsDialog.Listener {
private static final Logger LOG =
Logger.getLogger(ContactListActivity.class.getName());
@@ -63,7 +68,9 @@ ConnectionListener {
private ContactListAdapter adapter = null;
private ListView list = null;
private ListLoadingProgressBar loading = null;
- private ImageButton addContactButton = null, shareButton = null;
+ private ImageButton addContactButton = null, composeButton = null;
+ private ImageButton shareButton = null;
+ private NoContactsDialog noContactsDialog = null;
// Fields that are accessed from background threads must be volatile
@Inject private volatile DatabaseComponent db;
@@ -83,7 +90,7 @@ ConnectionListener {
// Give me all the width and all the unused height
list.setLayoutParams(MATCH_WRAP_1);
list.setAdapter(adapter);
- list.setOnItemClickListener(this);
+ list.setOnItemClickListener(adapter);
layout.addView(list);
// Show a progress bar while the list is loading
@@ -106,6 +113,13 @@ ConnectionListener {
footer.addView(addContactButton);
footer.addView(new HorizontalSpace(this));
+ composeButton = new ImageButton(this);
+ composeButton.setBackgroundResource(0);
+ composeButton.setImageResource(R.drawable.content_new_email);
+ composeButton.setOnClickListener(this);
+ footer.addView(composeButton);
+ footer.addView(new HorizontalSpace(this));
+
shareButton = new ImageButton(this);
shareButton.setBackgroundResource(0);
shareButton.setImageResource(R.drawable.social_share);
@@ -115,6 +129,12 @@ ConnectionListener {
layout.addView(footer);
setContentView(layout);
+
+ FragmentManager fm = getSupportFragmentManager();
+ Fragment f = fm.findFragmentByTag("NoContactsDialog");
+ if(f == null) noContactsDialog = new NoContactsDialog();
+ else noContactsDialog = (NoContactsDialog) f;
+ noContactsDialog.setListener(this);
}
@Override
@@ -122,21 +142,33 @@ ConnectionListener {
super.onResume();
db.addListener(this);
connectionRegistry.addListener(this);
- loadContacts();
+ loadHeaders();
}
- private void loadContacts() {
+ private void loadHeaders() {
+ clearHeaders();
dbUiExecutor.execute(new Runnable() {
public void run() {
try {
lifecycleManager.waitForDatabase();
long now = System.currentTimeMillis();
- Collection contacts = db.getContacts();
Map times = db.getLastConnected();
+ for(Contact c : db.getContacts()) {
+ Long lastConnected = times.get(c.getId());
+ if(lastConnected == null) continue;
+ try {
+ Collection headers =
+ db.getPrivateMessageHeaders(c.getId());
+ displayHeaders(c, lastConnected, headers);
+ } catch(NoSuchContactException e) {
+ if(LOG.isLoggable(INFO))
+ LOG.info("Contact removed");
+ }
+ }
long duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO))
- LOG.info("Load took " + duration + " ms");
- displayContacts(contacts, times);
+ LOG.info("Full load took " + duration + " ms");
+ hideProgressBar();
} catch(DbException e) {
if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
@@ -149,25 +181,54 @@ ConnectionListener {
});
}
- private void displayContacts(final Collection contacts,
- final Map times) {
+ private void clearHeaders() {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ list.setVisibility(GONE);
+ loading.setVisibility(VISIBLE);
+ adapter.clear();
+ adapter.notifyDataSetChanged();
+ }
+ });
+ }
+
+ private void displayHeaders(final Contact c, final long lastConnected,
+ final Collection headers) {
runOnUiThread(new Runnable() {
public void run() {
list.setVisibility(VISIBLE);
loading.setVisibility(GONE);
- adapter.clear();
- for(Contact c : contacts) {
- boolean now = connectionRegistry.isConnected(c.getId());
- Long last = times.get(c.getId());
- if(last != null)
- adapter.add(new ContactListItem(c, now, last));
- }
+ boolean connected = connectionRegistry.isConnected(c.getId());
+ // Remove the old item, if any
+ ContactListItem item = findItem(c.getId());
+ if(item != null) adapter.remove(item);
+ // Add a new item
+ adapter.add(new ContactListItem(c, connected, lastConnected,
+ headers));
adapter.sort(ItemComparator.INSTANCE);
adapter.notifyDataSetChanged();
}
});
}
+ private void hideProgressBar() {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ list.setVisibility(VISIBLE);
+ loading.setVisibility(GONE);
+ }
+ });
+ }
+
+ private ContactListItem findItem(ContactId c) {
+ int count = adapter.getCount();
+ for(int i = 0; i < count; i++) {
+ ContactListItem item = adapter.getItem(i);
+ if(item.getContactId().equals(c)) return item;
+ }
+ return null; // Not found
+ }
+
@Override
public void onPause() {
super.onPause();
@@ -178,6 +239,13 @@ ConnectionListener {
public void onClick(View view) {
if(view == addContactButton) {
startActivity(new Intent(this, AddContactActivity.class));
+ } else if(view == composeButton) {
+ if(adapter.isEmpty()) {
+ FragmentManager fm = getSupportFragmentManager();
+ noContactsDialog.show(fm, "NoContactsDialog");
+ } else {
+ startActivity(new Intent(this, WritePrivateMessageActivity.class));
+ }
} else if(view == shareButton) {
String apkPath = getPackageCodePath();
Intent i = new Intent(ACTION_SEND);
@@ -188,14 +256,73 @@ ConnectionListener {
}
}
- public void onItemClick(AdapterView> parent, View view, int position,
- long id) {
- // FIXME: Hook this up to an activity
+ public void eventOccurred(DatabaseEvent e) {
+ if(e instanceof ContactAddedEvent) {
+ loadHeaders();
+ } else if(e instanceof ContactRemovedEvent) {
+ // Reload the conversation, expecting NoSuchContactException
+ if(LOG.isLoggable(INFO)) LOG.info("Contact removed, reloading");
+ reloadHeaders(((ContactRemovedEvent) e).getContactId());
+ } else if(e instanceof MessageExpiredEvent) {
+ if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading");
+ loadHeaders();
+ } else if(e instanceof PrivateMessageAddedEvent) {
+ if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
+ reloadHeaders(((PrivateMessageAddedEvent) e).getContactId());
+ }
}
- public void eventOccurred(DatabaseEvent e) {
- if(e instanceof ContactAddedEvent) loadContacts();
- else if(e instanceof ContactRemovedEvent) loadContacts();
+ private void reloadHeaders(final ContactId c) {
+ dbUiExecutor.execute(new Runnable() {
+ public void run() {
+ try {
+ lifecycleManager.waitForDatabase();
+ long now = System.currentTimeMillis();
+ Collection headers =
+ db.getPrivateMessageHeaders(c);
+ long duration = System.currentTimeMillis() - now;
+ if(LOG.isLoggable(INFO))
+ LOG.info("Partial load took " + duration + " ms");
+ updateItem(c, headers);
+ } catch(NoSuchContactException e) {
+ if(LOG.isLoggable(INFO)) LOG.info("Contact removed");
+ removeItem(c);
+ } 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 updateItem(final ContactId c,
+ final Collection headers) {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ ContactListItem item = findItem(c);
+ if(item == null) return;
+ // Replace the item with a new item containing the new headers
+ adapter.remove(item);
+ item = new ContactListItem(item.getContact(),
+ item.isConnected(), item.getLastConnected(), headers);
+ adapter.add(item);
+ adapter.sort(ItemComparator.INSTANCE);
+ adapter.notifyDataSetChanged();
+ }
+ });
+ }
+
+ private void removeItem(final ContactId c) {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ ContactListItem item = findItem(c);
+ if(item != null) adapter.remove(item);
+ }
+ });
}
public void contactConnected(ContactId c) {
@@ -225,6 +352,12 @@ ConnectionListener {
});
}
+ public void contactCreationSelected() {
+ startActivity(new Intent(this, AddContactActivity.class));
+ }
+
+ public void contactCreationCancelled() {}
+
private static class ItemComparator implements Comparator {
private static final ItemComparator INSTANCE = new ItemComparator();
diff --git a/briar-android/src/net/sf/briar/android/contact/ContactListAdapter.java b/briar-android/src/net/sf/briar/android/contact/ContactListAdapter.java
index 3ff0f06da..2c3a98826 100644
--- a/briar-android/src/net/sf/briar/android/contact/ContactListAdapter.java
+++ b/briar-android/src/net/sf/briar/android/contact/ContactListAdapter.java
@@ -8,17 +8,21 @@ import java.util.ArrayList;
import net.sf.briar.R;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Resources;
import android.text.Html;
import android.text.format.DateUtils;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
-class ContactListAdapter extends ArrayAdapter {
+class ContactListAdapter extends ArrayAdapter
+implements OnItemClickListener {
ContactListAdapter(Context ctx) {
super(ctx, android.R.layout.simple_expandable_list_item_1,
@@ -32,6 +36,9 @@ class ContactListAdapter extends ArrayAdapter {
LinearLayout layout = new LinearLayout(ctx);
layout.setOrientation(HORIZONTAL);
layout.setGravity(CENTER_VERTICAL);
+ Resources res = ctx.getResources();
+ if(item.getUnreadCount() > 0)
+ layout.setBackgroundColor(res.getColor(R.color.unread_background));
ImageView bulb = new ImageView(ctx);
bulb.setPadding(5, 5, 5, 5);
@@ -46,7 +53,10 @@ class ContactListAdapter extends ArrayAdapter {
name.setTextSize(18);
name.setMaxLines(1);
name.setPadding(0, 10, 10, 10);
- name.setText(item.getContactName());
+ int unread = item.getUnreadCount();
+ String contactName = item.getContactName();
+ if(unread > 0) name.setText(contactName + " (" + unread + ")");
+ else name.setText(contactName);
layout.addView(name);
TextView connected = new TextView(ctx);
@@ -55,7 +65,6 @@ class ContactListAdapter extends ArrayAdapter {
if(item.isConnected()) {
connected.setText(R.string.contact_connected);
} else {
- Resources res = ctx.getResources();
String format = res.getString(R.string.format_last_connected);
long then = item.getLastConnected();
CharSequence ago = DateUtils.getRelativeTimeSpanString(then);
@@ -65,4 +74,15 @@ class ContactListAdapter extends ArrayAdapter {
return layout;
}
+
+ public void onItemClick(AdapterView> parent, View view, int position,
+ long id) {
+ ContactListItem item = getItem(position);
+ Intent i = new Intent(getContext(), ConversationActivity.class);
+ i.putExtra("net.sf.briar.CONTACT_ID", item.getContactId().getInt());
+ i.putExtra("net.sf.briar.CONTACT_NAME", item.getContactName());
+ i.putExtra("net.sf.briar.LOCAL_AUTHOR_ID",
+ item.getLocalAuthorId().getBytes());
+ getContext().startActivity(i);
+ }
}
diff --git a/briar-android/src/net/sf/briar/android/contact/ContactListItem.java b/briar-android/src/net/sf/briar/android/contact/ContactListItem.java
index 72b9d17e6..5787c1158 100644
--- a/briar-android/src/net/sf/briar/android/contact/ContactListItem.java
+++ b/briar-android/src/net/sf/briar/android/contact/ContactListItem.java
@@ -1,7 +1,15 @@
package net.sf.briar.android.contact;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import net.sf.briar.android.DescendingHeaderComparator;
+import net.sf.briar.api.AuthorId;
import net.sf.briar.api.Contact;
import net.sf.briar.api.ContactId;
+import net.sf.briar.api.db.PrivateMessageHeader;
// This class is not thread-safe
class ContactListItem {
@@ -9,11 +17,32 @@ class ContactListItem {
private final Contact contact;
private boolean connected;
private long lastConnected;
+ private final boolean empty;
+ private final long timestamp;
+ private final int unread;
- ContactListItem(Contact contact, boolean connected, long lastConnected) {
+ ContactListItem(Contact contact, boolean connected, long lastConnected,
+ Collection headers) {
this.contact = contact;
this.connected = connected;
this.lastConnected = lastConnected;
+ empty = headers.isEmpty();
+ if(empty) {
+ timestamp = 0;
+ unread = 0;
+ } else {
+ List list =
+ new ArrayList(headers);
+ Collections.sort(list, DescendingHeaderComparator.INSTANCE);
+ timestamp = list.get(0).getTimestamp();
+ int unread = 0;
+ for(PrivateMessageHeader h : list) if(!h.isRead()) unread++;
+ this.unread = unread;
+ }
+ }
+
+ Contact getContact() {
+ return contact;
}
ContactId getContactId() {
@@ -39,4 +68,20 @@ class ContactListItem {
void setConnected(boolean connected) {
this.connected = connected;
}
+
+ AuthorId getLocalAuthorId() {
+ return contact.getLocalAuthorId();
+ }
+
+ boolean isEmpty() {
+ return empty;
+ }
+
+ long getTimestamp() {
+ return timestamp;
+ }
+
+ int getUnreadCount() {
+ return unread;
+ }
}
\ No newline at end of file
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java b/briar-android/src/net/sf/briar/android/contact/ConversationActivity.java
similarity index 99%
rename from briar-android/src/net/sf/briar/android/messages/ConversationActivity.java
rename to briar-android/src/net/sf/briar/android/contact/ConversationActivity.java
index 2dce8e6dd..a0fe1020c 100644
--- a/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java
+++ b/briar-android/src/net/sf/briar/android/contact/ConversationActivity.java
@@ -1,4 +1,4 @@
-package net.sf.briar.android.messages;
+package net.sf.briar.android.contact;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.view.View.GONE;
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationAdapter.java b/briar-android/src/net/sf/briar/android/contact/ConversationAdapter.java
similarity index 97%
rename from briar-android/src/net/sf/briar/android/messages/ConversationAdapter.java
rename to briar-android/src/net/sf/briar/android/contact/ConversationAdapter.java
index 863aa88c3..3497efd10 100644
--- a/briar-android/src/net/sf/briar/android/messages/ConversationAdapter.java
+++ b/briar-android/src/net/sf/briar/android/contact/ConversationAdapter.java
@@ -1,4 +1,4 @@
-package net.sf.briar.android.messages;
+package net.sf.briar.android.contact;
import static android.widget.LinearLayout.HORIZONTAL;
import static java.text.DateFormat.SHORT;
diff --git a/briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java b/briar-android/src/net/sf/briar/android/contact/ReadPrivateMessageActivity.java
similarity index 99%
rename from briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java
rename to briar-android/src/net/sf/briar/android/contact/ReadPrivateMessageActivity.java
index c47b30603..677b12db5 100644
--- a/briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java
+++ b/briar-android/src/net/sf/briar/android/contact/ReadPrivateMessageActivity.java
@@ -1,4 +1,4 @@
-package net.sf.briar.android.messages;
+package net.sf.briar.android.contact;
import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_VERTICAL;
diff --git a/briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java b/briar-android/src/net/sf/briar/android/contact/WritePrivateMessageActivity.java
similarity index 97%
rename from briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java
rename to briar-android/src/net/sf/briar/android/contact/WritePrivateMessageActivity.java
index dfa935cc0..51e672086 100644
--- a/briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java
+++ b/briar-android/src/net/sf/briar/android/contact/WritePrivateMessageActivity.java
@@ -1,4 +1,4 @@
-package net.sf.briar.android.messages;
+package net.sf.briar.android.contact;
import static android.text.InputType.TYPE_CLASS_TEXT;
import static android.text.InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
@@ -19,9 +19,6 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import net.sf.briar.R;
-import net.sf.briar.android.contact.ContactItem;
-import net.sf.briar.android.contact.ContactItemComparator;
-import net.sf.briar.android.contact.ContactSpinnerAdapter;
import net.sf.briar.android.invitation.AddContactActivity;
import net.sf.briar.android.util.HorizontalSpace;
import net.sf.briar.api.AuthorId;
diff --git a/briar-android/src/net/sf/briar/android/groups/ConfigureGroupActivity.java b/briar-android/src/net/sf/briar/android/groups/ConfigureGroupActivity.java
index e3d12fcc1..6241ba2f3 100644
--- a/briar-android/src/net/sf/briar/android/groups/ConfigureGroupActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/ConfigureGroupActivity.java
@@ -19,7 +19,6 @@ import javax.inject.Inject;
import net.sf.briar.R;
import net.sf.briar.android.contact.SelectContactsDialog;
import net.sf.briar.android.invitation.AddContactActivity;
-import net.sf.briar.android.messages.NoContactsDialog;
import net.sf.briar.api.Contact;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.android.DatabaseUiExecutor;
diff --git a/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java b/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java
index b6d0dff45..dd7bb3981 100644
--- a/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java
@@ -24,7 +24,6 @@ import javax.inject.Inject;
import net.sf.briar.R;
import net.sf.briar.android.contact.SelectContactsDialog;
import net.sf.briar.android.invitation.AddContactActivity;
-import net.sf.briar.android.messages.NoContactsDialog;
import net.sf.briar.api.Contact;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.android.DatabaseUiExecutor;
diff --git a/briar-android/src/net/sf/briar/android/messages/NoContactsDialog.java b/briar-android/src/net/sf/briar/android/groups/NoContactsDialog.java
similarity index 96%
rename from briar-android/src/net/sf/briar/android/messages/NoContactsDialog.java
rename to briar-android/src/net/sf/briar/android/groups/NoContactsDialog.java
index ddef88e45..dcd39facb 100644
--- a/briar-android/src/net/sf/briar/android/messages/NoContactsDialog.java
+++ b/briar-android/src/net/sf/briar/android/groups/NoContactsDialog.java
@@ -1,4 +1,4 @@
-package net.sf.briar.android.messages;
+package net.sf.briar.android.groups;
import net.sf.briar.R;
import android.app.AlertDialog;
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java b/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java
deleted file mode 100644
index 719e33419..000000000
--- a/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java
+++ /dev/null
@@ -1,290 +0,0 @@
-package net.sf.briar.android.messages;
-
-import static android.view.Gravity.CENTER_HORIZONTAL;
-import static android.view.View.GONE;
-import static android.view.View.VISIBLE;
-import static android.widget.LinearLayout.VERTICAL;
-import static java.util.logging.Level.INFO;
-import static java.util.logging.Level.WARNING;
-import static net.sf.briar.android.util.CommonLayoutParams.MATCH_MATCH;
-import static net.sf.briar.android.util.CommonLayoutParams.MATCH_WRAP_1;
-
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.concurrent.Executor;
-import java.util.logging.Logger;
-
-import javax.inject.Inject;
-
-import net.sf.briar.R;
-import net.sf.briar.android.invitation.AddContactActivity;
-import net.sf.briar.android.util.HorizontalBorder;
-import net.sf.briar.android.util.ListLoadingProgressBar;
-import net.sf.briar.api.Contact;
-import net.sf.briar.api.ContactId;
-import net.sf.briar.api.android.DatabaseUiExecutor;
-import net.sf.briar.api.db.DatabaseComponent;
-import net.sf.briar.api.db.DbException;
-import net.sf.briar.api.db.NoSuchContactException;
-import net.sf.briar.api.db.PrivateMessageHeader;
-import net.sf.briar.api.db.event.ContactRemovedEvent;
-import net.sf.briar.api.db.event.DatabaseEvent;
-import net.sf.briar.api.db.event.DatabaseListener;
-import net.sf.briar.api.db.event.MessageExpiredEvent;
-import net.sf.briar.api.db.event.PrivateMessageAddedEvent;
-import net.sf.briar.api.lifecycle.LifecycleManager;
-import roboguice.activity.RoboFragmentActivity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-import android.widget.ListView;
-
-public class ConversationListActivity extends RoboFragmentActivity
-implements OnClickListener, DatabaseListener, NoContactsDialog.Listener {
-
- private static final Logger LOG =
- Logger.getLogger(ConversationListActivity.class.getName());
-
- private ConversationListAdapter adapter = null;
- private ListView list = null;
- private ListLoadingProgressBar loading = null;
- private NoContactsDialog noContactsDialog = null;
-
- // Fields that are accessed from background threads must be volatile
- @Inject private volatile DatabaseComponent db;
- @Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
- @Inject private volatile LifecycleManager lifecycleManager;
-
- @Override
- public void onCreate(Bundle state) {
- super.onCreate(state);
- LinearLayout layout = new LinearLayout(this);
- layout.setLayoutParams(MATCH_MATCH);
- layout.setOrientation(VERTICAL);
- layout.setGravity(CENTER_HORIZONTAL);
-
- adapter = new ConversationListAdapter(this);
- list = new ListView(this);
- // Give me all the width and all the unused height
- list.setLayoutParams(MATCH_WRAP_1);
- list.setAdapter(adapter);
- list.setOnItemClickListener(adapter);
- layout.addView(list);
-
- // Show a progress bar while the list is loading
- list.setVisibility(GONE);
- loading = new ListLoadingProgressBar(this);
- layout.addView(loading);
-
- layout.addView(new HorizontalBorder(this));
-
- ImageButton composeButton = new ImageButton(this);
- composeButton.setBackgroundResource(0);
- composeButton.setImageResource(R.drawable.content_new_email);
- composeButton.setOnClickListener(this);
- layout.addView(composeButton);
-
- setContentView(layout);
-
- FragmentManager fm = getSupportFragmentManager();
- Fragment f = fm.findFragmentByTag("NoContactsDialog");
- if(f == null) noContactsDialog = new NoContactsDialog();
- else noContactsDialog = (NoContactsDialog) f;
- noContactsDialog.setListener(this);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- db.addListener(this);
- loadHeaders();
- }
-
- private void loadHeaders() {
- clearHeaders();
- dbUiExecutor.execute(new Runnable() {
- public void run() {
- try {
- lifecycleManager.waitForDatabase();
- long now = System.currentTimeMillis();
- for(Contact c : db.getContacts()) {
- try {
- Collection headers =
- db.getPrivateMessageHeaders(c.getId());
- displayHeaders(c, headers);
- } catch(NoSuchContactException e) {
- if(LOG.isLoggable(INFO))
- LOG.info("Contact removed");
- }
- }
- long duration = System.currentTimeMillis() - now;
- if(LOG.isLoggable(INFO))
- LOG.info("Full load took " + duration + " ms");
- hideProgressBar();
- } 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 clearHeaders() {
- runOnUiThread(new Runnable() {
- public void run() {
- list.setVisibility(GONE);
- loading.setVisibility(VISIBLE);
- adapter.clear();
- adapter.notifyDataSetChanged();
- }
- });
- }
-
- private void displayHeaders(final Contact c,
- final Collection headers) {
- runOnUiThread(new Runnable() {
- public void run() {
- list.setVisibility(VISIBLE);
- loading.setVisibility(GONE);
- // Remove the old item, if any
- ConversationListItem item = findConversation(c.getId());
- if(item != null) adapter.remove(item);
- // Add a new item
- adapter.add(new ConversationListItem(c, headers));
- adapter.sort(ItemComparator.INSTANCE);
- adapter.notifyDataSetChanged();
- selectFirstUnread();
- }
- });
- }
-
- private void hideProgressBar() {
- runOnUiThread(new Runnable() {
- public void run() {
- list.setVisibility(VISIBLE);
- loading.setVisibility(GONE);
- }
- });
- }
-
- private ConversationListItem findConversation(ContactId c) {
- int count = adapter.getCount();
- for(int i = 0; i < count; i++) {
- ConversationListItem item = adapter.getItem(i);
- if(item.getContactId().equals(c)) return item;
- }
- return null; // Not found
- }
-
- private void selectFirstUnread() {
- int firstUnread = -1, count = adapter.getCount();
- for(int i = 0; i < count; i++) {
- if(adapter.getItem(i).getUnreadCount() > 0) {
- firstUnread = i;
- break;
- }
- }
- if(firstUnread == -1) list.setSelection(count - 1);
- else list.setSelection(firstUnread);
- }
-
- @Override
- public void onPause() {
- super.onPause();
- db.removeListener(this);
- }
-
- public void onClick(View view) {
- if(adapter.isEmpty()) {
- FragmentManager fm = getSupportFragmentManager();
- noContactsDialog.show(fm, "NoContactsDialog");
- } else {
- startActivity(new Intent(this, WritePrivateMessageActivity.class));
- }
- }
-
- public void eventOccurred(DatabaseEvent e) {
- if(e instanceof ContactRemovedEvent) {
- // Reload the conversation, expecting NoSuchContactException
- if(LOG.isLoggable(INFO)) LOG.info("Contact removed, reloading");
- loadHeaders(((ContactRemovedEvent) e).getContactId());
- } else if(e instanceof MessageExpiredEvent) {
- if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading");
- loadHeaders();
- } else if(e instanceof PrivateMessageAddedEvent) {
- if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
- loadHeaders(((PrivateMessageAddedEvent) e).getContactId());
- }
- }
-
- private void loadHeaders(final ContactId c) {
- dbUiExecutor.execute(new Runnable() {
- public void run() {
- try {
- lifecycleManager.waitForDatabase();
- long now = System.currentTimeMillis();
- Contact contact = db.getContact(c);
- Collection headers =
- db.getPrivateMessageHeaders(c);
- long duration = System.currentTimeMillis() - now;
- if(LOG.isLoggable(INFO))
- LOG.info("Partial load took " + duration + " ms");
- displayHeaders(contact, headers);
- } catch(NoSuchContactException e) {
- if(LOG.isLoggable(INFO)) LOG.info("Contact removed");
- removeConversation(c);
- } 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 removeConversation(final ContactId c) {
- runOnUiThread(new Runnable() {
- public void run() {
- ConversationListItem item = findConversation(c);
- if(item != null) {
- adapter.remove(item);
- selectFirstUnread();
- }
- }
- });
- }
-
- public void contactCreationSelected() {
- startActivity(new Intent(this, AddContactActivity.class));
- }
-
- public void contactCreationCancelled() {}
-
- private static class ItemComparator
- implements Comparator {
-
- private static final ItemComparator INSTANCE = new ItemComparator();
-
- public int compare(ConversationListItem a, ConversationListItem b) {
- // The item with the newest message comes first
- long aTime = a.getTimestamp(), bTime = b.getTimestamp();
- if(aTime > bTime) return -1;
- if(aTime < bTime) return 1;
- // Break ties by contact name
- String aName = a.getContactName(), bName = b.getContactName();
- return String.CASE_INSENSITIVE_ORDER.compare(aName, bName);
- }
- }
-}
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationListAdapter.java b/briar-android/src/net/sf/briar/android/messages/ConversationListAdapter.java
deleted file mode 100644
index e9ce83be2..000000000
--- a/briar-android/src/net/sf/briar/android/messages/ConversationListAdapter.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package net.sf.briar.android.messages;
-
-import static android.widget.LinearLayout.HORIZONTAL;
-import static java.text.DateFormat.SHORT;
-import static net.sf.briar.android.util.CommonLayoutParams.WRAP_WRAP_1;
-
-import java.util.ArrayList;
-
-import net.sf.briar.R;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.text.format.DateUtils;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.ArrayAdapter;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-class ConversationListAdapter extends ArrayAdapter
-implements OnItemClickListener {
-
- ConversationListAdapter(Context ctx) {
- super(ctx, android.R.layout.simple_expandable_list_item_1,
- new ArrayList());
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- ConversationListItem item = getItem(position);
- Context ctx = getContext();
- Resources res = ctx.getResources();
-
- LinearLayout layout = new LinearLayout(ctx);
- layout.setOrientation(HORIZONTAL);
- if(item.getUnreadCount() > 0)
- layout.setBackgroundColor(res.getColor(R.color.unread_background));
-
- TextView name = new TextView(ctx);
- // Give me all the unused width
- name.setLayoutParams(WRAP_WRAP_1);
- name.setTextSize(18);
- name.setMaxLines(1);
- name.setPadding(10, 10, 10, 10);
- int unread = item.getUnreadCount();
- String contactName = item.getContactName();
- if(unread > 0) name.setText(contactName + " (" + unread + ")");
- else name.setText(contactName);
- layout.addView(name);
-
- if(item.isEmpty()) {
- TextView noMessages = new TextView(ctx);
- noMessages.setTextSize(14);
- noMessages.setPadding(10, 0, 10, 10);
- noMessages.setTextColor(res.getColor(R.color.no_messages));
- noMessages.setText(R.string.no_messages);
- layout.addView(noMessages);
- } else {
- TextView date = new TextView(ctx);
- date.setTextSize(14);
- date.setPadding(0, 10, 10, 10);
- long then = item.getTimestamp(), now = System.currentTimeMillis();
- date.setText(DateUtils.formatSameDayTime(then, now, SHORT, SHORT));
- layout.addView(date);
- }
-
- return layout;
- }
-
- public void onItemClick(AdapterView> parent, View view, int position,
- long id) {
- ConversationListItem item = getItem(position);
- Intent i = new Intent(getContext(), ConversationActivity.class);
- i.putExtra("net.sf.briar.CONTACT_ID", item.getContactId().getInt());
- i.putExtra("net.sf.briar.CONTACT_NAME", item.getContactName());
- i.putExtra("net.sf.briar.LOCAL_AUTHOR_ID",
- item.getLocalAuthorId().getBytes());
- getContext().startActivity(i);
- }
-}
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationListItem.java b/briar-android/src/net/sf/briar/android/messages/ConversationListItem.java
deleted file mode 100644
index 231560a50..000000000
--- a/briar-android/src/net/sf/briar/android/messages/ConversationListItem.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package net.sf.briar.android.messages;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-import net.sf.briar.android.DescendingHeaderComparator;
-import net.sf.briar.api.AuthorId;
-import net.sf.briar.api.Contact;
-import net.sf.briar.api.ContactId;
-import net.sf.briar.api.db.PrivateMessageHeader;
-
-class ConversationListItem {
-
- private final Contact contact;
- private final boolean empty;
- private final long timestamp;
- private final int unread;
-
- ConversationListItem(Contact contact,
- Collection headers) {
- this.contact = contact;
- empty = headers.isEmpty();
- if(empty) {
- timestamp = 0;
- unread = 0;
- } else {
- List list =
- new ArrayList(headers);
- Collections.sort(list, DescendingHeaderComparator.INSTANCE);
- timestamp = list.get(0).getTimestamp();
- int unread = 0;
- for(PrivateMessageHeader h : list) if(!h.isRead()) unread++;
- this.unread = unread;
- }
- }
-
- ContactId getContactId() {
- return contact.getId();
- }
-
- String getContactName() {
- return contact.getAuthor().getName();
- }
-
- AuthorId getLocalAuthorId() {
- return contact.getLocalAuthorId();
- }
-
- boolean isEmpty() {
- return empty;
- }
-
- long getTimestamp() {
- return timestamp;
- }
-
- int getUnreadCount() {
- return unread;
- }
-}