Merged private message activities with contact list.

This makes the contact list behave how alpha testers expected: touching
a contact shows private messages exchanged with the contact.
This commit is contained in:
akwizgran
2013-12-05 12:17:17 +00:00
parent 3e6d7ba66a
commit f383532ecd
16 changed files with 238 additions and 499 deletions

View File

@@ -82,19 +82,15 @@
android:label="@string/add_contact_title" >
</activity>
<activity
android:name=".android.messages.ConversationActivity"
android:name="net.sf.briar.android.contact.ConversationActivity"
android:label="@string/app_name" >
</activity>
<activity
android:name=".android.messages.ConversationListActivity"
android:label="@string/messages_title" >
</activity>
<activity
android:name=".android.messages.ReadPrivateMessageActivity"
android:name="net.sf.briar.android.contact.ReadPrivateMessageActivity"
android:label="@string/app_name" >
</activity>
<activity
android:name=".android.messages.WritePrivateMessageActivity"
android:name="net.sf.briar.android.contact.WritePrivateMessageActivity"
android:label="@string/new_message_title" >
</activity>
</application>

View File

@@ -12,7 +12,6 @@
<string name="try_again">Wrong password, try again:</string>
<string name="expiry_warning">This software has expired.\nPlease install a newer version.</string>
<string name="contact_list_button">Contacts</string>
<string name="messages_button">Messages</string>
<string name="forums_button">Forums</string>
<string name="synchronize_button">Synchronize</string>
<string name="quit_button">Quit</string>

View File

@@ -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);

View File

@@ -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<Contact> contacts = db.getContacts();
Map<ContactId, Long> times = db.getLastConnected();
for(Contact c : db.getContacts()) {
Long lastConnected = times.get(c.getId());
if(lastConnected == null) continue;
try {
Collection<PrivateMessageHeader> 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<Contact> contacts,
final Map<ContactId, Long> 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<PrivateMessageHeader> 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<PrivateMessageHeader> 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<PrivateMessageHeader> 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<ContactListItem> {
private static final ItemComparator INSTANCE = new ItemComparator();

View File

@@ -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<ContactListItem> {
class ContactListAdapter extends ArrayAdapter<ContactListItem>
implements OnItemClickListener {
ContactListAdapter(Context ctx) {
super(ctx, android.R.layout.simple_expandable_list_item_1,
@@ -32,6 +36,9 @@ class ContactListAdapter extends ArrayAdapter<ContactListItem> {
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<ContactListItem> {
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<ContactListItem> {
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<ContactListItem> {
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);
}
}

View File

@@ -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<PrivateMessageHeader> headers) {
this.contact = contact;
this.connected = connected;
this.lastConnected = lastConnected;
empty = headers.isEmpty();
if(empty) {
timestamp = 0;
unread = 0;
} else {
List<PrivateMessageHeader> list =
new ArrayList<PrivateMessageHeader>(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;
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<PrivateMessageHeader> 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<PrivateMessageHeader> 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<PrivateMessageHeader> 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<ConversationListItem> {
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);
}
}
}

View File

@@ -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<ConversationListItem>
implements OnItemClickListener {
ConversationListAdapter(Context ctx) {
super(ctx, android.R.layout.simple_expandable_list_item_1,
new ArrayList<ConversationListItem>());
}
@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);
}
}

View File

@@ -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<PrivateMessageHeader> headers) {
this.contact = contact;
empty = headers.isEmpty();
if(empty) {
timestamp = 0;
unread = 0;
} else {
List<PrivateMessageHeader> list =
new ArrayList<PrivateMessageHeader>(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;
}
}