Display conversations and groups even if they're empty.

This commit is contained in:
akwizgran
2013-04-13 16:22:12 +01:00
parent 019c8163cf
commit 084b83bb45
11 changed files with 209 additions and 129 deletions

View File

@@ -4,6 +4,7 @@
<color name="content_background">#FFFFFF</color>
<color name="unread_background">#FFFFFF</color>
<color name="horizontal_border">#CCCCCC</color>
<color name="anonymous_author">#AAAAAA</color>
<color name="pseudonymous_author">#000000</color>
<color name="anonymous_author">#999999</color>
<color name="no_posts">#999999</color>
<color name="no_messages">#999999</color>
</resources>

View File

@@ -40,6 +40,7 @@
<string name="contact_added">Contact added</string>
<string name="done_button">Done</string>
<string name="messages_title">Messages</string>
<string name="no_messages">(No messages)</string>
<string name="format_from">From: %1$s</string>
<string name="format_to">To: %1$s</string>
<string name="compose_message_title">New Message</string>
@@ -47,6 +48,7 @@
<string name="to">To:</string>
<string name="anonymous">(Anonymous)</string>
<string name="groups_title">Groups</string>
<string name="no_posts">(No posts)</string>
<string name="create_group_title">New Group</string>
<string name="choose_group_name">Choose a name for your group:</string>
<string name="compose_group_title">New Post</string>

View File

@@ -17,7 +17,6 @@ import net.sf.briar.android.widgets.HorizontalSpace;
import net.sf.briar.api.Author;
import net.sf.briar.api.db.GroupMessageHeader;
import net.sf.briar.api.messaging.Rating;
import net.sf.briar.util.StringUtils;
import android.content.Context;
import android.content.res.Resources;
import android.text.format.DateUtils;
@@ -77,22 +76,20 @@ class GroupAdapter extends ArrayAdapter<GroupMessageHeader> {
name.setTextColor(res.getColor(R.color.anonymous_author));
name.setText(R.string.anonymous);
} else {
name.setTextColor(res.getColor(R.color.pseudonymous_author));
name.setText(author.getName());
}
authorLayout.addView(name);
innerLayout.addView(authorLayout);
if(item.getContentType().equals("text/plain")) {
if(!StringUtils.isNullOrEmpty(item.getSubject())) {
TextView subject = new TextView(ctx);
subject.setTextSize(14);
subject.setMaxLines(2);
subject.setPadding(10, 0, 10, 10);
if(!item.isRead()) subject.setTypeface(null, BOLD);
subject.setText(item.getSubject());
innerLayout.addView(subject);
}
TextView subject = new TextView(ctx);
subject.setTextSize(14);
subject.setMaxLines(2);
subject.setPadding(10, 0, 10, 10);
if(!item.isRead()) subject.setTypeface(null, BOLD);
String s = item.getSubject();
subject.setText(s == null ? "" : s);
innerLayout.addView(subject);
} else {
LinearLayout attachmentLayout = new LinearLayout(ctx);
attachmentLayout.setOrientation(HORIZONTAL);

View File

@@ -10,10 +10,10 @@ import static net.sf.briar.android.widgets.CommonLayoutParams.MATCH_MATCH;
import static net.sf.briar.android.widgets.CommonLayoutParams.MATCH_WRAP;
import static net.sf.briar.android.widgets.CommonLayoutParams.MATCH_WRAP_1;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
@@ -62,7 +62,6 @@ implements OnClickListener, DatabaseListener, NoGroupsDialog.Listener {
@Inject private volatile DatabaseComponent db;
@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
private volatile boolean restricted = false;
private volatile boolean noGroups = true;
@Override
public void onCreate(Bundle state) {
@@ -126,23 +125,39 @@ implements OnClickListener, DatabaseListener, NoGroupsDialog.Listener {
}
private void loadHeaders() {
clearHeaders();
dbUiExecutor.execute(new Runnable() {
public void run() {
try {
serviceConnection.waitForStartup();
long now = System.currentTimeMillis();
if(restricted) noGroups = db.getLocalGroups().isEmpty();
for(Group g : db.getSubscriptions()) {
// Filter out restricted/unrestricted groups
if(g.isRestricted() != restricted) continue;
if(!restricted) noGroups = false;
try {
Collection<GroupMessageHeader> headers =
db.getMessageHeaders(g.getId());
displayHeaders(g, headers);
} catch(NoSuchSubscriptionException e) {
if(LOG.isLoggable(INFO))
LOG.info("Subscription removed");
Collection<Group> subs = db.getSubscriptions();
if(restricted) {
Set<GroupId> local = new HashSet<GroupId>();
for(Group g : db.getLocalGroups()) local.add(g.getId());
for(Group g : subs) {
if(!g.isRestricted()) continue;
boolean postable = local.contains(g.getId());
try {
Collection<GroupMessageHeader> headers =
db.getMessageHeaders(g.getId());
displayHeaders(g, postable, headers);
} catch(NoSuchSubscriptionException e) {
if(LOG.isLoggable(INFO))
LOG.info("Subscription removed");
}
}
} else {
for(Group g : subs) {
if(g.isRestricted()) continue;
try {
Collection<GroupMessageHeader> headers =
db.getMessageHeaders(g.getId());
displayHeaders(g, true, headers);
} catch(NoSuchSubscriptionException e) {
if(LOG.isLoggable(INFO))
LOG.info("Subscription removed");
}
}
}
long duration = System.currentTimeMillis() - now;
@@ -160,20 +175,24 @@ implements OnClickListener, DatabaseListener, NoGroupsDialog.Listener {
});
}
private void displayHeaders(final Group g,
private void clearHeaders() {
runOnUiThread(new Runnable() {
public void run() {
adapter.clear();
}
});
}
private void displayHeaders(final Group g, final boolean postable,
final Collection<GroupMessageHeader> headers) {
runOnUiThread(new Runnable() {
public void run() {
// Remove the old item, if any
GroupListItem item = findGroup(g.getId());
if(item != null) adapter.remove(item);
// Add a new item if there are any headers to display
if(!headers.isEmpty()) {
List<GroupMessageHeader> headerList =
new ArrayList<GroupMessageHeader>(headers);
adapter.add(new GroupListItem(g, headerList));
adapter.sort(GroupComparator.INSTANCE);
}
// Add a new item
adapter.add(new GroupListItem(g, postable, headers));
adapter.sort(GroupComparator.INSTANCE);
adapter.notifyDataSetChanged();
selectFirstUnread();
}
@@ -219,7 +238,7 @@ implements OnClickListener, DatabaseListener, NoGroupsDialog.Listener {
startActivity(new Intent(this, CreateBlogActivity.class));
else startActivity(new Intent(this, CreateGroupActivity.class));
} else if(view == composeButton) {
if(noGroups) {
if(countPostableGroups() == 0) {
NoGroupsDialog dialog = new NoGroupsDialog();
dialog.setListener(this);
dialog.setRestricted(restricted);
@@ -232,6 +251,13 @@ implements OnClickListener, DatabaseListener, NoGroupsDialog.Listener {
}
}
private int countPostableGroups() {
int postable = 0, count = adapter.getCount();
for(int i = 0; i < count; i++)
if(adapter.getItem(i).isPostable()) postable++;
return postable;
}
public void eventOccurred(DatabaseEvent e) {
if(e instanceof GroupMessageAddedEvent) {
Group g = ((GroupMessageAddedEvent) e).getGroup();
@@ -258,12 +284,15 @@ implements OnClickListener, DatabaseListener, NoGroupsDialog.Listener {
try {
serviceConnection.waitForStartup();
long now = System.currentTimeMillis();
boolean postable;
if(restricted) postable = db.getLocalGroups().contains(g);
else postable = true;
Collection<GroupMessageHeader> headers =
db.getMessageHeaders(g.getId());
long duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO))
LOG.info("Partial load took " + duration + " ms");
displayHeaders(g, headers);
displayHeaders(g, postable, headers);
} catch(NoSuchSubscriptionException e) {
if(LOG.isLoggable(INFO)) LOG.info("Subscription removed");
removeGroup(g.getId());
@@ -306,8 +335,13 @@ implements OnClickListener, DatabaseListener, NoGroupsDialog.Listener {
private static final GroupComparator INSTANCE = new GroupComparator();
public int compare(GroupListItem a, GroupListItem b) {
return String.CASE_INSENSITIVE_ORDER.compare(a.getGroupName(),
b.getGroupName());
// 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 group name
String aName = a.getGroupName(), bName = b.getGroupName();
return String.CASE_INSENSITIVE_ORDER.compare(aName, bName);
}
}
}

View File

@@ -10,7 +10,6 @@ import java.util.ArrayList;
import net.sf.briar.R;
import net.sf.briar.android.widgets.HorizontalSpace;
import net.sf.briar.util.StringUtils;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@@ -36,12 +35,12 @@ implements OnItemClickListener {
public View getView(int position, View convertView, ViewGroup parent) {
GroupListItem item = getItem(position);
Context ctx = getContext();
Resources res = ctx.getResources();
LinearLayout layout = new LinearLayout(ctx);
layout.setOrientation(HORIZONTAL);
if(item.getUnreadCount() > 0) {
Resources res = ctx.getResources();
if(item.getUnreadCount() > 0)
layout.setBackgroundColor(res.getColor(R.color.unread_background));
}
LinearLayout innerLayout = new LinearLayout(ctx);
// Give me all the unused width
@@ -57,34 +56,43 @@ implements OnItemClickListener {
else name.setText(item.getGroupName());
innerLayout.addView(name);
if(item.getContentType().equals("text/plain")) {
if(!StringUtils.isNullOrEmpty(item.getSubject())) {
if(item.isEmpty()) {
TextView noPosts = new TextView(ctx);
noPosts.setTextSize(14);
noPosts.setPadding(10, 0, 10, 10);
noPosts.setTextColor(res.getColor(R.color.no_posts));
noPosts.setText(R.string.no_posts);
innerLayout.addView(noPosts);
layout.addView(innerLayout);
} else {
if(item.getContentType().equals("text/plain")) {
TextView subject = new TextView(ctx);
subject.setTextSize(14);
subject.setMaxLines(2);
subject.setPadding(10, 0, 10, 10);
if(item.getUnreadCount() > 0) subject.setTypeface(null, BOLD);
subject.setText(item.getSubject());
String s = item.getSubject();
subject.setText(s == null ? "" : s);
innerLayout.addView(subject);
} else {
LinearLayout attachmentLayout = new LinearLayout(ctx);
attachmentLayout.setOrientation(HORIZONTAL);
ImageView attachment = new ImageView(ctx);
attachment.setPadding(10, 0, 10, 10);
attachment.setImageResource(R.drawable.content_attachment);
attachmentLayout.addView(attachment);
attachmentLayout.addView(new HorizontalSpace(ctx));
innerLayout.addView(attachmentLayout);
}
} else {
LinearLayout attachmentLayout = new LinearLayout(ctx);
attachmentLayout.setOrientation(HORIZONTAL);
ImageView attachment = new ImageView(ctx);
attachment.setPadding(10, 0, 10, 10);
attachment.setImageResource(R.drawable.content_attachment);
attachmentLayout.addView(attachment);
attachmentLayout.addView(new HorizontalSpace(ctx));
innerLayout.addView(attachmentLayout);
}
layout.addView(innerLayout);
layout.addView(innerLayout);
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);
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;
}
@@ -93,6 +101,7 @@ implements OnItemClickListener {
long id) {
GroupListItem item = getItem(position);
Intent i = new Intent(getContext(), GroupActivity.class);
i.putExtra("net.sf.briar.RESTRICTED", item.isRestricted());
i.putExtra("net.sf.briar.GROUP_ID", item.getGroupId().getBytes());
i.putExtra("net.sf.briar.GROUP_NAME", item.getGroupName());
getContext().startActivity(i);

View File

@@ -1,5 +1,7 @@
package net.sf.briar.android.groups;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -12,24 +14,37 @@ import net.sf.briar.api.messaging.GroupId;
class GroupListItem {
private final Group group;
private final boolean postable, empty;
private final String authorName, contentType, subject;
private final long timestamp;
private final int unread;
GroupListItem(Group group, List<GroupMessageHeader> headers) {
if(headers.isEmpty()) throw new IllegalArgumentException();
GroupListItem(Group group, boolean postable,
Collection<GroupMessageHeader> headers) {
this.group = group;
Collections.sort(headers, DescendingHeaderComparator.INSTANCE);
GroupMessageHeader newest = headers.get(0);
Author a = newest.getAuthor();
if(a == null) authorName = null;
else authorName = a.getName();
contentType = newest.getContentType();
subject = newest.getSubject();
timestamp = newest.getTimestamp();
int unread = 0;
for(GroupMessageHeader h : headers) if(!h.isRead()) unread++;
this.unread = unread;
this.postable = postable;
empty = headers.isEmpty();
if(empty) {
authorName = null;
contentType = null;
subject = null;
timestamp = 0;
unread = 0;
} else {
List<GroupMessageHeader> list =
new ArrayList<GroupMessageHeader>(headers);
Collections.sort(list, DescendingHeaderComparator.INSTANCE);
GroupMessageHeader newest = list.get(0);
Author a = newest.getAuthor();
if(a == null) authorName = null;
else authorName = a.getName();
contentType = newest.getContentType();
subject = newest.getSubject();
timestamp = newest.getTimestamp();
int unread = 0;
for(GroupMessageHeader h : list) if(!h.isRead()) unread++;
this.unread = unread;
}
}
GroupId getGroupId() {
@@ -40,6 +55,18 @@ class GroupListItem {
return group.getName();
}
boolean isRestricted() {
return group.isRestricted();
}
boolean isPostable() {
return postable;
}
boolean isEmpty() {
return empty;
}
String getAuthorName() {
return authorName;
}

View File

@@ -150,7 +150,6 @@ implements OnClickListener {
author.setTextColor(res.getColor(R.color.anonymous_author));
author.setText(R.string.anonymous);
} else {
author.setTextColor(res.getColor(R.color.pseudonymous_author));
author.setText(authorName);
}
header.addView(author);

View File

@@ -11,7 +11,6 @@ import java.util.ArrayList;
import net.sf.briar.R;
import net.sf.briar.android.widgets.HorizontalSpace;
import net.sf.briar.api.db.PrivateMessageHeader;
import net.sf.briar.util.StringUtils;
import android.content.Context;
import android.content.res.Resources;
import android.text.format.DateUtils;
@@ -65,15 +64,14 @@ class ConversationAdapter extends ArrayAdapter<PrivateMessageHeader> {
innerLayout.addView(name);
if(item.getContentType().equals("text/plain")) {
if(!StringUtils.isNullOrEmpty(item.getSubject())) {
TextView subject = new TextView(ctx);
subject.setTextSize(14);
subject.setMaxLines(2);
subject.setPadding(10, 0, 10, 10);
if(!item.isRead()) subject.setTypeface(null, BOLD);
subject.setText(item.getSubject());
innerLayout.addView(subject);
}
TextView subject = new TextView(ctx);
subject.setTextSize(14);
subject.setMaxLines(2);
subject.setPadding(10, 0, 10, 10);
if(!item.isRead()) subject.setTypeface(null, BOLD);
String s = item.getSubject();
subject.setText(s == null ? "" : s);
innerLayout.addView(subject);
} else {
LinearLayout attachmentLayout = new LinearLayout(ctx);
attachmentLayout.setOrientation(HORIZONTAL);

View File

@@ -7,10 +7,8 @@ import static java.util.logging.Level.WARNING;
import static net.sf.briar.android.widgets.CommonLayoutParams.MATCH_MATCH;
import static net.sf.briar.android.widgets.CommonLayoutParams.MATCH_WRAP_1;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
@@ -57,7 +55,6 @@ implements OnClickListener, DatabaseListener, NoContactsDialog.Listener {
// Fields that are accessed from background threads must be volatile
@Inject private volatile DatabaseComponent db;
@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
private volatile boolean noContacts = true;
@Override
public void onCreate(Bundle state) {
@@ -101,18 +98,12 @@ implements OnClickListener, DatabaseListener, NoContactsDialog.Listener {
dbUiExecutor.execute(new Runnable() {
public void run() {
try {
// Wait for the service to be bound and started
serviceConnection.waitForStartup();
// Load the contact list from the database
long now = System.currentTimeMillis();
Collection<Contact> contacts = db.getContacts();
noContacts = contacts.isEmpty();
for(Contact c : contacts) {
for(Contact c : db.getContacts()) {
try {
// Load the headers from the database
Collection<PrivateMessageHeader> headers =
db.getPrivateMessageHeaders(c.getId());
// Display the headers in the UI
displayHeaders(c, headers);
} catch(NoSuchContactException e) {
if(LOG.isLoggable(INFO))
@@ -141,13 +132,9 @@ implements OnClickListener, DatabaseListener, NoContactsDialog.Listener {
// Remove the old item, if any
ConversationListItem item = findConversation(c.getId());
if(item != null) adapter.remove(item);
// Add a new item if there are any headers to display
if(!headers.isEmpty()) {
List<PrivateMessageHeader> headerList =
new ArrayList<PrivateMessageHeader>(headers);
adapter.add(new ConversationListItem(c, headerList));
adapter.sort(ConversationComparator.INSTANCE);
}
// Add a new item
adapter.add(new ConversationListItem(c, headers));
adapter.sort(ConversationComparator.INSTANCE);
adapter.notifyDataSetChanged();
selectFirstUnread();
}
@@ -188,7 +175,7 @@ implements OnClickListener, DatabaseListener, NoContactsDialog.Listener {
}
public void onClick(View view) {
if(noContacts) {
if(adapter.isEmpty()) {
NoContactsDialog dialog = new NoContactsDialog();
dialog.setListener(this);
dialog.show(getSupportFragmentManager(), "NoContactsDialog");
@@ -270,7 +257,9 @@ implements OnClickListener, DatabaseListener, NoContactsDialog.Listener {
long aTime = a.getTimestamp(), bTime = b.getTimestamp();
if(aTime > bTime) return -1;
if(aTime < bTime) return 1;
return 0;
// Break ties by contact name
String aName = a.getContactName(), bName = b.getContactName();
return String.CASE_INSENSITIVE_ORDER.compare(aName, bName);
}
}
}

View File

@@ -10,7 +10,6 @@ import static net.sf.briar.android.widgets.CommonLayoutParams.WRAP_WRAP_1;
import java.util.ArrayList;
import net.sf.briar.R;
import net.sf.briar.util.StringUtils;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@@ -35,12 +34,12 @@ implements OnItemClickListener {
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) {
Resources res = ctx.getResources();
if(item.getUnreadCount() > 0)
layout.setBackgroundColor(res.getColor(R.color.unread_background));
}
LinearLayout innerLayout = new LinearLayout(ctx);
// Give me all the unused width
@@ -58,23 +57,32 @@ implements OnItemClickListener {
else name.setText(contactName);
innerLayout.addView(name);
if(!StringUtils.isNullOrEmpty(item.getSubject())) {
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);
innerLayout.addView(noMessages);
layout.addView(innerLayout);
} else {
TextView subject = new TextView(ctx);
subject.setTextSize(14);
subject.setMaxLines(2);
subject.setPadding(10, 0, 10, 10);
if(unread > 0) subject.setTypeface(null, BOLD);
subject.setText(item.getSubject());
String s = item.getSubject();
subject.setText(s == null ? "" : s);
innerLayout.addView(subject);
}
layout.addView(innerLayout);
layout.addView(innerLayout);
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);
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;
}

View File

@@ -1,5 +1,7 @@
package net.sf.briar.android.messages;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -12,19 +14,29 @@ import net.sf.briar.api.db.PrivateMessageHeader;
class ConversationListItem {
private final Contact contact;
private final boolean empty;
private final String subject;
private final long timestamp;
private final int unread;
ConversationListItem(Contact contact, List<PrivateMessageHeader> headers) {
if(headers.isEmpty()) throw new IllegalArgumentException();
ConversationListItem(Contact contact,
Collection<PrivateMessageHeader> headers) {
this.contact = contact;
Collections.sort(headers, DescendingHeaderComparator.INSTANCE);
subject = headers.get(0).getSubject();
timestamp = headers.get(0).getTimestamp();
int unread = 0;
for(PrivateMessageHeader h : headers) if(!h.isRead()) unread++;
this.unread = unread;
empty = headers.isEmpty();
if(empty) {
subject = null;
timestamp = 0;
unread = 0;
} else {
List<PrivateMessageHeader> list =
new ArrayList<PrivateMessageHeader>(headers);
Collections.sort(list, DescendingHeaderComparator.INSTANCE);
subject = list.get(0).getSubject();
timestamp = list.get(0).getTimestamp();
int unread = 0;
for(PrivateMessageHeader h : list) if(!h.isRead()) unread++;
this.unread = unread;
}
}
ContactId getContactId() {
@@ -39,6 +51,10 @@ class ConversationListItem {
return contact.getLocalAuthorId();
}
boolean isEmpty() {
return empty;
}
String getSubject() {
return subject;
}