flash background after scrolling to message
This commit is contained in:
parent
4f654044b4
commit
acfcde8416
|
@ -115,12 +115,23 @@ public final class MessageWithContentReactions
|
||||||
return Iterables.tryFind(this.contents, c -> c.type == PartType.FILE).isPresent();
|
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() {
|
public boolean hasInReplyTo() {
|
||||||
return this.inReplyTo != null;
|
return this.inReplyTo != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Instant inReplyToSentAt() {
|
public EmbeddedSentAt inReplyToSentAt() {
|
||||||
return this.inReplyTo == null ? null : this.inReplyTo.sentAt;
|
if (this.inReplyTo == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new EmbeddedSentAt(this.sentAt, this.inReplyTo.sentAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String inReplyToSender() {
|
public String inReplyToSender() {
|
||||||
|
@ -152,9 +163,6 @@ public final class MessageWithContentReactions
|
||||||
|
|
||||||
public AvatarWithAccount getAvatar() {
|
public AvatarWithAccount getAvatar() {
|
||||||
final var address = getAddressWithName();
|
final var address = getAddressWithName();
|
||||||
if (address == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (isKnownSender()) {
|
if (isKnownSender()) {
|
||||||
if (this.senderAvatar != null) {
|
if (this.senderAvatar != null) {
|
||||||
return new AvatarWithAccount(accountId, address, AvatarType.PEP, this.senderAvatar);
|
return new AvatarWithAccount(accountId, address, AvatarType.PEP, this.senderAvatar);
|
||||||
|
@ -318,4 +326,14 @@ public final class MessageWithContentReactions
|
||||||
READ,
|
READ,
|
||||||
ERROR
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,8 +64,24 @@ public class BindingAdapters {
|
||||||
if (instant == null || instant.getEpochSecond() <= 0) {
|
if (instant == null || instant.getEpochSecond() <= 0) {
|
||||||
textView.setVisibility(View.GONE);
|
textView.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
|
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();
|
final Context context = textView.getContext();
|
||||||
final Instant now = Instant.now();
|
|
||||||
textView.setVisibility(View.VISIBLE);
|
textView.setVisibility(View.VISIBLE);
|
||||||
if (sameDay(instant, now) || now.minus(SIX_HOURS).isBefore(instant)) {
|
if (sameDay(instant, now) || now.minus(SIX_HOURS).isBefore(instant)) {
|
||||||
textView.setText(
|
textView.setText(
|
||||||
|
@ -89,7 +105,6 @@ public class BindingAdapters {
|
||||||
| DateUtils.FORMAT_ABBREV_ALL));
|
| DateUtils.FORMAT_ABBREV_ALL));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@BindingAdapter("time")
|
@BindingAdapter("time")
|
||||||
public static void setTime(final TextView textView, final Instant instant) {
|
public static void setTime(final TextView textView, final Instant instant) {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import im.conversations.android.databinding.ItemMessageReceivedBinding;
|
||||||
import im.conversations.android.databinding.ItemMessageSentBinding;
|
import im.conversations.android.databinding.ItemMessageSentBinding;
|
||||||
import im.conversations.android.databinding.ItemMessageSeparatorBinding;
|
import im.conversations.android.databinding.ItemMessageSeparatorBinding;
|
||||||
import im.conversations.android.ui.AvatarFetcher;
|
import im.conversations.android.ui.AvatarFetcher;
|
||||||
|
import im.conversations.android.ui.graphics.drawable.FlashBackgroundDrawable;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -93,6 +94,11 @@ public class MessageAdapter
|
||||||
@NonNull AbstractMessageViewHolder holder,
|
@NonNull AbstractMessageViewHolder holder,
|
||||||
@NonNull final MessageWithContentReactions message) {
|
@NonNull final MessageWithContentReactions message) {
|
||||||
holder.setItem(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;
|
final var inReplyTo = message.inReplyTo;
|
||||||
if (holder instanceof MessageReceivedViewHolder messageReceivedViewHolder) {
|
if (holder instanceof MessageReceivedViewHolder messageReceivedViewHolder) {
|
||||||
if (inReplyTo != null) {
|
if (inReplyTo != null) {
|
||||||
|
|
|
@ -21,6 +21,7 @@ import im.conversations.android.ui.RecyclerViewScroller;
|
||||||
import im.conversations.android.ui.adapter.MessageAdapter;
|
import im.conversations.android.ui.adapter.MessageAdapter;
|
||||||
import im.conversations.android.ui.adapter.MessageAdapterItems;
|
import im.conversations.android.ui.adapter.MessageAdapterItems;
|
||||||
import im.conversations.android.ui.adapter.MessageComparator;
|
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.ui.model.ChatViewModel;
|
||||||
import im.conversations.android.util.MainThreadExecutor;
|
import im.conversations.android.util.MainThreadExecutor;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -108,6 +109,7 @@ public class ChatFragment extends Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scrollToMessageId(final long messageId) {
|
private void scrollToMessageId(final long messageId) {
|
||||||
|
// TODO do not scroll if view is fully visible
|
||||||
LOGGER.info("scrollToMessageId({})", messageId);
|
LOGGER.info("scrollToMessageId({})", messageId);
|
||||||
this.chatViewModel.setShowDateSeparators(false);
|
this.chatViewModel.setShowDateSeparators(false);
|
||||||
final var future = this.chatViewModel.getMessagePosition(messageId);
|
final var future = this.chatViewModel.getMessagePosition(messageId);
|
||||||
|
@ -117,7 +119,11 @@ public class ChatFragment extends Fragment {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(final @NonNull Integer position) {
|
public void onSuccess(final @NonNull Integer position) {
|
||||||
recyclerViewScroller.scrollToPosition(
|
recyclerViewScroller.scrollToPosition(
|
||||||
position, () -> chatViewModel.setShowDateSeparators(true));
|
position,
|
||||||
|
() -> {
|
||||||
|
chatViewModel.setShowDateSeparators(true);
|
||||||
|
flashBackgroundAtPosition(position, messageId);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -128,4 +134,15 @@ public class ChatFragment extends Fragment {
|
||||||
},
|
},
|
||||||
MainThreadExecutor.getInstance());
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,7 +44,8 @@
|
||||||
android:visibility="@{message.hasInReplyTo ? View.VISIBLE : View.GONE}"
|
android:visibility="@{message.hasInReplyTo ? View.VISIBLE : View.GONE}"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/embeddedMessageSender"
|
android:id="@+id/embeddedMessageSender"
|
||||||
|
@ -86,12 +87,29 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:adjustViewBounds="true"
|
android:adjustViewBounds="true"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/textContentWrapper"
|
android:visibility="@{message.hasPreview ? View.VISIBLE : View.GONE}"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/downloadButton"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHeight_max="@dimen/message_preview_max_height"
|
app:layout_constraintHeight_max="@dimen/message_preview_max_height"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/embeddedMessage"
|
app:layout_constraintTop_toBottomOf="@+id/embeddedMessage"
|
||||||
app:layout_constraintWidth_max="@dimen/message_preview_max_width" />
|
app:layout_constraintWidth_max="@dimen/message_preview_max_width"
|
||||||
|
tools:visibility="gone" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/downloadButton"
|
||||||
|
style="@style/Widget.Material3.Button.OutlinedButton.Icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:text="@string/check_x_filesize_on_host"
|
||||||
|
android:visibility="@{message.hasDownloadButton ? View.VISIBLE : View.GONE}"
|
||||||
|
app:icon="@drawable/ic_download_24dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/textContentWrapper"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/image"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/textContentWrapper"
|
android:id="@+id/textContentWrapper"
|
||||||
|
@ -99,11 +117,11 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
android:visibility="visible"
|
android:visibility="@{message.hasTextContent ? View.VISIBLE : View.GONE}"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/image">
|
app:layout_constraintTop_toBottomOf="@+id/downloadButton">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textContent"
|
android:id="@+id/textContent"
|
||||||
|
@ -114,8 +132,6 @@
|
||||||
android:textColor="?colorOnSecondaryContainer"
|
android:textColor="?colorOnSecondaryContainer"
|
||||||
tools:text="Quisque sit amet metus faucibus, egestas est eu, hendrerit mauris. Suspendisse pretium nisl purus, vitae vestibulum sapien rhoncus nec. Quisque molestie ante felis, vel dapibus ex mattis a. Morbi venenatis vestibulum neque, vel ornare sapien. Aliquam erat volutpat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. " />
|
tools:text="Quisque sit amet metus faucibus, egestas est eu, hendrerit mauris. Suspendisse pretium nisl purus, vitae vestibulum sapien rhoncus nec. Quisque molestie ante felis, vel dapibus ex mattis a. Morbi venenatis vestibulum neque, vel ornare sapien. Aliquam erat volutpat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. " />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
android:background="@drawable/background_message_bubble"
|
android:background="@drawable/background_message_bubble"
|
||||||
android:backgroundTint="?colorTertiaryContainer"
|
android:backgroundTint="?colorTertiaryContainer"
|
||||||
android:minHeight="40dp"
|
android:minHeight="40dp"
|
||||||
android:padding="8dp"
|
|
||||||
app:layout_constrainedWidth="true"
|
app:layout_constrainedWidth="true"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="1.0"
|
app:layout_constraintHorizontal_bias="1.0"
|
||||||
|
@ -34,7 +33,8 @@
|
||||||
android:visibility="@{message.hasInReplyTo ? View.VISIBLE : View.GONE}"
|
android:visibility="@{message.hasInReplyTo ? View.VISIBLE : View.GONE}"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/embeddedMessageSender"
|
android:id="@+id/embeddedMessageSender"
|
||||||
|
@ -76,12 +76,29 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:adjustViewBounds="true"
|
android:adjustViewBounds="true"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/textContentWrapper"
|
android:visibility="@{message.hasPreview ? View.VISIBLE : View.GONE}"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/downloadButton"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHeight_max="@dimen/message_preview_max_height"
|
app:layout_constraintHeight_max="@dimen/message_preview_max_height"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/embeddedMessage"
|
app:layout_constraintTop_toBottomOf="@+id/embeddedMessage"
|
||||||
app:layout_constraintWidth_max="@dimen/message_preview_max_width" />
|
app:layout_constraintWidth_max="@dimen/message_preview_max_width"
|
||||||
|
tools:visibility="gone" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/downloadButton"
|
||||||
|
style="@style/Widget.Material3.Button.OutlinedButton.Icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:text="@string/check_x_filesize_on_host"
|
||||||
|
android:visibility="@{message.hasDownloadButton ? View.VISIBLE : View.GONE}"
|
||||||
|
app:icon="@drawable/ic_download_24dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/textContentWrapper"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/image"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/textContentWrapper"
|
android:id="@+id/textContentWrapper"
|
||||||
|
@ -89,11 +106,11 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
android:visibility="visible"
|
android:visibility="@{message.hasTextContent ? View.VISIBLE : View.GONE}"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/image">
|
app:layout_constraintTop_toBottomOf="@+id/downloadButton">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textContent"
|
android:id="@+id/textContent"
|
||||||
|
|
Loading…
Reference in a new issue