mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-21 15:19:53 +01:00
Show when private messages have been delivered.
This commit is contained in:
BIN
briar-android/res/drawable-hdpi/message_delivered.png
Normal file
BIN
briar-android/res/drawable-hdpi/message_delivered.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 284 B |
BIN
briar-android/res/drawable-mdpi/message_delivered.png
Normal file
BIN
briar-android/res/drawable-mdpi/message_delivered.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 238 B |
BIN
briar-android/res/drawable-xhdpi/message_delivered.png
Normal file
BIN
briar-android/res/drawable-xhdpi/message_delivered.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 392 B |
@@ -24,8 +24,10 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
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.EventListener;
|
||||||
import org.briarproject.api.event.MessageAddedEvent;
|
import org.briarproject.api.event.MessageAddedEvent;
|
||||||
import org.briarproject.api.event.MessageExpiredEvent;
|
import org.briarproject.api.event.MessageExpiredEvent;
|
||||||
|
import org.briarproject.api.event.MessagesAckedEvent;
|
||||||
import org.briarproject.api.messaging.Group;
|
import org.briarproject.api.messaging.Group;
|
||||||
import org.briarproject.api.messaging.GroupId;
|
import org.briarproject.api.messaging.GroupId;
|
||||||
import org.briarproject.api.messaging.Message;
|
import org.briarproject.api.messaging.Message;
|
||||||
@@ -381,9 +384,33 @@ implements EventListener, OnClickListener, OnItemClickListener {
|
|||||||
} else if(e instanceof MessageExpiredEvent) {
|
} else if(e instanceof MessageExpiredEvent) {
|
||||||
LOG.info("Message expired, reloading");
|
LOG.info("Message expired, reloading");
|
||||||
loadHeaders();
|
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) {
|
public void onClick(View view) {
|
||||||
String message = content.getText().toString();
|
String message = content.getText().toString();
|
||||||
if(message.equals("")) return;
|
if(message.equals("")) return;
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
package org.briarproject.android.contact;
|
package org.briarproject.android.contact;
|
||||||
|
|
||||||
|
import static android.view.Gravity.BOTTOM;
|
||||||
import static android.view.Gravity.LEFT;
|
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 android.widget.LinearLayout.VERTICAL;
|
||||||
import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP;
|
import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import org.briarproject.R;
|
import org.briarproject.R;
|
||||||
|
import org.briarproject.android.util.ElasticHorizontalSpace;
|
||||||
import org.briarproject.android.util.LayoutUtils;
|
import org.briarproject.android.util.LayoutUtils;
|
||||||
import org.briarproject.api.db.MessageHeader;
|
import org.briarproject.api.db.MessageHeader;
|
||||||
import org.briarproject.util.StringUtils;
|
import org.briarproject.util.StringUtils;
|
||||||
@@ -19,6 +22,7 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
@@ -60,23 +64,45 @@ class ConversationAdapter extends ArrayAdapter<ConversationItem> {
|
|||||||
attachment.setImageResource(R.drawable.content_attachment);
|
attachment.setImageResource(R.drawable.content_attachment);
|
||||||
content = attachment;
|
content = attachment;
|
||||||
}
|
}
|
||||||
content.setId(2);
|
|
||||||
content.setLayoutParams(MATCH_WRAP);
|
content.setLayoutParams(MATCH_WRAP);
|
||||||
content.setBackgroundColor(background);
|
content.setBackgroundColor(background);
|
||||||
content.setPadding(pad, pad, pad, 0);
|
content.setPadding(pad, pad, pad, 0);
|
||||||
layout.addView(content);
|
layout.addView(content);
|
||||||
|
|
||||||
TextView date = new TextView(ctx);
|
if(header.isLocal()) {
|
||||||
date.setId(1);
|
LinearLayout footer = new LinearLayout(ctx);
|
||||||
date.setLayoutParams(MATCH_WRAP);
|
footer.setLayoutParams(MATCH_WRAP);
|
||||||
if(header.isLocal()) date.setGravity(RIGHT);
|
footer.setOrientation(HORIZONTAL);
|
||||||
else date.setGravity(LEFT);
|
footer.setGravity(BOTTOM);
|
||||||
date.setTextColor(res.getColor(R.color.private_message_date));
|
footer.setPadding(pad, 0, pad, pad);
|
||||||
date.setBackgroundColor(background);
|
footer.setBackgroundColor(background);
|
||||||
date.setPadding(pad, 0, pad, pad);
|
|
||||||
long timestamp = header.getTimestamp();
|
footer.addView(new ElasticHorizontalSpace(ctx));
|
||||||
date.setText(DateUtils.getRelativeTimeSpanString(ctx, timestamp));
|
|
||||||
layout.addView(date);
|
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;
|
return layout;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ class ConversationItem {
|
|||||||
|
|
||||||
private final MessageHeader header;
|
private final MessageHeader header;
|
||||||
private byte[] body;
|
private byte[] body;
|
||||||
|
private boolean delivered;
|
||||||
|
|
||||||
ConversationItem(MessageHeader header) {
|
ConversationItem(MessageHeader header) {
|
||||||
this.header = header;
|
this.header = header;
|
||||||
body = null;
|
body = null;
|
||||||
|
delivered = header.isDelivered();
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageHeader getHeader() {
|
MessageHeader getHeader() {
|
||||||
@@ -24,4 +26,12 @@ class ConversationItem {
|
|||||||
void setBody(byte[] body) {
|
void setBody(byte[] body) {
|
||||||
this.body = body;
|
this.body = body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isDelivered() {
|
||||||
|
return delivered;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDelivered(boolean delivered) {
|
||||||
|
this.delivered = delivered;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ public class MessageHeader {
|
|||||||
private final Author.Status authorStatus;
|
private final Author.Status authorStatus;
|
||||||
private final String contentType;
|
private final String contentType;
|
||||||
private final long timestamp;
|
private final long timestamp;
|
||||||
private final boolean local, read;
|
private final boolean local, read, delivered;
|
||||||
|
|
||||||
public MessageHeader(MessageId id, MessageId parent, GroupId groupId,
|
public MessageHeader(MessageId id, MessageId parent, GroupId groupId,
|
||||||
Author author, Author.Status authorStatus, String contentType,
|
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.id = id;
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
@@ -26,6 +26,7 @@ public class MessageHeader {
|
|||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.local = local;
|
this.local = local;
|
||||||
this.read = read;
|
this.read = read;
|
||||||
|
this.delivered = delivered;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the message's unique identifier. */
|
/** Returns the message's unique identifier. */
|
||||||
@@ -79,4 +80,12 @@ public class MessageHeader {
|
|||||||
public boolean isRead() {
|
public boolean isRead() {
|
||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the message has been delivered. (This only applies to
|
||||||
|
* locally generated private messages.)
|
||||||
|
*/
|
||||||
|
public boolean isDelivered() {
|
||||||
|
return delivered;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,6 +53,7 @@ import org.briarproject.api.event.MessageExpiredEvent;
|
|||||||
import org.briarproject.api.event.MessageRequestedEvent;
|
import org.briarproject.api.event.MessageRequestedEvent;
|
||||||
import org.briarproject.api.event.MessageToAckEvent;
|
import org.briarproject.api.event.MessageToAckEvent;
|
||||||
import org.briarproject.api.event.MessageToRequestEvent;
|
import org.briarproject.api.event.MessageToRequestEvent;
|
||||||
|
import org.briarproject.api.event.MessagesAckedEvent;
|
||||||
import org.briarproject.api.event.RemoteRetentionTimeUpdatedEvent;
|
import org.briarproject.api.event.RemoteRetentionTimeUpdatedEvent;
|
||||||
import org.briarproject.api.event.RemoteSubscriptionsUpdatedEvent;
|
import org.briarproject.api.event.RemoteSubscriptionsUpdatedEvent;
|
||||||
import org.briarproject.api.event.RemoteTransportsUpdatedEvent;
|
import org.briarproject.api.event.RemoteTransportsUpdatedEvent;
|
||||||
@@ -1329,6 +1330,7 @@ DatabaseCleaner.Callback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void receiveAck(ContactId c, Ack a) throws DbException {
|
public void receiveAck(ContactId c, Ack a) throws DbException {
|
||||||
|
Collection<MessageId> acked = new ArrayList<MessageId>();
|
||||||
contactLock.readLock().lock();
|
contactLock.readLock().lock();
|
||||||
try {
|
try {
|
||||||
messageLock.writeLock().lock();
|
messageLock.writeLock().lock();
|
||||||
@@ -1338,8 +1340,10 @@ DatabaseCleaner.Callback {
|
|||||||
if(!db.containsContact(txn, c))
|
if(!db.containsContact(txn, c))
|
||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
for(MessageId m : a.getMessageIds()) {
|
for(MessageId m : a.getMessageIds()) {
|
||||||
if(db.containsVisibleMessage(txn, c, m))
|
if(db.containsVisibleMessage(txn, c, m)) {
|
||||||
db.raiseSeenFlag(txn, c, m);
|
db.raiseSeenFlag(txn, c, m);
|
||||||
|
acked.add(m);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
} catch(DbException e) {
|
} catch(DbException e) {
|
||||||
@@ -1352,6 +1356,7 @@ DatabaseCleaner.Callback {
|
|||||||
} finally {
|
} finally {
|
||||||
contactLock.readLock().unlock();
|
contactLock.readLock().unlock();
|
||||||
}
|
}
|
||||||
|
callListeners(new MessagesAckedEvent(c, acked));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void receiveMessage(ContactId c, Message m) throws DbException {
|
public void receiveMessage(ContactId c, Message m) throws DbException {
|
||||||
|
|||||||
@@ -1482,14 +1482,17 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
Author remoteAuthor = new Author(remoteId, remoteName, remoteKey);
|
Author remoteAuthor = new Author(remoteId, remoteName, remoteKey);
|
||||||
if(rs.next()) throw new DbException();
|
if(rs.next()) throw new DbException();
|
||||||
// Get the message headers
|
// Get the message headers
|
||||||
sql = "SELECT messageId, parentId, m.groupId, contentType,"
|
sql = "SELECT m.messageId, parentId, m.groupId, contentType,"
|
||||||
+ " timestamp, local, read"
|
+ " timestamp, local, read, seen"
|
||||||
+ " FROM messages AS m"
|
+ " FROM messages AS m"
|
||||||
+ " JOIN groups AS g"
|
+ " JOIN groups AS g"
|
||||||
+ " ON m.groupId = g.groupId"
|
+ " ON m.groupId = g.groupId"
|
||||||
+ " JOIN groupVisibilities AS gv"
|
+ " JOIN groupVisibilities AS gv"
|
||||||
+ " ON m.groupId = gv.groupId"
|
+ " 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";
|
+ " AND inbox = TRUE";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, c.getInt());
|
ps.setInt(1, c.getInt());
|
||||||
@@ -1504,15 +1507,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
long timestamp = rs.getLong(5);
|
long timestamp = rs.getLong(5);
|
||||||
boolean local = rs.getBoolean(6);
|
boolean local = rs.getBoolean(6);
|
||||||
boolean read = rs.getBoolean(7);
|
boolean read = rs.getBoolean(7);
|
||||||
if(local) {
|
boolean seen = rs.getBoolean(8);
|
||||||
headers.add(new MessageHeader(id, parent, groupId,
|
Author author = local ? localAuthor : remoteAuthor;
|
||||||
localAuthor, VERIFIED, contentType, timestamp,
|
headers.add(new MessageHeader(id, parent, groupId, author,
|
||||||
true, read));
|
VERIFIED, contentType, timestamp, local, read, seen));
|
||||||
} else {
|
|
||||||
headers.add(new MessageHeader(id, parent, groupId,
|
|
||||||
remoteAuthor, VERIFIED, contentType, timestamp,
|
|
||||||
false, read));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
@@ -1723,12 +1721,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
boolean read = rs.getBoolean(9);
|
boolean read = rs.getBoolean(9);
|
||||||
boolean isSelf = rs.getBoolean(10);
|
boolean isSelf = rs.getBoolean(10);
|
||||||
boolean isContact = rs.getBoolean(11);
|
boolean isContact = rs.getBoolean(11);
|
||||||
Author.Status authorStatus;
|
Author.Status status;
|
||||||
if(author == null) authorStatus = ANONYMOUS;
|
if(author == null) status = ANONYMOUS;
|
||||||
else if(isSelf || isContact) authorStatus = VERIFIED;
|
else if(isSelf || isContact) status = VERIFIED;
|
||||||
else authorStatus = UNKNOWN;
|
else status = UNKNOWN;
|
||||||
headers.add(new MessageHeader(id, parent, g, author,
|
headers.add(new MessageHeader(id, parent, g, author, status,
|
||||||
authorStatus, contentType, timestamp, local, read));
|
contentType, timestamp, local, read, false));
|
||||||
}
|
}
|
||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
|
|||||||
@@ -1531,7 +1531,9 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
db.getInboxMessageHeaders(txn, contactId));
|
db.getInboxMessageHeaders(txn, contactId));
|
||||||
|
|
||||||
// Add a message to the inbox group - the header should be returned
|
// 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 =
|
Collection<MessageHeader> headers =
|
||||||
db.getInboxMessageHeaders(txn, contactId);
|
db.getInboxMessageHeaders(txn, contactId);
|
||||||
assertEquals(1, headers.size());
|
assertEquals(1, headers.size());
|
||||||
@@ -1542,6 +1544,9 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
assertEquals(localAuthor, header.getAuthor());
|
assertEquals(localAuthor, header.getAuthor());
|
||||||
assertEquals(contentType, header.getContentType());
|
assertEquals(contentType, header.getContentType());
|
||||||
assertEquals(timestamp, header.getTimestamp());
|
assertEquals(timestamp, header.getTimestamp());
|
||||||
|
assertEquals(local, header.isLocal());
|
||||||
|
assertEquals(false, header.isRead());
|
||||||
|
assertEquals(seen, header.isDelivered());
|
||||||
assertFalse(header.isRead());
|
assertFalse(header.isRead());
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
|
|||||||
Reference in New Issue
Block a user