From 1c282a883567e1ad1488334c944808f4bcd03a84 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Thu, 3 Apr 2014 17:22:48 +0100 Subject: [PATCH] Show when private messages have been delivered. --- .../res/drawable-hdpi/message_delivered.png | Bin 0 -> 284 bytes .../res/drawable-mdpi/message_delivered.png | Bin 0 -> 238 bytes .../res/drawable-xhdpi/message_delivered.png | Bin 0 -> 392 bytes .../android/contact/ConversationActivity.java | 27 +++++++++ .../android/contact/ConversationAdapter.java | 52 +++++++++++++----- .../android/contact/ConversationItem.java | 10 ++++ .../briarproject/api/db/MessageHeader.java | 13 ++++- .../api/event/MessagesAckedEvent.java | 27 +++++++++ .../db/DatabaseComponentImpl.java | 7 ++- .../src/org/briarproject/db/JdbcDatabase.java | 34 ++++++------ .../org/briarproject/db/H2DatabaseTest.java | 7 ++- 11 files changed, 142 insertions(+), 35 deletions(-) create mode 100644 briar-android/res/drawable-hdpi/message_delivered.png create mode 100644 briar-android/res/drawable-mdpi/message_delivered.png create mode 100644 briar-android/res/drawable-xhdpi/message_delivered.png create mode 100644 briar-api/src/org/briarproject/api/event/MessagesAckedEvent.java diff --git a/briar-android/res/drawable-hdpi/message_delivered.png b/briar-android/res/drawable-hdpi/message_delivered.png new file mode 100644 index 0000000000000000000000000000000000000000..6edef05a95505d6e4605621b5975f179b331c04e GIT binary patch literal 284 zcmV+%0ptFOP)Px#)=5M`R7l6|l)nxEK@f+(lTd0TDv?zvC{E!$cnF<)o@u=Uuc5$nB;@ExXpks0 zS6t5CKTdbQZg-RK+eu~y%9P0yQPU7Llki=oKzJS415&skfS-b6GyLWh3~zg`e=Dp4 zQPTpBaqyo6qNWEN0)`d-ArLifQPa%`Ujt{L83(sjFavg?W|RSb0Xm-RKa)Na6-62g)CC+VjS9U%Jc mfbb2*&-JrXttRYT!T4~s`ej!WZF``b89ZJ6T-G@yGywpZJz$Uk literal 0 HcmV?d00001 diff --git a/briar-android/res/drawable-xhdpi/message_delivered.png b/briar-android/res/drawable-xhdpi/message_delivered.png new file mode 100644 index 0000000000000000000000000000000000000000..a40d4d94c0fca5e85e08ce21f3ce99f8c74ceeea GIT binary patch literal 392 zcmV;30eAk1P)Px$LP$#B#otDVY<=MVj3&aU}3<xY+hjZ_HZ{I8=B_;hAX(O82h?DH|g_7W> zz&UVHx&a420|vlGsRlCeJtgg9_MJ*e@GT!4pgIR|6=(vjNy+DC0JsK@rX?@jfHqOxrFzD0Tlw|?0000 acked) { + runOnUiThread(new Runnable() { + public void run() { + Set ackedSet = new HashSet(acked); + boolean changed = false; + int count = adapter.getCount(); + for(int i = 0; i < count; i++) { + ConversationItem item = adapter.getItem(i); + if(ackedSet.contains(item.getHeader().getId())) { + item.setDelivered(true); + changed = true; + } + } + if(changed) adapter.notifyDataSetChanged(); + } + }); + } + public void onClick(View view) { String message = content.getText().toString(); if(message.equals("")) return; diff --git a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java index a315cf16a..249392b54 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java @@ -1,13 +1,16 @@ package org.briarproject.android.contact; +import static android.view.Gravity.BOTTOM; import static android.view.Gravity.LEFT; -import static android.view.Gravity.RIGHT; +import static android.view.View.INVISIBLE; +import static android.widget.LinearLayout.HORIZONTAL; import static android.widget.LinearLayout.VERTICAL; import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP; import java.util.ArrayList; import org.briarproject.R; +import org.briarproject.android.util.ElasticHorizontalSpace; import org.briarproject.android.util.LayoutUtils; import org.briarproject.api.db.MessageHeader; import org.briarproject.util.StringUtils; @@ -19,6 +22,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageButton; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -60,23 +64,45 @@ class ConversationAdapter extends ArrayAdapter { attachment.setImageResource(R.drawable.content_attachment); content = attachment; } - content.setId(2); content.setLayoutParams(MATCH_WRAP); content.setBackgroundColor(background); content.setPadding(pad, pad, pad, 0); layout.addView(content); - TextView date = new TextView(ctx); - date.setId(1); - date.setLayoutParams(MATCH_WRAP); - if(header.isLocal()) date.setGravity(RIGHT); - else date.setGravity(LEFT); - date.setTextColor(res.getColor(R.color.private_message_date)); - date.setBackgroundColor(background); - date.setPadding(pad, 0, pad, pad); - long timestamp = header.getTimestamp(); - date.setText(DateUtils.getRelativeTimeSpanString(ctx, timestamp)); - layout.addView(date); + if(header.isLocal()) { + LinearLayout footer = new LinearLayout(ctx); + footer.setLayoutParams(MATCH_WRAP); + footer.setOrientation(HORIZONTAL); + footer.setGravity(BOTTOM); + footer.setPadding(pad, 0, pad, pad); + footer.setBackgroundColor(background); + + footer.addView(new ElasticHorizontalSpace(ctx)); + + ImageView delivered = new ImageView(ctx); + delivered.setPadding(0, 0, pad, 0); + delivered.setImageResource(R.drawable.message_delivered); + if(!item.isDelivered()) delivered.setVisibility(INVISIBLE); + footer.addView(delivered); + + TextView date = new TextView(ctx); + date.setTextColor(res.getColor(R.color.private_message_date)); + long timestamp = header.getTimestamp(); + date.setText(DateUtils.getRelativeTimeSpanString(ctx, timestamp)); + footer.addView(date); + + layout.addView(footer); + } else { + TextView date = new TextView(ctx); + date.setLayoutParams(MATCH_WRAP); + date.setGravity(LEFT); + date.setTextColor(res.getColor(R.color.private_message_date)); + date.setBackgroundColor(background); + date.setPadding(pad, 0, pad, pad); + long timestamp = header.getTimestamp(); + date.setText(DateUtils.getRelativeTimeSpanString(ctx, timestamp)); + layout.addView(date); + } return layout; } diff --git a/briar-android/src/org/briarproject/android/contact/ConversationItem.java b/briar-android/src/org/briarproject/android/contact/ConversationItem.java index 1982bb58e..c1959f0c5 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationItem.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationItem.java @@ -7,10 +7,12 @@ class ConversationItem { private final MessageHeader header; private byte[] body; + private boolean delivered; ConversationItem(MessageHeader header) { this.header = header; body = null; + delivered = header.isDelivered(); } MessageHeader getHeader() { @@ -24,4 +26,12 @@ class ConversationItem { void setBody(byte[] body) { this.body = body; } + + boolean isDelivered() { + return delivered; + } + + void setDelivered(boolean delivered) { + this.delivered = delivered; + } } diff --git a/briar-api/src/org/briarproject/api/db/MessageHeader.java b/briar-api/src/org/briarproject/api/db/MessageHeader.java index 6fa1e223f..779e4cfa0 100644 --- a/briar-api/src/org/briarproject/api/db/MessageHeader.java +++ b/briar-api/src/org/briarproject/api/db/MessageHeader.java @@ -12,11 +12,11 @@ public class MessageHeader { private final Author.Status authorStatus; private final String contentType; private final long timestamp; - private final boolean local, read; + private final boolean local, read, delivered; public MessageHeader(MessageId id, MessageId parent, GroupId groupId, Author author, Author.Status authorStatus, String contentType, - long timestamp, boolean local, boolean read) { + long timestamp, boolean local, boolean read, boolean delivered) { this.id = id; this.parent = parent; this.groupId = groupId; @@ -26,6 +26,7 @@ public class MessageHeader { this.timestamp = timestamp; this.local = local; this.read = read; + this.delivered = delivered; } /** Returns the message's unique identifier. */ @@ -79,4 +80,12 @@ public class MessageHeader { public boolean isRead() { return read; } + + /** + * Returns true if the message has been delivered. (This only applies to + * locally generated private messages.) + */ + public boolean isDelivered() { + return delivered; + } } diff --git a/briar-api/src/org/briarproject/api/event/MessagesAckedEvent.java b/briar-api/src/org/briarproject/api/event/MessagesAckedEvent.java new file mode 100644 index 000000000..cb7676455 --- /dev/null +++ b/briar-api/src/org/briarproject/api/event/MessagesAckedEvent.java @@ -0,0 +1,27 @@ +package org.briarproject.api.event; + +import java.util.Collection; + +import org.briarproject.api.ContactId; +import org.briarproject.api.messaging.MessageId; + +/** An event that is broadcast when messages are acked by a contact. */ +public class MessagesAckedEvent extends Event { + + private final ContactId contactId; + private final Collection acked; + + public MessagesAckedEvent(ContactId contactId, + Collection acked ) { + this.contactId = contactId; + this.acked = acked; + } + + public ContactId getContactId() { + return contactId; + } + + public Collection getMessageIds() { + return acked; + } +} diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java index 872e55d15..ee0426ecb 100644 --- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java +++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java @@ -53,6 +53,7 @@ import org.briarproject.api.event.MessageExpiredEvent; import org.briarproject.api.event.MessageRequestedEvent; import org.briarproject.api.event.MessageToAckEvent; import org.briarproject.api.event.MessageToRequestEvent; +import org.briarproject.api.event.MessagesAckedEvent; import org.briarproject.api.event.RemoteRetentionTimeUpdatedEvent; import org.briarproject.api.event.RemoteSubscriptionsUpdatedEvent; import org.briarproject.api.event.RemoteTransportsUpdatedEvent; @@ -1329,6 +1330,7 @@ DatabaseCleaner.Callback { } public void receiveAck(ContactId c, Ack a) throws DbException { + Collection acked = new ArrayList(); contactLock.readLock().lock(); try { messageLock.writeLock().lock(); @@ -1338,8 +1340,10 @@ DatabaseCleaner.Callback { if(!db.containsContact(txn, c)) throw new NoSuchContactException(); for(MessageId m : a.getMessageIds()) { - if(db.containsVisibleMessage(txn, c, m)) + if(db.containsVisibleMessage(txn, c, m)) { db.raiseSeenFlag(txn, c, m); + acked.add(m); + } } db.commitTransaction(txn); } catch(DbException e) { @@ -1352,6 +1356,7 @@ DatabaseCleaner.Callback { } finally { contactLock.readLock().unlock(); } + callListeners(new MessagesAckedEvent(c, acked)); } public void receiveMessage(ContactId c, Message m) throws DbException { diff --git a/briar-core/src/org/briarproject/db/JdbcDatabase.java b/briar-core/src/org/briarproject/db/JdbcDatabase.java index ecb1b724c..72c35f358 100644 --- a/briar-core/src/org/briarproject/db/JdbcDatabase.java +++ b/briar-core/src/org/briarproject/db/JdbcDatabase.java @@ -1482,14 +1482,17 @@ abstract class JdbcDatabase implements Database { Author remoteAuthor = new Author(remoteId, remoteName, remoteKey); if(rs.next()) throw new DbException(); // Get the message headers - sql = "SELECT messageId, parentId, m.groupId, contentType," - + " timestamp, local, read" + sql = "SELECT m.messageId, parentId, m.groupId, contentType," + + " timestamp, local, read, seen" + " FROM messages AS m" + " JOIN groups AS g" + " ON m.groupId = g.groupId" + " JOIN groupVisibilities AS gv" + " ON m.groupId = gv.groupId" - + " WHERE contactId = ?" + + " JOIN statuses AS s" + + " ON m.messageId = s.messageId" + + " AND gv.contactId = s.contactId" + + " WHERE gv.contactId = ?" + " AND inbox = TRUE"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); @@ -1504,15 +1507,10 @@ abstract class JdbcDatabase implements Database { long timestamp = rs.getLong(5); boolean local = rs.getBoolean(6); boolean read = rs.getBoolean(7); - if(local) { - headers.add(new MessageHeader(id, parent, groupId, - localAuthor, VERIFIED, contentType, timestamp, - true, read)); - } else { - headers.add(new MessageHeader(id, parent, groupId, - remoteAuthor, VERIFIED, contentType, timestamp, - false, read)); - } + boolean seen = rs.getBoolean(8); + Author author = local ? localAuthor : remoteAuthor; + headers.add(new MessageHeader(id, parent, groupId, author, + VERIFIED, contentType, timestamp, local, read, seen)); } rs.close(); ps.close(); @@ -1723,12 +1721,12 @@ abstract class JdbcDatabase implements Database { boolean read = rs.getBoolean(9); boolean isSelf = rs.getBoolean(10); boolean isContact = rs.getBoolean(11); - Author.Status authorStatus; - if(author == null) authorStatus = ANONYMOUS; - else if(isSelf || isContact) authorStatus = VERIFIED; - else authorStatus = UNKNOWN; - headers.add(new MessageHeader(id, parent, g, author, - authorStatus, contentType, timestamp, local, read)); + Author.Status status; + if(author == null) status = ANONYMOUS; + else if(isSelf || isContact) status = VERIFIED; + else status = UNKNOWN; + headers.add(new MessageHeader(id, parent, g, author, status, + contentType, timestamp, local, read, false)); } rs.close(); ps.close(); diff --git a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java index 361e9ff3e..40658bf42 100644 --- a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java +++ b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java @@ -1531,7 +1531,9 @@ public class H2DatabaseTest extends BriarTestCase { db.getInboxMessageHeaders(txn, contactId)); // Add a message to the inbox group - the header should be returned - db.addMessage(txn, message, true); + boolean local = true, seen = false; + db.addMessage(txn, message, local); + db.addStatus(txn, contactId, messageId, false, seen); Collection headers = db.getInboxMessageHeaders(txn, contactId); assertEquals(1, headers.size()); @@ -1542,6 +1544,9 @@ public class H2DatabaseTest extends BriarTestCase { assertEquals(localAuthor, header.getAuthor()); assertEquals(contentType, header.getContentType()); assertEquals(timestamp, header.getTimestamp()); + assertEquals(local, header.isLocal()); + assertEquals(false, header.isRead()); + assertEquals(seen, header.isDelivered()); assertFalse(header.isRead()); db.commitTransaction(txn);