Refactor use of MessageRecord to increase flexibility of ConversationAdapter.
parent
5c110ca359
commit
9c63b37bb4
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue