Merge branch '46-improve-message-status-indicators' into 'master'
Improve how the status of messages is indicated. Remove the Toast that always says 'Message Sent' and show graphical indicators instead that show either: * message is waiting to be sent * message was sent (or requested to be sent) * message was delivered Please note that I didn't change the icons and did not migrate the UI to XML files to keep my change minimally invasive. Closes #46 See merge request !9
|
Before Width: | Height: | Size: 284 B After Width: | Height: | Size: 479 B |
BIN
briar-android/res/drawable-hdpi/message_sent.png
Normal file
|
After Width: | Height: | Size: 284 B |
BIN
briar-android/res/drawable-hdpi/message_stored.png
Normal file
|
After Width: | Height: | Size: 323 B |
|
Before Width: | Height: | Size: 238 B After Width: | Height: | Size: 313 B |
BIN
briar-android/res/drawable-mdpi/message_sent.png
Normal file
|
After Width: | Height: | Size: 238 B |
BIN
briar-android/res/drawable-mdpi/message_stored.png
Normal file
|
After Width: | Height: | Size: 237 B |
|
Before Width: | Height: | Size: 392 B After Width: | Height: | Size: 627 B |
BIN
briar-android/res/drawable-xhdpi/message_sent.png
Normal file
|
After Width: | Height: | Size: 392 B |
BIN
briar-android/res/drawable-xhdpi/message_stored.png
Normal file
|
After Width: | Height: | Size: 443 B |
BIN
briar-android/res/drawable-xxhdpi/message_stored.png
Normal file
|
After Width: | Height: | Size: 683 B |
BIN
briar-android/res/drawable-xxxhdpi/message_stored.png
Normal file
|
After Width: | Height: | Size: 969 B |
@@ -8,7 +8,6 @@ import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static android.widget.LinearLayout.HORIZONTAL;
|
||||
import static android.widget.LinearLayout.VERTICAL;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.android.contact.ReadPrivateMessageActivity.RESULT_PREV_NEXT;
|
||||
@@ -45,6 +44,7 @@ import org.briarproject.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.api.db.DatabaseComponent;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.MessageHeader;
|
||||
import org.briarproject.api.db.MessageHeader.State;
|
||||
import org.briarproject.api.db.NoSuchContactException;
|
||||
import org.briarproject.api.db.NoSuchMessageException;
|
||||
import org.briarproject.api.db.NoSuchSubscriptionException;
|
||||
@@ -55,6 +55,7 @@ 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.event.MessagesSentEvent;
|
||||
import org.briarproject.api.messaging.Group;
|
||||
import org.briarproject.api.messaging.GroupId;
|
||||
import org.briarproject.api.messaging.Message;
|
||||
@@ -76,7 +77,6 @@ import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class ConversationActivity extends BriarActivity
|
||||
implements EventListener, OnClickListener, OnItemClickListener {
|
||||
@@ -384,25 +384,31 @@ implements EventListener, OnClickListener, OnItemClickListener {
|
||||
} else if (e instanceof MessageExpiredEvent) {
|
||||
LOG.info("Message expired, reloading");
|
||||
loadHeaders();
|
||||
} else if (e instanceof MessagesSentEvent) {
|
||||
MessagesSentEvent m = (MessagesSentEvent) e;
|
||||
if (m.getContactId().equals(contactId)) {
|
||||
LOG.info("Messages sent");
|
||||
markMessages(m.getMessageIds(), State.SENT);
|
||||
}
|
||||
} else if (e instanceof MessagesAckedEvent) {
|
||||
MessagesAckedEvent m = (MessagesAckedEvent) e;
|
||||
if (m.getContactId().equals(contactId)) {
|
||||
LOG.info("Messages acked");
|
||||
markMessagesDelivered(m.getMessageIds());
|
||||
markMessages(m.getMessageIds(), State.DELIVERED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void markMessagesDelivered(final Collection<MessageId> acked) {
|
||||
private void markMessages(final Collection<MessageId> messageIds, final State state) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
Set<MessageId> ackedSet = new HashSet<MessageId>(acked);
|
||||
Set<MessageId> messages = new HashSet<MessageId>(messageIds);
|
||||
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);
|
||||
if (messages.contains(item.getHeader().getId())) {
|
||||
item.setStatus(state);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
@@ -417,7 +423,6 @@ implements EventListener, OnClickListener, OnItemClickListener {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
|
||||
createMessage(StringUtils.toUtf8(message), timestamp);
|
||||
Toast.makeText(this, R.string.message_sent_toast, LENGTH_SHORT).show();
|
||||
content.setText("");
|
||||
hideSoftKeyboard();
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ 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.api.db.MessageHeader.State;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import android.content.Context;
|
||||
@@ -79,11 +80,16 @@ class ConversationAdapter extends ArrayAdapter<ConversationItem> {
|
||||
|
||||
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);
|
||||
ImageView status = new ImageView(ctx);
|
||||
status.setPadding(0, 0, pad, 0);
|
||||
if (item.getStatus() == State.DELIVERED) {
|
||||
status.setImageResource(R.drawable.message_delivered);
|
||||
} else if (item.getStatus() == State.SENT) {
|
||||
status.setImageResource(R.drawable.message_sent);
|
||||
} else {
|
||||
status.setImageResource(R.drawable.message_stored);
|
||||
}
|
||||
footer.addView(status);
|
||||
|
||||
TextView date = new TextView(ctx);
|
||||
date.setTextColor(res.getColor(R.color.private_message_date));
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import org.briarproject.api.db.MessageHeader;
|
||||
import org.briarproject.api.db.MessageHeader.State;
|
||||
|
||||
// This class is not thread-safe
|
||||
class ConversationItem {
|
||||
|
||||
private final MessageHeader header;
|
||||
private byte[] body;
|
||||
private boolean delivered;
|
||||
private State status;
|
||||
|
||||
ConversationItem(MessageHeader header) {
|
||||
this.header = header;
|
||||
body = null;
|
||||
delivered = header.isDelivered();
|
||||
status = header.getStatus();
|
||||
}
|
||||
|
||||
MessageHeader getHeader() {
|
||||
@@ -27,11 +28,11 @@ class ConversationItem {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
boolean isDelivered() {
|
||||
return delivered;
|
||||
State getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
void setDelivered(boolean delivered) {
|
||||
this.delivered = delivered;
|
||||
void setStatus(State state) {
|
||||
this.status = state;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,17 +6,20 @@ import org.briarproject.api.messaging.MessageId;
|
||||
|
||||
public class MessageHeader {
|
||||
|
||||
public enum State { STORED, SENT, DELIVERED };
|
||||
|
||||
private final MessageId id, parent;
|
||||
private final GroupId groupId;
|
||||
private final Author author;
|
||||
private final Author.Status authorStatus;
|
||||
private final String contentType;
|
||||
private final long timestamp;
|
||||
private final boolean local, read, delivered;
|
||||
private final boolean local, read;
|
||||
private final State status;
|
||||
|
||||
public MessageHeader(MessageId id, MessageId parent, GroupId groupId,
|
||||
Author author, Author.Status authorStatus, String contentType,
|
||||
long timestamp, boolean local, boolean read, boolean delivered) {
|
||||
long timestamp, boolean local, boolean read, State status) {
|
||||
this.id = id;
|
||||
this.parent = parent;
|
||||
this.groupId = groupId;
|
||||
@@ -26,7 +29,7 @@ public class MessageHeader {
|
||||
this.timestamp = timestamp;
|
||||
this.local = local;
|
||||
this.read = read;
|
||||
this.delivered = delivered;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/** Returns the message's unique identifier. */
|
||||
@@ -82,10 +85,9 @@ public class MessageHeader {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the message has been delivered. (This only applies to
|
||||
* locally generated private messages.)
|
||||
* Returns message status. (This only applies to locally generated private messages.)
|
||||
*/
|
||||
public boolean isDelivered() {
|
||||
return delivered;
|
||||
public State getStatus() {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 sent to a contact. */
|
||||
public class MessagesSentEvent extends Event {
|
||||
|
||||
private final ContactId contactId;
|
||||
private final Collection<MessageId> messageIds;
|
||||
|
||||
public MessagesSentEvent(ContactId contactId,
|
||||
Collection<MessageId> messageIds) {
|
||||
this.contactId = contactId;
|
||||
this.messageIds = messageIds;
|
||||
}
|
||||
|
||||
public ContactId getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
public Collection<MessageId> getMessageIds() {
|
||||
return messageIds;
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,7 @@ 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.MessagesSentEvent;
|
||||
import org.briarproject.api.event.RemoteRetentionTimeUpdatedEvent;
|
||||
import org.briarproject.api.event.RemoteSubscriptionsUpdatedEvent;
|
||||
import org.briarproject.api.event.RemoteTransportsUpdatedEvent;
|
||||
@@ -380,6 +381,7 @@ DatabaseCleaner.Callback {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
if (messages.isEmpty()) return null;
|
||||
if (!ids.isEmpty()) eventBus.broadcast(new MessagesSentEvent(c, ids));
|
||||
return Collections.unmodifiableList(messages);
|
||||
}
|
||||
|
||||
@@ -455,6 +457,7 @@ DatabaseCleaner.Callback {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
if (messages.isEmpty()) return null;
|
||||
if (!ids.isEmpty()) eventBus.broadcast(new MessagesSentEvent(c, ids));
|
||||
return Collections.unmodifiableList(messages);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.briarproject.api.TransportProperties;
|
||||
import org.briarproject.api.db.DbClosedException;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.MessageHeader;
|
||||
import org.briarproject.api.db.MessageHeader.State;
|
||||
import org.briarproject.api.messaging.Group;
|
||||
import org.briarproject.api.messaging.GroupId;
|
||||
import org.briarproject.api.messaging.Message;
|
||||
@@ -1452,7 +1453,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
if (rs.next()) throw new DbException();
|
||||
// Get the message headers
|
||||
sql = "SELECT m.messageId, parentId, m.groupId, contentType,"
|
||||
+ " timestamp, local, read, seen"
|
||||
+ " timestamp, local, read, seen, s.txCount"
|
||||
+ " FROM messages AS m"
|
||||
+ " JOIN groups AS g"
|
||||
+ " ON m.groupId = g.groupId"
|
||||
@@ -1478,8 +1479,15 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
boolean read = rs.getBoolean(7);
|
||||
boolean seen = rs.getBoolean(8);
|
||||
Author author = local ? localAuthor : remoteAuthor;
|
||||
|
||||
// initialize message status
|
||||
State status;
|
||||
if (seen) status = State.DELIVERED;
|
||||
else if (rs.getInt(9) > 0) status = State.SENT;
|
||||
else status = State.STORED;
|
||||
|
||||
headers.add(new MessageHeader(id, parent, groupId, author,
|
||||
VERIFIED, contentType, timestamp, local, read, seen));
|
||||
VERIFIED, contentType, timestamp, local, read, status));
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1631,6 +1639,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to get group messages.
|
||||
* The message status won't be used.
|
||||
*/
|
||||
public Collection<MessageHeader> getMessageHeaders(Connection txn,
|
||||
GroupId g) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
@@ -1669,12 +1681,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
boolean read = rs.getBoolean(9);
|
||||
boolean isSelf = rs.getBoolean(10);
|
||||
boolean isContact = rs.getBoolean(11);
|
||||
|
||||
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));
|
||||
contentType, timestamp, local, read, State.STORED));
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
|
||||