From 1c282a883567e1ad1488334c944808f4bcd03a84 Mon Sep 17 00:00:00 2001 From: akwizgran <akwizgran@users.sourceforge.net> 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)<h;3K|Lk000e1NJLTq000;O000;W1^@s6;CDUv00001b5ch_0Itp) z=>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-<E* zFt<r8@JCv;js@ZXOJE3$J=b4+3y*Ens07#onirlp2NB5~xaNhY4q6~t0Ry1wx&G@P i3#=w;`sFGw6Pq{lbXMMsP2|u30000<MNUMnLSTZyJ!`Q5 literal 0 HcmV?d00001 diff --git a/briar-android/res/drawable-mdpi/message_delivered.png b/briar-android/res/drawable-mdpi/message_delivered.png new file mode 100644 index 0000000000000000000000000000000000000000..1f3807209e1db00dc0b3f07fa5f2fd0e2bd813ad GIT binary patch literal 238 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|R(QHNhFF|_ zI@zA<kby|M{c86M=WdpCEocrFKj;_sfkjSXGJ8Rp_@?s0!ZOW!N)-(Xw(c)ODyBqi z=sP=Q&mR4sd+QC(q|Wu4ReOcmj=8dU-XD(p4Z0iNDF05HTebb|#DW>62g)CC<lHJZ zYH2TB#p37o+v`AhLgJSAR=Me(XBs3bB9|X~^jySa-%4rLCt?nqH4V)d>+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)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800001b5ch_0Itp) z=>Px$LP<nHR9M69ma$3$K@>$#B#otDVY<=MVj3&aU}3<<R?uH?e!)c1Z<yAIjXz+S zCPBnXWC$YIOs9<~*aXtpOdu??$&A@-p>xY+hjZ_HZ{I8=B_;hAX(O82h?DH|g_7W> zz&UVHx&a420|vlGsRlCeJtgg9_MJ*e@GT!4pgIR|6=(vjNy+DC0JsK@rX?@jfHq<g zxC9P?Z(x5K+-pD^u?EzETP5vBgohFQ3)ocBzJwg|eFewB70}a0)IySLBW}jv^-y@8 zfi5rv)`5;TVmUH-Kc;n+oK#D|oip(aG?cV&6M8;M-jAIf?;=vtegX{#&#S~t$%7V= zgLcO%vFm{CS(68ELN|yVXW|KX1rB1T{nrLOm7s{^(RYD1Vg<MdR)HNQZLf$07JM5j mY2Sfu*ZYU`i<Ol0XY>OxrFzD0Tlw|?0000<MNUMnLSTYe<D|3z literal 0 HcmV?d00001 diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java index 7cde33e7db..91885fcc5d 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java @@ -24,8 +24,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.Executor; import java.util.logging.Logger; @@ -52,6 +54,7 @@ import org.briarproject.api.event.Event; import org.briarproject.api.event.EventListener; import org.briarproject.api.event.MessageAddedEvent; import org.briarproject.api.event.MessageExpiredEvent; +import org.briarproject.api.event.MessagesAckedEvent; import org.briarproject.api.messaging.Group; import org.briarproject.api.messaging.GroupId; import org.briarproject.api.messaging.Message; @@ -381,9 +384,33 @@ implements EventListener, OnClickListener, OnItemClickListener { } else if(e instanceof MessageExpiredEvent) { LOG.info("Message expired, reloading"); loadHeaders(); + } else if(e instanceof MessagesAckedEvent) { + MessagesAckedEvent m = (MessagesAckedEvent) e; + if(m.getContactId().equals(contactId)) { + LOG.info("Messages acked"); + markMessagesDelivered(m.getMessageIds()); + } } } + private void markMessagesDelivered(final Collection<MessageId> acked) { + runOnUiThread(new Runnable() { + public void run() { + Set<MessageId> ackedSet = new HashSet<MessageId>(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 a315cf16ac..249392b545 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<ConversationItem> { 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 1982bb58ec..c1959f0c5d 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 6fa1e223f9..779e4cfa0e 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 0000000000..cb76764559 --- /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<MessageId> acked; + + public MessagesAckedEvent(ContactId contactId, + Collection<MessageId> acked ) { + this.contactId = contactId; + this.acked = acked; + } + + public ContactId getContactId() { + return contactId; + } + + public Collection<MessageId> getMessageIds() { + return acked; + } +} diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java index 872e55d154..ee0426ecbf 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<MessageId> acked = new ArrayList<MessageId>(); 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 ecb1b724c7..72c35f358c 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<Connection> { 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<Connection> { 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<Connection> { 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 361e9ff3ef..40658bf428 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<MessageHeader> 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); -- GitLab