Skip to content

Commit

Permalink
Display a loading message if group update message is taking a while t…
Browse files Browse the repository at this point in the history
…o load.
  • Loading branch information
alan-signal authored and greyson-signal committed Sep 23, 2020
1 parent 5ae9690 commit 34ef8b5
Show file tree
Hide file tree
Showing 10 changed files with 122 additions and 64 deletions.
Expand Up @@ -4,6 +4,7 @@

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LifecycleOwner;

import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.conversation.ConversationMessage;
Expand All @@ -22,7 +23,8 @@
import java.util.Set;

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> nextMessageRecord,
@NonNull GlideRequests glideRequests,
Expand Down
Expand Up @@ -27,6 +27,7 @@
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LifecycleOwner;
import androidx.paging.PagedList;
import androidx.paging.PagedListAdapter;
import androidx.recyclerview.widget.DiffUtil;
Expand Down Expand Up @@ -84,6 +85,7 @@ public class ConversationAdapter
private static final long FOOTER_ID = Long.MIN_VALUE + 1;

private final ItemClickListener clickListener;
private final LifecycleOwner lifecycleOwner;
private final GlideRequests glideRequests;
private final Locale locale;
private final Recipient recipient;
Expand All @@ -99,12 +101,14 @@ public class ConversationAdapter
private View headerView;
private View footerView;

ConversationAdapter(@NonNull GlideRequests glideRequests,
ConversationAdapter(@NonNull LifecycleOwner lifecycleOwner,
@NonNull GlideRequests glideRequests,
@NonNull Locale locale,
@Nullable ItemClickListener clickListener,
@NonNull Recipient recipient)
{
super(new DiffCallback());
this.lifecycleOwner = lifecycleOwner;

this.glideRequests = glideRequests;
this.locale = locale;
Expand Down Expand Up @@ -216,7 +220,8 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi
ConversationMessage previousMessage = adapterPosition < getItemCount() - 1 && !isFooterPosition(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(nextMessage != null ? nextMessage.getMessageRecord() : null),
glideRequests,
Expand Down
Expand Up @@ -479,7 +479,7 @@ private void initializeResources() {
private void initializeListAdapter() {
if (this.recipient != null && this.threadId != -1) {
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);
setStickyHeaderDecoration(adapter);
ConversationAdapter.initializePool(list.getRecycledViewPool());
Expand Down
Expand Up @@ -54,6 +54,7 @@
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;

import com.annimon.stream.Stream;

Expand All @@ -79,8 +80,6 @@
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory;
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.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
Expand Down Expand Up @@ -116,9 +115,9 @@
import org.thoughtcrime.securesms.util.SearchUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.UrlClickHandler;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.VibrateUtil;
import org.thoughtcrime.securesms.util.UrlClickHandler;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.views.Stub;
import org.whispersystems.libsignal.util.guava.Optional;
Expand Down Expand Up @@ -250,7 +249,8 @@ protected void onFinishInflate() {
}

@Override
public void bind(@NonNull ConversationMessage conversationMessage,
public void bind(@NonNull LifecycleOwner lifecycleOwner,
@NonNull ConversationMessage conversationMessage,
@NonNull Optional<MessageRecord> previousMessageRecord,
@NonNull Optional<MessageRecord> nextMessageRecord,
@NonNull GlideRequests glideRequests,
Expand Down
Expand Up @@ -14,6 +14,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.Transformations;
Expand All @@ -29,13 +30,12 @@
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.Debouncer;
import org.thoughtcrime.securesms.util.ExpirationUtil;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import org.whispersystems.libsignal.util.guava.Optional;

import java.util.Locale;
Expand All @@ -44,9 +44,7 @@
import java.util.concurrent.ExecutionException;

public final class ConversationUpdateItem extends LinearLayout
implements RecipientForeverObserver,
BindableConversationItem,
Observer<SpannableString>
implements BindableConversationItem
{
private static final String TAG = ConversationUpdateItem.class.getSimpleName();

Expand All @@ -62,7 +60,8 @@ public final class ConversationUpdateItem extends LinearLayout
private Locale locale;
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) {
super(context);
Expand All @@ -85,7 +84,8 @@ public void onFinishInflate() {
}

@Override
public void bind(@NonNull ConversationMessage conversationMessage,
public void bind(@NonNull LifecycleOwner lifecycleOwner,
@NonNull ConversationMessage conversationMessage,
@NonNull Optional<MessageRecord> previousMessageRecord,
@NonNull Optional<MessageRecord> nextMessageRecord,
@NonNull GlideRequests glideRequests,
Expand All @@ -97,13 +97,7 @@ public void bind(@NonNull ConversationMessage conversationMessage,
{
this.batchSelected = batchSelected;

bind(conversationMessage, locale);
}

@Override
protected void onDetachedFromWindow() {
unbind();
super.onDetachedFromWindow();
bind(lifecycleOwner, conversationMessage, locale);
}

@Override
Expand All @@ -116,49 +110,66 @@ public ConversationMessage getConversationMessage() {
return conversationMessage;
}

private void bind(@NonNull ConversationMessage conversationMessage, @NonNull Locale locale) {
if (this.sender != null) {
this.sender.removeForeverObserver(this);
}

observeDisplayBody(null);
setBodyText(null);

private void bind(@NonNull LifecycleOwner lifecycleOwner, @NonNull ConversationMessage conversationMessage, @NonNull Locale locale) {
this.conversationMessage = conversationMessage;
this.messageRecord = conversationMessage.getMessageRecord();
this.sender = messageRecord.getIndividualRecipient().live();
this.locale = locale;

this.sender.observeForever(this);
observeSender(lifecycleOwner, messageRecord.getIndividualRecipient());

UpdateDescription updateDescription = Objects.requireNonNull(messageRecord.getUpdateDisplayBody(getContext()));
LiveData<String> liveUpdateMessage = LiveUpdateMessage.fromMessageDescription(updateDescription);
LiveData<SpannableString> spannableStringMessage = Transformations.map(liveUpdateMessage, SpannableString::new);
LiveData<SpannableString> spannableStringMessage = toSpannable(loading(liveUpdateMessage));

present(conversationMessage);

observeDisplayBody(spannableStringMessage);
observeDisplayBody(lifecycleOwner, spannableStringMessage);
}

/** 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 observeDisplayBody(@Nullable LiveData<SpannableString> displayBody) {
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 != null) {
this.displayBody.removeObserver(this);
this.displayBody.removeObserver(updateObserver);
}

this.displayBody = displayBody;

if (this.displayBody != null) {
this.displayBody.observeForever(this);
this.displayBody.observe(lifecycleOwner, updateObserver);
}
}
}

private void setBodyText(@Nullable CharSequence text) {
if (text == null) {
bodyClearDebouncer.publish(() -> body.setText(null));
body.setVisibility(INVISIBLE);
} else {
bodyClearDebouncer.clear();
body.setText(text);
body.setVisibility(VISIBLE);
}
Expand Down Expand Up @@ -257,28 +268,25 @@ private void setEndSessionRecord() {
icon.setColorFilter(getIconTintFilter());
}

@Override
public void onRecipientChanged(@NonNull Recipient recipient) {
present(conversationMessage);
}

@Override
public void setOnClickListener(View.OnClickListener l) {
super.setOnClickListener(new InternalClickListener(l));
}

@Override
public void unbind() {
if (sender != null) {
sender.removeForeverObserver(this);
}
private final class SenderObserver implements Observer<Recipient> {

observeDisplayBody(null);
@Override
public void onChanged(Recipient recipient) {
present(conversationMessage);
}
}

@Override
public void onChanged(SpannableString update) {
setBodyText(update);
private final class UpdateObserver implements Observer<SpannableString> {

@Override
public void onChanged(SpannableString update) {
setBodyText(update);
}
}

private class InternalClickListener implements View.OnClickListener {
Expand Down
Expand Up @@ -91,7 +91,7 @@ public boolean onOptionsItemSelected(MenuItem item) {

private void initializeList() {
RecyclerView list = findViewById(R.id.message_details_list);
adapter = new MessageDetailsAdapter(glideRequests);
adapter = new MessageDetailsAdapter(this, glideRequests);

list.setAdapter(adapter);
list.setItemAnimator(null);
Expand Down
Expand Up @@ -5,6 +5,7 @@
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.lifecycle.LifecycleOwner;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
Expand All @@ -20,13 +21,15 @@ final class MessageDetailsAdapter extends ListAdapter<MessageDetailsAdapter.Mess

private static final Object EXPIRATION_TIMER_CHANGE_PAYLOAD = new Object();

private final LifecycleOwner lifecycleOwner;
private final GlideRequests glideRequests;
private boolean running;

MessageDetailsAdapter(GlideRequests glideRequests) {
MessageDetailsAdapter(@NonNull LifecycleOwner lifecycleOwner, @NonNull GlideRequests glideRequests) {
super(new MessageDetailsDiffer());
this.glideRequests = glideRequests;
running = true;
this.lifecycleOwner = lifecycleOwner;
this.glideRequests = glideRequests;
this.running = true;
}

@Override
Expand All @@ -46,7 +49,7 @@ final class MessageDetailsAdapter extends ListAdapter<MessageDetailsAdapter.Mess
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
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) {
((RecipientHeaderViewHolder) holder).bind((RecipientHeader) getItem(position).data);
} else if (holder instanceof RecipientViewHolder) {
Expand Down
Expand Up @@ -8,6 +8,8 @@
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LifecycleOwner;
import androidx.recyclerview.widget.RecyclerView;

import org.thoughtcrime.securesms.R;
Expand Down Expand Up @@ -64,9 +66,9 @@ final class MessageHeaderViewHolder extends RecyclerView.ViewHolder {
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();
bindMessageView(conversationMessage);
bindMessageView(lifecycleOwner, conversationMessage);
bindErrorState(messageRecord);
bindSentReceivedDates(messageRecord);
bindExpirationTime(messageRecord, running);
Expand All @@ -77,7 +79,7 @@ void partialBind(ConversationMessage conversationMessage, boolean running) {
bindExpirationTime(conversationMessage.getMessageRecord(), running);
}

private void bindMessageView(ConversationMessage conversationMessage) {
private void bindMessageView(@NonNull LifecycleOwner lifecycleOwner, @Nullable ConversationMessage conversationMessage) {
if (conversationItem == null) {
if (conversationMessage.getMessageRecord().isGroupAction()) {
conversationItem = (ConversationItem) updateStub.inflate();
Expand All @@ -87,7 +89,7 @@ private void bindMessageView(ConversationMessage conversationMessage) {
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) {
Expand Down

0 comments on commit 34ef8b5

Please sign in to comment.