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 97f24ddb5..4ba8dbc68 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
@@ -115,12 +115,23 @@ public final class MessageWithContentReactions
return Iterables.tryFind(this.contents, c -> c.type == PartType.FILE).isPresent();
}
+ public boolean hasDownloadButton() {
+ return hasPreview();
+ }
+
+ public boolean hasTextContent() {
+ return Iterables.tryFind(this.contents, c -> c.type == PartType.TEXT).isPresent();
+ }
+
public boolean hasInReplyTo() {
return this.inReplyTo != null;
}
- public Instant inReplyToSentAt() {
- return this.inReplyTo == null ? null : this.inReplyTo.sentAt;
+ public EmbeddedSentAt inReplyToSentAt() {
+ if (this.inReplyTo == null) {
+ return null;
+ }
+ return new EmbeddedSentAt(this.sentAt, this.inReplyTo.sentAt);
}
public String inReplyToSender() {
@@ -152,9 +163,6 @@ public final class MessageWithContentReactions
public AvatarWithAccount getAvatar() {
final var address = getAddressWithName();
- if (address == null) {
- return null;
- }
if (isKnownSender()) {
if (this.senderAvatar != null) {
return new AvatarWithAccount(accountId, address, AvatarType.PEP, this.senderAvatar);
@@ -318,4 +326,14 @@ public final class MessageWithContentReactions
READ,
ERROR
}
+
+ public static class EmbeddedSentAt {
+ public final Instant sentAt;
+ public final Instant embeddedSentAt;
+
+ public EmbeddedSentAt(Instant sentAt, Instant embeddedSentAt) {
+ this.sentAt = sentAt;
+ this.embeddedSentAt = embeddedSentAt;
+ }
+ }
}
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 4fca5f824..e529b6272 100644
--- a/app/src/main/java/im/conversations/android/ui/BindingAdapters.java
+++ b/app/src/main/java/im/conversations/android/ui/BindingAdapters.java
@@ -64,30 +64,45 @@ public class BindingAdapters {
if (instant == null || instant.getEpochSecond() <= 0) {
textView.setVisibility(View.GONE);
} else {
- final Context context = textView.getContext();
- final Instant now = Instant.now();
- textView.setVisibility(View.VISIBLE);
- if (sameDay(instant, now) || now.minus(SIX_HOURS).isBefore(instant)) {
- textView.setText(
- DateUtils.formatDateTime(
- context, instant.toEpochMilli(), DateUtils.FORMAT_SHOW_TIME));
- } else if (sameYear(instant, now) || now.minus(THREE_MONTH).isBefore(instant)) {
- textView.setText(
- DateUtils.formatDateTime(
- context,
- instant.toEpochMilli(),
- DateUtils.FORMAT_SHOW_DATE
- | DateUtils.FORMAT_NO_YEAR
- | DateUtils.FORMAT_ABBREV_ALL));
- } else {
- textView.setText(
- DateUtils.formatDateTime(
- context,
- instant.toEpochMilli(),
- DateUtils.FORMAT_SHOW_DATE
- | DateUtils.FORMAT_NO_MONTH_DAY
- | DateUtils.FORMAT_ABBREV_ALL));
- }
+ setDatetime(textView, Instant.now(), instant);
+ }
+ }
+
+ @BindingAdapter("datetime")
+ public static void setDatetime(
+ final TextView textView,
+ final MessageWithContentReactions.EmbeddedSentAt embeddedSentAt) {
+ if (embeddedSentAt == null || embeddedSentAt.embeddedSentAt.getEpochSecond() <= 0) {
+ textView.setVisibility(View.GONE);
+ } else {
+ setDatetime(textView, embeddedSentAt.sentAt, embeddedSentAt.embeddedSentAt);
+ }
+ }
+
+ private static void setDatetime(
+ final TextView textView, final Instant now, final Instant instant) {
+ final Context context = textView.getContext();
+ textView.setVisibility(View.VISIBLE);
+ if (sameDay(instant, now) || now.minus(SIX_HOURS).isBefore(instant)) {
+ textView.setText(
+ DateUtils.formatDateTime(
+ context, instant.toEpochMilli(), DateUtils.FORMAT_SHOW_TIME));
+ } else if (sameYear(instant, now) || now.minus(THREE_MONTH).isBefore(instant)) {
+ textView.setText(
+ DateUtils.formatDateTime(
+ context,
+ instant.toEpochMilli(),
+ DateUtils.FORMAT_SHOW_DATE
+ | DateUtils.FORMAT_NO_YEAR
+ | DateUtils.FORMAT_ABBREV_ALL));
+ } else {
+ textView.setText(
+ DateUtils.formatDateTime(
+ context,
+ instant.toEpochMilli(),
+ DateUtils.FORMAT_SHOW_DATE
+ | DateUtils.FORMAT_NO_MONTH_DAY
+ | DateUtils.FORMAT_ABBREV_ALL));
}
}
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 dbfe3f920..8726b8142 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
@@ -15,6 +15,7 @@ import im.conversations.android.databinding.ItemMessageReceivedBinding;
import im.conversations.android.databinding.ItemMessageSentBinding;
import im.conversations.android.databinding.ItemMessageSeparatorBinding;
import im.conversations.android.ui.AvatarFetcher;
+import im.conversations.android.ui.graphics.drawable.FlashBackgroundDrawable;
import java.util.function.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -93,6 +94,11 @@ public class MessageAdapter
@NonNull AbstractMessageViewHolder holder,
@NonNull final MessageWithContentReactions message) {
holder.setItem(message);
+ if (holder.itemView.getBackground() instanceof FlashBackgroundDrawable backgroundDrawable) {
+ if (backgroundDrawable.needsReset(message.id)) {
+ holder.itemView.setBackground(null);
+ }
+ }
final var inReplyTo = message.inReplyTo;
if (holder instanceof MessageReceivedViewHolder messageReceivedViewHolder) {
if (inReplyTo != null) {
diff --git a/app/src/main/java/im/conversations/android/ui/fragment/main/ChatFragment.java b/app/src/main/java/im/conversations/android/ui/fragment/main/ChatFragment.java
index 45f707040..2fa6f054b 100644
--- a/app/src/main/java/im/conversations/android/ui/fragment/main/ChatFragment.java
+++ b/app/src/main/java/im/conversations/android/ui/fragment/main/ChatFragment.java
@@ -21,6 +21,7 @@ import im.conversations.android.ui.RecyclerViewScroller;
import im.conversations.android.ui.adapter.MessageAdapter;
import im.conversations.android.ui.adapter.MessageAdapterItems;
import im.conversations.android.ui.adapter.MessageComparator;
+import im.conversations.android.ui.graphics.drawable.FlashBackgroundDrawable;
import im.conversations.android.ui.model.ChatViewModel;
import im.conversations.android.util.MainThreadExecutor;
import org.slf4j.Logger;
@@ -108,6 +109,7 @@ public class ChatFragment extends Fragment {
}
private void scrollToMessageId(final long messageId) {
+ // TODO do not scroll if view is fully visible
LOGGER.info("scrollToMessageId({})", messageId);
this.chatViewModel.setShowDateSeparators(false);
final var future = this.chatViewModel.getMessagePosition(messageId);
@@ -117,7 +119,11 @@ public class ChatFragment extends Fragment {
@Override
public void onSuccess(final @NonNull Integer position) {
recyclerViewScroller.scrollToPosition(
- position, () -> chatViewModel.setShowDateSeparators(true));
+ position,
+ () -> {
+ chatViewModel.setShowDateSeparators(true);
+ flashBackgroundAtPosition(position, messageId);
+ });
}
@Override
@@ -128,4 +134,15 @@ public class ChatFragment extends Fragment {
},
MainThreadExecutor.getInstance());
}
+
+ private void flashBackgroundAtPosition(final int position, final long messageId) {
+ final var layoutManager = this.binding.messages.getLayoutManager();
+ if (layoutManager instanceof LinearLayoutManager llm) {
+ final var view = llm.findViewByPosition(position);
+ if (view == null) {
+ return;
+ }
+ FlashBackgroundDrawable.flashBackground(view, messageId);
+ }
+ }
}
diff --git a/app/src/main/java/im/conversations/android/ui/graphics/drawable/FlashBackgroundDrawable.java b/app/src/main/java/im/conversations/android/ui/graphics/drawable/FlashBackgroundDrawable.java
new file mode 100644
index 000000000..01da6c962
--- /dev/null
+++ b/app/src/main/java/im/conversations/android/ui/graphics/drawable/FlashBackgroundDrawable.java
@@ -0,0 +1,41 @@
+package im.conversations.android.ui.graphics.drawable;
+
+import android.content.Context;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.view.View;
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import com.google.android.material.color.MaterialColors;
+
+public class FlashBackgroundDrawable extends AnimationDrawable {
+
+ private final long messageId;
+
+ private FlashBackgroundDrawable(final Context context, final long messageId) {
+ this.messageId = messageId;
+ @ColorInt
+ int backgroundColor =
+ MaterialColors.getColor(
+ context,
+ com.google.android.material.R.attr.colorSurfaceVariant,
+ "colorSurfaceVariant not found");
+ for (int i = 0; i < 3; ++i) {
+ this.addFrame(new ColorDrawable(backgroundColor), 250);
+ this.addFrame(new ColorDrawable(android.graphics.Color.TRANSPARENT), 250);
+ }
+ this.setEnterFadeDuration(125);
+ this.setExitFadeDuration(125);
+ this.setOneShot(true);
+ }
+
+ public boolean needsReset(final long messageId) {
+ return this.messageId != messageId || !this.isRunning();
+ }
+
+ public static void flashBackground(@NonNull final View view, final long messageId) {
+ final var animationDrawable = new FlashBackgroundDrawable(view.getContext(), messageId);
+ view.setBackground(animationDrawable);
+ view.post(animationDrawable::start);
+ }
+}
diff --git a/app/src/main/res/layout/item_message_received.xml b/app/src/main/res/layout/item_message_received.xml
index f4e1c7ce1..50cf7f856 100644
--- a/app/src/main/res/layout/item_message_received.xml
+++ b/app/src/main/res/layout/item_message_received.xml
@@ -44,7 +44,8 @@
android:visibility="@{message.hasInReplyTo ? View.VISIBLE : View.GONE}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent">
+ app:layout_constraintTop_toTopOf="parent"
+ tools:visibility="visible">
+ app:layout_constraintWidth_max="@dimen/message_preview_max_width"
+ tools:visibility="gone" />
+
+
+ app:layout_constraintTop_toBottomOf="@+id/downloadButton">
-
-
diff --git a/app/src/main/res/layout/item_message_sent.xml b/app/src/main/res/layout/item_message_sent.xml
index ed1eb7678..401ea0203 100644
--- a/app/src/main/res/layout/item_message_sent.xml
+++ b/app/src/main/res/layout/item_message_sent.xml
@@ -16,7 +16,6 @@
android:background="@drawable/background_message_bubble"
android:backgroundTint="?colorTertiaryContainer"
android:minHeight="40dp"
- android:padding="8dp"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
@@ -34,7 +33,8 @@
android:visibility="@{message.hasInReplyTo ? View.VISIBLE : View.GONE}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent">
+ app:layout_constraintTop_toTopOf="parent"
+ tools:visibility="visible">
+ app:layout_constraintWidth_max="@dimen/message_preview_max_width"
+ tools:visibility="gone" />
+
+
+ app:layout_constraintTop_toBottomOf="@+id/downloadButton">