mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 11:19:04 +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.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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user