make ConversationAdapter basically work

This commit is contained in:
B. Petersen
2018-09-20 00:31:18 +02:00
parent b86da2555a
commit 3091f29926
8 changed files with 115 additions and 139 deletions
@@ -23,7 +23,7 @@ public interface BindableConversationItem extends Unbindable {
@NonNull Optional<DcMsg> nextMessageRecord,
@NonNull GlideRequests glideRequests,
@NonNull Locale locale,
@NonNull int[] messageIdsSelected,
@NonNull Set<DcMsg> batchSelected,
@NonNull Recipient recipients,
boolean pulseHighlight);
@@ -75,14 +75,14 @@ import java.util.Set;
*
*/
public class ConversationAdapter <V extends View & BindableConversationItem>
extends RecyclerView.Adapter<RecyclerView.ViewHolder>
extends RecyclerView.Adapter
implements StickyHeaderDecoration.StickyHeaderAdapter<HeaderViewHolder>
{
private static final int MAX_CACHE_SIZE = 40;
private static final String TAG = ConversationAdapter.class.getSimpleName();
private final Map<String,SoftReference<MessageRecord>> messageRecordCache =
Collections.synchronizedMap(new LRUCache<String, SoftReference<MessageRecord>>(MAX_CACHE_SIZE));
private final Map<Integer,SoftReference<DcMsg>> messageRecordCache =
Collections.synchronizedMap(new LRUCache<Integer,SoftReference<DcMsg>>(MAX_CACHE_SIZE)); // TODO: use the cache
private static final int MESSAGE_TYPE_OUTGOING = 0;
private static final int MESSAGE_TYPE_INCOMING = 1;
@@ -106,8 +106,7 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
private final @NonNull MessageDigest digest;
private ApplicationDcContext dcContext;
private int[] dcMsgList = new int[0];
private DcChat dcChat;
private @NonNull int[] dcMsgList = new int[0];
private int recordToPulseHighlight;
protected static class ViewHolder extends RecyclerView.ViewHolder {
@@ -130,6 +129,10 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
return dcMsgList.length;
}
private DcMsg getMsg(int position) {
return dcContext.getMsg(dcMsgList[position]);
}
static class HeaderViewHolder extends RecyclerView.ViewHolder {
TextView textView;
@@ -197,10 +200,10 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
ConversationAdapter.ViewHolder holder = (ConversationAdapter.ViewHolder)viewHolder;
Optional<DcMsg> previous = position <= 0? Optional.absent() : Optional.of(dcContext.getMsg(dcMsgList[position -1]));
Optional<DcMsg> next = position >= dcMsgList.length ? Optional.absent() : Optional.of(dcContext.getMsg(dcMsgList[position +1]));
Optional<DcMsg> previous = position <= 0? Optional.absent() : Optional.of(getMsg(position-1));
Optional<DcMsg> next = position >= dcMsgList.length-1? Optional.absent() : Optional.of(getMsg(position+1));
boolean pulseHighlight = dcMsgList[position] == recordToPulseHighlight;
holder.getItem().bind(dcContext.getMsg(dcMsgList[position]), previous, next, glideRequests, locale, dcMsgList, recipient, pulseHighlight);
holder.getItem().bind(getMsg(position), previous, next, glideRequests, locale, batchSelected, recipient, pulseHighlight);
}
@Override
@@ -238,18 +241,42 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
}
}
@Override
public int getItemViewType(int i) {
DcMsg messageRecord = getMsg(i);
int type = messageRecord.getType();
if (messageRecord.isUpdate()) {
return MESSAGE_TYPE_UPDATE;
} else if (type==DcMsg.DC_MSG_AUDIO || type==DcMsg.DC_MSG_VOICE) {
if (messageRecord.isOutgoing()) return MESSAGE_TYPE_AUDIO_OUTGOING;
else return MESSAGE_TYPE_AUDIO_INCOMING;
} else if (type==DcMsg.DC_MSG_FILE) {
if (messageRecord.isOutgoing()) return MESSAGE_TYPE_DOCUMENT_OUTGOING;
else return MESSAGE_TYPE_DOCUMENT_INCOMING;
} else if (type==DcMsg.DC_MSG_IMAGE || type==DcMsg.DC_MSG_GIF || type==DcMsg.DC_MSG_VIDEO) {
if (messageRecord.isOutgoing()) return MESSAGE_TYPE_THUMBNAIL_OUTGOING;
else return MESSAGE_TYPE_THUMBNAIL_INCOMING;
} else if (messageRecord.isOutgoing()) {
return MESSAGE_TYPE_OUTGOING;
} else {
return MESSAGE_TYPE_INCOMING;
}
}
public int findLastSeenPosition(long lastSeen) {
/* TODO -- we shoud do this without loading all messages in the chat
if (lastSeen <= 0) return -1;
if (isActive()) return -1;
int count = getItemCount();
for (int i = 0;i<count;i++) {
DcMsg msg = dcContext.getMsg(dcMsgList[i]);
DcMsg msg = getMsg(i);
if (msg.isOutgoing() || msg.getTimestamp() <= lastSeen) {
return i;
}
}
*/
return -1;
}
@@ -275,21 +302,6 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
}
}
private boolean hasAudio(MessageRecord messageRecord) {
return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getAudioSlide() != null;
}
private boolean hasDocument(MessageRecord messageRecord) {
return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getDocumentSlide() != null;
}
private boolean hasThumbnail(MessageRecord messageRecord) {
return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getThumbnailSlide() != null;
}
private @Nullable View header;
private @Nullable View footer;
protected boolean isFooterPosition(int position) {
// return hasFooterView() && position == getItemCount() - 1;
return false;
@@ -326,7 +338,7 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
if (position >= getItemCount()) return 0;
if (position < 0) return 0;
DcMsg msg = dcContext.getMsg(dcMsgList[position]);
DcMsg msg = getMsg(position);
return msg.getTimestamp();
}
@@ -346,7 +358,7 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
@Override
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) {
DcMsg msg = dcContext.getMsg(dcMsgList[position]);
DcMsg msg = getMsg(position);
viewHolder.setText(DateUtils.getRelativeDate(getContext(), locale, msg.getTimestamp()));
}
@@ -404,18 +416,16 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
}
}
public void setFooterView(@Nullable View footer) {
// TODO: must not be implemented, remove calls to this function
public void changeData(@Nullable int[] dcMsgList) {
this.dcMsgList = dcMsgList==null? new int[0] : dcMsgList;
messageRecordCache.clear();
notifyDataSetChanged();
}
public void setHeaderView(@Nullable View footer) {
public void setHeaderView(@Nullable View headerView) {
// TODO: must not be implemented, remove calls to this function
}
public void changeCursor(Cursor cursor) {
// TODO: implement this function
}
public void addFastRecord(@NonNull MessageRecord record) {
// TODO: i think this is not need, we simply should reload the view
}
@@ -60,6 +60,7 @@ import org.thoughtcrime.securesms.ConversationAdapter.HeaderViewHolder;
import org.thoughtcrime.securesms.ConversationAdapter.ItemClickListener;
import org.thoughtcrime.securesms.connect.ApplicationDcContext;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.connect.DcMsgListLoader;
import org.thoughtcrime.securesms.contactshare.ContactUtil;
import org.thoughtcrime.securesms.contactshare.SharedContactDetailsActivity;
import org.thoughtcrime.securesms.contactshare.Contact;
@@ -94,7 +95,7 @@ import java.util.Set;
@SuppressLint("StaticFieldLeak")
public class ConversationFragment extends Fragment
implements LoaderManager.LoaderCallbacks<Cursor>
implements LoaderManager.LoaderCallbacks<int[]>
{
private static final String TAG = ConversationFragment.class.getSimpleName();
private static final String KEY_LIMIT = "limit";
@@ -461,54 +462,29 @@ public class ConversationFragment extends Fragment
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
public Loader<int[]> onCreateLoader(int id, Bundle args) {
Log.w(TAG, "onCreateLoader");
loaderStartTime = System.currentTimeMillis();
int limit = args.getInt(KEY_LIMIT, PARTIAL_CONVERSATION_LIMIT);
int offset = 0;
if (limit != 0 && startingPosition > limit) {
offset = Math.max(startingPosition - (limit / 2) + 1, 0);
startingPosition -= offset - 1;
}
return new ConversationLoader(getActivity(), threadId, offset, limit, lastSeen);
return new DcMsgListLoader(getActivity(), (int)threadId, 0, 0);
}
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
public void onLoadFinished(Loader<int[]> arg0, int[] dcMsgList) {
long loadTime = System.currentTimeMillis() - loaderStartTime;
int count = cursor.getCount();
Log.w(TAG, "onLoadFinished - took " + loadTime + " ms to load a cursor of size " + count);
ConversationLoader loader = (ConversationLoader)cursorLoader;
int count = dcMsgList.length;
Log.w(TAG, "onLoadFinished - took " + loadTime + " ms to load a message list of size " + count);
ConversationAdapter adapter = getListAdapter();
if (adapter == null) {
return;
}
if (cursor.getCount() >= PARTIAL_CONVERSATION_LIMIT && loader.hasLimit()) {
adapter.setFooterView(topLoadMoreView);
} else {
adapter.setFooterView(null);
}
if (lastSeen == -1) {
setLastSeen(loader.getLastSeen());
//setLastSeen(loader.getLastSeen()); -- TODO
}
if (!loader.hasSent() && !recipient.isSystemContact() && !recipient.isGroupRecipient() && recipient.getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) {
adapter.setHeaderView(unknownSenderView);
} else {
adapter.setHeaderView(null);
}
if (loader.hasOffset()) {
adapter.setHeaderView(bottomLoadMoreView);
previousOffset = loader.getOffset();
}
adapter.changeCursor(cursor);
adapter.changeData(dcMsgList);
int lastSeenPosition = adapter.findLastSeenPosition(lastSeen);
@@ -536,9 +512,9 @@ public class ConversationFragment extends Fragment
}
@Override
public void onLoaderReset(Loader<Cursor> arg0) {
public void onLoaderReset(Loader<int[]> arg0) {
if (list.getAdapter() != null) {
getListAdapter().changeCursor(null);
getListAdapter().changeData(null);
}
}
@@ -123,7 +123,7 @@ public class ConversationItem extends LinearLayout
private AlertView alertView;
private ViewGroup container;
private @NonNull int[] batchSelected = new int[0];
private @NonNull Set<DcMsg> batchSelected = new HashSet<>();
private @NonNull Recipient conversationRecipient;
private @NonNull Stub<ConversationItemThumbnail> mediaThumbnailStub;
private @NonNull Stub<AudioView> audioViewStub;
@@ -193,14 +193,14 @@ public class ConversationItem extends LinearLayout
@NonNull Optional<DcMsg> nextDcMsg,
@NonNull GlideRequests glideRequests,
@NonNull Locale locale,
@NonNull int[] messageIdsSelected,
@NonNull Set<DcMsg> batchSelected,
@NonNull Recipient recipients,
boolean pulseHighlight)
{
this.messageRecord = messageRecord;
this.locale = locale;
this.glideRequests = glideRequests;
this.batchSelected = messageIdsSelected;
this.batchSelected = batchSelected;
this.conversationRecipient = recipients;
this.groupThread = conversationRecipient.isGroupRecipient();
this.recipient = dcContext.getRecipient(dcContext.getChat(messageRecord.getChatId()));
@@ -329,20 +329,8 @@ public class ConversationItem extends LinearLayout
}
}
private boolean batchIsEmpty() {
return batchSelected.length == 0;
}
private boolean batchContains(DcMsg msg) {
for(int ii = 0; ii < batchSelected.length; ii++) {
if(batchSelected[ii] == msg.getId())
return true;
}
return false;
}
private void setInteractionState(DcMsg messageRecord, boolean pulseHighlight) {
if (batchContains(messageRecord)) {
if (batchSelected.contains(messageRecord)) {
setBackgroundResource(R.drawable.conversation_item_background);
setSelected(true);
} else if (pulseHighlight) {
@@ -354,20 +342,20 @@ public class ConversationItem extends LinearLayout
}
if (mediaThumbnailStub.resolved()) {
mediaThumbnailStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchIsEmpty());
mediaThumbnailStub.get().setClickable(!shouldInterceptClicks(messageRecord) && batchIsEmpty());
mediaThumbnailStub.get().setLongClickable(batchIsEmpty());
mediaThumbnailStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
mediaThumbnailStub.get().setClickable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
mediaThumbnailStub.get().setLongClickable(batchSelected.isEmpty());
}
if (audioViewStub.resolved()) {
audioViewStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchIsEmpty());
audioViewStub.get().setClickable(batchIsEmpty());
audioViewStub.get().setEnabled(batchIsEmpty());
audioViewStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
audioViewStub.get().setClickable(batchSelected.isEmpty());
audioViewStub.get().setEnabled(batchSelected.isEmpty());
}
if (documentViewStub.resolved()) {
documentViewStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchIsEmpty());
documentViewStub.get().setClickable(batchIsEmpty());
documentViewStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
documentViewStub.get().setClickable(batchSelected.isEmpty());
}
}
@@ -413,7 +401,7 @@ public class ConversationItem extends LinearLayout
if (isCaptionlessMms(messageRecord)) {
bodyText.setVisibility(View.GONE);
} else {
bodyText.setText(linkifyMessageBody(new SpannableString(messageRecord.getText()), batchIsEmpty()));
bodyText.setText(linkifyMessageBody(new SpannableString(messageRecord.getText()), batchSelected.isEmpty()));
bodyText.setVisibility(View.VISIBLE);
}
}
@@ -701,7 +689,7 @@ public class ConversationItem extends LinearLayout
}
private boolean shouldInterceptClicks(DcMsg messageRecord) {
return batchIsEmpty() && (messageRecord.isFailed() ||
return batchSelected.isEmpty() && (messageRecord.isFailed() ||
messageRecord.isSetupMessage());
}
@@ -824,7 +812,7 @@ public class ConversationItem extends LinearLayout
private class SharedContactEventListener implements SharedContactView.EventListener {
@Override
public void onAddToContactsClicked(@NonNull Contact contact) {
if (eventListener != null && batchIsEmpty()) {
if (eventListener != null && batchSelected.isEmpty()) {
eventListener.onAddToContactsClicked(contact);
} else {
passthroughClickListener.onClick(sharedContactStub.get());
@@ -833,7 +821,7 @@ public class ConversationItem extends LinearLayout
@Override
public void onInviteClicked(@NonNull List<Recipient> choices) {
if (eventListener != null && batchIsEmpty()) {
if (eventListener != null && batchSelected.isEmpty()) {
eventListener.onInviteSharedContactClicked(choices);
} else {
passthroughClickListener.onClick(sharedContactStub.get());
@@ -842,7 +830,7 @@ public class ConversationItem extends LinearLayout
@Override
public void onMessageClicked(@NonNull List<Recipient> choices) {
if (eventListener != null && batchIsEmpty()) {
if (eventListener != null && batchSelected.isEmpty()) {
eventListener.onMessageSharedContactClicked(choices);
} else {
passthroughClickListener.onClick(sharedContactStub.get());
@@ -884,7 +872,7 @@ public class ConversationItem extends LinearLayout
private class ThumbnailClickListener implements SlideClickListener {
public void onClick(final View v, final Slide slide) {
if (shouldInterceptClicks(messageRecord) || !batchIsEmpty()) {
if (shouldInterceptClicks(messageRecord) || !batchSelected.isEmpty()) {
performClick();
} else if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) {
Intent intent = new Intent(context, MediaPreviewActivity.class);
@@ -182,8 +182,8 @@ class ConversationListAdapter extends RecyclerView.Adapter {
void onSwitchToArchive();
}
void changeData(DcChatlist chatlist) {
dcChatlist = chatlist;
void changeData(@Nullable DcChatlist chatlist) {
dcChatlist = chatlist==null? new DcChatlist(0) : chatlist;
notifyDataSetChanged();
}
}
@@ -76,7 +76,7 @@ public class ConversationUpdateItem extends LinearLayout
@NonNull Optional<DcMsg> nextMessageRecord,
@NonNull GlideRequests glideRequests,
@NonNull Locale locale,
@NonNull int[] messageIdsSelected,
@NonNull Set<DcMsg> batchSelected,
@NonNull Recipient conversationRecipient,
boolean pulseUpdate)
{
@@ -0,0 +1,39 @@
package org.thoughtcrime.securesms.connect;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Log;
import com.b44t.messenger.DcChatlist;
import org.thoughtcrime.securesms.util.AsyncLoader;
public class DcMsgListLoader extends AsyncLoader<int[]> {
private static final String TAG = DcMsgListLoader.class.getName();
private final int chatId;
private final int listflags;
private final int marker1before;
public DcMsgListLoader(Context context, int chatId, int listflags, int marker1before) {
super(context);
this.chatId = chatId;
this.listflags = listflags;
this.marker1before = marker1before;
}
@Override
public @NonNull
int[] loadInBackground() {
try {
return DcHelper.getContext(getContext()).getChatMsgs(chatId, listflags, marker1before);
} catch (Exception e) {
Log.w(TAG, e);
}
return new int[0];
}
}
@@ -1,37 +0,0 @@
package org.thoughtcrime.securesms;
import android.database.Cursor;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.when;
public class ConversationAdapterTest extends BaseUnitTest {
private Cursor cursor = mock(Cursor.class);
private ConversationAdapter adapter;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
adapter = new ConversationAdapter(context, cursor);
when(cursor.getColumnIndexOrThrow(anyString())).thenReturn(0);
}
@Test
public void testGetItemIdEquals() throws Exception {
when(cursor.getString(anyInt())).thenReturn(null).thenReturn("SMS::1::1");
long firstId = adapter.getItemId(cursor);
when(cursor.getString(anyInt())).thenReturn(null).thenReturn("MMS::1::1");
long secondId = adapter.getItemId(cursor);
assertNotEquals(firstId, secondId);
when(cursor.getString(anyInt())).thenReturn(null).thenReturn("MMS::2::1");
long thirdId = adapter.getItemId(cursor);
assertNotEquals(secondId, thirdId);
}
}