Signal-Android/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageHeaderViewHolder.java

290 wiersze
11 KiB
Java

package org.thoughtcrime.securesms.messagedetails;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.StyleSpan;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LifecycleOwner;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.exoplayer2.MediaItem;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.conversation.ConversationItem;
import org.thoughtcrime.securesms.conversation.ConversationMessage;
import org.thoughtcrime.securesms.conversation.colors.Colorizable;
import org.thoughtcrime.securesms.conversation.colors.Colorizer;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4Playable;
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackPolicyEnforcer;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.ExpirationUtil;
import org.thoughtcrime.securesms.util.Projection;
import org.thoughtcrime.securesms.util.ProjectionList;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.HashSet;
import java.util.Locale;
import java.util.Optional;
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;
private GlideRequests glideRequests;
private ConversationItem conversationItem;
private ExpiresUpdater expiresUpdater;
MessageHeaderViewHolder(@NonNull View itemView, GlideRequests glideRequests, @NonNull Colorizer colorizer) {
super(itemView);
this.glideRequests = glideRequests;
this.colorizer = colorizer;
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);
}
void bind(@NonNull LifecycleOwner lifecycleOwner, @Nullable ConversationMessage conversationMessage, boolean running) {
MessageRecord messageRecord = conversationMessage.getMessageRecord();
bindMessageView(lifecycleOwner, conversationMessage);
bindErrorState(messageRecord);
bindSentReceivedDates(messageRecord);
bindExpirationTime(messageRecord, running);
bindTransport(messageRecord);
}
void partialBind(ConversationMessage conversationMessage, boolean running) {
bindExpirationTime(conversationMessage.getMessageRecord(), running);
}
private void bindMessageView(@NonNull LifecycleOwner lifecycleOwner, @Nullable ConversationMessage conversationMessage) {
if (conversationItem == null) {
if (conversationMessage.getMessageRecord().isGroupAction()) {
conversationItem = (ConversationItem) updateStub.inflate();
} else if (conversationMessage.getMessageRecord().isOutgoing()) {
conversationItem = (ConversationItem) sentStub.inflate();
} else {
conversationItem = (ConversationItem) receivedStub.inflate();
}
}
conversationItem.bind(lifecycleOwner,
conversationMessage,
Optional.empty(),
Optional.empty(),
glideRequests,
Locale.getDefault(),
new HashSet<>(),
conversationMessage.getMessageRecord().getRecipient(),
null,
false,
false,
false,
true,
colorizer,
false);
}
private void bindErrorState(MessageRecord messageRecord) {
if (messageRecord.hasFailedWithNetworkFailures()) {
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()) {
sentDate.setText(formatBoldString(R.string.message_details_header_sent, "-"));
receivedDate.setVisibility(View.GONE);
} else {
Locale dateLocale = Locale.getDefault();
SimpleDateFormat dateFormatter = DateUtils.getDetailedDateFormatter(itemView.getContext(), dateLocale);
sentDate.setText(formatBoldString(R.string.message_details_header_sent, dateFormatter.format(new Date(messageRecord.getDateSent()))));
sentDate.setOnLongClickListener(v -> {
copyToClipboard(String.valueOf(messageRecord.getDateSent()));
return true;
});
if (messageRecord.getDateReceived() != messageRecord.getDateSent() && !messageRecord.isOutgoing()) {
receivedDate.setText(formatBoldString(R.string.message_details_header_received, dateFormatter.format(new Date(messageRecord.getDateReceived()))));
receivedDate.setOnLongClickListener(v -> {
copyToClipboard(String.valueOf(messageRecord.getDateReceived()));
return true;
});
receivedDate.setVisibility(View.VISIBLE);
} else {
receivedDate.setVisibility(View.GONE);
}
}
}
private void bindExpirationTime(final MessageRecord messageRecord, boolean running) {
if (expiresUpdater != null) {
expiresUpdater.stop();
expiresUpdater = null;
}
if (messageRecord.getExpiresIn() <= 0 || messageRecord.getExpireStarted() <= 0) {
expiresIn.setVisibility(View.GONE);
return;
}
expiresIn.setVisibility(View.VISIBLE);
if (running) {
expiresUpdater = new ExpiresUpdater(messageRecord);
ThreadUtil.runOnMain(expiresUpdater);
}
}
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);
}
transport.setText(formatBoldString(R.string.message_details_header_via, transportText));
}
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;
}
private void copyToClipboard(String text) {
((ClipboardManager) itemView.getContext().getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("text", text));
}
@Override
public void showProjectionArea() {
conversationItem.showProjectionArea();
}
@Override
public void hideProjectionArea() {
conversationItem.hideProjectionArea();
}
@Override
public @Nullable MediaItem getMediaItem() {
return conversationItem.getMediaItem();
}
@Override
public @Nullable GiphyMp4PlaybackPolicyEnforcer getPlaybackPolicyEnforcer() {
return conversationItem.getPlaybackPolicyEnforcer();
}
@Override
public @NonNull Projection getGiphyMp4PlayableProjection(@NonNull ViewGroup recyclerview) {
return conversationItem.getGiphyMp4PlayableProjection(recyclerview);
}
@Override
public boolean canPlayContent() {
return conversationItem.canPlayContent();
}
@Override
public boolean shouldProjectContent() {
return conversationItem.shouldProjectContent();
}
@Override
public @NonNull ProjectionList getColorizerProjections(@NonNull ViewGroup coordinateRoot) {
return conversationItem.getColorizerProjections(coordinateRoot);
}
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);
expiresIn.setText(formatBoldString(R.string.message_details_header_disappears, duration));
if (running && expirationTime > 1) {
ThreadUtil.runOnMainDelayed(this, 500);
}
}
void stop() {
running = false;
}
}
}