mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user