migrate conversation list to RecyclerView

fixes #2488
fixes #2333
// FREEBIE
pull/1/head
Jake McGinty 9 years ago committed by Moxie Marlinspike
parent cbcd53a8a0
commit 99d3374d35

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="true" android:state_selected="true" <item android:drawable="@color/textsecure_primary_alpha33" android:state_selected="true" />
android:drawable="@drawable/list_selected_holo_light" /> </selector>
</selector>

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="true" android:state_selected="true"
android:drawable="@drawable/list_selected_holo_dark" />
</selector>

@ -5,18 +5,12 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
<ListView android:id="@android:id/list" <android.support.v7.widget.RecyclerView
android:layout_width="fill_parent" android:id="@android:id/list"
android:layout_height="0dp" android:layout_width="fill_parent"
android:layout_weight="1.0" android:layout_height="0dp"
android:drawSelectorOnTop="false" android:layout_weight="1.0"
android:transcriptMode="normal" android:scrollbars="vertical"
android:scrollbarAlwaysDrawVerticalTrack="false" android:cacheColorHint="?conversation_background" />
android:scrollbarStyle="insideOverlay"
android:stackFromBottom="true"
android:fadingEdge="none"
android:divider="@android:color/transparent"
android:dividerHeight="0dp"
android:cacheColorHint="?conversation_background" />
</LinearLayout> </LinearLayout>

@ -4,11 +4,9 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingRight="10dip" android:paddingRight="10dip"
android:orientation="vertical" android:orientation="vertical"
android:background="?conversation_item_background" android:background="@drawable/conversation_item_background"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools">
xmlns:dots="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView android:id="@+id/group_message_status" <TextView android:id="@+id/group_message_status"
android:layout_width="wrap_content" android:layout_width="wrap_content"

@ -2,12 +2,11 @@
<org.thoughtcrime.securesms.ConversationItem <org.thoughtcrime.securesms.ConversationItem
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/conversation_item" android:id="@+id/conversation_item"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:background="?conversation_item_background"> android:background="@drawable/conversation_item_background">
<RelativeLayout android:layout_width="match_parent" <RelativeLayout android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

@ -133,7 +133,6 @@
<item name="quick_camera_icon">@drawable/quick_camera_light</item> <item name="quick_camera_icon">@drawable/quick_camera_light</item>
<item name="conversation_item_background">@drawable/conversation_item_background</item>
<item name="conversation_item_sent_indicator_text_background">@drawable/conversation_item_sent_indicator_text_shape</item> <item name="conversation_item_sent_indicator_text_background">@drawable/conversation_item_sent_indicator_text_shape</item>
<item name="dialog_info_icon">@drawable/ic_info_outline_light</item> <item name="dialog_info_icon">@drawable/ic_info_outline_light</item>
@ -204,7 +203,6 @@
<item name="conversation_group_member_name">#99ffffff</item> <item name="conversation_group_member_name">#99ffffff</item>
<item name="conversation_item_background">@drawable/conversation_item_background_dark</item>
<item name="conversation_item_bubble_background">#ff333333</item> <item name="conversation_item_bubble_background">#ff333333</item>
<item name="conversation_item_sent_text_primary_color">#ffffffff</item> <item name="conversation_item_sent_text_primary_color">#ffffffff</item>
<item name="conversation_item_sent_text_secondary_color">#aaeeeeee</item> <item name="conversation_item_sent_text_secondary_color">#aaeeeeee</item>

@ -0,0 +1,17 @@
package org.thoughtcrime.securesms;
import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import java.util.Locale;
import java.util.Set;
public interface BindableConversationItem extends Unbindable {
void bind(@NonNull MasterSecret masterSecret,
@NonNull MessageRecord messageRecord,
@NonNull Locale locale,
@NonNull Set<MessageRecord> batchSelected,
boolean groupThread, boolean pushDestination);
}

@ -1368,11 +1368,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
public void onFocusChange(View v, boolean hasFocus) {} public void onFocusChange(View v, boolean hasFocus) {}
} }
@Override
public void setComposeText(String text) {
this.composeText.setText(text);
}
@Override @Override
public void setThreadId(long threadId) { public void setThreadId(long threadId) {
this.threadId = threadId; this.threadId = threadId;

@ -18,13 +18,18 @@ package org.thoughtcrime.securesms;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AbsListView;
import android.support.v4.widget.CursorAdapter;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.MmsSmsDatabase; import org.thoughtcrime.securesms.database.MmsSmsDatabase;
@ -39,7 +44,7 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.thoughtcrime.securesms.ConversationFragment.SelectionClickListener; import org.thoughtcrime.securesms.util.ViewUtil;
/** /**
* A cursor adapter for a conversation thread. Ultimately * A cursor adapter for a conversation thread. Ultimately
@ -49,7 +54,9 @@ import org.thoughtcrime.securesms.ConversationFragment.SelectionClickListener;
* @author Moxie Marlinspike * @author Moxie Marlinspike
* *
*/ */
public class ConversationAdapter extends CursorAdapter implements AbsListView.RecyclerListener { public class ConversationAdapter <V extends View & BindableConversationItem>
extends CursorRecyclerViewAdapter<ConversationAdapter.ViewHolder>
{
private static final int MAX_CACHE_SIZE = 40; private static final int MAX_CACHE_SIZE = 40;
private final Map<String,SoftReference<MessageRecord>> messageRecordCache = private final Map<String,SoftReference<MessageRecord>> messageRecordCache =
@ -61,46 +68,46 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re
private final Set<MessageRecord> batchSelected = Collections.synchronizedSet(new HashSet<MessageRecord>()); private final Set<MessageRecord> batchSelected = Collections.synchronizedSet(new HashSet<MessageRecord>());
private final SelectionClickListener selectionClickListener; private final ItemClickListener clickListener;
private final Context context;
private final MasterSecret masterSecret; private final MasterSecret masterSecret;
private final Locale locale; private final Locale locale;
private final boolean groupThread; private final boolean groupThread;
private final boolean pushDestination; private final boolean pushDestination;
private final MmsSmsDatabase db;
private final LayoutInflater inflater; private final LayoutInflater inflater;
public ConversationAdapter(Context context, MasterSecret masterSecret, Locale locale, protected static class ViewHolder extends RecyclerView.ViewHolder {
SelectionClickListener selectionClickListener, boolean groupThread, public <V extends View & BindableConversationItem> ViewHolder(final @NonNull V itemView) {
boolean pushDestination) super(itemView);
{ }
super(context, null, 0);
this.context = context; @SuppressWarnings("unchecked")
this.masterSecret = masterSecret; public <V extends View & BindableConversationItem> V getView() {
this.locale = locale; return (V)itemView;
this.selectionClickListener = selectionClickListener; }
this.groupThread = groupThread;
this.pushDestination = pushDestination;
this.inflater = LayoutInflater.from(context);
} }
@Override public interface ItemClickListener {
public void bindView(View view, Context context, Cursor cursor) { void onItemClick(ConversationItem item);
long id = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID)); void onItemLongClick(ConversationItem item);
String type = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT)); }
MessageRecord messageRecord = getMessageRecord(id, cursor, type);
switch (getItemViewType(cursor)) { public ConversationAdapter(@NonNull Context context,
case MESSAGE_TYPE_INCOMING: @NonNull MasterSecret masterSecret,
case MESSAGE_TYPE_OUTGOING: @NonNull Locale locale,
((ConversationItem) view).set(masterSecret, messageRecord, locale, batchSelected, @Nullable ItemClickListener clickListener,
selectionClickListener, groupThread, pushDestination); @Nullable Cursor cursor,
break; boolean groupThread,
case MESSAGE_TYPE_UPDATE: boolean pushDestination)
((ConversationUpdateItem)view).set(messageRecord); {
break; super(context, cursor);
default: this.masterSecret = masterSecret;
throw new AssertionError("Unknown type!"); this.locale = locale;
} this.clickListener = clickListener;
this.groupThread = groupThread;
this.pushDestination = pushDestination;
this.inflater = LayoutInflater.from(context);
this.db = DatabaseFactory.getMmsSmsDatabase(context);
} }
@Override @Override
@ -109,40 +116,50 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re
super.changeCursor(cursor); super.changeCursor(cursor);
} }
@Override @Override public void onBindViewHolder(ViewHolder viewHolder, @NonNull Cursor cursor) {
public View newView(Context context, Cursor cursor, ViewGroup parent) { long id = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID));
View view; String type = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT));
MessageRecord messageRecord = getMessageRecord(id, cursor, type);
int type = getItemViewType(cursor);
viewHolder.getView().bind(masterSecret, messageRecord, locale, batchSelected, groupThread, pushDestination);
switch (type) { }
case ConversationAdapter.MESSAGE_TYPE_OUTGOING:
view = inflater.inflate(R.layout.conversation_item_sent, parent, false); @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
break; final V itemView = ViewUtil.inflate(inflater, parent, getLayoutForViewType(viewType));
case ConversationAdapter.MESSAGE_TYPE_INCOMING: if (viewType == MESSAGE_TYPE_INCOMING || viewType == MESSAGE_TYPE_OUTGOING) {
view = inflater.inflate(R.layout.conversation_item_received, parent, false); itemView.setOnClickListener(new OnClickListener() {
break; @Override
case ConversationAdapter.MESSAGE_TYPE_UPDATE: public void onClick(View view) {
view = inflater.inflate(R.layout.conversation_item_update, parent, false); if (clickListener != null) clickListener.onItemClick((ConversationItem)itemView);
break; }
default: throw new IllegalArgumentException("unsupported item view type given to ConversationAdapter"); });
itemView.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
if (clickListener != null) clickListener.onItemLongClick((ConversationItem)itemView);
return true;
}
});
} }
return view; return new ViewHolder(itemView);
} }
@Override @Override public void onViewRecycled(ViewHolder holder) {
public int getViewTypeCount() { holder.getView().unbind();
return 3;
} }
@Override private @LayoutRes int getLayoutForViewType(int viewType) {
public int getItemViewType(int position) { switch (viewType) {
Cursor cursor = (Cursor)getItem(position); case ConversationAdapter.MESSAGE_TYPE_OUTGOING: return R.layout.conversation_item_sent;
return getItemViewType(cursor); case ConversationAdapter.MESSAGE_TYPE_INCOMING: return R.layout.conversation_item_received;
case ConversationAdapter.MESSAGE_TYPE_UPDATE: return R.layout.conversation_item_update;
default: throw new IllegalArgumentException("unsupported item view type given to ConversationAdapter");
}
} }
private int getItemViewType(Cursor cursor) { @Override
public int getItemViewType(@NonNull Cursor cursor) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.ID)); long id = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.ID));
String type = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT)); String type = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT));
MessageRecord messageRecord = getMessageRecord(id, cursor, type); MessageRecord messageRecord = getMessageRecord(id, cursor, type);
@ -153,43 +170,33 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re
} }
private MessageRecord getMessageRecord(long messageId, Cursor cursor, String type) { private MessageRecord getMessageRecord(long messageId, Cursor cursor, String type) {
SoftReference<MessageRecord> reference = messageRecordCache.get(type + messageId); final SoftReference<MessageRecord> reference = messageRecordCache.get(type + messageId);
if (reference != null) { if (reference != null) {
MessageRecord record = reference.get(); final MessageRecord record = reference.get();
if (record != null) return record;
if (record != null)
return record;
} }
MmsSmsDatabase.Reader reader = DatabaseFactory.getMmsSmsDatabase(context) final MessageRecord messageRecord = db.readerFor(cursor, masterSecret).getCurrent();
.readerFor(cursor, masterSecret);
MessageRecord messageRecord = reader.getCurrent();
messageRecordCache.put(type + messageId, new SoftReference<>(messageRecord)); messageRecordCache.put(type + messageId, new SoftReference<>(messageRecord));
return messageRecord; return messageRecord;
} }
public void close() { public void close() {
this.getCursor().close(); getCursor().close();
} }
public void toggleBatchSelected(MessageRecord messageRecord) { public void toggleSelection(MessageRecord messageRecord) {
if (batchSelected.contains(messageRecord)) { if (!batchSelected.remove(messageRecord)) {
batchSelected.remove(messageRecord);
} else {
batchSelected.add(messageRecord); batchSelected.add(messageRecord);
} }
} }
public Set<MessageRecord> getBatchSelected() { public void clearSelection() {
return batchSelected; batchSelected.clear();
} }
@Override public Set<MessageRecord> getSelectedItems() {
public void onMovedToScrapHeap(View view) { return Collections.unmodifiableSet(new HashSet<>(batchSelected));
((Unbindable) view).unbind();
} }
} }

@ -8,12 +8,13 @@ import android.database.Cursor;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.ListFragment; import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.support.v7.view.ActionMode; import android.support.v7.view.ActionMode;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.ClipboardManager; import android.text.ClipboardManager;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
@ -24,12 +25,11 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.Window; import android.view.Window;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast; import android.widget.Toast;
import com.afollestad.materialdialogs.AlertDialogWrapper; import com.afollestad.materialdialogs.AlertDialogWrapper;
import org.thoughtcrime.securesms.ConversationAdapter.ItemClickListener;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsSmsDatabase; import org.thoughtcrime.securesms.database.MmsSmsDatabase;
@ -45,21 +45,22 @@ import org.thoughtcrime.securesms.util.FutureTaskListener;
import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask;
import org.thoughtcrime.securesms.util.SaveAttachmentTask; import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment; import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set;
public class ConversationFragment extends ListFragment public class ConversationFragment extends Fragment
implements LoaderManager.LoaderCallbacks<Cursor> implements LoaderManager.LoaderCallbacks<Cursor>
{ {
private static final String TAG = ConversationFragment.class.getSimpleName(); private static final String TAG = ConversationFragment.class.getSimpleName();
private final ActionModeCallback actionModeCallback = new ActionModeCallback(); private final ActionModeCallback actionModeCallback = new ActionModeCallback();
private final SelectionClickListener selectionClickListener = new ConversationFragmentSelectionClickListener(); private final ItemClickListener selectionClickListener = new ConversationFragmentItemClickListener();
private ConversationFragmentListener listener; private ConversationFragmentListener listener;
@ -68,6 +69,7 @@ public class ConversationFragment extends ListFragment
private long threadId; private long threadId;
private ActionMode actionMode; private ActionMode actionMode;
private Locale locale; private Locale locale;
private RecyclerView list;
@Override @Override
public void onCreate(Bundle icicle) { public void onCreate(Bundle icicle) {
@ -78,16 +80,24 @@ public class ConversationFragment extends ListFragment
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
return inflater.inflate(R.layout.conversation_fragment, container, false); final View view = inflater.inflate(R.layout.conversation_fragment, container, false);
list = ViewUtil.findById(view, android.R.id.list);
return view;
} }
@Override @Override
public void onActivityCreated(Bundle bundle) { public void onActivityCreated(Bundle bundle) {
super.onActivityCreated(bundle); super.onActivityCreated(bundle);
final LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
layoutManager.setReverseLayout(true);
list.setHasFixedSize(false);
list.setScrollContainer(true);
list.setLayoutManager(layoutManager);
initializeResources(); initializeResources();
initializeListAdapter(); initializeListAdapter();
initializeContextualActionBar();
} }
@Override @Override
@ -100,8 +110,8 @@ public class ConversationFragment extends ListFragment
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
if (getListAdapter() != null) { if (list.getAdapter() != null) {
((ConversationAdapter) getListAdapter()).notifyDataSetChanged(); list.getAdapter().notifyDataSetChanged();
} }
} }
@ -125,21 +135,15 @@ public class ConversationFragment extends ListFragment
private void initializeListAdapter() { private void initializeListAdapter() {
if (this.recipients != null && this.threadId != -1) { if (this.recipients != null && this.threadId != -1) {
this.setListAdapter(new ConversationAdapter(getActivity(), masterSecret, locale, selectionClickListener, list.setAdapter(new ConversationAdapter(getActivity(), masterSecret, locale, selectionClickListener, null,
(!this.recipients.isSingleRecipient()) || this.recipients.isGroupRecipient(), (!this.recipients.isSingleRecipient()) || this.recipients.isGroupRecipient(),
DirectoryHelper.isPushDestination(getActivity(), this.recipients))); DirectoryHelper.isPushDestination(getActivity(), this.recipients)));
getListView().setRecyclerListener((ConversationAdapter)getListAdapter());
getLoaderManager().restartLoader(0, null, this); getLoaderManager().restartLoader(0, null, this);
} }
} }
private void initializeContextualActionBar() {
getListView().setOnItemClickListener(selectionClickListener);
getListView().setOnItemLongClickListener(selectionClickListener);
}
private void setCorrectMenuVisibility(Menu menu) { private void setCorrectMenuVisibility(Menu menu) {
List<MessageRecord> messageRecords = getSelectedMessageRecords(); Set<MessageRecord> messageRecords = getListAdapter().getSelectedItems();
if (actionMode != null && messageRecords.size() == 0) { if (actionMode != null && messageRecords.size() == 0) {
actionMode.finish(); actionMode.finish();
@ -152,7 +156,7 @@ public class ConversationFragment extends ListFragment
menu.findItem(R.id.menu_context_save_attachment).setVisible(false); menu.findItem(R.id.menu_context_save_attachment).setVisible(false);
menu.findItem(R.id.menu_context_resend).setVisible(false); menu.findItem(R.id.menu_context_resend).setVisible(false);
} else { } else {
MessageRecord messageRecord = messageRecords.get(0); MessageRecord messageRecord = messageRecords.iterator().next();
menu.findItem(R.id.menu_context_resend).setVisible(messageRecord.isFailed()); menu.findItem(R.id.menu_context_resend).setVisible(messageRecord.isFailed());
menu.findItem(R.id.menu_context_save_attachment).setVisible(messageRecord.isMms() && menu.findItem(R.id.menu_context_save_attachment).setVisible(messageRecord.isMms() &&
@ -165,17 +169,17 @@ public class ConversationFragment extends ListFragment
} }
} }
private ConversationAdapter getListAdapter() {
return (ConversationAdapter) list.getAdapter();
}
private MessageRecord getSelectedMessageRecord() { private MessageRecord getSelectedMessageRecord() {
List<MessageRecord> messageRecords = getSelectedMessageRecords(); Set<MessageRecord> messageRecords = getListAdapter().getSelectedItems();
if (messageRecords.size() == 1) return messageRecords.get(0); if (messageRecords.size() == 1) return messageRecords.iterator().next();
else throw new AssertionError(); else throw new AssertionError();
} }
private List<MessageRecord> getSelectedMessageRecords() {
return new LinkedList<>(((ConversationAdapter)getListAdapter()).getBatchSelected());
}
public void reload(Recipients recipients, long threadId) { public void reload(Recipients recipients, long threadId) {
this.recipients = recipients; this.recipients = recipients;
@ -186,17 +190,18 @@ public class ConversationFragment extends ListFragment
} }
public void scrollToBottom() { public void scrollToBottom() {
final ListView list = getListView();
list.post(new Runnable() { list.post(new Runnable() {
@Override @Override
public void run() { public void run() {
list.setSelection(getListAdapter().getCount() - 1); list.stopScroll();
list.smoothScrollToPosition(0);
} }
}); });
} }
private void handleCopyMessage(final List<MessageRecord> messageRecords) { private void handleCopyMessage(final Set<MessageRecord> messageRecords) {
Collections.sort(messageRecords, new Comparator<MessageRecord>() { List<MessageRecord> messageList = new LinkedList<>(messageRecords);
Collections.sort(messageList, new Comparator<MessageRecord>() {
@Override @Override
public int compare(MessageRecord lhs, MessageRecord rhs) { public int compare(MessageRecord lhs, MessageRecord rhs) {
if (lhs.getDateReceived() < rhs.getDateReceived()) return -1; if (lhs.getDateReceived() < rhs.getDateReceived()) return -1;
@ -209,7 +214,7 @@ public class ConversationFragment extends ListFragment
ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
boolean first = true; boolean first = true;
for (MessageRecord messageRecord : messageRecords) { for (MessageRecord messageRecord : messageList) {
String body = messageRecord.getDisplayBody().toString(); String body = messageRecord.getDisplayBody().toString();
if (body != null) { if (body != null) {
@ -225,7 +230,7 @@ public class ConversationFragment extends ListFragment
clipboard.setText(result); clipboard.setText(result);
} }
private void handleDeleteMessages(final List<MessageRecord> messageRecords) { private void handleDeleteMessages(final Set<MessageRecord> messageRecords) {
AlertDialogWrapper.Builder builder = new AlertDialogWrapper.Builder(getActivity()); AlertDialogWrapper.Builder builder = new AlertDialogWrapper.Builder(getActivity());
builder.setTitle(R.string.ConversationFragment_confirm_message_delete); builder.setTitle(R.string.ConversationFragment_confirm_message_delete);
builder.setIconAttribute(R.attr.dialog_alert_icon); builder.setIconAttribute(R.attr.dialog_alert_icon);
@ -319,53 +324,41 @@ public class ConversationFragment extends ListFragment
@Override @Override
public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) { public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) {
if (getListAdapter() != null) { if (list.getAdapter() != null) {
((CursorAdapter) getListAdapter()).changeCursor(cursor); getListAdapter().changeCursor(cursor);
} }
} }
@Override @Override
public void onLoaderReset(Loader<Cursor> arg0) { public void onLoaderReset(Loader<Cursor> arg0) {
if (getListAdapter() != null) { if (list.getAdapter() != null) {
((CursorAdapter) getListAdapter()).changeCursor(null); getListAdapter().changeCursor(null);
} }
} }
public interface ConversationFragmentListener { public interface ConversationFragmentListener {
public void setComposeText(String text); void setThreadId(long threadId);
public void setThreadId(long threadId);
} }
public interface SelectionClickListener extends private class ConversationFragmentItemClickListener implements ItemClickListener {
AdapterView.OnItemLongClickListener, AdapterView.OnItemClickListener {}
private class ConversationFragmentSelectionClickListener @Override public void onItemClick(ConversationItem item) {
implements SelectionClickListener if (actionMode != null) {
{ MessageRecord messageRecord = item.getMessageRecord();
@Override ((ConversationAdapter) list.getAdapter()).toggleSelection(messageRecord);
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { list.getAdapter().notifyDataSetChanged();
if (actionMode != null && view instanceof ConversationItem) {
MessageRecord messageRecord = ((ConversationItem)view).getMessageRecord();
((ConversationAdapter) getListAdapter()).toggleBatchSelected(messageRecord);
((ConversationAdapter) getListAdapter()).notifyDataSetChanged();
setCorrectMenuVisibility(actionMode.getMenu()); setCorrectMenuVisibility(actionMode.getMenu());
} }
} }
@Override @Override public void onItemLongClick(ConversationItem item) {
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { if (actionMode == null) {
if (actionMode == null && view instanceof ConversationItem) { ((ConversationAdapter) list.getAdapter()).toggleSelection(item.getMessageRecord());
MessageRecord messageRecord = ((ConversationItem)view).getMessageRecord(); list.getAdapter().notifyDataSetChanged();
((ConversationAdapter) getListAdapter()).toggleBatchSelected(messageRecord);
((ConversationAdapter) getListAdapter()).notifyDataSetChanged();
actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(actionModeCallback); actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(actionModeCallback);
return true;
} }
return false;
} }
} }
@ -395,8 +388,8 @@ public class ConversationFragment extends ListFragment
@Override @Override
public void onDestroyActionMode(ActionMode mode) { public void onDestroyActionMode(ActionMode mode) {
((ConversationAdapter)getListAdapter()).getBatchSelected().clear(); ((ConversationAdapter)list.getAdapter()).clearSelection();
((ConversationAdapter)getListAdapter()).notifyDataSetChanged(); list.getAdapter().notifyDataSetChanged();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getActivity().getWindow().setStatusBarColor(statusBarColor); getActivity().getWindow().setStatusBarColor(statusBarColor);
@ -409,11 +402,11 @@ public class ConversationFragment extends ListFragment
public boolean onActionItemClicked(ActionMode mode, MenuItem item) { public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch(item.getItemId()) { switch(item.getItemId()) {
case R.id.menu_context_copy: case R.id.menu_context_copy:
handleCopyMessage(getSelectedMessageRecords()); handleCopyMessage(getListAdapter().getSelectedItems());
actionMode.finish(); actionMode.finish();
return true; return true;
case R.id.menu_context_delete_message: case R.id.menu_context_delete_message:
handleDeleteMessages(getSelectedMessageRecords()); handleDeleteMessages(getListAdapter().getSelectedItems());
actionMode.finish(); actionMode.finish();
return true; return true;
case R.id.menu_context_details: case R.id.menu_context_details:
@ -436,5 +429,5 @@ public class ConversationFragment extends ListFragment
return false; return false;
} }
}; }
} }

@ -23,10 +23,10 @@ import android.content.Intent;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.util.Linkify;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -40,7 +40,6 @@ import android.widget.Toast;
import com.afollestad.materialdialogs.AlertDialogWrapper; import com.afollestad.materialdialogs.AlertDialogWrapper;
import org.thoughtcrime.securesms.ConversationFragment.SelectionClickListener;
import org.thoughtcrime.securesms.components.AvatarImageView; import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.ThumbnailView; import org.thoughtcrime.securesms.components.ThumbnailView;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
@ -74,7 +73,9 @@ import java.util.Set;
* *
*/ */
public class ConversationItem extends LinearLayout implements Recipient.RecipientModifiedListener, Unbindable { public class ConversationItem extends LinearLayout
implements Recipient.RecipientModifiedListener, BindableConversationItem
{
private final static String TAG = ConversationItem.class.getSimpleName(); private final static String TAG = ConversationItem.class.getSimpleName();
private MessageRecord messageRecord; private MessageRecord messageRecord;
@ -97,16 +98,13 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien
private View pendingIndicator; private View pendingIndicator;
private ImageView pendingApprovalIndicator; private ImageView pendingApprovalIndicator;
private StatusManager statusManager; private StatusManager statusManager;
private Set<MessageRecord> batchSelected; private Set<MessageRecord> batchSelected;
private SelectionClickListener selectionClickListener; private ThumbnailView mediaThumbnail;
private ThumbnailView mediaThumbnail; private Button mmsDownloadButton;
private Button mmsDownloadButton; private TextView mmsDownloadingLabel;
private TextView mmsDownloadingLabel;
private int defaultBubbleColor; private int defaultBubbleColor;
private Drawable selectedBackground;
private Drawable normalBackground;
private final MmsDownloadClickListener mmsDownloadClickListener = new MmsDownloadClickListener(); private final MmsDownloadClickListener mmsDownloadClickListener = new MmsDownloadClickListener();
private final MmsPreferencesClickListener mmsPreferencesClickListener = new MmsPreferencesClickListener(); private final MmsPreferencesClickListener mmsPreferencesClickListener = new MmsPreferencesClickListener();
@ -114,9 +112,8 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien
private final Context context; private final Context context;
public ConversationItem(Context context) { public ConversationItem(Context context) {
super(context); this(context, null);
this.context = context; }
}
public ConversationItem(Context context, AttributeSet attrs) { public ConversationItem(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
@ -131,7 +128,7 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien
ViewGroup pendingIndicatorStub = (ViewGroup) findViewById(R.id.pending_indicator_stub); ViewGroup pendingIndicatorStub = (ViewGroup) findViewById(R.id.pending_indicator_stub);
if (pendingIndicatorStub != null) { if (pendingIndicatorStub != null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); LayoutInflater inflater = LayoutInflater.from(context);
if (Build.VERSION.SDK_INT >= 11) inflater.inflate(R.layout.conversation_item_pending_v11, pendingIndicatorStub, true); if (Build.VERSION.SDK_INT >= 11) inflater.inflate(R.layout.conversation_item_pending_v11, pendingIndicatorStub, true);
else inflater.inflate(R.layout.conversation_item_pending, pendingIndicatorStub, true); else inflater.inflate(R.layout.conversation_item_pending, pendingIndicatorStub, true);
} }
@ -154,33 +151,33 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien
this.statusManager = new StatusManager(pendingIndicator, sentIndicator, deliveredIndicator, failedIndicator, pendingApprovalIndicator); this.statusManager = new StatusManager(pendingIndicator, sentIndicator, deliveredIndicator, failedIndicator, pendingApprovalIndicator);
setOnClickListener(clickListener); setOnClickListener(clickListener);
PassthroughClickListener passthroughClickListener = new PassthroughClickListener();
if (mmsDownloadButton != null) mmsDownloadButton.setOnClickListener(mmsDownloadClickListener); if (mmsDownloadButton != null) mmsDownloadButton.setOnClickListener(mmsDownloadClickListener);
if (mediaThumbnail != null) { mediaThumbnail.setThumbnailClickListener(new ThumbnailClickListener());
mediaThumbnail.setThumbnailClickListener(new ThumbnailClickListener()); mediaThumbnail.setDownloadClickListener(new ThumbnailDownloadClickListener());
mediaThumbnail.setOnLongClickListener(new MultiSelectLongClickListener()); mediaThumbnail.setOnLongClickListener(passthroughClickListener);
mediaThumbnail.setDownloadClickListener(new ThumbnailDownloadClickListener()); bodyText.setOnLongClickListener(passthroughClickListener);
} bodyText.setOnClickListener(passthroughClickListener);
} }
public void set(@NonNull MasterSecret masterSecret, @Override
@NonNull MessageRecord messageRecord, public void bind(@NonNull MasterSecret masterSecret,
@NonNull Locale locale, @NonNull MessageRecord messageRecord,
@NonNull Set<MessageRecord> batchSelected, @NonNull Locale locale,
@NonNull SelectionClickListener selectionClickListener, @NonNull Set<MessageRecord> batchSelected,
boolean groupThread, boolean pushDestination) boolean groupThread, boolean pushDestination)
{ {
this.masterSecret = masterSecret; this.masterSecret = masterSecret;
this.messageRecord = messageRecord; this.messageRecord = messageRecord;
this.locale = locale; this.locale = locale;
this.batchSelected = batchSelected; this.batchSelected = batchSelected;
this.selectionClickListener = selectionClickListener;
this.groupThread = groupThread; this.groupThread = groupThread;
this.pushDestination = pushDestination; this.pushDestination = pushDestination;
this.recipient = messageRecord.getIndividualRecipient(); this.recipient = messageRecord.getIndividualRecipient();
this.recipient.addListener(this); this.recipient.addListener(this);
setSelectionBackgroundDrawables(messageRecord); setSelectionState(messageRecord);
setBodyText(messageRecord); setBodyText(messageRecord);
setBubbleState(messageRecord, recipient); setBubbleState(messageRecord, recipient);
setStatusIcons(messageRecord); setStatusIcons(messageRecord);
@ -198,8 +195,6 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien
final TypedArray attrs = context.obtainStyledAttributes(attributes); final TypedArray attrs = context.obtainStyledAttributes(attributes);
defaultBubbleColor = attrs.getColor(0, Color.WHITE); defaultBubbleColor = attrs.getColor(0, Color.WHITE);
selectedBackground = attrs.getDrawable(1);
normalBackground = attrs.getDrawable(2);
attrs.recycle(); attrs.recycle();
} }
@ -227,12 +222,11 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien
} }
} }
private void setSelectionBackgroundDrawables(MessageRecord messageRecord) { private void setSelectionState(MessageRecord messageRecord) {
if (batchSelected.contains(messageRecord)) { setSelected(batchSelected.contains(messageRecord));
setBackgroundDrawable(selectedBackground); mediaThumbnail.setClickable(batchSelected.isEmpty());
} else { mediaThumbnail.setLongClickable(batchSelected.isEmpty());
setBackgroundDrawable(normalBackground); bodyText.setAutoLinkMask(batchSelected.isEmpty() ? Linkify.ALL : 0);
}
} }
private boolean isCaptionlessMms(MessageRecord messageRecord) { private boolean isCaptionlessMms(MessageRecord messageRecord) {
@ -255,11 +249,6 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien
bodyText.setText(messageRecord.getDisplayBody()); bodyText.setText(messageRecord.getDisplayBody());
bodyText.setVisibility(View.VISIBLE); bodyText.setVisibility(View.VISIBLE);
} }
if (bodyText.isClickable() && bodyText.isFocusable()) {
bodyText.setOnLongClickListener(new MultiSelectLongClickListener());
bodyText.setOnClickListener(new MultiSelectLongClickListener());
}
} }
private void setMediaAttributes(MessageRecord messageRecord) { private void setMediaAttributes(MessageRecord messageRecord) {
@ -334,12 +323,9 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien
private void setEvents(MessageRecord messageRecord) { private void setEvents(MessageRecord messageRecord) {
setClickable(batchSelected.isEmpty() && setClickable(batchSelected.isEmpty() &&
(messageRecord.isFailed() || (messageRecord.isFailed() ||
messageRecord.isPendingInsecureSmsFallback() || messageRecord.isPendingInsecureSmsFallback() ||
messageRecord.isBundleKeyExchange())); messageRecord.isBundleKeyExchange()));
if (messageRecord.isFailed()) {
setOnLongClickListener(new MultiSelectLongClickListener());
}
} }
private void setGroupMessageStatus(MessageRecord messageRecord, Recipient recipient) { private void setGroupMessageStatus(MessageRecord messageRecord, Recipient recipient) {
@ -410,7 +396,7 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien
} }
private class ThumbnailDownloadClickListener implements ThumbnailView.ThumbnailClickListener { private class ThumbnailDownloadClickListener implements ThumbnailView.ThumbnailClickListener {
@Override public void onClick(View v, Slide slide) { @Override public void onClick(View v, final Slide slide) {
DatabaseFactory.getPartDatabase(context).setTransferState(messageRecord.getId(), slide.getPart().getPartId(), PartDatabase.TRANSFER_PROGRESS_STARTED); DatabaseFactory.getPartDatabase(context).setTransferState(messageRecord.getId(), slide.getPart().getPartId(), PartDatabase.TRANSFER_PROGRESS_STARTED);
} }
} }
@ -430,10 +416,9 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien
} }
public void onClick(final View v, final Slide slide) { public void onClick(final View v, final Slide slide) {
if (!batchSelected.isEmpty()) { if (batchSelected.isEmpty() &&
selectionClickListener.onItemClick(null, ConversationItem.this, -1, -1); MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) &&
} else if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) && slide.getThumbnailUri() != null)
slide.getThumbnailUri() != null)
{ {
Intent intent = new Intent(context, MediaPreviewActivity.class); Intent intent = new Intent(context, MediaPreviewActivity.class);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@ -483,11 +468,22 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien
} }
} }
private class PassthroughClickListener implements View.OnLongClickListener, View.OnClickListener {
@Override public boolean onLongClick(View v) {
performLongClick();
return true;
}
@Override public void onClick(View v) {
performClick();
}
}
private class ClickListener implements View.OnClickListener { private class ClickListener implements View.OnClickListener {
public void onClick(View v) { public void onClick(View v) {
if (messageRecord.isFailed() && !batchSelected.isEmpty()) { if (!batchSelected.isEmpty()) return;
selectionClickListener.onItemClick(null, ConversationItem.this, -1, -1);
} else if(messageRecord.isFailed()) { if (messageRecord.isFailed()) {
Intent intent = new Intent(context, MessageDetailsActivity.class); Intent intent = new Intent(context, MessageDetailsActivity.class);
intent.putExtra(MessageDetailsActivity.MASTER_SECRET_EXTRA, masterSecret); intent.putExtra(MessageDetailsActivity.MASTER_SECRET_EXTRA, masterSecret);
intent.putExtra(MessageDetailsActivity.MESSAGE_ID_EXTRA, messageRecord.getId()); intent.putExtra(MessageDetailsActivity.MESSAGE_ID_EXTRA, messageRecord.getId());
@ -502,19 +498,6 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien
} }
} }
private class MultiSelectLongClickListener implements OnLongClickListener, OnClickListener {
@Override
public boolean onLongClick(View view) {
selectionClickListener.onItemLongClick(null, ConversationItem.this, -1, -1);
return true;
}
@Override
public void onClick(View view) {
selectionClickListener.onItemClick(null, ConversationItem.this, -1, -1);
}
}
private void handleMessageApproval() { private void handleMessageApproval() {
final int title; final int title;
final int message; final int message;

@ -100,8 +100,12 @@ public class ConversationListAdapter extends CursorRecyclerViewAdapter<Conversat
parent, false), clickListener); parent, false), clickListener);
} }
@Override public void onViewRecycled(ViewHolder holder) {
holder.getItem().unbind();
}
@Override @Override
public void onBindViewHolder(ViewHolder viewHolder, Cursor cursor) { public void onBindViewHolder(ViewHolder viewHolder, @NonNull Cursor cursor) {
ThreadDatabase.Reader reader = threadDatabase.readerFor(cursor, masterCipher); ThreadDatabase.Reader reader = threadDatabase.readerFor(cursor, masterCipher);
ThreadRecord record = reader.getCurrent(); ThreadRecord record = reader.getCurrent();

@ -159,12 +159,6 @@ public class ConversationListFragment extends Fragment
private void initializeListAdapter() { private void initializeListAdapter() {
list.setAdapter(new ConversationListAdapter(getActivity(), masterSecret, locale, null, this)); list.setAdapter(new ConversationListAdapter(getActivity(), masterSecret, locale, null, this));
list.setRecyclerListener(new RecyclerListener() {
@Override
public void onViewRecycled(ViewHolder holder) {
((ConversationListItem)holder.itemView).unbind();
}
});
getLoaderManager().restartLoader(0, null, this); getLoaderManager().restartLoader(0, null, this);
} }

@ -49,7 +49,7 @@ import static org.thoughtcrime.securesms.util.SpanUtil.color;
*/ */
public class ConversationListItem extends RelativeLayout public class ConversationListItem extends RelativeLayout
implements Recipients.RecipientsModifiedListener implements Recipients.RecipientsModifiedListener, Unbindable
{ {
private final static String TAG = ConversationListItem.class.getSimpleName(); private final static String TAG = ConversationListItem.class.getSimpleName();
@ -115,9 +115,9 @@ public class ConversationListItem extends RelativeLayout
this.contactPhotoImage.setAvatar(recipients, true); this.contactPhotoImage.setAvatar(recipients, true);
} }
@Override
public void unbind() { public void unbind() {
if (this.recipients != null) if (this.recipients != null) this.recipients.removeListener(this);
this.recipients.removeListener(this);
} }
private void setBatchState(boolean batch) { private void setBatchState(boolean batch) {

@ -2,20 +2,25 @@ package org.thoughtcrime.securesms;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.support.annotation.NonNull;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import java.util.Locale;
import java.util.Set;
public class ConversationUpdateItem extends LinearLayout public class ConversationUpdateItem extends LinearLayout
implements Recipients.RecipientsModifiedListener, Recipient.RecipientModifiedListener, Unbindable, View.OnClickListener implements Recipients.RecipientsModifiedListener, Recipient.RecipientModifiedListener, BindableConversationItem, View.OnClickListener
{ {
private static final String TAG = ConversationUpdateItem.class.getSimpleName(); private static final String TAG = ConversationUpdateItem.class.getSimpleName();
@ -42,7 +47,17 @@ public class ConversationUpdateItem extends LinearLayout
setOnClickListener(this); setOnClickListener(this);
} }
public void set(MessageRecord messageRecord) { @Override
public void bind(@NonNull MasterSecret masterSecret,
@NonNull MessageRecord messageRecord,
@NonNull Locale locale,
@NonNull Set<MessageRecord> batchSelected,
boolean groupThread, boolean pushDestination)
{
bind(messageRecord);
}
private void bind(@NonNull MessageRecord messageRecord) {
this.messageRecord = messageRecord; this.messageRecord = messageRecord;
this.sender = messageRecord.getIndividualRecipient(); this.sender = messageRecord.getIndividualRecipient();
@ -73,7 +88,7 @@ public class ConversationUpdateItem extends LinearLayout
Util.runOnMain(new Runnable() { Util.runOnMain(new Runnable() {
@Override @Override
public void run() { public void run() {
set(messageRecord); bind(messageRecord);
} }
}); });
} }

@ -19,6 +19,7 @@ package org.thoughtcrime.securesms;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -64,7 +65,7 @@ public class ImageMediaAdapter extends CursorRecyclerViewAdapter<ViewHolder> {
} }
@Override @Override
public void onBindViewHolder(final ViewHolder viewHolder, final Cursor cursor) { public void onBindViewHolder(final ViewHolder viewHolder, final @NonNull Cursor cursor) {
final ThumbnailView imageView = viewHolder.imageView; final ThumbnailView imageView = viewHolder.imageView;
final ImageRecord imageRecord = ImageRecord.from(cursor); final ImageRecord imageRecord = ImageRecord.from(cursor);

@ -28,7 +28,6 @@ import android.view.LayoutInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView; import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
@ -173,8 +172,8 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
toFromRes = R.string.message_details_header__from; toFromRes = R.string.message_details_header__from;
} }
toFrom.setText(toFromRes); toFrom.setText(toFromRes);
conversationItem.set(masterSecret, messageRecord, dynamicLanguage.getCurrentLocale(), conversationItem.bind(masterSecret, messageRecord, dynamicLanguage.getCurrentLocale(),
new HashSet<MessageRecord>(), new NullSelectionListener(), new HashSet<MessageRecord>(),
recipients != messageRecord.getRecipients(), recipients != messageRecord.getRecipients(),
DirectoryHelper.isPushDestination(this, recipients)); DirectoryHelper.isPushDestination(this, recipients));
recipientsList.setAdapter(new MessageDetailsRecipientAdapter(this, masterSecret, messageRecord, recipientsList.setAdapter(new MessageDetailsRecipientAdapter(this, masterSecret, messageRecord,
@ -306,13 +305,4 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
} }
} }
} }
private static class NullSelectionListener implements ConversationFragment.SelectionClickListener {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {}
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
return false;
}
}
} }

@ -19,6 +19,7 @@ package org.thoughtcrime.securesms.database;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.DataSetObserver; import android.database.DataSetObserver;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
/** /**
@ -90,17 +91,34 @@ public abstract class CursorRecyclerViewAdapter<VH extends RecyclerView.ViewHold
: 0; : 0;
} }
public abstract void onBindViewHolder(VH viewHolder, Cursor cursor); public abstract void onBindViewHolder(VH viewHolder, @NonNull Cursor cursor);
@Override @Override
public void onBindViewHolder(VH viewHolder, int position) { public void onBindViewHolder(VH viewHolder, int position) {
moveToPositionOrThrow(position);
onBindViewHolder(viewHolder, cursor);
}
@Override public int getItemViewType(int position) {
moveToPositionOrThrow(position);
return getItemViewType(cursor);
}
public int getItemViewType(@NonNull Cursor cursor) {
return 0;
}
private void assertActiveCursor() {
if (!isActiveCursor()) { if (!isActiveCursor()) {
throw new IllegalStateException("this should only be called when the cursor is valid"); throw new IllegalStateException("this should only be called when the cursor is valid");
} }
}
private void moveToPositionOrThrow(final int position) {
assertActiveCursor();
if (!cursor.moveToPosition(position)) { if (!cursor.moveToPosition(position)) {
throw new IllegalStateException("couldn't move cursor to position " + position); throw new IllegalStateException("couldn't move cursor to position " + position);
} }
onBindViewHolder(viewHolder, cursor);
} }
private boolean isActiveCursor() { private boolean isActiveCursor() {

@ -56,7 +56,7 @@ public class MmsSmsDatabase extends Database {
MmsSmsColumns.MISMATCHED_IDENTITIES, MmsSmsColumns.MISMATCHED_IDENTITIES,
MmsDatabase.NETWORK_FAILURE, TRANSPORT}; MmsDatabase.NETWORK_FAILURE, TRANSPORT};
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC"; String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;

@ -16,15 +16,18 @@
*/ */
package org.thoughtcrime.securesms.util; package org.thoughtcrime.securesms.util;
import android.content.Context;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build.VERSION; import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES; import android.os.Build.VERSION_CODES;
import android.support.annotation.IdRes; import android.support.annotation.IdRes;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.view.animation.FastOutSlowInInterpolator; import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextUtils.TruncateAt; import android.text.TextUtils.TruncateAt;
import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewStub; import android.view.ViewStub;
@ -110,4 +113,12 @@ public class ViewUtil {
view.setVisibility(View.VISIBLE); view.setVisibility(View.VISIBLE);
view.startAnimation(animation); view.startAnimation(animation);
} }
@SuppressWarnings("unchecked")
public static <T extends View> T inflate(@NonNull LayoutInflater inflater,
@NonNull ViewGroup parent,
@LayoutRes int layoutResId)
{
return (T)(inflater.inflate(layoutResId, parent, false));
}
} }

Loading…
Cancel
Save