2020-06-16 20:00:24 +00:00
|
|
|
package org.thoughtcrime.securesms.messagedetails;
|
|
|
|
|
|
|
|
import android.content.ClipData;
|
|
|
|
import android.content.ClipboardManager;
|
|
|
|
import android.content.Context;
|
2021-08-30 20:52:46 +00:00
|
|
|
import android.text.SpannableStringBuilder;
|
|
|
|
import android.text.Spanned;
|
|
|
|
import android.text.style.StyleSpan;
|
2020-06-16 20:00:24 +00:00
|
|
|
import android.view.View;
|
2021-04-20 18:12:35 +00:00
|
|
|
import android.view.ViewGroup;
|
2020-06-16 20:00:24 +00:00
|
|
|
import android.view.ViewStub;
|
|
|
|
import android.widget.TextView;
|
|
|
|
|
|
|
|
import androidx.annotation.NonNull;
|
2020-09-23 17:47:00 +00:00
|
|
|
import androidx.annotation.Nullable;
|
|
|
|
import androidx.lifecycle.LifecycleOwner;
|
2020-06-16 20:00:24 +00:00
|
|
|
import androidx.recyclerview.widget.RecyclerView;
|
|
|
|
|
2021-08-22 20:00:43 +00:00
|
|
|
import com.google.android.exoplayer2.MediaItem;
|
2021-04-20 18:12:35 +00:00
|
|
|
|
2021-03-01 20:44:33 +00:00
|
|
|
import org.signal.core.util.ThreadUtil;
|
2020-12-04 23:31:58 +00:00
|
|
|
import org.signal.core.util.concurrent.SignalExecutors;
|
2020-06-16 20:00:24 +00:00
|
|
|
import org.thoughtcrime.securesms.R;
|
|
|
|
import org.thoughtcrime.securesms.conversation.ConversationItem;
|
2020-07-28 14:17:06 +00:00
|
|
|
import org.thoughtcrime.securesms.conversation.ConversationMessage;
|
2021-05-03 14:34:41 +00:00
|
|
|
import org.thoughtcrime.securesms.conversation.colors.Colorizable;
|
|
|
|
import org.thoughtcrime.securesms.conversation.colors.Colorizer;
|
2020-06-16 20:00:24 +00:00
|
|
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
2021-04-20 18:12:35 +00:00
|
|
|
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4Playable;
|
|
|
|
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackPolicyEnforcer;
|
2020-06-16 20:00:24 +00:00
|
|
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
|
|
import org.thoughtcrime.securesms.sms.MessageSender;
|
|
|
|
import org.thoughtcrime.securesms.util.DateUtils;
|
|
|
|
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
2021-08-22 20:00:43 +00:00
|
|
|
import org.thoughtcrime.securesms.util.Projection;
|
2021-10-25 17:12:08 +00:00
|
|
|
import org.thoughtcrime.securesms.util.ProjectionList;
|
2020-06-16 20:00:24 +00:00
|
|
|
|
|
|
|
import java.sql.Date;
|
|
|
|
import java.text.SimpleDateFormat;
|
|
|
|
import java.util.HashSet;
|
|
|
|
import java.util.Locale;
|
2022-03-14 19:49:46 +00:00
|
|
|
import java.util.Optional;
|
2021-05-03 14:34:41 +00:00
|
|
|
|
|
|
|
final class MessageHeaderViewHolder extends RecyclerView.ViewHolder implements GiphyMp4Playable, Colorizable {
|
|
|
|
private final TextView sentDate;
|
|
|
|
private final TextView receivedDate;
|
|
|
|
private final TextView expiresIn;
|
|
|
|
private final TextView transport;
|
|
|
|
private final TextView errorText;
|
|
|
|
private final View resendButton;
|
|
|
|
private final View messageMetadata;
|
|
|
|
private final ViewStub updateStub;
|
|
|
|
private final ViewStub sentStub;
|
|
|
|
private final ViewStub receivedStub;
|
|
|
|
private final Colorizer colorizer;
|
2020-06-16 20:00:24 +00:00
|
|
|
|
|
|
|
private GlideRequests glideRequests;
|
|
|
|
private ConversationItem conversationItem;
|
|
|
|
private ExpiresUpdater expiresUpdater;
|
|
|
|
|
2021-05-03 14:34:41 +00:00
|
|
|
MessageHeaderViewHolder(@NonNull View itemView, GlideRequests glideRequests, @NonNull Colorizer colorizer) {
|
2020-06-16 20:00:24 +00:00
|
|
|
super(itemView);
|
|
|
|
this.glideRequests = glideRequests;
|
2021-05-03 14:34:41 +00:00
|
|
|
this.colorizer = colorizer;
|
2020-06-16 20:00:24 +00:00
|
|
|
|
2020-06-16 22:12:14 +00:00
|
|
|
sentDate = itemView.findViewById(R.id.message_details_header_sent_time);
|
|
|
|
receivedDate = itemView.findViewById(R.id.message_details_header_received_time);
|
|
|
|
expiresIn = itemView.findViewById(R.id.message_details_header_expires_in);
|
|
|
|
transport = itemView.findViewById(R.id.message_details_header_transport);
|
|
|
|
errorText = itemView.findViewById(R.id.message_details_header_error_text);
|
|
|
|
resendButton = itemView.findViewById(R.id.message_details_header_resend_button);
|
|
|
|
messageMetadata = itemView.findViewById(R.id.message_details_header_message_metadata);
|
|
|
|
updateStub = itemView.findViewById(R.id.message_details_header_message_view_update);
|
|
|
|
sentStub = itemView.findViewById(R.id.message_details_header_message_view_sent_multimedia);
|
|
|
|
receivedStub = itemView.findViewById(R.id.message_details_header_message_view_received_multimedia);
|
2020-06-16 20:00:24 +00:00
|
|
|
}
|
|
|
|
|
2020-09-23 17:47:00 +00:00
|
|
|
void bind(@NonNull LifecycleOwner lifecycleOwner, @Nullable ConversationMessage conversationMessage, boolean running) {
|
2020-08-05 20:45:52 +00:00
|
|
|
MessageRecord messageRecord = conversationMessage.getMessageRecord();
|
2020-09-23 17:47:00 +00:00
|
|
|
bindMessageView(lifecycleOwner, conversationMessage);
|
2020-06-16 20:00:24 +00:00
|
|
|
bindErrorState(messageRecord);
|
|
|
|
bindSentReceivedDates(messageRecord);
|
|
|
|
bindExpirationTime(messageRecord, running);
|
|
|
|
bindTransport(messageRecord);
|
|
|
|
}
|
|
|
|
|
2020-08-05 20:45:52 +00:00
|
|
|
void partialBind(ConversationMessage conversationMessage, boolean running) {
|
|
|
|
bindExpirationTime(conversationMessage.getMessageRecord(), running);
|
2020-06-16 20:00:24 +00:00
|
|
|
}
|
|
|
|
|
2020-09-23 17:47:00 +00:00
|
|
|
private void bindMessageView(@NonNull LifecycleOwner lifecycleOwner, @Nullable ConversationMessage conversationMessage) {
|
2020-06-16 20:00:24 +00:00
|
|
|
if (conversationItem == null) {
|
2020-08-05 20:45:52 +00:00
|
|
|
if (conversationMessage.getMessageRecord().isGroupAction()) {
|
|
|
|
conversationItem = (ConversationItem) updateStub.inflate();
|
|
|
|
} else if (conversationMessage.getMessageRecord().isOutgoing()) {
|
|
|
|
conversationItem = (ConversationItem) sentStub.inflate();
|
|
|
|
} else {
|
|
|
|
conversationItem = (ConversationItem) receivedStub.inflate();
|
|
|
|
}
|
2020-06-16 20:00:24 +00:00
|
|
|
}
|
2021-04-20 18:12:35 +00:00
|
|
|
conversationItem.bind(lifecycleOwner,
|
|
|
|
conversationMessage,
|
2022-03-14 19:49:46 +00:00
|
|
|
Optional.empty(),
|
|
|
|
Optional.empty(),
|
2021-04-20 18:12:35 +00:00
|
|
|
glideRequests,
|
|
|
|
Locale.getDefault(),
|
|
|
|
new HashSet<>(),
|
|
|
|
conversationMessage.getMessageRecord().getRecipient(),
|
|
|
|
null,
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
false,
|
2021-05-03 14:34:41 +00:00
|
|
|
true,
|
|
|
|
colorizer);
|
2020-06-16 20:00:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private void bindErrorState(MessageRecord messageRecord) {
|
2020-06-26 15:10:54 +00:00
|
|
|
if (messageRecord.hasFailedWithNetworkFailures()) {
|
2020-06-16 20:00:24 +00:00
|
|
|
errorText.setVisibility(View.VISIBLE);
|
|
|
|
resendButton.setVisibility(View.VISIBLE);
|
|
|
|
resendButton.setOnClickListener(unused -> {
|
|
|
|
resendButton.setOnClickListener(null);
|
|
|
|
SignalExecutors.BOUNDED.execute(() -> MessageSender.resend(itemView.getContext().getApplicationContext(), messageRecord));
|
|
|
|
});
|
|
|
|
messageMetadata.setVisibility(View.GONE);
|
|
|
|
} else if (messageRecord.isFailed()) {
|
|
|
|
errorText.setVisibility(View.VISIBLE);
|
|
|
|
resendButton.setVisibility(View.GONE);
|
|
|
|
resendButton.setOnClickListener(null);
|
|
|
|
messageMetadata.setVisibility(View.GONE);
|
|
|
|
} else {
|
|
|
|
errorText.setVisibility(View.GONE);
|
|
|
|
resendButton.setVisibility(View.GONE);
|
|
|
|
resendButton.setOnClickListener(null);
|
|
|
|
messageMetadata.setVisibility(View.VISIBLE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void bindSentReceivedDates(MessageRecord messageRecord) {
|
|
|
|
sentDate.setOnLongClickListener(null);
|
|
|
|
receivedDate.setOnLongClickListener(null);
|
|
|
|
|
|
|
|
if (messageRecord.isPending() || messageRecord.isFailed()) {
|
2022-06-06 15:12:03 +00:00
|
|
|
sentDate.setText(formatBoldString(R.string.message_details_header_sent, "-"));
|
2021-08-30 20:52:46 +00:00
|
|
|
receivedDate.setVisibility(View.GONE);
|
2020-06-16 20:00:24 +00:00
|
|
|
} else {
|
|
|
|
Locale dateLocale = Locale.getDefault();
|
|
|
|
SimpleDateFormat dateFormatter = DateUtils.getDetailedDateFormatter(itemView.getContext(), dateLocale);
|
2022-06-06 15:12:03 +00:00
|
|
|
sentDate.setText(formatBoldString(R.string.message_details_header_sent, dateFormatter.format(new Date(messageRecord.getDateSent()))));
|
2020-06-16 20:00:24 +00:00
|
|
|
sentDate.setOnLongClickListener(v -> {
|
|
|
|
copyToClipboard(String.valueOf(messageRecord.getDateSent()));
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
|
|
|
|
if (messageRecord.getDateReceived() != messageRecord.getDateSent() && !messageRecord.isOutgoing()) {
|
2022-06-06 15:12:03 +00:00
|
|
|
receivedDate.setText(formatBoldString(R.string.message_details_header_received, dateFormatter.format(new Date(messageRecord.getDateReceived()))));
|
2020-06-16 20:00:24 +00:00
|
|
|
receivedDate.setOnLongClickListener(v -> {
|
|
|
|
copyToClipboard(String.valueOf(messageRecord.getDateReceived()));
|
|
|
|
return true;
|
|
|
|
});
|
2021-08-30 20:52:46 +00:00
|
|
|
receivedDate.setVisibility(View.VISIBLE);
|
2020-06-16 20:00:24 +00:00
|
|
|
} else {
|
2021-08-30 20:52:46 +00:00
|
|
|
receivedDate.setVisibility(View.GONE);
|
2020-06-16 20:00:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void bindExpirationTime(final MessageRecord messageRecord, boolean running) {
|
|
|
|
if (expiresUpdater != null) {
|
|
|
|
expiresUpdater.stop();
|
|
|
|
expiresUpdater = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (messageRecord.getExpiresIn() <= 0 || messageRecord.getExpireStarted() <= 0) {
|
2021-08-30 20:52:46 +00:00
|
|
|
expiresIn.setVisibility(View.GONE);
|
2020-06-16 20:00:24 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-08-30 20:52:46 +00:00
|
|
|
expiresIn.setVisibility(View.VISIBLE);
|
2020-06-16 20:00:24 +00:00
|
|
|
if (running) {
|
|
|
|
expiresUpdater = new ExpiresUpdater(messageRecord);
|
2021-03-01 20:44:33 +00:00
|
|
|
ThreadUtil.runOnMain(expiresUpdater);
|
2020-06-16 20:00:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void bindTransport(MessageRecord messageRecord) {
|
|
|
|
final String transportText;
|
|
|
|
if (messageRecord.isOutgoing() && messageRecord.isFailed()) {
|
|
|
|
transportText = "-";
|
|
|
|
} else if (messageRecord.isPending()) {
|
|
|
|
transportText = itemView.getContext().getString(R.string.ConversationFragment_pending);
|
|
|
|
} else if (messageRecord.isPush()) {
|
|
|
|
transportText = itemView.getContext().getString(R.string.ConversationFragment_push);
|
|
|
|
} else if (messageRecord.isMms()) {
|
|
|
|
transportText = itemView.getContext().getString(R.string.ConversationFragment_mms);
|
|
|
|
} else {
|
|
|
|
transportText = itemView.getContext().getString(R.string.ConversationFragment_sms);
|
|
|
|
}
|
|
|
|
|
2022-06-06 15:12:03 +00:00
|
|
|
transport.setText(formatBoldString(R.string.message_details_header_via, transportText));
|
2021-08-30 20:52:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private CharSequence formatBoldString(int boldTextRes, CharSequence otherText) {
|
|
|
|
SpannableStringBuilder builder = new SpannableStringBuilder();
|
|
|
|
StyleSpan boldSpan = new StyleSpan(android.graphics.Typeface.BOLD);
|
|
|
|
CharSequence boldText = itemView.getContext().getString(boldTextRes);
|
|
|
|
|
|
|
|
builder.append(boldText).append(" ").append(otherText);
|
|
|
|
builder.setSpan(boldSpan, 0, boldText.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
|
|
|
|
|
|
|
return builder;
|
2020-06-16 20:00:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private void copyToClipboard(String text) {
|
|
|
|
((ClipboardManager) itemView.getContext().getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("text", text));
|
|
|
|
}
|
|
|
|
|
2021-04-20 18:12:35 +00:00
|
|
|
@Override
|
|
|
|
public void showProjectionArea() {
|
|
|
|
conversationItem.showProjectionArea();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void hideProjectionArea() {
|
|
|
|
conversationItem.hideProjectionArea();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-08-22 20:00:43 +00:00
|
|
|
public @Nullable MediaItem getMediaItem() {
|
|
|
|
return conversationItem.getMediaItem();
|
2021-04-20 18:12:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public @Nullable GiphyMp4PlaybackPolicyEnforcer getPlaybackPolicyEnforcer() {
|
|
|
|
return conversationItem.getPlaybackPolicyEnforcer();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-07-23 16:47:43 +00:00
|
|
|
public @NonNull Projection getGiphyMp4PlayableProjection(@NonNull ViewGroup recyclerview) {
|
|
|
|
return conversationItem.getGiphyMp4PlayableProjection(recyclerview);
|
2021-04-20 18:12:35 +00:00
|
|
|
}
|
|
|
|
|
2021-08-22 20:00:43 +00:00
|
|
|
@Override
|
|
|
|
public boolean canPlayContent() {
|
2021-04-20 18:12:35 +00:00
|
|
|
return conversationItem.canPlayContent();
|
|
|
|
}
|
|
|
|
|
2022-01-28 20:12:35 +00:00
|
|
|
@Override
|
|
|
|
public boolean shouldProjectContent() {
|
|
|
|
return conversationItem.shouldProjectContent();
|
|
|
|
}
|
|
|
|
|
2021-08-22 20:00:43 +00:00
|
|
|
@Override
|
2021-10-25 17:12:08 +00:00
|
|
|
public @NonNull ProjectionList getColorizerProjections(@NonNull ViewGroup coordinateRoot) {
|
2021-10-01 19:09:54 +00:00
|
|
|
return conversationItem.getColorizerProjections(coordinateRoot);
|
2021-05-03 14:34:41 +00:00
|
|
|
}
|
|
|
|
|
2020-06-16 20:00:24 +00:00
|
|
|
private class ExpiresUpdater implements Runnable {
|
|
|
|
|
|
|
|
private final long expireStartedTimestamp;
|
|
|
|
private final long expiresInTimestamp;
|
|
|
|
private boolean running;
|
|
|
|
|
|
|
|
ExpiresUpdater(MessageRecord messageRecord) {
|
|
|
|
expireStartedTimestamp = messageRecord.getExpireStarted();
|
|
|
|
expiresInTimestamp = messageRecord.getExpiresIn();
|
|
|
|
running = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
long elapsed = System.currentTimeMillis() - expireStartedTimestamp;
|
|
|
|
long remaining = expiresInTimestamp - elapsed;
|
|
|
|
int expirationTime = Math.max((int) (remaining / 1000), 1);
|
|
|
|
String duration = ExpirationUtil.getExpirationDisplayValue(itemView.getContext(), expirationTime);
|
|
|
|
|
2022-06-06 15:12:03 +00:00
|
|
|
expiresIn.setText(formatBoldString(R.string.message_details_header_disappears, duration));
|
2020-06-16 20:00:24 +00:00
|
|
|
|
|
|
|
if (running && expirationTime > 1) {
|
2021-03-01 20:44:33 +00:00
|
|
|
ThreadUtil.runOnMainDelayed(this, 500);
|
2020-06-16 20:00:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void stop() {
|
|
|
|
running = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|