diff --git a/app/src/main/java/im/conversations/android/database/dao/MessageDao.java b/app/src/main/java/im/conversations/android/database/dao/MessageDao.java
index 92055ecde..670890022 100644
--- a/app/src/main/java/im/conversations/android/database/dao/MessageDao.java
+++ b/app/src/main/java/im/conversations/android/database/dao/MessageDao.java
@@ -426,7 +426,7 @@ public abstract class MessageDao {
@Transaction
@Query(
"SELECT c.accountId,m.id as id,type as"
- + " chatType,sentAt,outgoing,toBare,toResource,fromBare,fromResource,senderIdentity"
+ + " chatType,sentAt,outgoing,toBare,toResource,fromBare,fromResource,acknowledged,senderIdentity"
+ " as sender,(SELECT name FROM roster WHERE roster.accountId=c.accountId AND"
+ " roster.address=m.senderIdentity) as senderRosterName,(SELECT nick FROM nick"
+ " WHERE nick.accountId=c.accountId AND nick.address=m.senderIdentity) as"
@@ -456,7 +456,7 @@ public abstract class MessageDao {
@Transaction
@Query(
"SELECT c.accountId,m.id as id,type as"
- + " chatType,sentAt,outgoing,toBare,toResource,fromBare,fromResource,senderIdentity"
+ + " chatType,sentAt,outgoing,toBare,toResource,fromBare,fromResource,acknowledged,senderIdentity"
+ " as sender,(SELECT name FROM roster WHERE roster.accountId=c.accountId AND"
+ " roster.address=m.senderIdentity) as senderRosterName,(SELECT nick FROM nick"
+ " WHERE nick.accountId=c.accountId AND nick.address=m.senderIdentity) as"
diff --git a/app/src/main/java/im/conversations/android/database/model/MessageWithContentReactions.java b/app/src/main/java/im/conversations/android/database/model/MessageWithContentReactions.java
index 854845276..81ae1d6e5 100644
--- a/app/src/main/java/im/conversations/android/database/model/MessageWithContentReactions.java
+++ b/app/src/main/java/im/conversations/android/database/model/MessageWithContentReactions.java
@@ -3,6 +3,7 @@ package im.conversations.android.database.model;
import androidx.room.Relation;
import com.google.common.base.Objects;
import com.google.common.base.Strings;
+import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
@@ -53,6 +54,7 @@ public class MessageWithContentReactions implements IndividualName, KnownSender
public Modification modification;
public long version;
+ public boolean acknowledged;
public Long inReplyToMessageEntityId;
public Encryption encryption;
public IdentityKey identityKey;
@@ -176,6 +178,27 @@ public class MessageWithContentReactions implements IndividualName, KnownSender
return new EncryptionTuple(this.encryption, this.trust);
}
+ public State getState() {
+ if (outgoing) {
+ final var status =
+ Collections2.transform(
+ Collections2.filter(
+ this.states, s -> s != null && s.fromBare.equals(toBare)),
+ s -> s.type);
+ if (status.contains(StateType.DISPLAYED)) {
+ return State.READ;
+ } else if (status.contains(StateType.DELIVERED)) {
+ return State.DELIVERED;
+ } else if (acknowledged) {
+ return State.DELIVERED_TO_SERVER;
+ } else {
+ return State.NONE;
+ }
+ } else {
+ return State.NONE;
+ }
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -251,4 +274,12 @@ public class MessageWithContentReactions implements IndividualName, KnownSender
this.trust = trust;
}
}
+
+ public enum State {
+ NONE,
+ DELIVERED_TO_SERVER,
+ DELIVERED,
+ READ,
+ ERROR
+ }
}
diff --git a/app/src/main/java/im/conversations/android/ui/BindingAdapters.java b/app/src/main/java/im/conversations/android/ui/BindingAdapters.java
index 20e75bd74..f059dc696 100644
--- a/app/src/main/java/im/conversations/android/ui/BindingAdapters.java
+++ b/app/src/main/java/im/conversations/android/ui/BindingAdapters.java
@@ -6,9 +6,11 @@ import android.view.KeyEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.databinding.BindingAdapter;
import androidx.lifecycle.LiveData;
+import com.google.android.material.color.MaterialColors;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
import com.google.common.base.Supplier;
@@ -144,4 +146,37 @@ public class BindingAdapters {
throw new IllegalArgumentException(String.format("Unknown encryption %s", encryption));
}
}
+
+ @BindingAdapter("state")
+ public static void setState(
+ final ImageView imageView, final MessageWithContentReactions.State state) {
+ if (state == null || state == MessageWithContentReactions.State.NONE) {
+ imageView.setVisibility(View.INVISIBLE);
+ } else {
+ @DrawableRes
+ final var drawableRes =
+ switch (state) {
+ case DELIVERED_TO_SERVER -> R.drawable.ic_check_24dp;
+ case DELIVERED, READ -> R.drawable.ic_done_all_24dp;
+ case ERROR -> R.drawable.ic_error_outline_24dp;
+ default -> throw new IllegalArgumentException(
+ String.format("State %s not implemented", state));
+ };
+ imageView.setImageResource(drawableRes);
+ if (state == MessageWithContentReactions.State.READ) {
+ // the two color candidates are colorTertiary and colorPrimary
+ // depending on the exact color scheme one might 'pop' more than the other
+ imageView.setImageTintList(
+ MaterialColors.getColorStateListOrNull(
+ imageView.getContext(),
+ com.google.android.material.R.attr.colorPrimary));
+ } else {
+ imageView.setImageTintList(
+ MaterialColors.getColorStateListOrNull(
+ imageView.getContext(),
+ com.google.android.material.R.attr.colorOnSurface));
+ }
+ imageView.setVisibility(View.VISIBLE);
+ }
+ }
}
diff --git a/app/src/main/java/im/conversations/android/ui/adapter/MessageAdapter.java b/app/src/main/java/im/conversations/android/ui/adapter/MessageAdapter.java
index a450cb75b..84298dc27 100644
--- a/app/src/main/java/im/conversations/android/ui/adapter/MessageAdapter.java
+++ b/app/src/main/java/im/conversations/android/ui/adapter/MessageAdapter.java
@@ -11,6 +11,7 @@ import androidx.recyclerview.widget.RecyclerView;
import im.conversations.android.R;
import im.conversations.android.database.model.MessageWithContentReactions;
import im.conversations.android.databinding.ItemMessageReceivedBinding;
+import im.conversations.android.databinding.ItemMessageSentBinding;
import im.conversations.android.ui.AvatarFetcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -19,6 +20,9 @@ public class MessageAdapter
extends PagingDataAdapter<
MessageWithContentReactions, MessageAdapter.AbstractMessageViewHolder> {
+ private static final int VIEW_TYPE_RECEIVED = 0;
+ private static final int VIEW_TYPE_SENT = 1;
+
private static final Logger LOGGER = LoggerFactory.getLogger(MessageAdapter.class);
public MessageAdapter(
@@ -26,15 +30,29 @@ public class MessageAdapter
super(diffCallback);
}
+ @Override
+ public int getItemViewType(final int position) {
+ final var message = getItem(position);
+ if (message != null && message.outgoing) {
+ return VIEW_TYPE_SENT;
+ } else {
+ return VIEW_TYPE_RECEIVED;
+ }
+ }
+
@NonNull
@Override
public AbstractMessageViewHolder onCreateViewHolder(
final @NonNull ViewGroup parent, final int viewType) {
final var layoutInflater = LayoutInflater.from(parent.getContext());
- if (viewType == 0) {
+ if (viewType == VIEW_TYPE_RECEIVED) {
return new MessageReceivedViewHolder(
DataBindingUtil.inflate(
layoutInflater, R.layout.item_message_received, parent, false));
+ } else if (viewType == VIEW_TYPE_SENT) {
+ return new MessageSentViewHolder(
+ DataBindingUtil.inflate(
+ layoutInflater, R.layout.item_message_sent, parent, false));
}
throw new IllegalArgumentException(String.format("viewType %d not implemented", viewType));
}
@@ -83,4 +101,19 @@ public class MessageAdapter
this.binding.setMessage(message);
}
}
+
+ public static class MessageSentViewHolder extends AbstractMessageViewHolder {
+
+ private final ItemMessageSentBinding binding;
+
+ public MessageSentViewHolder(@NonNull ItemMessageSentBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ @Override
+ protected void setMessage(MessageWithContentReactions message) {
+ this.binding.setMessage(message);
+ }
+ }
}
diff --git a/app/src/main/res/drawable/ic_done_all_24dp.xml b/app/src/main/res/drawable/ic_done_all_24dp.xml
new file mode 100644
index 000000000..a363e6616
--- /dev/null
+++ b/app/src/main/res/drawable/ic_done_all_24dp.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_error_outline_24dp.xml b/app/src/main/res/drawable/ic_error_outline_24dp.xml
new file mode 100644
index 000000000..d712eaad1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_error_outline_24dp.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout/item_message_sent.xml b/app/src/main/res/layout/item_message_sent.xml
new file mode 100644
index 000000000..27dad1898
--- /dev/null
+++ b/app/src/main/res/layout/item_message_sent.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file