display outgoing messages

This commit is contained in:
Daniel Gultsch 2023-03-27 14:41:23 +02:00
parent d52cbb8e8c
commit 5b777ef657
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
7 changed files with 210 additions and 3 deletions

View file

@ -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"

View file

@ -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
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M18,7l-1.41,-1.41 -6.34,6.34 1.41,1.41L18,7zM22.24,5.59L11.66,16.17 7.48,12l-1.41,1.41L11.66,19l12,-12 -1.42,-1.41zM0.41,13.41L6,19l1.41,-1.41L1.83,12 0.41,13.41z" />
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M11,15h2v2h-2zM11,7h2v6h-2zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z" />
</vector>

View file

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="6dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:background="@drawable/background_message_received"
android:backgroundTint="?colorTertiaryContainer"
android:minHeight="40dp"
android:padding="8dp"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/textContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{message.textContent}"
android:textAppearance="?textAppearanceBodyMedium"
android:textColor="?colorOnSecondaryContainer"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Fusce vitae!" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2sp"
android:layout_marginEnd="4sp"
android:textAppearance="?textAppearanceLabelSmall"
android:textColor="?colorOnSurface"
app:layout_constraintEnd_toStartOf="@+id/encryption"
app:layout_constraintTop_toBottomOf="@+id/content"
app:time="@{message.sentAt}"
tools:text="11:42 PM" />
<ImageView
android:id="@+id/encryption"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginEnd="4sp"
android:src="@drawable/ic_lock_outline_24dp"
android:visibility="gone"
app:encryption="@{message.getEncryption()}"
app:layout_constraintBottom_toBottomOf="@+id/time"
app:layout_constraintEnd_toStartOf="@+id/status"
app:layout_constraintTop_toTopOf="@+id/time"
app:tint="?colorOnSurface" />
<ImageView
android:id="@+id/status"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginEnd="8dp"
android:src="@drawable/ic_done_all_24dp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="@+id/time"
app:layout_constraintEnd_toEndOf="@+id/content"
app:layout_constraintTop_toTopOf="@+id/time"
app:state="@{message.state}" />
</androidx.constraintlayout.widget.ConstraintLayout>
<data>
<import type="android.view.View" />
<variable
name="message"
type="im.conversations.android.database.model.MessageWithContentReactions" />
</data>
</layout>