Display a loading message if group update message is taking a while to load.
parent
5ae96905bb
commit
34ef8b52f6
|
@ -4,6 +4,7 @@ import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.contactshare.Contact;
|
import org.thoughtcrime.securesms.contactshare.Contact;
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationMessage;
|
import org.thoughtcrime.securesms.conversation.ConversationMessage;
|
||||||
|
@ -22,7 +23,8 @@ import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public interface BindableConversationItem extends Unbindable {
|
public interface BindableConversationItem extends Unbindable {
|
||||||
void bind(@NonNull ConversationMessage messageRecord,
|
void bind(@NonNull LifecycleOwner lifecycleOwner,
|
||||||
|
@NonNull ConversationMessage messageRecord,
|
||||||
@NonNull Optional<MessageRecord> previousMessageRecord,
|
@NonNull Optional<MessageRecord> previousMessageRecord,
|
||||||
@NonNull Optional<MessageRecord> nextMessageRecord,
|
@NonNull Optional<MessageRecord> nextMessageRecord,
|
||||||
@NonNull GlideRequests glideRequests,
|
@NonNull GlideRequests glideRequests,
|
||||||
|
|
|
@ -27,6 +27,7 @@ import androidx.annotation.LayoutRes;
|
||||||
import androidx.annotation.MainThread;
|
import androidx.annotation.MainThread;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
import androidx.paging.PagedList;
|
import androidx.paging.PagedList;
|
||||||
import androidx.paging.PagedListAdapter;
|
import androidx.paging.PagedListAdapter;
|
||||||
import androidx.recyclerview.widget.DiffUtil;
|
import androidx.recyclerview.widget.DiffUtil;
|
||||||
|
@ -84,6 +85,7 @@ public class ConversationAdapter
|
||||||
private static final long FOOTER_ID = Long.MIN_VALUE + 1;
|
private static final long FOOTER_ID = Long.MIN_VALUE + 1;
|
||||||
|
|
||||||
private final ItemClickListener clickListener;
|
private final ItemClickListener clickListener;
|
||||||
|
private final LifecycleOwner lifecycleOwner;
|
||||||
private final GlideRequests glideRequests;
|
private final GlideRequests glideRequests;
|
||||||
private final Locale locale;
|
private final Locale locale;
|
||||||
private final Recipient recipient;
|
private final Recipient recipient;
|
||||||
|
@ -99,12 +101,14 @@ public class ConversationAdapter
|
||||||
private View headerView;
|
private View headerView;
|
||||||
private View footerView;
|
private View footerView;
|
||||||
|
|
||||||
ConversationAdapter(@NonNull GlideRequests glideRequests,
|
ConversationAdapter(@NonNull LifecycleOwner lifecycleOwner,
|
||||||
|
@NonNull GlideRequests glideRequests,
|
||||||
@NonNull Locale locale,
|
@NonNull Locale locale,
|
||||||
@Nullable ItemClickListener clickListener,
|
@Nullable ItemClickListener clickListener,
|
||||||
@NonNull Recipient recipient)
|
@NonNull Recipient recipient)
|
||||||
{
|
{
|
||||||
super(new DiffCallback());
|
super(new DiffCallback());
|
||||||
|
this.lifecycleOwner = lifecycleOwner;
|
||||||
|
|
||||||
this.glideRequests = glideRequests;
|
this.glideRequests = glideRequests;
|
||||||
this.locale = locale;
|
this.locale = locale;
|
||||||
|
@ -216,7 +220,8 @@ public class ConversationAdapter
|
||||||
ConversationMessage previousMessage = adapterPosition < getItemCount() - 1 && !isFooterPosition(adapterPosition + 1) ? getItem(adapterPosition + 1) : null;
|
ConversationMessage previousMessage = adapterPosition < getItemCount() - 1 && !isFooterPosition(adapterPosition + 1) ? getItem(adapterPosition + 1) : null;
|
||||||
ConversationMessage nextMessage = adapterPosition > 0 && !isHeaderPosition(adapterPosition - 1) ? getItem(adapterPosition - 1) : null;
|
ConversationMessage nextMessage = adapterPosition > 0 && !isHeaderPosition(adapterPosition - 1) ? getItem(adapterPosition - 1) : null;
|
||||||
|
|
||||||
conversationViewHolder.getBindable().bind(conversationMessage,
|
conversationViewHolder.getBindable().bind(lifecycleOwner,
|
||||||
|
conversationMessage,
|
||||||
Optional.fromNullable(previousMessage != null ? previousMessage.getMessageRecord() : null),
|
Optional.fromNullable(previousMessage != null ? previousMessage.getMessageRecord() : null),
|
||||||
Optional.fromNullable(nextMessage != null ? nextMessage.getMessageRecord() : null),
|
Optional.fromNullable(nextMessage != null ? nextMessage.getMessageRecord() : null),
|
||||||
glideRequests,
|
glideRequests,
|
||||||
|
|
|
@ -479,7 +479,7 @@ public class ConversationFragment extends LoggingFragment {
|
||||||
private void initializeListAdapter() {
|
private void initializeListAdapter() {
|
||||||
if (this.recipient != null && this.threadId != -1) {
|
if (this.recipient != null && this.threadId != -1) {
|
||||||
Log.d(TAG, "Initializing adapter for " + recipient.getId());
|
Log.d(TAG, "Initializing adapter for " + recipient.getId());
|
||||||
ConversationAdapter adapter = new ConversationAdapter(GlideApp.with(this), locale, selectionClickListener, this.recipient.get());
|
ConversationAdapter adapter = new ConversationAdapter(this, GlideApp.with(this), locale, selectionClickListener, this.recipient.get());
|
||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
setStickyHeaderDecoration(adapter);
|
setStickyHeaderDecoration(adapter);
|
||||||
ConversationAdapter.initializePool(list.getRecycledViewPool());
|
ConversationAdapter.initializePool(list.getRecycledViewPool());
|
||||||
|
|
|
@ -54,6 +54,7 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
|
@ -79,8 +80,6 @@ import org.thoughtcrime.securesms.contactshare.Contact;
|
||||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.MessageDatabase;
|
import org.thoughtcrime.securesms.database.MessageDatabase;
|
||||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
|
||||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
|
||||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
|
@ -116,9 +115,9 @@ import org.thoughtcrime.securesms.util.LongClickMovementMethod;
|
||||||
import org.thoughtcrime.securesms.util.SearchUtil;
|
import org.thoughtcrime.securesms.util.SearchUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.UrlClickHandler;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.thoughtcrime.securesms.util.VibrateUtil;
|
import org.thoughtcrime.securesms.util.VibrateUtil;
|
||||||
import org.thoughtcrime.securesms.util.UrlClickHandler;
|
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
import org.thoughtcrime.securesms.util.views.Stub;
|
import org.thoughtcrime.securesms.util.views.Stub;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
@ -250,7 +249,8 @@ public class ConversationItem extends LinearLayout implements BindableConversati
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bind(@NonNull ConversationMessage conversationMessage,
|
public void bind(@NonNull LifecycleOwner lifecycleOwner,
|
||||||
|
@NonNull ConversationMessage conversationMessage,
|
||||||
@NonNull Optional<MessageRecord> previousMessageRecord,
|
@NonNull Optional<MessageRecord> previousMessageRecord,
|
||||||
@NonNull Optional<MessageRecord> nextMessageRecord,
|
@NonNull Optional<MessageRecord> nextMessageRecord,
|
||||||
@NonNull GlideRequests glideRequests,
|
@NonNull GlideRequests glideRequests,
|
||||||
|
|
|
@ -14,6 +14,7 @@ import android.widget.TextView;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.Observer;
|
import androidx.lifecycle.Observer;
|
||||||
import androidx.lifecycle.Transformations;
|
import androidx.lifecycle.Transformations;
|
||||||
|
@ -29,13 +30,12 @@ import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
|
|
||||||
import org.thoughtcrime.securesms.util.DateUtils;
|
import org.thoughtcrime.securesms.util.DateUtils;
|
||||||
import org.thoughtcrime.securesms.util.Debouncer;
|
|
||||||
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
||||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||||
|
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -44,9 +44,7 @@ import java.util.Set;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
public final class ConversationUpdateItem extends LinearLayout
|
public final class ConversationUpdateItem extends LinearLayout
|
||||||
implements RecipientForeverObserver,
|
implements BindableConversationItem
|
||||||
BindableConversationItem,
|
|
||||||
Observer<SpannableString>
|
|
||||||
{
|
{
|
||||||
private static final String TAG = ConversationUpdateItem.class.getSimpleName();
|
private static final String TAG = ConversationUpdateItem.class.getSimpleName();
|
||||||
|
|
||||||
|
@ -62,7 +60,8 @@ public final class ConversationUpdateItem extends LinearLayout
|
||||||
private Locale locale;
|
private Locale locale;
|
||||||
private LiveData<SpannableString> displayBody;
|
private LiveData<SpannableString> displayBody;
|
||||||
|
|
||||||
private final Debouncer bodyClearDebouncer = new Debouncer(150);
|
private final UpdateObserver updateObserver = new UpdateObserver();
|
||||||
|
private final SenderObserver senderObserver = new SenderObserver();
|
||||||
|
|
||||||
public ConversationUpdateItem(Context context) {
|
public ConversationUpdateItem(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
|
@ -85,7 +84,8 @@ public final class ConversationUpdateItem extends LinearLayout
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bind(@NonNull ConversationMessage conversationMessage,
|
public void bind(@NonNull LifecycleOwner lifecycleOwner,
|
||||||
|
@NonNull ConversationMessage conversationMessage,
|
||||||
@NonNull Optional<MessageRecord> previousMessageRecord,
|
@NonNull Optional<MessageRecord> previousMessageRecord,
|
||||||
@NonNull Optional<MessageRecord> nextMessageRecord,
|
@NonNull Optional<MessageRecord> nextMessageRecord,
|
||||||
@NonNull GlideRequests glideRequests,
|
@NonNull GlideRequests glideRequests,
|
||||||
|
@ -97,13 +97,7 @@ public final class ConversationUpdateItem extends LinearLayout
|
||||||
{
|
{
|
||||||
this.batchSelected = batchSelected;
|
this.batchSelected = batchSelected;
|
||||||
|
|
||||||
bind(conversationMessage, locale);
|
bind(lifecycleOwner, conversationMessage, locale);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDetachedFromWindow() {
|
|
||||||
unbind();
|
|
||||||
super.onDetachedFromWindow();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -116,49 +110,66 @@ public final class ConversationUpdateItem extends LinearLayout
|
||||||
return conversationMessage;
|
return conversationMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bind(@NonNull ConversationMessage conversationMessage, @NonNull Locale locale) {
|
private void bind(@NonNull LifecycleOwner lifecycleOwner, @NonNull ConversationMessage conversationMessage, @NonNull Locale locale) {
|
||||||
if (this.sender != null) {
|
|
||||||
this.sender.removeForeverObserver(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
observeDisplayBody(null);
|
|
||||||
setBodyText(null);
|
|
||||||
|
|
||||||
this.conversationMessage = conversationMessage;
|
this.conversationMessage = conversationMessage;
|
||||||
this.messageRecord = conversationMessage.getMessageRecord();
|
this.messageRecord = conversationMessage.getMessageRecord();
|
||||||
this.sender = messageRecord.getIndividualRecipient().live();
|
|
||||||
this.locale = locale;
|
this.locale = locale;
|
||||||
|
|
||||||
this.sender.observeForever(this);
|
observeSender(lifecycleOwner, messageRecord.getIndividualRecipient());
|
||||||
|
|
||||||
UpdateDescription updateDescription = Objects.requireNonNull(messageRecord.getUpdateDisplayBody(getContext()));
|
UpdateDescription updateDescription = Objects.requireNonNull(messageRecord.getUpdateDisplayBody(getContext()));
|
||||||
LiveData<String> liveUpdateMessage = LiveUpdateMessage.fromMessageDescription(updateDescription);
|
LiveData<String> liveUpdateMessage = LiveUpdateMessage.fromMessageDescription(updateDescription);
|
||||||
LiveData<SpannableString> spannableStringMessage = Transformations.map(liveUpdateMessage, SpannableString::new);
|
LiveData<SpannableString> spannableStringMessage = toSpannable(loading(liveUpdateMessage));
|
||||||
|
|
||||||
present(conversationMessage);
|
present(conversationMessage);
|
||||||
|
|
||||||
observeDisplayBody(spannableStringMessage);
|
observeDisplayBody(lifecycleOwner, spannableStringMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void observeDisplayBody(@Nullable LiveData<SpannableString> displayBody) {
|
/** After a short delay, if the main data hasn't shown yet, then a loading message is displayed. */
|
||||||
|
private @NonNull LiveData<String> loading(@NonNull LiveData<String> string) {
|
||||||
|
return LiveDataUtil.until(string, LiveDataUtil.delay(250, getContext().getString(R.string.ConversationUpdateItem_loading)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LiveData<SpannableString> toSpannable(LiveData<String> loading) {
|
||||||
|
return Transformations.map(loading, source -> source == null ? null : new SpannableString(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unbind() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private void observeSender(@NonNull LifecycleOwner lifecycleOwner, @Nullable Recipient recipient) {
|
||||||
|
if (sender != null) {
|
||||||
|
sender.getLiveData().removeObserver(senderObserver);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recipient != null) {
|
||||||
|
sender = recipient.live();
|
||||||
|
sender.getLiveData().observe(lifecycleOwner, senderObserver);
|
||||||
|
} else {
|
||||||
|
sender = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void observeDisplayBody(@NonNull LifecycleOwner lifecycleOwner, @Nullable LiveData<SpannableString> displayBody) {
|
||||||
if (this.displayBody != displayBody) {
|
if (this.displayBody != displayBody) {
|
||||||
if (this.displayBody != null) {
|
if (this.displayBody != null) {
|
||||||
this.displayBody.removeObserver(this);
|
this.displayBody.removeObserver(updateObserver);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.displayBody = displayBody;
|
this.displayBody = displayBody;
|
||||||
|
|
||||||
if (this.displayBody != null) {
|
if (this.displayBody != null) {
|
||||||
this.displayBody.observeForever(this);
|
this.displayBody.observe(lifecycleOwner, updateObserver);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setBodyText(@Nullable CharSequence text) {
|
private void setBodyText(@Nullable CharSequence text) {
|
||||||
if (text == null) {
|
if (text == null) {
|
||||||
bodyClearDebouncer.publish(() -> body.setText(null));
|
body.setVisibility(INVISIBLE);
|
||||||
} else {
|
} else {
|
||||||
bodyClearDebouncer.clear();
|
|
||||||
body.setText(text);
|
body.setText(text);
|
||||||
body.setVisibility(VISIBLE);
|
body.setVisibility(VISIBLE);
|
||||||
}
|
}
|
||||||
|
@ -257,28 +268,25 @@ public final class ConversationUpdateItem extends LinearLayout
|
||||||
icon.setColorFilter(getIconTintFilter());
|
icon.setColorFilter(getIconTintFilter());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRecipientChanged(@NonNull Recipient recipient) {
|
|
||||||
present(conversationMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setOnClickListener(View.OnClickListener l) {
|
public void setOnClickListener(View.OnClickListener l) {
|
||||||
super.setOnClickListener(new InternalClickListener(l));
|
super.setOnClickListener(new InternalClickListener(l));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private final class SenderObserver implements Observer<Recipient> {
|
||||||
public void unbind() {
|
|
||||||
if (sender != null) {
|
|
||||||
sender.removeForeverObserver(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
observeDisplayBody(null);
|
@Override
|
||||||
|
public void onChanged(Recipient recipient) {
|
||||||
|
present(conversationMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private final class UpdateObserver implements Observer<SpannableString> {
|
||||||
public void onChanged(SpannableString update) {
|
|
||||||
setBodyText(update);
|
@Override
|
||||||
|
public void onChanged(SpannableString update) {
|
||||||
|
setBodyText(update);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class InternalClickListener implements View.OnClickListener {
|
private class InternalClickListener implements View.OnClickListener {
|
||||||
|
|
|
@ -91,7 +91,7 @@ public final class MessageDetailsActivity extends PassphraseRequiredActivity {
|
||||||
|
|
||||||
private void initializeList() {
|
private void initializeList() {
|
||||||
RecyclerView list = findViewById(R.id.message_details_list);
|
RecyclerView list = findViewById(R.id.message_details_list);
|
||||||
adapter = new MessageDetailsAdapter(glideRequests);
|
adapter = new MessageDetailsAdapter(this, glideRequests);
|
||||||
|
|
||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
list.setItemAnimator(null);
|
list.setItemAnimator(null);
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.view.LayoutInflater;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
import androidx.recyclerview.widget.DiffUtil;
|
import androidx.recyclerview.widget.DiffUtil;
|
||||||
import androidx.recyclerview.widget.ListAdapter;
|
import androidx.recyclerview.widget.ListAdapter;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
@ -20,13 +21,15 @@ final class MessageDetailsAdapter extends ListAdapter<MessageDetailsAdapter.Mess
|
||||||
|
|
||||||
private static final Object EXPIRATION_TIMER_CHANGE_PAYLOAD = new Object();
|
private static final Object EXPIRATION_TIMER_CHANGE_PAYLOAD = new Object();
|
||||||
|
|
||||||
|
private final LifecycleOwner lifecycleOwner;
|
||||||
private final GlideRequests glideRequests;
|
private final GlideRequests glideRequests;
|
||||||
private boolean running;
|
private boolean running;
|
||||||
|
|
||||||
MessageDetailsAdapter(GlideRequests glideRequests) {
|
MessageDetailsAdapter(@NonNull LifecycleOwner lifecycleOwner, @NonNull GlideRequests glideRequests) {
|
||||||
super(new MessageDetailsDiffer());
|
super(new MessageDetailsDiffer());
|
||||||
this.glideRequests = glideRequests;
|
this.lifecycleOwner = lifecycleOwner;
|
||||||
running = true;
|
this.glideRequests = glideRequests;
|
||||||
|
this.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -46,7 +49,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((ConversationMessage) getItem(position).data, running);
|
((MessageHeaderViewHolder) holder).bind(lifecycleOwner, (ConversationMessage) getItem(position).data, running);
|
||||||
} 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) {
|
||||||
|
|
|
@ -8,6 +8,8 @@ import android.view.ViewStub;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
|
@ -64,9 +66,9 @@ final class MessageHeaderViewHolder extends RecyclerView.ViewHolder {
|
||||||
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(ConversationMessage conversationMessage, boolean running) {
|
void bind(@NonNull LifecycleOwner lifecycleOwner, @Nullable ConversationMessage conversationMessage, boolean running) {
|
||||||
MessageRecord messageRecord = conversationMessage.getMessageRecord();
|
MessageRecord messageRecord = conversationMessage.getMessageRecord();
|
||||||
bindMessageView(conversationMessage);
|
bindMessageView(lifecycleOwner, conversationMessage);
|
||||||
bindErrorState(messageRecord);
|
bindErrorState(messageRecord);
|
||||||
bindSentReceivedDates(messageRecord);
|
bindSentReceivedDates(messageRecord);
|
||||||
bindExpirationTime(messageRecord, running);
|
bindExpirationTime(messageRecord, running);
|
||||||
|
@ -77,7 +79,7 @@ final class MessageHeaderViewHolder extends RecyclerView.ViewHolder {
|
||||||
bindExpirationTime(conversationMessage.getMessageRecord(), running);
|
bindExpirationTime(conversationMessage.getMessageRecord(), running);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bindMessageView(ConversationMessage conversationMessage) {
|
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();
|
||||||
|
@ -87,7 +89,7 @@ final class MessageHeaderViewHolder extends RecyclerView.ViewHolder {
|
||||||
conversationItem = (ConversationItem) receivedStub.inflate();
|
conversationItem = (ConversationItem) receivedStub.inflate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conversationItem.bind(conversationMessage, Optional.absent(), Optional.absent(), glideRequests, Locale.getDefault(), new HashSet<>(), conversationMessage.getMessageRecord().getRecipient(), null, false);
|
conversationItem.bind(lifecycleOwner, conversationMessage, Optional.absent(), Optional.absent(), glideRequests, Locale.getDefault(), new HashSet<>(), conversationMessage.getMessageRecord().getRecipient(), null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bindErrorState(MessageRecord messageRecord) {
|
private void bindErrorState(MessageRecord messageRecord) {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package org.thoughtcrime.securesms.util.livedata;
|
package org.thoughtcrime.securesms.util.livedata;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MediatorLiveData;
|
import androidx.lifecycle.MediatorLiveData;
|
||||||
|
@ -11,7 +13,6 @@ import org.thoughtcrime.securesms.util.concurrent.SerialMonoLifoExecutor;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
import org.whispersystems.libsignal.util.guava.Function;
|
import org.whispersystems.libsignal.util.guava.Function;
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -86,7 +87,7 @@ public final class LiveDataUtil {
|
||||||
* Merges the supplied live data streams.
|
* Merges the supplied live data streams.
|
||||||
*/
|
*/
|
||||||
public static <T> LiveData<T> merge(@NonNull List<LiveData<T>> liveDataList) {
|
public static <T> LiveData<T> merge(@NonNull List<LiveData<T>> liveDataList) {
|
||||||
Set<LiveData<T>> set = new LinkedHashSet<>(liveDataList);
|
Set<LiveData<T>> set = new LinkedHashSet<>(liveDataList.size());
|
||||||
|
|
||||||
set.addAll(liveDataList);
|
set.addAll(liveDataList);
|
||||||
|
|
||||||
|
@ -110,6 +111,40 @@ public final class LiveDataUtil {
|
||||||
return new MutableLiveData<>(item);
|
return new MutableLiveData<>(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits {@param whileWaiting} until {@param main} starts emitting.
|
||||||
|
*/
|
||||||
|
public static @NonNull <T> LiveData<T> until(@NonNull LiveData<T> main,
|
||||||
|
@NonNull LiveData<T> whileWaiting)
|
||||||
|
{
|
||||||
|
MediatorLiveData<T> mediatorLiveData = new MediatorLiveData<>();
|
||||||
|
|
||||||
|
mediatorLiveData.addSource(whileWaiting, mediatorLiveData::setValue);
|
||||||
|
|
||||||
|
mediatorLiveData.addSource(main, value -> {
|
||||||
|
mediatorLiveData.removeSource(whileWaiting);
|
||||||
|
mediatorLiveData.setValue(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return mediatorLiveData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After {@param delay} ms after observation, emits a single Object, {@param value}.
|
||||||
|
*/
|
||||||
|
public static <T> LiveData<T> delay(long delay, T value) {
|
||||||
|
return new MutableLiveData<T>() {
|
||||||
|
boolean emittedValue;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActive() {
|
||||||
|
if (emittedValue) return;
|
||||||
|
new Handler().postDelayed(() -> setValue(value), delay);
|
||||||
|
emittedValue = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public interface Combine<A, B, R> {
|
public interface Combine<A, B, R> {
|
||||||
@NonNull R apply(@NonNull A a, @NonNull B b);
|
@NonNull R apply(@NonNull A a, @NonNull B b);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1701,6 +1701,9 @@
|
||||||
<!-- conversation_item_received -->
|
<!-- conversation_item_received -->
|
||||||
<string name="conversation_item_received__contact_photo_description">Contact photo</string>
|
<string name="conversation_item_received__contact_photo_description">Contact photo</string>
|
||||||
|
|
||||||
|
<!-- ConversationUpdateItem -->
|
||||||
|
<string name="ConversationUpdateItem_loading">Loading</string>
|
||||||
|
|
||||||
<!-- audio_view -->
|
<!-- audio_view -->
|
||||||
<string name="audio_view__play_pause_accessibility_description">Play … Pause</string>
|
<string name="audio_view__play_pause_accessibility_description">Play … Pause</string>
|
||||||
<string name="audio_view__download_accessibility_description">Download</string>
|
<string name="audio_view__download_accessibility_description">Download</string>
|
||||||
|
|
Loading…
Reference in New Issue