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 @Transaction
@Query( @Query(
"SELECT c.accountId,m.id as id,type as" "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" + " as sender,(SELECT name FROM roster WHERE roster.accountId=c.accountId AND"
+ " roster.address=m.senderIdentity) as senderRosterName,(SELECT nick FROM nick" + " roster.address=m.senderIdentity) as senderRosterName,(SELECT nick FROM nick"
+ " WHERE nick.accountId=c.accountId AND nick.address=m.senderIdentity) as" + " WHERE nick.accountId=c.accountId AND nick.address=m.senderIdentity) as"
@ -456,7 +456,7 @@ public abstract class MessageDao {
@Transaction @Transaction
@Query( @Query(
"SELECT c.accountId,m.id as id,type as" "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" + " as sender,(SELECT name FROM roster WHERE roster.accountId=c.accountId AND"
+ " roster.address=m.senderIdentity) as senderRosterName,(SELECT nick FROM nick" + " roster.address=m.senderIdentity) as senderRosterName,(SELECT nick FROM nick"
+ " WHERE nick.accountId=c.accountId AND nick.address=m.senderIdentity) as" + " 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 androidx.room.Relation;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
@ -53,6 +54,7 @@ public class MessageWithContentReactions implements IndividualName, KnownSender
public Modification modification; public Modification modification;
public long version; public long version;
public boolean acknowledged;
public Long inReplyToMessageEntityId; public Long inReplyToMessageEntityId;
public Encryption encryption; public Encryption encryption;
public IdentityKey identityKey; public IdentityKey identityKey;
@ -176,6 +178,27 @@ public class MessageWithContentReactions implements IndividualName, KnownSender
return new EncryptionTuple(this.encryption, this.trust); 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 @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
@ -251,4 +274,12 @@ public class MessageWithContentReactions implements IndividualName, KnownSender
this.trust = trust; 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.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.databinding.BindingAdapter; import androidx.databinding.BindingAdapter;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import com.google.android.material.color.MaterialColors;
import com.google.android.material.textfield.TextInputEditText; import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.textfield.TextInputLayout;
import com.google.common.base.Supplier; import com.google.common.base.Supplier;
@ -144,4 +146,37 @@ public class BindingAdapters {
throw new IllegalArgumentException(String.format("Unknown encryption %s", encryption)); 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.R;
import im.conversations.android.database.model.MessageWithContentReactions; import im.conversations.android.database.model.MessageWithContentReactions;
import im.conversations.android.databinding.ItemMessageReceivedBinding; import im.conversations.android.databinding.ItemMessageReceivedBinding;
import im.conversations.android.databinding.ItemMessageSentBinding;
import im.conversations.android.ui.AvatarFetcher; import im.conversations.android.ui.AvatarFetcher;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -19,6 +20,9 @@ public class MessageAdapter
extends PagingDataAdapter< extends PagingDataAdapter<
MessageWithContentReactions, MessageAdapter.AbstractMessageViewHolder> { 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); private static final Logger LOGGER = LoggerFactory.getLogger(MessageAdapter.class);
public MessageAdapter( public MessageAdapter(
@ -26,15 +30,29 @@ public class MessageAdapter
super(diffCallback); 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 @NonNull
@Override @Override
public AbstractMessageViewHolder onCreateViewHolder( public AbstractMessageViewHolder onCreateViewHolder(
final @NonNull ViewGroup parent, final int viewType) { final @NonNull ViewGroup parent, final int viewType) {
final var layoutInflater = LayoutInflater.from(parent.getContext()); final var layoutInflater = LayoutInflater.from(parent.getContext());
if (viewType == 0) { if (viewType == VIEW_TYPE_RECEIVED) {
return new MessageReceivedViewHolder( return new MessageReceivedViewHolder(
DataBindingUtil.inflate( DataBindingUtil.inflate(
layoutInflater, R.layout.item_message_received, parent, false)); 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)); throw new IllegalArgumentException(String.format("viewType %d not implemented", viewType));
} }
@ -83,4 +101,19 @@ public class MessageAdapter
this.binding.setMessage(message); 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>