kopia lustrzana https://github.com/ryukoposting/Signal-Android
Fix leak in Message Details for disappearing messages.
rodzic
96ea4c0cc2
commit
4215b0391d
|
@ -16,17 +16,12 @@ import org.thoughtcrime.securesms.conversation.colors.Colorizer;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
final class MessageDetailsAdapter extends ListAdapter<MessageDetailsAdapter.MessageDetailsViewState<?>, RecyclerView.ViewHolder> {
|
final class MessageDetailsAdapter extends ListAdapter<MessageDetailsAdapter.MessageDetailsViewState<?>, RecyclerView.ViewHolder> {
|
||||||
|
|
||||||
private static final Object EXPIRATION_TIMER_CHANGE_PAYLOAD = new Object();
|
|
||||||
|
|
||||||
private final LifecycleOwner lifecycleOwner;
|
private final LifecycleOwner lifecycleOwner;
|
||||||
private final GlideRequests glideRequests;
|
private final GlideRequests glideRequests;
|
||||||
private final Colorizer colorizer;
|
private final Colorizer colorizer;
|
||||||
private Callbacks callbacks;
|
private final Callbacks callbacks;
|
||||||
private boolean running;
|
|
||||||
|
|
||||||
MessageDetailsAdapter(@NonNull LifecycleOwner lifecycleOwner, @NonNull GlideRequests glideRequests, @NonNull Colorizer colorizer, @NonNull Callbacks callbacks) {
|
MessageDetailsAdapter(@NonNull LifecycleOwner lifecycleOwner, @NonNull GlideRequests glideRequests, @NonNull Colorizer colorizer, @NonNull Callbacks callbacks) {
|
||||||
super(new MessageDetailsDiffer());
|
super(new MessageDetailsDiffer());
|
||||||
|
@ -34,7 +29,6 @@ final class MessageDetailsAdapter extends ListAdapter<MessageDetailsAdapter.Mess
|
||||||
this.glideRequests = glideRequests;
|
this.glideRequests = glideRequests;
|
||||||
this.colorizer = colorizer;
|
this.colorizer = colorizer;
|
||||||
this.callbacks = callbacks;
|
this.callbacks = callbacks;
|
||||||
this.running = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -54,7 +48,7 @@ final class MessageDetailsAdapter extends ListAdapter<MessageDetailsAdapter.Mess
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||||
if (holder instanceof MessageHeaderViewHolder) {
|
if (holder instanceof MessageHeaderViewHolder) {
|
||||||
((MessageHeaderViewHolder) holder).bind(lifecycleOwner, (ConversationMessage) getItem(position).data, running);
|
((MessageHeaderViewHolder) holder).bind(lifecycleOwner, (ConversationMessage) getItem(position).data);
|
||||||
} else if (holder instanceof RecipientHeaderViewHolder) {
|
} else if (holder instanceof RecipientHeaderViewHolder) {
|
||||||
((RecipientHeaderViewHolder) holder).bind((RecipientHeader) getItem(position).data);
|
((RecipientHeaderViewHolder) holder).bind((RecipientHeader) getItem(position).data);
|
||||||
} else if (holder instanceof RecipientViewHolder) {
|
} else if (holder instanceof RecipientViewHolder) {
|
||||||
|
@ -64,34 +58,11 @@ final class MessageDetailsAdapter extends ListAdapter<MessageDetailsAdapter.Mess
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) {
|
|
||||||
if (payloads.isEmpty()) {
|
|
||||||
super.onBindViewHolder(holder, position, payloads);
|
|
||||||
} else if (holder instanceof MessageHeaderViewHolder) {
|
|
||||||
((MessageHeaderViewHolder) holder).partialBind((ConversationMessage) getItem(position).data, running);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemViewType(int position) {
|
public int getItemViewType(int position) {
|
||||||
return getItem(position).itemType;
|
return getItem(position).itemType;
|
||||||
}
|
}
|
||||||
|
|
||||||
void resumeMessageExpirationTimer() {
|
|
||||||
running = true;
|
|
||||||
if (getItemCount() > 0) {
|
|
||||||
notifyItemChanged(0, EXPIRATION_TIMER_CHANGE_PAYLOAD);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void pauseMessageExpirationTimer() {
|
|
||||||
running = false;
|
|
||||||
if (getItemCount() > 0) {
|
|
||||||
notifyItemChanged(0, EXPIRATION_TIMER_CHANGE_PAYLOAD);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class MessageDetailsDiffer extends DiffUtil.ItemCallback<MessageDetailsViewState<?>> {
|
private static class MessageDetailsDiffer extends DiffUtil.ItemCallback<MessageDetailsViewState<?>> {
|
||||||
@Override
|
@Override
|
||||||
public boolean areItemsTheSame(@NonNull MessageDetailsViewState<?> oldItem, @NonNull MessageDetailsViewState<?> newItem) {
|
public boolean areItemsTheSame(@NonNull MessageDetailsViewState<?> oldItem, @NonNull MessageDetailsViewState<?> newItem) {
|
||||||
|
|
|
@ -76,18 +76,6 @@ public final class MessageDetailsFragment extends FullScreenDialogFragment {
|
||||||
initializeVideoPlayer(view);
|
initializeVideoPlayer(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
adapter.resumeMessageExpirationTimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
adapter.pauseMessageExpirationTimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDismiss(@NonNull DialogInterface dialog) {
|
public void onDismiss(@NonNull DialogInterface dialog) {
|
||||||
super.onDismiss(dialog);
|
super.onDismiss(dialog);
|
||||||
|
@ -104,7 +92,7 @@ public final class MessageDetailsFragment extends FullScreenDialogFragment {
|
||||||
View toolbarShadow = view.findViewById(R.id.toolbar_shadow);
|
View toolbarShadow = view.findViewById(R.id.toolbar_shadow);
|
||||||
|
|
||||||
colorizer = new Colorizer();
|
colorizer = new Colorizer();
|
||||||
adapter = new MessageDetailsAdapter(this, glideRequests, colorizer, this::onErrorClicked);
|
adapter = new MessageDetailsAdapter(getViewLifecycleOwner(), glideRequests, colorizer, this::onErrorClicked);
|
||||||
recyclerViewColorizer = new RecyclerViewColorizer(list);
|
recyclerViewColorizer = new RecyclerViewColorizer(list);
|
||||||
|
|
||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
|
|
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.messagedetails;
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
import android.content.ClipboardManager;
|
import android.content.ClipboardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.os.CountDownTimer;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
|
@ -13,12 +14,12 @@ import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.MediaItem;
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
|
|
||||||
import org.signal.core.util.ThreadUtil;
|
|
||||||
import org.signal.core.util.concurrent.SignalExecutors;
|
import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationItem;
|
import org.thoughtcrime.securesms.conversation.ConversationItem;
|
||||||
|
@ -40,6 +41,7 @@ import java.text.SimpleDateFormat;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
final class MessageHeaderViewHolder extends RecyclerView.ViewHolder implements GiphyMp4Playable, Colorizable {
|
final class MessageHeaderViewHolder extends RecyclerView.ViewHolder implements GiphyMp4Playable, Colorizable {
|
||||||
private final TextView sentDate;
|
private final TextView sentDate;
|
||||||
|
@ -53,10 +55,10 @@ final class MessageHeaderViewHolder extends RecyclerView.ViewHolder implements G
|
||||||
private final ViewStub sentStub;
|
private final ViewStub sentStub;
|
||||||
private final ViewStub receivedStub;
|
private final ViewStub receivedStub;
|
||||||
private final Colorizer colorizer;
|
private final Colorizer colorizer;
|
||||||
|
private final GlideRequests glideRequests;
|
||||||
|
|
||||||
private GlideRequests glideRequests;
|
|
||||||
private ConversationItem conversationItem;
|
private ConversationItem conversationItem;
|
||||||
private ExpiresUpdater expiresUpdater;
|
private CountDownTimer expiresUpdater;
|
||||||
|
|
||||||
MessageHeaderViewHolder(@NonNull View itemView, GlideRequests glideRequests, @NonNull Colorizer colorizer) {
|
MessageHeaderViewHolder(@NonNull View itemView, GlideRequests glideRequests, @NonNull Colorizer colorizer) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
|
@ -75,20 +77,16 @@ final class MessageHeaderViewHolder extends RecyclerView.ViewHolder implements G
|
||||||
receivedStub = itemView.findViewById(R.id.message_details_header_message_view_received_multimedia);
|
receivedStub = itemView.findViewById(R.id.message_details_header_message_view_received_multimedia);
|
||||||
}
|
}
|
||||||
|
|
||||||
void bind(@NonNull LifecycleOwner lifecycleOwner, @Nullable ConversationMessage conversationMessage, boolean running) {
|
void bind(@NonNull LifecycleOwner lifecycleOwner, @NonNull ConversationMessage conversationMessage) {
|
||||||
MessageRecord messageRecord = conversationMessage.getMessageRecord();
|
MessageRecord messageRecord = conversationMessage.getMessageRecord();
|
||||||
bindMessageView(lifecycleOwner, conversationMessage);
|
bindMessageView(lifecycleOwner, conversationMessage);
|
||||||
bindErrorState(messageRecord);
|
bindErrorState(messageRecord);
|
||||||
bindSentReceivedDates(messageRecord);
|
bindSentReceivedDates(messageRecord);
|
||||||
bindExpirationTime(messageRecord, running);
|
bindExpirationTime(lifecycleOwner, messageRecord);
|
||||||
bindTransport(messageRecord);
|
bindTransport(messageRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
void partialBind(ConversationMessage conversationMessage, boolean running) {
|
private void bindMessageView(@NonNull LifecycleOwner lifecycleOwner, @NonNull ConversationMessage conversationMessage) {
|
||||||
bindExpirationTime(conversationMessage.getMessageRecord(), running);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void bindMessageView(@NonNull LifecycleOwner lifecycleOwner, @Nullable ConversationMessage conversationMessage) {
|
|
||||||
if (conversationItem == null) {
|
if (conversationItem == null) {
|
||||||
if (conversationMessage.getMessageRecord().isGroupAction()) {
|
if (conversationMessage.getMessageRecord().isGroupAction()) {
|
||||||
conversationItem = (ConversationItem) updateStub.inflate();
|
conversationItem = (ConversationItem) updateStub.inflate();
|
||||||
|
@ -166,9 +164,9 @@ final class MessageHeaderViewHolder extends RecyclerView.ViewHolder implements G
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bindExpirationTime(final MessageRecord messageRecord, boolean running) {
|
private void bindExpirationTime(@NonNull LifecycleOwner lifecycleOwner, @NonNull MessageRecord messageRecord) {
|
||||||
if (expiresUpdater != null) {
|
if (expiresUpdater != null) {
|
||||||
expiresUpdater.stop();
|
expiresUpdater.cancel();
|
||||||
expiresUpdater = null;
|
expiresUpdater = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,10 +176,36 @@ final class MessageHeaderViewHolder extends RecyclerView.ViewHolder implements G
|
||||||
}
|
}
|
||||||
|
|
||||||
expiresIn.setVisibility(View.VISIBLE);
|
expiresIn.setVisibility(View.VISIBLE);
|
||||||
if (running) {
|
|
||||||
expiresUpdater = new ExpiresUpdater(messageRecord);
|
lifecycleOwner.getLifecycle().addObserver(new DefaultLifecycleObserver() {
|
||||||
ThreadUtil.runOnMain(expiresUpdater);
|
@Override
|
||||||
|
public void onResume(@NonNull LifecycleOwner owner) {
|
||||||
|
if (expiresUpdater != null) {
|
||||||
|
expiresUpdater.cancel();
|
||||||
}
|
}
|
||||||
|
expiresUpdater = new CountDownTimer(messageRecord.getExpiresIn(), TimeUnit.SECONDS.toMillis(1)) {
|
||||||
|
@Override
|
||||||
|
public void onTick(long millisUntilFinished) {
|
||||||
|
int expirationTime = Math.max((int) (TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished)), 1);
|
||||||
|
String duration = ExpirationUtil.getExpirationDisplayValue(itemView.getContext(), expirationTime);
|
||||||
|
|
||||||
|
expiresIn.setText(formatBoldString(R.string.message_details_header_disappears, duration));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFinish() {}
|
||||||
|
};
|
||||||
|
expiresUpdater.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause(@NonNull LifecycleOwner owner) {
|
||||||
|
if (expiresUpdater != null) {
|
||||||
|
expiresUpdater.cancel();
|
||||||
|
expiresUpdater = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bindTransport(MessageRecord messageRecord) {
|
private void bindTransport(MessageRecord messageRecord) {
|
||||||
|
@ -255,35 +279,4 @@ final class MessageHeaderViewHolder extends RecyclerView.ViewHolder implements G
|
||||||
public @NonNull ProjectionList getColorizerProjections(@NonNull ViewGroup coordinateRoot) {
|
public @NonNull ProjectionList getColorizerProjections(@NonNull ViewGroup coordinateRoot) {
|
||||||
return conversationItem.getColorizerProjections(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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Ładowanie…
Reference in New Issue