diff --git a/res/drawable/conversation_item_background.xml b/res/drawable/conversation_item_background.xml
index fb3ecc7153..7014300913 100644
--- a/res/drawable/conversation_item_background.xml
+++ b/res/drawable/conversation_item_background.xml
@@ -1,5 +1,4 @@
-
-
+
+
\ No newline at end of file
diff --git a/res/drawable/conversation_item_background_dark.xml b/res/drawable/conversation_item_background_dark.xml
deleted file mode 100644
index d7fb6e05f6..0000000000
--- a/res/drawable/conversation_item_background_dark.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
diff --git a/res/layout/conversation_fragment.xml b/res/layout/conversation_fragment.xml
index 8cd2df13d6..5f4a3d3806 100644
--- a/res/layout/conversation_fragment.xml
+++ b/res/layout/conversation_fragment.xml
@@ -5,18 +5,12 @@
android:layout_height="match_parent"
android:orientation="vertical">
-
+
-
\ No newline at end of file
+
diff --git a/res/layout/conversation_item_received.xml b/res/layout/conversation_item_received.xml
index 97ef8db754..4f38aaf108 100644
--- a/res/layout/conversation_item_received.xml
+++ b/res/layout/conversation_item_received.xml
@@ -4,11 +4,9 @@
android:layout_height="wrap_content"
android:paddingRight="10dip"
android:orientation="vertical"
- android:background="?conversation_item_background"
+ android:background="@drawable/conversation_item_background"
xmlns:android="http://schemas.android.com/apk/res/android"
- 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">
+ xmlns:tools="http://schemas.android.com/tools">
+ android:background="@drawable/conversation_item_background">
@drawable/quick_camera_light
- - @drawable/conversation_item_background
- @drawable/conversation_item_sent_indicator_text_shape
- @drawable/ic_info_outline_light
@@ -204,7 +203,6 @@
- #99ffffff
- - @drawable/conversation_item_background_dark
- #ff333333
- #ffffffff
- #aaeeeeee
diff --git a/src/org/thoughtcrime/securesms/BindableConversationItem.java b/src/org/thoughtcrime/securesms/BindableConversationItem.java
new file mode 100644
index 0000000000..971907c8a3
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/BindableConversationItem.java
@@ -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 batchSelected,
+ boolean groupThread, boolean pushDestination);
+}
diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java
index 0e9e9b2686..2ec67386a4 100644
--- a/src/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationActivity.java
@@ -1368,11 +1368,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
public void onFocusChange(View v, boolean hasFocus) {}
}
- @Override
- public void setComposeText(String text) {
- this.composeText.setText(text);
- }
-
@Override
public void setThreadId(long threadId) {
this.threadId = threadId;
diff --git a/src/org/thoughtcrime/securesms/ConversationAdapter.java b/src/org/thoughtcrime/securesms/ConversationAdapter.java
index c6cc4cb44b..ed636731e8 100644
--- a/src/org/thoughtcrime/securesms/ConversationAdapter.java
+++ b/src/org/thoughtcrime/securesms/ConversationAdapter.java
@@ -18,13 +18,18 @@ package org.thoughtcrime.securesms;
import android.content.Context;
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.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.support.v4.widget.CursorAdapter;
import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
@@ -39,7 +44,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
-import org.thoughtcrime.securesms.ConversationFragment.SelectionClickListener;
+import org.thoughtcrime.securesms.util.ViewUtil;
/**
* A cursor adapter for a conversation thread. Ultimately
@@ -49,7 +54,9 @@ import org.thoughtcrime.securesms.ConversationFragment.SelectionClickListener;
* @author Moxie Marlinspike
*
*/
-public class ConversationAdapter extends CursorAdapter implements AbsListView.RecyclerListener {
+public class ConversationAdapter
+ extends CursorRecyclerViewAdapter
+{
private static final int MAX_CACHE_SIZE = 40;
private final Map> messageRecordCache =
@@ -61,46 +68,46 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re
private final Set batchSelected = Collections.synchronizedSet(new HashSet());
- private final SelectionClickListener selectionClickListener;
- private final Context context;
+ private final ItemClickListener clickListener;
private final MasterSecret masterSecret;
private final Locale locale;
private final boolean groupThread;
private final boolean pushDestination;
+ private final MmsSmsDatabase db;
private final LayoutInflater inflater;
- public ConversationAdapter(Context context, MasterSecret masterSecret, Locale locale,
- SelectionClickListener selectionClickListener, boolean groupThread,
- boolean pushDestination)
- {
- super(context, null, 0);
- this.context = context;
- this.masterSecret = masterSecret;
- this.locale = locale;
- this.selectionClickListener = selectionClickListener;
- this.groupThread = groupThread;
- this.pushDestination = pushDestination;
- this.inflater = LayoutInflater.from(context);
+ protected static class ViewHolder extends RecyclerView.ViewHolder {
+ public ViewHolder(final @NonNull V itemView) {
+ super(itemView);
+ }
+
+ @SuppressWarnings("unchecked")
+ public V getView() {
+ return (V)itemView;
+ }
}
- @Override
- public void bindView(View view, Context context, Cursor cursor) {
- long id = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID));
- String type = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT));
- MessageRecord messageRecord = getMessageRecord(id, cursor, type);
+ public interface ItemClickListener {
+ void onItemClick(ConversationItem item);
+ void onItemLongClick(ConversationItem item);
+ }
- switch (getItemViewType(cursor)) {
- case MESSAGE_TYPE_INCOMING:
- case MESSAGE_TYPE_OUTGOING:
- ((ConversationItem) view).set(masterSecret, messageRecord, locale, batchSelected,
- selectionClickListener, groupThread, pushDestination);
- break;
- case MESSAGE_TYPE_UPDATE:
- ((ConversationUpdateItem)view).set(messageRecord);
- break;
- default:
- throw new AssertionError("Unknown type!");
- }
+ public ConversationAdapter(@NonNull Context context,
+ @NonNull MasterSecret masterSecret,
+ @NonNull Locale locale,
+ @Nullable ItemClickListener clickListener,
+ @Nullable Cursor cursor,
+ boolean groupThread,
+ boolean pushDestination)
+ {
+ super(context, cursor);
+ this.masterSecret = masterSecret;
+ this.locale = locale;
+ this.clickListener = clickListener;
+ this.groupThread = groupThread;
+ this.pushDestination = pushDestination;
+ this.inflater = LayoutInflater.from(context);
+ this.db = DatabaseFactory.getMmsSmsDatabase(context);
}
@Override
@@ -109,40 +116,50 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re
super.changeCursor(cursor);
}
- @Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
- View view;
-
- int type = getItemViewType(cursor);
-
- switch (type) {
- case ConversationAdapter.MESSAGE_TYPE_OUTGOING:
- view = inflater.inflate(R.layout.conversation_item_sent, parent, false);
- break;
- case ConversationAdapter.MESSAGE_TYPE_INCOMING:
- view = inflater.inflate(R.layout.conversation_item_received, parent, false);
- break;
- case ConversationAdapter.MESSAGE_TYPE_UPDATE:
- view = inflater.inflate(R.layout.conversation_item_update, parent, false);
- break;
- default: throw new IllegalArgumentException("unsupported item view type given to ConversationAdapter");
+ @Override public void onBindViewHolder(ViewHolder viewHolder, @NonNull Cursor cursor) {
+ long id = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID));
+ String type = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT));
+ MessageRecord messageRecord = getMessageRecord(id, cursor, type);
+
+ viewHolder.getView().bind(masterSecret, messageRecord, locale, batchSelected, groupThread, pushDestination);
+ }
+
+ @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ final V itemView = ViewUtil.inflate(inflater, parent, getLayoutForViewType(viewType));
+ if (viewType == MESSAGE_TYPE_INCOMING || viewType == MESSAGE_TYPE_OUTGOING) {
+ itemView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (clickListener != null) clickListener.onItemClick((ConversationItem)itemView);
+ }
+ });
+ 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
- public int getViewTypeCount() {
- return 3;
+ @Override public void onViewRecycled(ViewHolder holder) {
+ holder.getView().unbind();
}
- @Override
- public int getItemViewType(int position) {
- Cursor cursor = (Cursor)getItem(position);
- return getItemViewType(cursor);
+ private @LayoutRes int getLayoutForViewType(int viewType) {
+ switch (viewType) {
+ case ConversationAdapter.MESSAGE_TYPE_OUTGOING: return R.layout.conversation_item_sent;
+ 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));
String type = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT));
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) {
- SoftReference reference = messageRecordCache.get(type + messageId);
-
+ final SoftReference reference = messageRecordCache.get(type + messageId);
if (reference != null) {
- MessageRecord record = reference.get();
-
- if (record != null)
- return record;
+ final MessageRecord record = reference.get();
+ if (record != null) return record;
}
- MmsSmsDatabase.Reader reader = DatabaseFactory.getMmsSmsDatabase(context)
- .readerFor(cursor, masterSecret);
-
- MessageRecord messageRecord = reader.getCurrent();
-
+ final MessageRecord messageRecord = db.readerFor(cursor, masterSecret).getCurrent();
messageRecordCache.put(type + messageId, new SoftReference<>(messageRecord));
return messageRecord;
}
public void close() {
- this.getCursor().close();
+ getCursor().close();
}
- public void toggleBatchSelected(MessageRecord messageRecord) {
- if (batchSelected.contains(messageRecord)) {
- batchSelected.remove(messageRecord);
- } else {
+ public void toggleSelection(MessageRecord messageRecord) {
+ if (!batchSelected.remove(messageRecord)) {
batchSelected.add(messageRecord);
}
}
- public Set getBatchSelected() {
- return batchSelected;
+ public void clearSelection() {
+ batchSelected.clear();
}
- @Override
- public void onMovedToScrapHeap(View view) {
- ((Unbindable) view).unbind();
+ public Set getSelectedItems() {
+ return Collections.unmodifiableSet(new HashSet<>(batchSelected));
}
}
diff --git a/src/org/thoughtcrime/securesms/ConversationFragment.java b/src/org/thoughtcrime/securesms/ConversationFragment.java
index 05c1a26053..43c27040e1 100644
--- a/src/org/thoughtcrime/securesms/ConversationFragment.java
+++ b/src/org/thoughtcrime/securesms/ConversationFragment.java
@@ -8,12 +8,13 @@ import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Build;
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.content.Loader;
-import android.support.v4.widget.CursorAdapter;
import android.support.v7.app.AppCompatActivity;
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.TextUtils;
import android.util.Log;
@@ -24,12 +25,11 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
-import android.widget.AdapterView;
-import android.widget.ListView;
import android.widget.Toast;
import com.afollestad.materialdialogs.AlertDialogWrapper;
+import org.thoughtcrime.securesms.ConversationAdapter.ItemClickListener;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
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.SaveAttachmentTask;
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.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
-public class ConversationFragment extends ListFragment
+public class ConversationFragment extends Fragment
implements LoaderManager.LoaderCallbacks
{
private static final String TAG = ConversationFragment.class.getSimpleName();
- private final ActionModeCallback actionModeCallback = new ActionModeCallback();
- private final SelectionClickListener selectionClickListener = new ConversationFragmentSelectionClickListener();
+ private final ActionModeCallback actionModeCallback = new ActionModeCallback();
+ private final ItemClickListener selectionClickListener = new ConversationFragmentItemClickListener();
private ConversationFragmentListener listener;
@@ -68,6 +69,7 @@ public class ConversationFragment extends ListFragment
private long threadId;
private ActionMode actionMode;
private Locale locale;
+ private RecyclerView list;
@Override
public void onCreate(Bundle icicle) {
@@ -78,16 +80,24 @@ public class ConversationFragment extends ListFragment
@Override
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
public void onActivityCreated(Bundle 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();
initializeListAdapter();
- initializeContextualActionBar();
}
@Override
@@ -100,8 +110,8 @@ public class ConversationFragment extends ListFragment
public void onResume() {
super.onResume();
- if (getListAdapter() != null) {
- ((ConversationAdapter) getListAdapter()).notifyDataSetChanged();
+ if (list.getAdapter() != null) {
+ list.getAdapter().notifyDataSetChanged();
}
}
@@ -125,21 +135,15 @@ public class ConversationFragment extends ListFragment
private void initializeListAdapter() {
if (this.recipients != null && this.threadId != -1) {
- this.setListAdapter(new ConversationAdapter(getActivity(), masterSecret, locale, selectionClickListener,
- (!this.recipients.isSingleRecipient()) || this.recipients.isGroupRecipient(),
- DirectoryHelper.isPushDestination(getActivity(), this.recipients)));
- getListView().setRecyclerListener((ConversationAdapter)getListAdapter());
+ list.setAdapter(new ConversationAdapter(getActivity(), masterSecret, locale, selectionClickListener, null,
+ (!this.recipients.isSingleRecipient()) || this.recipients.isGroupRecipient(),
+ DirectoryHelper.isPushDestination(getActivity(), this.recipients)));
getLoaderManager().restartLoader(0, null, this);
}
}
- private void initializeContextualActionBar() {
- getListView().setOnItemClickListener(selectionClickListener);
- getListView().setOnItemLongClickListener(selectionClickListener);
- }
-
private void setCorrectMenuVisibility(Menu menu) {
- List messageRecords = getSelectedMessageRecords();
+ Set messageRecords = getListAdapter().getSelectedItems();
if (actionMode != null && messageRecords.size() == 0) {
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_resend).setVisible(false);
} 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_save_attachment).setVisible(messageRecord.isMms() &&
@@ -165,17 +169,17 @@ public class ConversationFragment extends ListFragment
}
}
+ private ConversationAdapter getListAdapter() {
+ return (ConversationAdapter) list.getAdapter();
+ }
+
private MessageRecord getSelectedMessageRecord() {
- List messageRecords = getSelectedMessageRecords();
+ Set messageRecords = getListAdapter().getSelectedItems();
- if (messageRecords.size() == 1) return messageRecords.get(0);
+ if (messageRecords.size() == 1) return messageRecords.iterator().next();
else throw new AssertionError();
}
- private List getSelectedMessageRecords() {
- return new LinkedList<>(((ConversationAdapter)getListAdapter()).getBatchSelected());
- }
-
public void reload(Recipients recipients, long threadId) {
this.recipients = recipients;
@@ -186,17 +190,18 @@ public class ConversationFragment extends ListFragment
}
public void scrollToBottom() {
- final ListView list = getListView();
list.post(new Runnable() {
@Override
public void run() {
- list.setSelection(getListAdapter().getCount() - 1);
+ list.stopScroll();
+ list.smoothScrollToPosition(0);
}
});
}
- private void handleCopyMessage(final List messageRecords) {
- Collections.sort(messageRecords, new Comparator() {
+ private void handleCopyMessage(final Set messageRecords) {
+ List messageList = new LinkedList<>(messageRecords);
+ Collections.sort(messageList, new Comparator() {
@Override
public int compare(MessageRecord lhs, MessageRecord rhs) {
if (lhs.getDateReceived() < rhs.getDateReceived()) return -1;
@@ -209,7 +214,7 @@ public class ConversationFragment extends ListFragment
ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
boolean first = true;
- for (MessageRecord messageRecord : messageRecords) {
+ for (MessageRecord messageRecord : messageList) {
String body = messageRecord.getDisplayBody().toString();
if (body != null) {
@@ -225,7 +230,7 @@ public class ConversationFragment extends ListFragment
clipboard.setText(result);
}
- private void handleDeleteMessages(final List messageRecords) {
+ private void handleDeleteMessages(final Set messageRecords) {
AlertDialogWrapper.Builder builder = new AlertDialogWrapper.Builder(getActivity());
builder.setTitle(R.string.ConversationFragment_confirm_message_delete);
builder.setIconAttribute(R.attr.dialog_alert_icon);
@@ -319,53 +324,41 @@ public class ConversationFragment extends ListFragment
@Override
public void onLoadFinished(Loader arg0, Cursor cursor) {
- if (getListAdapter() != null) {
- ((CursorAdapter) getListAdapter()).changeCursor(cursor);
+ if (list.getAdapter() != null) {
+ getListAdapter().changeCursor(cursor);
}
}
@Override
public void onLoaderReset(Loader arg0) {
- if (getListAdapter() != null) {
- ((CursorAdapter) getListAdapter()).changeCursor(null);
+ if (list.getAdapter() != null) {
+ getListAdapter().changeCursor(null);
}
}
public interface ConversationFragmentListener {
- public void setComposeText(String text);
-
- public void setThreadId(long threadId);
+ void setThreadId(long threadId);
}
- public interface SelectionClickListener extends
- AdapterView.OnItemLongClickListener, AdapterView.OnItemClickListener {}
+ private class ConversationFragmentItemClickListener implements ItemClickListener {
- private class ConversationFragmentSelectionClickListener
- implements SelectionClickListener
- {
- @Override
- public void onItemClick(AdapterView> parent, View view, int position, long id) {
- if (actionMode != null && view instanceof ConversationItem) {
- MessageRecord messageRecord = ((ConversationItem)view).getMessageRecord();
- ((ConversationAdapter) getListAdapter()).toggleBatchSelected(messageRecord);
- ((ConversationAdapter) getListAdapter()).notifyDataSetChanged();
+ @Override public void onItemClick(ConversationItem item) {
+ if (actionMode != null) {
+ MessageRecord messageRecord = item.getMessageRecord();
+ ((ConversationAdapter) list.getAdapter()).toggleSelection(messageRecord);
+ list.getAdapter().notifyDataSetChanged();
setCorrectMenuVisibility(actionMode.getMenu());
}
}
- @Override
- public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {
- if (actionMode == null && view instanceof ConversationItem) {
- MessageRecord messageRecord = ((ConversationItem)view).getMessageRecord();
- ((ConversationAdapter) getListAdapter()).toggleBatchSelected(messageRecord);
- ((ConversationAdapter) getListAdapter()).notifyDataSetChanged();
+ @Override public void onItemLongClick(ConversationItem item) {
+ if (actionMode == null) {
+ ((ConversationAdapter) list.getAdapter()).toggleSelection(item.getMessageRecord());
+ list.getAdapter().notifyDataSetChanged();
actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(actionModeCallback);
- return true;
}
-
- return false;
}
}
@@ -395,8 +388,8 @@ public class ConversationFragment extends ListFragment
@Override
public void onDestroyActionMode(ActionMode mode) {
- ((ConversationAdapter)getListAdapter()).getBatchSelected().clear();
- ((ConversationAdapter)getListAdapter()).notifyDataSetChanged();
+ ((ConversationAdapter)list.getAdapter()).clearSelection();
+ list.getAdapter().notifyDataSetChanged();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getActivity().getWindow().setStatusBarColor(statusBarColor);
@@ -409,11 +402,11 @@ public class ConversationFragment extends ListFragment
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch(item.getItemId()) {
case R.id.menu_context_copy:
- handleCopyMessage(getSelectedMessageRecords());
+ handleCopyMessage(getListAdapter().getSelectedItems());
actionMode.finish();
return true;
case R.id.menu_context_delete_message:
- handleDeleteMessages(getSelectedMessageRecords());
+ handleDeleteMessages(getListAdapter().getSelectedItems());
actionMode.finish();
return true;
case R.id.menu_context_details:
@@ -436,5 +429,5 @@ public class ConversationFragment extends ListFragment
return false;
}
- };
+ }
}
diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java
index 44201532b1..78a3e850fb 100644
--- a/src/org/thoughtcrime/securesms/ConversationItem.java
+++ b/src/org/thoughtcrime/securesms/ConversationItem.java
@@ -23,10 +23,10 @@ import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.PorterDuff;
-import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.NonNull;
import android.text.TextUtils;
+import android.text.util.Linkify;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
@@ -40,7 +40,6 @@ import android.widget.Toast;
import com.afollestad.materialdialogs.AlertDialogWrapper;
-import org.thoughtcrime.securesms.ConversationFragment.SelectionClickListener;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.ThumbnailView;
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 MessageRecord messageRecord;
@@ -97,16 +98,13 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien
private View pendingIndicator;
private ImageView pendingApprovalIndicator;
- private StatusManager statusManager;
- private Set batchSelected;
- private SelectionClickListener selectionClickListener;
- private ThumbnailView mediaThumbnail;
- private Button mmsDownloadButton;
- private TextView mmsDownloadingLabel;
+ private StatusManager statusManager;
+ private Set batchSelected;
+ private ThumbnailView mediaThumbnail;
+ private Button mmsDownloadButton;
+ private TextView mmsDownloadingLabel;
- private int defaultBubbleColor;
- private Drawable selectedBackground;
- private Drawable normalBackground;
+ private int defaultBubbleColor;
private final MmsDownloadClickListener mmsDownloadClickListener = new MmsDownloadClickListener();
private final MmsPreferencesClickListener mmsPreferencesClickListener = new MmsPreferencesClickListener();
@@ -114,9 +112,8 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien
private final Context context;
public ConversationItem(Context context) {
- super(context);
- this.context = context;
- }
+ this(context, null);
+ }
public ConversationItem(Context context, AttributeSet 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);
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);
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);
setOnClickListener(clickListener);
+ PassthroughClickListener passthroughClickListener = new PassthroughClickListener();
if (mmsDownloadButton != null) mmsDownloadButton.setOnClickListener(mmsDownloadClickListener);
- if (mediaThumbnail != null) {
- mediaThumbnail.setThumbnailClickListener(new ThumbnailClickListener());
- mediaThumbnail.setOnLongClickListener(new MultiSelectLongClickListener());
- mediaThumbnail.setDownloadClickListener(new ThumbnailDownloadClickListener());
- }
+ mediaThumbnail.setThumbnailClickListener(new ThumbnailClickListener());
+ mediaThumbnail.setDownloadClickListener(new ThumbnailDownloadClickListener());
+ mediaThumbnail.setOnLongClickListener(passthroughClickListener);
+ bodyText.setOnLongClickListener(passthroughClickListener);
+ bodyText.setOnClickListener(passthroughClickListener);
}
- public void set(@NonNull MasterSecret masterSecret,
- @NonNull MessageRecord messageRecord,
- @NonNull Locale locale,
- @NonNull Set batchSelected,
- @NonNull SelectionClickListener selectionClickListener,
- boolean groupThread, boolean pushDestination)
+ @Override
+ public void bind(@NonNull MasterSecret masterSecret,
+ @NonNull MessageRecord messageRecord,
+ @NonNull Locale locale,
+ @NonNull Set batchSelected,
+ boolean groupThread, boolean pushDestination)
{
this.masterSecret = masterSecret;
this.messageRecord = messageRecord;
this.locale = locale;
this.batchSelected = batchSelected;
- this.selectionClickListener = selectionClickListener;
this.groupThread = groupThread;
this.pushDestination = pushDestination;
this.recipient = messageRecord.getIndividualRecipient();
this.recipient.addListener(this);
- setSelectionBackgroundDrawables(messageRecord);
+ setSelectionState(messageRecord);
setBodyText(messageRecord);
setBubbleState(messageRecord, recipient);
setStatusIcons(messageRecord);
@@ -198,8 +195,6 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien
final TypedArray attrs = context.obtainStyledAttributes(attributes);
defaultBubbleColor = attrs.getColor(0, Color.WHITE);
- selectedBackground = attrs.getDrawable(1);
- normalBackground = attrs.getDrawable(2);
attrs.recycle();
}
@@ -227,12 +222,11 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien
}
}
- private void setSelectionBackgroundDrawables(MessageRecord messageRecord) {
- if (batchSelected.contains(messageRecord)) {
- setBackgroundDrawable(selectedBackground);
- } else {
- setBackgroundDrawable(normalBackground);
- }
+ private void setSelectionState(MessageRecord messageRecord) {
+ setSelected(batchSelected.contains(messageRecord));
+ mediaThumbnail.setClickable(batchSelected.isEmpty());
+ mediaThumbnail.setLongClickable(batchSelected.isEmpty());
+ bodyText.setAutoLinkMask(batchSelected.isEmpty() ? Linkify.ALL : 0);
}
private boolean isCaptionlessMms(MessageRecord messageRecord) {
@@ -255,11 +249,6 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien
bodyText.setText(messageRecord.getDisplayBody());
bodyText.setVisibility(View.VISIBLE);
}
-
- if (bodyText.isClickable() && bodyText.isFocusable()) {
- bodyText.setOnLongClickListener(new MultiSelectLongClickListener());
- bodyText.setOnClickListener(new MultiSelectLongClickListener());
- }
}
private void setMediaAttributes(MessageRecord messageRecord) {
@@ -334,12 +323,9 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien
private void setEvents(MessageRecord messageRecord) {
setClickable(batchSelected.isEmpty() &&
- (messageRecord.isFailed() ||
+ (messageRecord.isFailed() ||
messageRecord.isPendingInsecureSmsFallback() ||
messageRecord.isBundleKeyExchange()));
- if (messageRecord.isFailed()) {
- setOnLongClickListener(new MultiSelectLongClickListener());
- }
}
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 {
- @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);
}
}
@@ -430,10 +416,9 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien
}
public void onClick(final View v, final Slide slide) {
- if (!batchSelected.isEmpty()) {
- selectionClickListener.onItemClick(null, ConversationItem.this, -1, -1);
- } else if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) &&
- slide.getThumbnailUri() != null)
+ if (batchSelected.isEmpty() &&
+ MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) &&
+ slide.getThumbnailUri() != null)
{
Intent intent = new Intent(context, MediaPreviewActivity.class);
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 {
public void onClick(View v) {
- if (messageRecord.isFailed() && !batchSelected.isEmpty()) {
- selectionClickListener.onItemClick(null, ConversationItem.this, -1, -1);
- } else if(messageRecord.isFailed()) {
+ if (!batchSelected.isEmpty()) return;
+
+ if (messageRecord.isFailed()) {
Intent intent = new Intent(context, MessageDetailsActivity.class);
intent.putExtra(MessageDetailsActivity.MASTER_SECRET_EXTRA, masterSecret);
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() {
final int title;
final int message;
diff --git a/src/org/thoughtcrime/securesms/ConversationListAdapter.java b/src/org/thoughtcrime/securesms/ConversationListAdapter.java
index 1e728e4482..0fb908b7e9 100644
--- a/src/org/thoughtcrime/securesms/ConversationListAdapter.java
+++ b/src/org/thoughtcrime/securesms/ConversationListAdapter.java
@@ -100,8 +100,12 @@ public class ConversationListAdapter extends CursorRecyclerViewAdapter batchSelected,
+ boolean groupThread, boolean pushDestination)
+ {
+ bind(messageRecord);
+ }
+
+ private void bind(@NonNull MessageRecord messageRecord) {
this.messageRecord = messageRecord;
this.sender = messageRecord.getIndividualRecipient();
@@ -73,7 +88,7 @@ public class ConversationUpdateItem extends LinearLayout
Util.runOnMain(new Runnable() {
@Override
public void run() {
- set(messageRecord);
+ bind(messageRecord);
}
});
}
diff --git a/src/org/thoughtcrime/securesms/ImageMediaAdapter.java b/src/org/thoughtcrime/securesms/ImageMediaAdapter.java
index 6691399db0..61d7565a42 100644
--- a/src/org/thoughtcrime/securesms/ImageMediaAdapter.java
+++ b/src/org/thoughtcrime/securesms/ImageMediaAdapter.java
@@ -19,6 +19,7 @@ package org.thoughtcrime.securesms;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
+import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.LayoutInflater;
@@ -64,7 +65,7 @@ public class ImageMediaAdapter extends CursorRecyclerViewAdapter {
}
@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 ImageRecord imageRecord = ImageRecord.from(cursor);
diff --git a/src/org/thoughtcrime/securesms/MessageDetailsActivity.java b/src/org/thoughtcrime/securesms/MessageDetailsActivity.java
index dad593a094..132e989220 100644
--- a/src/org/thoughtcrime/securesms/MessageDetailsActivity.java
+++ b/src/org/thoughtcrime/securesms/MessageDetailsActivity.java
@@ -28,7 +28,6 @@ import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
@@ -173,8 +172,8 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
toFromRes = R.string.message_details_header__from;
}
toFrom.setText(toFromRes);
- conversationItem.set(masterSecret, messageRecord, dynamicLanguage.getCurrentLocale(),
- new HashSet(), new NullSelectionListener(),
+ conversationItem.bind(masterSecret, messageRecord, dynamicLanguage.getCurrentLocale(),
+ new HashSet(),
recipients != messageRecord.getRecipients(),
DirectoryHelper.isPushDestination(this, recipients));
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;
- }
- }
}
diff --git a/src/org/thoughtcrime/securesms/database/CursorRecyclerViewAdapter.java b/src/org/thoughtcrime/securesms/database/CursorRecyclerViewAdapter.java
index b24e587fae..18e09beca5 100644
--- a/src/org/thoughtcrime/securesms/database/CursorRecyclerViewAdapter.java
+++ b/src/org/thoughtcrime/securesms/database/CursorRecyclerViewAdapter.java
@@ -19,6 +19,7 @@ package org.thoughtcrime.securesms.database;
import android.content.Context;
import android.database.Cursor;
import android.database.DataSetObserver;
+import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
/**
@@ -90,17 +91,34 @@ public abstract class CursorRecyclerViewAdapter T inflate(@NonNull LayoutInflater inflater,
+ @NonNull ViewGroup parent,
+ @LayoutRes int layoutResId)
+ {
+ return (T)(inflater.inflate(layoutResId, parent, false));
+ }
}