Refactor use of MessageRecord to increase flexibility of ConversationAdapter.

master
Cody Henthorne 2020-07-28 10:17:06 -04:00 committed by Greyson Parrelli
parent 5c110ca359
commit 9c63b37bb4
10 changed files with 253 additions and 190 deletions

View File

@ -5,6 +5,7 @@ import androidx.annotation.Nullable;
import android.view.View; import android.view.View;
import org.thoughtcrime.securesms.contactshare.Contact; import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.conversation.ConversationMessage;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.database.model.ReactionRecord; import org.thoughtcrime.securesms.database.model.ReactionRecord;
@ -21,17 +22,17 @@ 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 MessageRecord messageRecord, void bind(@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,
@NonNull Locale locale, @NonNull Locale locale,
@NonNull Set<MessageRecord> batchSelected, @NonNull Set<ConversationMessage> batchSelected,
@NonNull Recipient recipients, @NonNull Recipient recipients,
@Nullable String searchQuery, @Nullable String searchQuery,
boolean pulseHighlight); boolean pulseHighlight);
MessageRecord getMessageRecord(); ConversationMessage getConversationMessage();
void setEventListener(@Nullable EventListener listener); void setEventListener(@Nullable EventListener listener);

View File

@ -65,8 +65,8 @@ import java.util.Set;
* manager, so position 0 is at the bottom of the screen. That's why the "header" is at the bottom, * manager, so position 0 is at the bottom of the screen. That's why the "header" is at the bottom,
* the "footer" is at the top, and we refer to the "next" record as having a lower index. * the "footer" is at the top, and we refer to the "next" record as having a lower index.
*/ */
public class ConversationAdapter<V extends View & BindableConversationItem> public class ConversationAdapter
extends PagedListAdapter<MessageRecord, RecyclerView.ViewHolder> extends PagedListAdapter<ConversationMessage, RecyclerView.ViewHolder>
implements StickyHeaderDecoration.StickyHeaderAdapter<ConversationAdapter.StickyHeaderViewHolder> implements StickyHeaderDecoration.StickyHeaderAdapter<ConversationAdapter.StickyHeaderViewHolder>
{ {
@ -89,16 +89,16 @@ public class ConversationAdapter<V extends View & BindableConversationItem>
private final Locale locale; private final Locale locale;
private final Recipient recipient; private final Recipient recipient;
private final Set<MessageRecord> selected; private final Set<ConversationMessage> selected;
private final List<MessageRecord> fastRecords; private final List<ConversationMessage> fastRecords;
private final Set<Long> releasedFastRecords; private final Set<Long> releasedFastRecords;
private final Calendar calendar; private final Calendar calendar;
private final MessageDigest digest; private final MessageDigest digest;
private String searchQuery; private String searchQuery;
private MessageRecord recordToPulseHighlight; private ConversationMessage recordToPulseHighlight;
private View headerView; private View headerView;
private View footerView; private View footerView;
ConversationAdapter(@NonNull GlideRequests glideRequests, ConversationAdapter(@NonNull GlideRequests glideRequests,
@NonNull Locale locale, @NonNull Locale locale,
@ -130,7 +130,8 @@ public class ConversationAdapter<V extends View & BindableConversationItem>
return MESSAGE_TYPE_FOOTER; return MESSAGE_TYPE_FOOTER;
} }
MessageRecord messageRecord = getItem(position); ConversationMessage conversationMessage = getItem(position);
MessageRecord messageRecord = (conversationMessage != null) ? conversationMessage.getMessageRecord() : null;
if (messageRecord == null) { if (messageRecord == null) {
return MESSAGE_TYPE_PLACEHOLDER; return MESSAGE_TYPE_PLACEHOLDER;
@ -153,16 +154,13 @@ public class ConversationAdapter<V extends View & BindableConversationItem>
return FOOTER_ID; return FOOTER_ID;
} }
MessageRecord record = getItem(position); ConversationMessage message = getItem(position);
if (record == null) { if (message == null) {
return -1; return -1;
} }
String unique = (record.isMms() ? "MMS::" : "SMS::") + record.getId(); return message.getUniqueId(digest);
byte[] bytes = digest.digest(unique.getBytes());
return Conversions.byteArrayToLong(bytes);
} }
@Override @Override
@ -175,22 +173,23 @@ public class ConversationAdapter<V extends View & BindableConversationItem>
case MESSAGE_TYPE_UPDATE: case MESSAGE_TYPE_UPDATE:
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
V itemView = CachedInflater.from(parent.getContext()).inflate(getLayoutForViewType(viewType), parent, false); View itemView = CachedInflater.from(parent.getContext()).inflate(getLayoutForViewType(viewType), parent, false);
BindableConversationItem bindable = (BindableConversationItem) itemView;
itemView.setOnClickListener(view -> { itemView.setOnClickListener(view -> {
if (clickListener != null) { if (clickListener != null) {
clickListener.onItemClick(itemView.getMessageRecord()); clickListener.onItemClick(bindable.getConversationMessage());
} }
}); });
itemView.setOnLongClickListener(view -> { itemView.setOnLongClickListener(view -> {
if (clickListener != null) { if (clickListener != null) {
clickListener.onItemLongClick(itemView, itemView.getMessageRecord()); clickListener.onItemLongClick(itemView, bindable.getConversationMessage());
} }
return true; return true;
}); });
itemView.setEventListener(clickListener); bindable.setEventListener(clickListener);
Log.d(TAG, String.format(Locale.US, "Inflate time: %d ms for View type: %d", System.currentTimeMillis() - start, viewType)); Log.d(TAG, String.format(Locale.US, "Inflate time: %d ms for View type: %d", System.currentTimeMillis() - start, viewType));
return new ConversationViewHolder(itemView); return new ConversationViewHolder(itemView);
@ -215,23 +214,23 @@ public class ConversationAdapter<V extends View & BindableConversationItem>
case MESSAGE_TYPE_OUTGOING_MULTIMEDIA: case MESSAGE_TYPE_OUTGOING_MULTIMEDIA:
case MESSAGE_TYPE_UPDATE: case MESSAGE_TYPE_UPDATE:
ConversationViewHolder conversationViewHolder = (ConversationViewHolder) holder; ConversationViewHolder conversationViewHolder = (ConversationViewHolder) holder;
MessageRecord messageRecord = Objects.requireNonNull(getItem(position)); ConversationMessage conversationMessage = Objects.requireNonNull(getItem(position));
int adapterPosition = holder.getAdapterPosition(); int adapterPosition = holder.getAdapterPosition();
MessageRecord previousRecord = adapterPosition < getItemCount() - 1 && !isFooterPosition(adapterPosition + 1) ? getItem(adapterPosition + 1) : null; ConversationMessage previousMessage = adapterPosition < getItemCount() - 1 && !isFooterPosition(adapterPosition + 1) ? getItem(adapterPosition + 1) : null;
MessageRecord nextRecord = adapterPosition > 0 && !isHeaderPosition(adapterPosition - 1) ? getItem(adapterPosition - 1) : null; ConversationMessage nextMessage = adapterPosition > 0 && !isHeaderPosition(adapterPosition - 1) ? getItem(adapterPosition - 1) : null;
conversationViewHolder.getView().bind(messageRecord, conversationViewHolder.getBindable().bind(conversationMessage,
Optional.fromNullable(previousRecord), Optional.fromNullable(previousMessage != null ? previousMessage.getMessageRecord() : null),
Optional.fromNullable(nextRecord), Optional.fromNullable(nextMessage != null ? nextMessage.getMessageRecord() : null),
glideRequests, glideRequests,
locale, locale,
selected, selected,
recipient, recipient,
searchQuery, searchQuery,
messageRecord == recordToPulseHighlight); conversationMessage == recordToPulseHighlight);
if (messageRecord == recordToPulseHighlight) { if (conversationMessage == recordToPulseHighlight) {
recordToPulseHighlight = null; recordToPulseHighlight = null;
} }
break; break;
@ -245,13 +244,13 @@ public class ConversationAdapter<V extends View & BindableConversationItem>
} }
@Override @Override
public void submitList(@Nullable PagedList<MessageRecord> pagedList) { public void submitList(@Nullable PagedList<ConversationMessage> pagedList) {
cleanFastRecords(); cleanFastRecords();
super.submitList(pagedList); super.submitList(pagedList);
} }
@Override @Override
protected @Nullable MessageRecord getItem(int position) { protected @Nullable ConversationMessage getItem(int position) {
position = hasHeader() ? position - 1 : position; position = hasHeader() ? position - 1 : position;
if (position < fastRecords.size()) { if (position < fastRecords.size()) {
@ -272,7 +271,7 @@ public class ConversationAdapter<V extends View & BindableConversationItem>
@Override @Override
public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) { public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) {
if (holder instanceof ConversationViewHolder) { if (holder instanceof ConversationViewHolder) {
((ConversationViewHolder) holder).getView().unbind(); ((ConversationViewHolder) holder).getBindable().unbind();
} else if (holder instanceof HeaderFooterViewHolder) { } else if (holder instanceof HeaderFooterViewHolder) {
((HeaderFooterViewHolder) holder).unbind(); ((HeaderFooterViewHolder) holder).unbind();
} }
@ -285,11 +284,11 @@ public class ConversationAdapter<V extends View & BindableConversationItem>
if (position >= getItemCount()) return -1; if (position >= getItemCount()) return -1;
if (position < 0) return -1; if (position < 0) return -1;
MessageRecord record = getItem(position); ConversationMessage conversationMessage = getItem(position);
if (record == null) return -1; if (conversationMessage == null) return -1;
calendar.setTime(new Date(record.getDateSent())); calendar.setTime(new Date(conversationMessage.getMessageRecord().getDateSent()));
return Util.hashCode(calendar.get(Calendar.YEAR), calendar.get(Calendar.DAY_OF_YEAR)); return Util.hashCode(calendar.get(Calendar.YEAR), calendar.get(Calendar.DAY_OF_YEAR));
} }
@ -300,8 +299,8 @@ public class ConversationAdapter<V extends View & BindableConversationItem>
@Override @Override
public void onBindHeaderViewHolder(StickyHeaderViewHolder viewHolder, int position) { public void onBindHeaderViewHolder(StickyHeaderViewHolder viewHolder, int position) {
MessageRecord messageRecord = Objects.requireNonNull(getItem(position)); ConversationMessage conversationMessage = Objects.requireNonNull(getItem(position));
viewHolder.setText(DateUtils.getRelativeDate(viewHolder.itemView.getContext(), locale, messageRecord.getDateReceived())); viewHolder.setText(DateUtils.getRelativeDate(viewHolder.itemView.getContext(), locale, conversationMessage.getMessageRecord().getDateReceived()));
} }
void onBindLastSeenViewHolder(StickyHeaderViewHolder viewHolder, int position) { void onBindLastSeenViewHolder(StickyHeaderViewHolder viewHolder, int position) {
@ -328,12 +327,12 @@ public class ConversationAdapter<V extends View & BindableConversationItem>
if (position >= getItemCount()) return 0; if (position >= getItemCount()) return 0;
if (position < 0) return 0; if (position < 0) return 0;
MessageRecord messageRecord = getItem(position); ConversationMessage conversationMessage = getItem(position);
if (messageRecord == null || messageRecord.isOutgoing()) { if (conversationMessage == null || conversationMessage.getMessageRecord().isOutgoing()) {
return 0; return 0;
} else { } else {
return messageRecord.getDateReceived(); return conversationMessage.getMessageRecord().getDateReceived();
} }
} }
@ -403,8 +402,8 @@ public class ConversationAdapter<V extends View & BindableConversationItem>
* for a database change. * for a database change.
*/ */
@MainThread @MainThread
void addFastRecord(MessageRecord record) { void addFastRecord(ConversationMessage conversationMessage) {
fastRecords.add(0, record); fastRecords.add(0, conversationMessage);
notifyDataSetChanged(); notifyDataSetChanged();
} }
@ -422,7 +421,7 @@ public class ConversationAdapter<V extends View & BindableConversationItem>
/** /**
* Returns set of records that are selected in multi-select mode. * Returns set of records that are selected in multi-select mode.
*/ */
Set<MessageRecord> getSelectedItems() { Set<ConversationMessage> getSelectedItems() {
return new HashSet<>(selected); return new HashSet<>(selected);
} }
@ -436,11 +435,11 @@ public class ConversationAdapter<V extends View & BindableConversationItem>
/** /**
* Toggles the selected state of a record in multi-select mode. * Toggles the selected state of a record in multi-select mode.
*/ */
void toggleSelection(MessageRecord record) { void toggleSelection(ConversationMessage conversationMessage) {
if (selected.contains(record)) { if (selected.contains(conversationMessage)) {
selected.remove(record); selected.remove(conversationMessage);
} else { } else {
selected.add(record); selected.add(conversationMessage);
} }
} }
@ -464,11 +463,11 @@ public class ConversationAdapter<V extends View & BindableConversationItem>
Util.assertMainThread(); Util.assertMainThread();
synchronized (releasedFastRecords) { synchronized (releasedFastRecords) {
Iterator<MessageRecord> recordIterator = fastRecords.iterator(); Iterator<ConversationMessage> messageIterator = fastRecords.iterator();
while (recordIterator.hasNext()) { while (messageIterator.hasNext()) {
long id = recordIterator.next().getId(); long id = messageIterator.next().getMessageRecord().getId();
if (releasedFastRecords.contains(id)) { if (releasedFastRecords.contains(id)) {
recordIterator.remove(); messageIterator.remove();
releasedFastRecords.remove(id); releasedFastRecords.remove(id);
} }
} }
@ -510,18 +509,17 @@ public class ConversationAdapter<V extends View & BindableConversationItem>
} }
} }
public @Nullable MessageRecord getLastVisibleMessageRecord(int position) { public @Nullable ConversationMessage getLastVisibleConversationMessage(int position) {
return getItem(position - ((hasFooter() && position == getItemCount() - 1) ? 1 : 0)); return getItem(position - ((hasFooter() && position == getItemCount() - 1) ? 1 : 0));
} }
static class ConversationViewHolder extends RecyclerView.ViewHolder { static class ConversationViewHolder extends RecyclerView.ViewHolder {
public <V extends View & BindableConversationItem> ConversationViewHolder(final @NonNull V itemView) { public ConversationViewHolder(final @NonNull View itemView) {
super(itemView); super(itemView);
} }
public <V extends View & BindableConversationItem> V getView() { public BindableConversationItem getBindable() {
//noinspection unchecked return (BindableConversationItem) itemView;
return (V)itemView;
} }
} }
@ -530,7 +528,7 @@ public class ConversationAdapter<V extends View & BindableConversationItem>
StickyHeaderViewHolder(View itemView) { StickyHeaderViewHolder(View itemView) {
super(itemView); super(itemView);
textView = ViewUtil.findById(itemView, R.id.text); textView = itemView.findViewById(R.id.text);
} }
StickyHeaderViewHolder(TextView textView) { StickyHeaderViewHolder(TextView textView) {
@ -571,21 +569,21 @@ public class ConversationAdapter<V extends View & BindableConversationItem>
} }
} }
private static class DiffCallback extends DiffUtil.ItemCallback<MessageRecord> { private static class DiffCallback extends DiffUtil.ItemCallback<ConversationMessage> {
@Override @Override
public boolean areItemsTheSame(@NonNull MessageRecord oldItem, @NonNull MessageRecord newItem) { public boolean areItemsTheSame(@NonNull ConversationMessage oldItem, @NonNull ConversationMessage newItem) {
return oldItem.isMms() == newItem.isMms() && oldItem.getId() == newItem.getId(); return oldItem.getMessageRecord().isMms() == newItem.getMessageRecord().isMms() && oldItem.getMessageRecord().getId() == newItem.getMessageRecord().getId();
} }
@Override @Override
public boolean areContentsTheSame(@NonNull MessageRecord oldItem, @NonNull MessageRecord newItem) { public boolean areContentsTheSame(@NonNull ConversationMessage oldItem, @NonNull ConversationMessage newItem) {
// Corner rounding is not part of the model, so we can't use this yet // Corner rounding is not part of the model, so we can't use this yet
return false; return false;
} }
} }
interface ItemClickListener extends BindableConversationItem.EventListener { interface ItemClickListener extends BindableConversationItem.EventListener {
void onItemClick(MessageRecord item); void onItemClick(ConversationMessage item);
void onItemLongClick(View maskTarget, MessageRecord item); void onItemLongClick(View maskTarget, ConversationMessage item);
} }
} }

View File

@ -7,6 +7,8 @@ import androidx.annotation.NonNull;
import androidx.paging.DataSource; import androidx.paging.DataSource;
import androidx.paging.PositionalDataSource; import androidx.paging.PositionalDataSource;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.database.DatabaseContentProviders; import org.thoughtcrime.securesms.database.DatabaseContentProviders;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsSmsDatabase; import org.thoughtcrime.securesms.database.MmsSmsDatabase;
@ -24,7 +26,7 @@ import java.util.concurrent.Executor;
/** /**
* Core data source for loading an individual conversation. * Core data source for loading an individual conversation.
*/ */
class ConversationDataSource extends PositionalDataSource<MessageRecord> { class ConversationDataSource extends PositionalDataSource<ConversationMessage> {
private static final String TAG = Log.tag(ConversationDataSource.class); private static final String TAG = Log.tag(ConversationDataSource.class);
@ -57,7 +59,7 @@ class ConversationDataSource extends PositionalDataSource<MessageRecord> {
} }
@Override @Override
public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<MessageRecord> callback) { public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<ConversationMessage> callback) {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
MmsSmsDatabase db = DatabaseFactory.getMmsSmsDatabase(context); MmsSmsDatabase db = DatabaseFactory.getMmsSmsDatabase(context);
@ -76,14 +78,18 @@ class ConversationDataSource extends PositionalDataSource<MessageRecord> {
if (!isInvalid()) { if (!isInvalid()) {
SizeFixResult<MessageRecord> result = SizeFixResult.ensureMultipleOfPageSize(records, params.requestedStartPosition, params.pageSize, totalCount); SizeFixResult<MessageRecord> result = SizeFixResult.ensureMultipleOfPageSize(records, params.requestedStartPosition, params.pageSize, totalCount);
callback.onResult(result.getItems(), params.requestedStartPosition, result.getTotal()); List<ConversationMessage> items = Stream.of(result.getItems())
.map(ConversationMessage::new)
.toList();
callback.onResult(items, params.requestedStartPosition, result.getTotal());
} }
Log.d(TAG, "[Initial Load] " + (System.currentTimeMillis() - start) + " ms | thread: " + threadId + ", start: " + params.requestedStartPosition + ", size: " + params.requestedLoadSize + (isInvalid() ? " -- invalidated" : "")); Log.d(TAG, "[Initial Load] " + (System.currentTimeMillis() - start) + " ms | thread: " + threadId + ", start: " + params.requestedStartPosition + ", size: " + params.requestedLoadSize + (isInvalid() ? " -- invalidated" : ""));
} }
@Override @Override
public void loadRange(@NonNull LoadRangeParams params, @NonNull LoadRangeCallback<MessageRecord> callback) { public void loadRange(@NonNull LoadRangeParams params, @NonNull LoadRangeCallback<ConversationMessage> callback) {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
MmsSmsDatabase db = DatabaseFactory.getMmsSmsDatabase(context); MmsSmsDatabase db = DatabaseFactory.getMmsSmsDatabase(context);
@ -96,12 +102,15 @@ class ConversationDataSource extends PositionalDataSource<MessageRecord> {
} }
} }
callback.onResult(records); List<ConversationMessage> items = Stream.of(records)
.map(ConversationMessage::new)
.toList();
callback.onResult(items);
Log.d(TAG, "[Update] " + (System.currentTimeMillis() - start) + " ms | thread: " + threadId + ", start: " + params.startPosition + ", size: " + params.loadSize + (isInvalid() ? " -- invalidated" : "")); Log.d(TAG, "[Update] " + (System.currentTimeMillis() - start) + " ms | thread: " + threadId + ", start: " + params.startPosition + ", size: " + params.loadSize + (isInvalid() ? " -- invalidated" : ""));
} }
static class Factory extends DataSource.Factory<Integer, MessageRecord> { static class Factory extends DataSource.Factory<Integer, ConversationMessage> {
private final Context context; private final Context context;
private final long threadId; private final long threadId;
@ -114,7 +123,7 @@ class ConversationDataSource extends PositionalDataSource<MessageRecord> {
} }
@Override @Override
public @NonNull DataSource<Integer, MessageRecord> create() { public @NonNull DataSource<Integer, ConversationMessage> create() {
return new ConversationDataSource(context, threadId, invalidator); return new ConversationDataSource(context, threadId, invalidator);
} }
} }

View File

@ -55,6 +55,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.OnScrollListener; import androidx.recyclerview.widget.RecyclerView.OnScrollListener;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
import com.google.android.collect.Sets; import com.google.android.collect.Sets;
@ -210,8 +211,8 @@ public class ConversationFragment extends LoggingFragment {
typingView = (ConversationTypingView) inflater.inflate(R.layout.conversation_typing_view, container, false); typingView = (ConversationTypingView) inflater.inflate(R.layout.conversation_typing_view, container, false);
new ConversationItemSwipeCallback( new ConversationItemSwipeCallback(
messageRecord -> actionMode == null && conversationMessage -> actionMode == null &&
MenuState.canReplyToMessage(MenuState.isActionMessage(messageRecord), messageRecord, messageRequestViewModel.shouldShowMessageRequest()), MenuState.canReplyToMessage(MenuState.isActionMessage(conversationMessage.getMessageRecord()), conversationMessage.getMessageRecord(), messageRequestViewModel.shouldShowMessageRequest()),
this::handleReplyMessage this::handleReplyMessage
).attachToRecyclerView(list); ).attachToRecyclerView(list);
@ -288,9 +289,9 @@ public class ConversationFragment extends LoggingFragment {
final long lastVisibleMessageTimestamp; final long lastVisibleMessageTimestamp;
if (firstVisiblePosition > 0 && lastVisiblePosition != RecyclerView.NO_POSITION) { if (firstVisiblePosition > 0 && lastVisiblePosition != RecyclerView.NO_POSITION) {
MessageRecord message = getListAdapter().getLastVisibleMessageRecord(lastVisiblePosition); ConversationMessage message = getListAdapter().getLastVisibleConversationMessage(lastVisiblePosition);
lastVisibleMessageTimestamp = message != null ? message.getDateReceived() : 0; lastVisibleMessageTimestamp = message != null ? message.getMessageRecord().getDateReceived() : 0;
} else { } else {
lastVisibleMessageTimestamp = 0; lastVisibleMessageTimestamp = 0;
} }
@ -519,14 +520,14 @@ public class ConversationFragment extends LoggingFragment {
} }
private void setCorrectMenuVisibility(@NonNull Menu menu) { private void setCorrectMenuVisibility(@NonNull Menu menu) {
Set<MessageRecord> messageRecords = getListAdapter().getSelectedItems(); Set<ConversationMessage> messages = getListAdapter().getSelectedItems();
if (actionMode != null && messageRecords.size() == 0) { if (actionMode != null && messages.size() == 0) {
actionMode.finish(); actionMode.finish();
return; return;
} }
MenuState menuState = MenuState.getMenuState(messageRecords, messageRequestViewModel.shouldShowMessageRequest()); MenuState menuState = MenuState.getMenuState(Stream.of(messages).map(ConversationMessage::getMessageRecord).collect(Collectors.toSet()), messageRequestViewModel.shouldShowMessageRequest());
menu.findItem(R.id.menu_context_forward).setVisible(menuState.shouldShowForwardAction()); menu.findItem(R.id.menu_context_forward).setVisible(menuState.shouldShowForwardAction());
menu.findItem(R.id.menu_context_reply).setVisible(menuState.shouldShowReplyAction()); menu.findItem(R.id.menu_context_reply).setVisible(menuState.shouldShowReplyAction());
@ -544,8 +545,8 @@ public class ConversationFragment extends LoggingFragment {
return (SmoothScrollingLinearLayoutManager) list.getLayoutManager(); return (SmoothScrollingLinearLayoutManager) list.getLayoutManager();
} }
private MessageRecord getSelectedMessageRecord() { private ConversationMessage getSelectedConversationMessage() {
Set<MessageRecord> messageRecords = getListAdapter().getSelectedItems(); Set<ConversationMessage> messageRecords = getListAdapter().getSelectedItems();
if (messageRecords.size() == 1) return messageRecords.iterator().next(); if (messageRecords.size() == 1) return messageRecords.iterator().next();
else throw new AssertionError(); else throw new AssertionError();
@ -581,8 +582,8 @@ public class ConversationFragment extends LoggingFragment {
list.addItemDecoration(lastSeenDecoration); list.addItemDecoration(lastSeenDecoration);
} }
private void handleCopyMessage(final Set<MessageRecord> messageRecords) { private void handleCopyMessage(final Set<ConversationMessage> conversationMessages) {
List<MessageRecord> messageList = new LinkedList<>(messageRecords); List<MessageRecord> messageList = Stream.of(conversationMessages).map(ConversationMessage::getMessageRecord).toList();
Collections.sort(messageList, new Comparator<MessageRecord>() { Collections.sort(messageList, new Comparator<MessageRecord>() {
@Override @Override
public int compare(MessageRecord lhs, MessageRecord rhs) { public int compare(MessageRecord lhs, MessageRecord rhs) {
@ -611,7 +612,8 @@ public class ConversationFragment extends LoggingFragment {
clipboard.setText(result); clipboard.setText(result);
} }
private void handleDeleteMessages(final Set<MessageRecord> messageRecords) { private void handleDeleteMessages(final Set<ConversationMessage> conversationMessages) {
Set<MessageRecord> messageRecords = Stream.of(conversationMessages).map(ConversationMessage::getMessageRecord).collect(Collectors.toSet());
if (FeatureFlags.remoteDelete()) { if (FeatureFlags.remoteDelete()) {
buildRemoteDeleteConfirmationDialog(messageRecords).show(); buildRemoteDeleteConfirmationDialog(messageRecords).show();
} else { } else {
@ -725,11 +727,12 @@ public class ConversationFragment extends LoggingFragment {
} }
} }
private void handleDisplayDetails(MessageRecord message) { private void handleDisplayDetails(ConversationMessage message) {
startActivity(MessageDetailsActivity.getIntentForMessageDetails(requireContext(), message, recipient.getId(), threadId)); startActivity(MessageDetailsActivity.getIntentForMessageDetails(requireContext(), message.getMessageRecord(), recipient.getId(), threadId));
} }
private void handleForwardMessage(MessageRecord message) { private void handleForwardMessage(ConversationMessage conversationMessage) {
MessageRecord message = conversationMessage.getMessageRecord();
if (message.isViewOnce()) { if (message.isViewOnce()) {
throw new AssertionError("Cannot forward a view-once message."); throw new AssertionError("Cannot forward a view-once message.");
} }
@ -812,13 +815,13 @@ public class ConversationFragment extends LoggingFragment {
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, message); }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, message);
} }
private void handleReplyMessage(final MessageRecord message) { private void handleReplyMessage(final ConversationMessage message) {
if (getActivity() != null) { if (getActivity() != null) {
//noinspection ConstantConditions //noinspection ConstantConditions
((AppCompatActivity) getActivity()).getSupportActionBar().collapseActionView(); ((AppCompatActivity) getActivity()).getSupportActionBar().collapseActionView();
} }
listener.handleReplyMessage(message); listener.handleReplyMessage(message.getMessageRecord());
} }
private void handleSaveAttachment(final MediaMmsMessageRecord message) { private void handleSaveAttachment(final MediaMmsMessageRecord message) {
@ -858,7 +861,7 @@ public class ConversationFragment extends LoggingFragment {
if (getListAdapter() != null) { if (getListAdapter() != null) {
clearHeaderIfNotTyping(getListAdapter()); clearHeaderIfNotTyping(getListAdapter());
setLastSeen(0); setLastSeen(0);
getListAdapter().addFastRecord(messageRecord); getListAdapter().addFastRecord(new ConversationMessage(messageRecord));
list.post(() -> list.scrollToPosition(0)); list.post(() -> list.scrollToPosition(0));
} }
@ -871,7 +874,7 @@ public class ConversationFragment extends LoggingFragment {
if (getListAdapter() != null) { if (getListAdapter() != null) {
clearHeaderIfNotTyping(getListAdapter()); clearHeaderIfNotTyping(getListAdapter());
setLastSeen(0); setLastSeen(0);
getListAdapter().addFastRecord(messageRecord); getListAdapter().addFastRecord(new ConversationMessage(messageRecord));
list.post(() -> list.scrollToPosition(0)); list.post(() -> list.scrollToPosition(0));
} }
@ -1085,9 +1088,9 @@ public class ConversationFragment extends LoggingFragment {
private class ConversationFragmentItemClickListener implements ItemClickListener { private class ConversationFragmentItemClickListener implements ItemClickListener {
@Override @Override
public void onItemClick(MessageRecord messageRecord) { public void onItemClick(ConversationMessage conversationMessage) {
if (actionMode != null) { if (actionMode != null) {
((ConversationAdapter) list.getAdapter()).toggleSelection(messageRecord); ((ConversationAdapter) list.getAdapter()).toggleSelection(conversationMessage);
list.getAdapter().notifyDataSetChanged(); list.getAdapter().notifyDataSetChanged();
if (getListAdapter().getSelectedItems().size() == 0) { if (getListAdapter().getSelectedItems().size() == 0) {
@ -1100,10 +1103,12 @@ public class ConversationFragment extends LoggingFragment {
} }
@Override @Override
public void onItemLongClick(View maskTarget, MessageRecord messageRecord) { public void onItemLongClick(View maskTarget, ConversationMessage conversationMessage) {
if (actionMode != null) return; if (actionMode != null) return;
MessageRecord messageRecord = conversationMessage.getMessageRecord();
if (messageRecord.isSecure() && if (messageRecord.isSecure() &&
!messageRecord.isRemoteDelete() && !messageRecord.isRemoteDelete() &&
!messageRecord.isUpdate() && !messageRecord.isUpdate() &&
@ -1113,12 +1118,12 @@ public class ConversationFragment extends LoggingFragment {
{ {
isReacting = true; isReacting = true;
list.setLayoutFrozen(true); list.setLayoutFrozen(true);
listener.handleReaction(maskTarget, messageRecord, new ReactionsToolbarListener(messageRecord), () -> { listener.handleReaction(maskTarget, messageRecord, new ReactionsToolbarListener(conversationMessage), () -> {
isReacting = false; isReacting = false;
list.setLayoutFrozen(false); list.setLayoutFrozen(false);
}); });
} else { } else {
((ConversationAdapter) list.getAdapter()).toggleSelection(messageRecord); ((ConversationAdapter) list.getAdapter()).toggleSelection(conversationMessage);
list.getAdapter().notifyDataSetChanged(); list.getAdapter().notifyDataSetChanged();
actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(actionModeCallback); actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(actionModeCallback);
@ -1287,8 +1292,8 @@ public class ConversationFragment extends LoggingFragment {
} }
} }
private void handleEnterMultiSelect(@NonNull MessageRecord messageRecord) { private void handleEnterMultiSelect(@NonNull ConversationMessage conversationMessage) {
((ConversationAdapter) list.getAdapter()).toggleSelection(messageRecord); ((ConversationAdapter) list.getAdapter()).toggleSelection(conversationMessage);
list.getAdapter().notifyDataSetChanged(); list.getAdapter().notifyDataSetChanged();
actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(actionModeCallback); actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(actionModeCallback);
@ -1342,23 +1347,23 @@ public class ConversationFragment extends LoggingFragment {
private class ReactionsToolbarListener implements Toolbar.OnMenuItemClickListener { private class ReactionsToolbarListener implements Toolbar.OnMenuItemClickListener {
private final MessageRecord messageRecord; private final ConversationMessage conversationMessage;
private ReactionsToolbarListener(@NonNull MessageRecord messageRecord) { private ReactionsToolbarListener(@NonNull ConversationMessage conversationMessage) {
this.messageRecord = messageRecord; this.conversationMessage = conversationMessage;
} }
@Override @Override
public boolean onMenuItemClick(MenuItem item) { public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.action_info: handleDisplayDetails(messageRecord); return true; case R.id.action_info: handleDisplayDetails(conversationMessage); return true;
case R.id.action_delete: handleDeleteMessages(Sets.newHashSet(messageRecord)); return true; case R.id.action_delete: handleDeleteMessages(Sets.newHashSet(conversationMessage)); return true;
case R.id.action_copy: handleCopyMessage(Sets.newHashSet(messageRecord)); return true; case R.id.action_copy: handleCopyMessage(Sets.newHashSet(conversationMessage)); return true;
case R.id.action_reply: handleReplyMessage(messageRecord); return true; case R.id.action_reply: handleReplyMessage(conversationMessage); return true;
case R.id.action_multiselect: handleEnterMultiSelect(messageRecord); return true; case R.id.action_multiselect: handleEnterMultiSelect(conversationMessage); return true;
case R.id.action_forward: handleForwardMessage(messageRecord); return true; case R.id.action_forward: handleForwardMessage(conversationMessage); return true;
case R.id.action_download: handleSaveAttachment((MediaMmsMessageRecord) messageRecord); return true; case R.id.action_download: handleSaveAttachment((MediaMmsMessageRecord) conversationMessage.getMessageRecord()); return true;
default: return false; default: return false;
} }
} }
} }
@ -1417,24 +1422,24 @@ public class ConversationFragment extends LoggingFragment {
actionMode.finish(); actionMode.finish();
return true; return true;
case R.id.menu_context_details: case R.id.menu_context_details:
handleDisplayDetails(getSelectedMessageRecord()); handleDisplayDetails(getSelectedConversationMessage());
actionMode.finish(); actionMode.finish();
return true; return true;
case R.id.menu_context_forward: case R.id.menu_context_forward:
handleForwardMessage(getSelectedMessageRecord()); handleForwardMessage(getSelectedConversationMessage());
actionMode.finish(); actionMode.finish();
return true; return true;
case R.id.menu_context_resend: case R.id.menu_context_resend:
handleResendMessage(getSelectedMessageRecord()); handleResendMessage(getSelectedConversationMessage().getMessageRecord());
actionMode.finish(); actionMode.finish();
return true; return true;
case R.id.menu_context_save_attachment: case R.id.menu_context_save_attachment:
handleSaveAttachment((MediaMmsMessageRecord)getSelectedMessageRecord()); handleSaveAttachment((MediaMmsMessageRecord) getSelectedConversationMessage().getMessageRecord());
actionMode.finish(); actionMode.finish();
return true; return true;
case R.id.menu_context_reply: case R.id.menu_context_reply:
maybeShowSwipeToReplyTooltip(); maybeShowSwipeToReplyTooltip();
handleReplyMessage(getSelectedMessageRecord()); handleReplyMessage(getSelectedConversationMessage());
actionMode.finish(); actionMode.finish();
return true; return true;
} }

View File

@ -139,11 +139,12 @@ public class ConversationItem extends LinearLayout implements BindableConversati
private static final Rect SWIPE_RECT = new Rect(); private static final Rect SWIPE_RECT = new Rect();
private MessageRecord messageRecord; private ConversationMessage conversationMessage;
private Locale locale; private MessageRecord messageRecord;
private boolean groupThread; private Locale locale;
private LiveRecipient recipient; private boolean groupThread;
private GlideRequests glideRequests; private LiveRecipient recipient;
private GlideRequests glideRequests;
protected ConversationItemBodyBubble bodyBubble; protected ConversationItemBodyBubble bodyBubble;
protected View reply; protected View reply;
@ -160,7 +161,7 @@ public class ConversationItem extends LinearLayout implements BindableConversati
private ViewGroup container; private ViewGroup container;
protected ReactionsConversationView reactionsView; protected ReactionsConversationView reactionsView;
private @NonNull Set<MessageRecord> batchSelected = new HashSet<>(); private @NonNull Set<ConversationMessage> batchSelected = new HashSet<>();
private @NonNull Outliner outliner = new Outliner(); private @NonNull Outliner outliner = new Outliner();
private LiveRecipient conversationRecipient; private LiveRecipient conversationRecipient;
private Stub<ConversationItemThumbnail> mediaThumbnailStub; private Stub<ConversationItemThumbnail> mediaThumbnailStub;
@ -234,22 +235,23 @@ public class ConversationItem extends LinearLayout implements BindableConversati
} }
@Override @Override
public void bind(@NonNull MessageRecord messageRecord, public void bind(@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,
@NonNull Locale locale, @NonNull Locale locale,
@NonNull Set<MessageRecord> batchSelected, @NonNull Set<ConversationMessage> batchSelected,
@NonNull Recipient conversationRecipient, @NonNull Recipient conversationRecipient,
@Nullable String searchQuery, @Nullable String searchQuery,
boolean pulseHighlight) boolean pulseHighlight)
{ {
if (this.recipient != null) this.recipient.removeForeverObserver(this); if (this.recipient != null) this.recipient.removeForeverObserver(this);
if (this.conversationRecipient != null) this.conversationRecipient.removeForeverObserver(this); if (this.conversationRecipient != null) this.conversationRecipient.removeForeverObserver(this);
conversationRecipient = conversationRecipient.resolve(); conversationRecipient = conversationRecipient.resolve();
this.messageRecord = messageRecord; this.conversationMessage = conversationMessage;
this.messageRecord = conversationMessage.getMessageRecord();
this.locale = locale; this.locale = locale;
this.glideRequests = glideRequests; this.glideRequests = glideRequests;
this.batchSelected = batchSelected; this.batchSelected = batchSelected;
@ -263,7 +265,7 @@ public class ConversationItem extends LinearLayout implements BindableConversati
setGutterSizes(messageRecord, groupThread); setGutterSizes(messageRecord, groupThread);
setMessageShape(messageRecord, previousMessageRecord, nextMessageRecord, groupThread); setMessageShape(messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
setMediaAttributes(messageRecord, previousMessageRecord, nextMessageRecord, conversationRecipient, groupThread); setMediaAttributes(messageRecord, previousMessageRecord, nextMessageRecord, conversationRecipient, groupThread);
setInteractionState(messageRecord, pulseHighlight); setInteractionState(conversationMessage, pulseHighlight);
setBodyText(messageRecord, searchQuery); setBodyText(messageRecord, searchQuery);
setBubbleState(messageRecord); setBubbleState(messageRecord);
setStatusIcons(messageRecord); setStatusIcons(messageRecord);
@ -381,8 +383,8 @@ public class ConversationItem extends LinearLayout implements BindableConversati
} }
} }
public MessageRecord getMessageRecord() { public ConversationMessage getConversationMessage() {
return messageRecord; return conversationMessage;
} }
/// MessageRecord Attribute Parsers /// MessageRecord Attribute Parsers
@ -424,8 +426,8 @@ public class ConversationItem extends LinearLayout implements BindableConversati
} }
} }
private void setInteractionState(MessageRecord messageRecord, boolean pulseHighlight) { private void setInteractionState(ConversationMessage conversationMessage, boolean pulseHighlight) {
if (batchSelected.contains(messageRecord)) { if (batchSelected.contains(conversationMessage)) {
setBackgroundResource(R.drawable.conversation_item_background); setBackgroundResource(R.drawable.conversation_item_background);
setSelected(true); setSelected(true);
} else if (pulseHighlight) { } else if (pulseHighlight) {
@ -437,19 +439,19 @@ public class ConversationItem extends LinearLayout implements BindableConversati
} }
if (mediaThumbnailStub.resolved()) { if (mediaThumbnailStub.resolved()) {
mediaThumbnailStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty()); mediaThumbnailStub.get().setFocusable(!shouldInterceptClicks(conversationMessage.getMessageRecord()) && batchSelected.isEmpty());
mediaThumbnailStub.get().setClickable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty()); mediaThumbnailStub.get().setClickable(!shouldInterceptClicks(conversationMessage.getMessageRecord()) && batchSelected.isEmpty());
mediaThumbnailStub.get().setLongClickable(batchSelected.isEmpty()); mediaThumbnailStub.get().setLongClickable(batchSelected.isEmpty());
} }
if (audioViewStub.resolved()) { if (audioViewStub.resolved()) {
audioViewStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty()); audioViewStub.get().setFocusable(!shouldInterceptClicks(conversationMessage.getMessageRecord()) && batchSelected.isEmpty());
audioViewStub.get().setClickable(batchSelected.isEmpty()); audioViewStub.get().setClickable(batchSelected.isEmpty());
audioViewStub.get().setEnabled(batchSelected.isEmpty()); audioViewStub.get().setEnabled(batchSelected.isEmpty());
} }
if (documentViewStub.resolved()) { if (documentViewStub.resolved()) {
documentViewStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty()); documentViewStub.get().setFocusable(!shouldInterceptClicks(conversationMessage.getMessageRecord()) && batchSelected.isEmpty());
documentViewStub.get().setClickable(batchSelected.isEmpty()); documentViewStub.get().setClickable(batchSelected.isEmpty());
} }
} }

View File

@ -114,8 +114,8 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
private void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder) { private void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder) {
if (cannotSwipeViewHolder(viewHolder)) return; if (cannotSwipeViewHolder(viewHolder)) return;
ConversationItem item = ((ConversationItem) viewHolder.itemView); ConversationItem item = ((ConversationItem) viewHolder.itemView);
MessageRecord messageRecord = item.getMessageRecord(); ConversationMessage messageRecord = item.getConversationMessage();
onSwipeListener.onSwipe(messageRecord); onSwipeListener.onSwipe(messageRecord);
} }
@ -169,7 +169,7 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
if (!(viewHolder.itemView instanceof ConversationItem)) return true; if (!(viewHolder.itemView instanceof ConversationItem)) return true;
ConversationItem item = ((ConversationItem) viewHolder.itemView); ConversationItem item = ((ConversationItem) viewHolder.itemView);
return !swipeAvailabilityProvider.isSwipeAvailable(item.getMessageRecord()) || return !swipeAvailabilityProvider.isSwipeAvailable(item.getConversationMessage()) ||
item.disallowSwipe(latestDownX, latestDownY); item.disallowSwipe(latestDownX, latestDownY);
} }
@ -192,10 +192,10 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
} }
interface SwipeAvailabilityProvider { interface SwipeAvailabilityProvider {
boolean isSwipeAvailable(MessageRecord messageRecord); boolean isSwipeAvailable(ConversationMessage conversationMessage);
} }
interface OnSwipeListener { interface OnSwipeListener {
void onSwipe(MessageRecord messageRecord); void onSwipe(ConversationMessage conversationMessage);
} }
} }

View File

@ -0,0 +1,44 @@
package org.thoughtcrime.securesms.conversation;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.util.Conversions;
import java.security.MessageDigest;
/**
* A view level model used to pass arbitrary message related information needed
* for various presentations.
*/
public class ConversationMessage {
private final MessageRecord messageRecord;
public ConversationMessage(@NonNull MessageRecord messageRecord) {
this.messageRecord = messageRecord;
}
public @NonNull MessageRecord getMessageRecord() {
return messageRecord;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final ConversationMessage that = (ConversationMessage) o;
return messageRecord.equals(that.messageRecord);
}
@Override
public int hashCode() {
return messageRecord.hashCode();
}
public long getUniqueId(@NonNull MessageDigest digest) {
String unique = (messageRecord.isMms() ? "MMS::" : "SMS::") + messageRecord.getId();
byte[] bytes = digest.digest(unique.getBytes());
return Conversions.byteArrayToLong(bytes);
}
}

View File

@ -50,13 +50,14 @@ public final class ConversationUpdateItem extends LinearLayout
{ {
private static final String TAG = ConversationUpdateItem.class.getSimpleName(); private static final String TAG = ConversationUpdateItem.class.getSimpleName();
private Set<MessageRecord> batchSelected; private Set<ConversationMessage> batchSelected;
private ImageView icon; private ImageView icon;
private TextView title; private TextView title;
private TextView body; private TextView body;
private TextView date; private TextView date;
private LiveRecipient sender; private LiveRecipient sender;
private ConversationMessage conversationMessage;
private MessageRecord messageRecord; private MessageRecord messageRecord;
private Locale locale; private Locale locale;
private LiveData<SpannableString> displayBody; private LiveData<SpannableString> displayBody;
@ -84,19 +85,19 @@ public final class ConversationUpdateItem extends LinearLayout
} }
@Override @Override
public void bind(@NonNull MessageRecord messageRecord, public void bind(@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,
@NonNull Locale locale, @NonNull Locale locale,
@NonNull Set<MessageRecord> batchSelected, @NonNull Set<ConversationMessage> batchSelected,
@NonNull Recipient conversationRecipient, @NonNull Recipient conversationRecipient,
@Nullable String searchQuery, @Nullable String searchQuery,
boolean pulseUpdate) boolean pulseUpdate)
{ {
this.batchSelected = batchSelected; this.batchSelected = batchSelected;
bind(messageRecord, locale); bind(conversationMessage, locale);
} }
@Override @Override
@ -111,11 +112,11 @@ public final class ConversationUpdateItem extends LinearLayout
} }
@Override @Override
public MessageRecord getMessageRecord() { public ConversationMessage getConversationMessage() {
return messageRecord; return conversationMessage;
} }
private void bind(@NonNull MessageRecord messageRecord, @NonNull Locale locale) { private void bind(@NonNull ConversationMessage conversationMessage, @NonNull Locale locale) {
if (this.sender != null) { if (this.sender != null) {
this.sender.removeForeverObserver(this); this.sender.removeForeverObserver(this);
} }
@ -123,9 +124,10 @@ public final class ConversationUpdateItem extends LinearLayout
observeDisplayBody(null); observeDisplayBody(null);
setBodyText(null); setBodyText(null);
this.messageRecord = messageRecord; this.conversationMessage = conversationMessage;
this.sender = messageRecord.getIndividualRecipient().live(); this.messageRecord = conversationMessage.getMessageRecord();
this.locale = locale; this.sender = messageRecord.getIndividualRecipient().live();
this.locale = locale;
this.sender.observeForever(this); this.sender.observeForever(this);
@ -133,7 +135,7 @@ public final class ConversationUpdateItem extends LinearLayout
LiveData<String> liveUpdateMessage = LiveUpdateMessage.fromMessageDescription(updateDescription); LiveData<String> liveUpdateMessage = LiveUpdateMessage.fromMessageDescription(updateDescription);
LiveData<SpannableString> spannableStringMessage = Transformations.map(liveUpdateMessage, SpannableString::new); LiveData<SpannableString> spannableStringMessage = Transformations.map(liveUpdateMessage, SpannableString::new);
present(messageRecord); present(conversationMessage);
observeDisplayBody(spannableStringMessage); observeDisplayBody(spannableStringMessage);
} }
@ -162,7 +164,8 @@ public final class ConversationUpdateItem extends LinearLayout
} }
} }
private void present(MessageRecord messageRecord) { private void present(ConversationMessage conversationMessage) {
MessageRecord messageRecord = conversationMessage.getMessageRecord();
if (messageRecord.isGroupAction()) setGroupRecord(); if (messageRecord.isGroupAction()) setGroupRecord();
else if (messageRecord.isCallLog()) setCallRecord(messageRecord); else if (messageRecord.isCallLog()) setCallRecord(messageRecord);
else if (messageRecord.isJoined()) setJoinedRecord(); else if (messageRecord.isJoined()) setJoinedRecord();
@ -174,8 +177,8 @@ public final class ConversationUpdateItem extends LinearLayout
else if (messageRecord.isProfileChange()) setProfileNameChangeRecord(); else if (messageRecord.isProfileChange()) setProfileNameChangeRecord();
else throw new AssertionError("Neither group nor log nor joined."); else throw new AssertionError("Neither group nor log nor joined.");
if (batchSelected.contains(messageRecord)) setSelected(true); if (batchSelected.contains(conversationMessage)) setSelected(true);
else setSelected(false); else setSelected(false);
} }
private void setCallRecord(MessageRecord messageRecord) { private void setCallRecord(MessageRecord messageRecord) {
@ -256,7 +259,7 @@ public final class ConversationUpdateItem extends LinearLayout
@Override @Override
public void onRecipientChanged(@NonNull Recipient recipient) { public void onRecipientChanged(@NonNull Recipient recipient) {
present(messageRecord); present(conversationMessage);
} }
@Override @Override

View File

@ -28,14 +28,14 @@ class ConversationViewModel extends ViewModel {
private static final String TAG = Log.tag(ConversationViewModel.class); private static final String TAG = Log.tag(ConversationViewModel.class);
private final Application context; private final Application context;
private final MediaRepository mediaRepository; private final MediaRepository mediaRepository;
private final ConversationRepository conversationRepository; private final ConversationRepository conversationRepository;
private final MutableLiveData<List<Media>> recentMedia; private final MutableLiveData<List<Media>> recentMedia;
private final MutableLiveData<Long> threadId; private final MutableLiveData<Long> threadId;
private final LiveData<PagedList<MessageRecord>> messages; private final LiveData<PagedList<ConversationMessage>> messages;
private final LiveData<ConversationData> conversationMetadata; private final LiveData<ConversationData> conversationMetadata;
private final Invalidator invalidator; private final Invalidator invalidator;
private int jumpToPosition; private int jumpToPosition;
@ -55,12 +55,12 @@ class ConversationViewModel extends ViewModel {
return conversationData; return conversationData;
}); });
LiveData<Pair<Long, PagedList<MessageRecord>>> messagesForThreadId = Transformations.switchMap(metadata, data -> { LiveData<Pair<Long, PagedList<ConversationMessage>>> messagesForThreadId = Transformations.switchMap(metadata, data -> {
DataSource.Factory<Integer, MessageRecord> factory = new ConversationDataSource.Factory(context, data.getThreadId(), invalidator); DataSource.Factory<Integer, ConversationMessage> factory = new ConversationDataSource.Factory(context, data.getThreadId(), invalidator);
PagedList.Config config = new PagedList.Config.Builder() PagedList.Config config = new PagedList.Config.Builder()
.setPageSize(25) .setPageSize(25)
.setInitialLoadSizeHint(25) .setInitialLoadSizeHint(25)
.build(); .build();
final int startPosition; final int startPosition;
if (data.shouldJumpToMessage()) { if (data.shouldJumpToMessage()) {
@ -109,7 +109,7 @@ class ConversationViewModel extends ViewModel {
return conversationMetadata; return conversationMetadata;
} }
@NonNull LiveData<PagedList<MessageRecord>> getMessages() { @NonNull LiveData<PagedList<ConversationMessage>> getMessages() {
return messages; return messages;
} }

View File

@ -12,6 +12,7 @@ import androidx.recyclerview.widget.RecyclerView;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.conversation.ConversationItem; import org.thoughtcrime.securesms.conversation.ConversationItem;
import org.thoughtcrime.securesms.conversation.ConversationMessage;
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 org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.MessageSender;
@ -80,7 +81,7 @@ final class MessageHeaderViewHolder extends RecyclerView.ViewHolder {
else if (messageRecord.isOutgoing()) conversationItem = (ConversationItem) sentStub.inflate(); else if (messageRecord.isOutgoing()) conversationItem = (ConversationItem) sentStub.inflate();
else conversationItem = (ConversationItem) receivedStub.inflate(); else conversationItem = (ConversationItem) receivedStub.inflate();
} }
conversationItem.bind(messageRecord, Optional.absent(), Optional.absent(), glideRequests, Locale.getDefault(), new HashSet<>(), messageRecord.getRecipient(), null, false); conversationItem.bind(new ConversationMessage(messageRecord), Optional.absent(), Optional.absent(), glideRequests, Locale.getDefault(), new HashSet<>(), messageRecord.getRecipient(), null, false);
} }
private void bindErrorState(MessageRecord messageRecord) { private void bindErrorState(MessageRecord messageRecord) {