Support for multi-select in the conversation list.

// FREEBIE

Closes #1601
Closes #2214

Fixes #2188
Fixes #786
This commit is contained in:
Moxie Marlinspike 2014-12-13 18:10:59 -08:00
parent ebf6a2d833
commit ed556fbd3a
5 changed files with 227 additions and 114 deletions

View File

@ -118,7 +118,7 @@
<string name="ConversationFragment_transport_s_sent_received_s">Transport: %1$s\nSent/Received: %2$s</string> <string name="ConversationFragment_transport_s_sent_received_s">Transport: %1$s\nSent/Received: %2$s</string>
<string name="ConversationFragment_sender_s_transport_s_sent_s_received_s">Sender: %1$s\nTransport: %2$s\nSent: %3$s\nReceived: %4$s</string> <string name="ConversationFragment_sender_s_transport_s_sent_s_received_s">Sender: %1$s\nTransport: %2$s\nSent: %3$s\nReceived: %4$s</string>
<string name="ConversationFragment_confirm_message_delete">Confirm message delete</string> <string name="ConversationFragment_confirm_message_delete">Confirm message delete</string>
<string name="ConversationFragment_are_you_sure_you_want_to_permanently_delete_this_message">Are you sure that you want to permanently delete this message?</string> <string name="ConversationFragment_are_you_sure_you_want_to_permanently_delete_all_selected_messages">Are you sure that you want to permanently delete all selected messages?</string>
<string name="ConversationFragment_save_to_sd_card">Save to storage?</string> <string name="ConversationFragment_save_to_sd_card">Save to storage?</string>
<string name="ConversationFragment_this_media_has_been_stored_in_an_encrypted_database_warning">Saving this media to storage will allow any other apps on your phone to access it.\n\nContinue?</string> <string name="ConversationFragment_this_media_has_been_stored_in_an_encrypted_database_warning">Saving this media to storage will allow any other apps on your phone to access it.\n\nContinue?</string>
<string name="ConversationFragment_error_while_saving_attachment_to_sd_card">Error while saving attachment to storage!</string> <string name="ConversationFragment_error_while_saving_attachment_to_sd_card">Error while saving attachment to storage!</string>
@ -130,6 +130,8 @@
<string name="ConversationFragment_push">PUSH</string> <string name="ConversationFragment_push">PUSH</string>
<string name="ConversationFragment_mms">MMS</string> <string name="ConversationFragment_mms">MMS</string>
<string name="ConversationFragment_sms">SMS</string> <string name="ConversationFragment_sms">SMS</string>
<string name="ConversationFragment_deleting">Deleting...</string>
<string name="ConversationFragment_deleting_messages">Deleting messages...</string>
<!-- ConversationListFragment --> <!-- ConversationListFragment -->
<string name="ConversationListFragment_delete_threads_question">Delete threads?</string> <string name="ConversationListFragment_delete_threads_question">Delete threads?</string>

View File

@ -35,7 +35,11 @@ import org.thoughtcrime.securesms.util.LRUCache;
import java.lang.ref.SoftReference; import java.lang.ref.SoftReference;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set;
import org.thoughtcrime.securesms.ConversationFragment.SelectionClickListener;
/** /**
* A cursor adapter for a conversation thread. Ultimately * A cursor adapter for a conversation thread. Ultimately
@ -55,19 +59,23 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re
public static final int MESSAGE_TYPE_INCOMING = 1; public static final int MESSAGE_TYPE_INCOMING = 1;
public static final int MESSAGE_TYPE_GROUP_ACTION = 2; public static final int MESSAGE_TYPE_GROUP_ACTION = 2;
private final Handler failedIconClickHandler; private final Set<MessageRecord> batchSelected = Collections.synchronizedSet(new HashSet<MessageRecord>());
private final Context context;
private final MasterSecret masterSecret;
private final boolean groupThread;
private final boolean pushDestination;
private final LayoutInflater inflater;
public ConversationAdapter(Context context, MasterSecret masterSecret, private final SelectionClickListener selectionClickListener;
private final Handler failedIconClickHandler;
private final Context context;
private final MasterSecret masterSecret;
private final boolean groupThread;
private final boolean pushDestination;
private final LayoutInflater inflater;
public ConversationAdapter(Context context, MasterSecret masterSecret, SelectionClickListener selectionClickListener,
Handler failedIconClickHandler, boolean groupThread, boolean pushDestination) Handler failedIconClickHandler, boolean groupThread, boolean pushDestination)
{ {
super(context, null, 0); super(context, null, 0);
this.context = context; this.context = context;
this.masterSecret = masterSecret; this.masterSecret = masterSecret;
this.selectionClickListener = selectionClickListener;
this.failedIconClickHandler = failedIconClickHandler; this.failedIconClickHandler = failedIconClickHandler;
this.groupThread = groupThread; this.groupThread = groupThread;
this.pushDestination = pushDestination; this.pushDestination = pushDestination;
@ -81,7 +89,8 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re
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);
item.set(masterSecret, messageRecord, failedIconClickHandler, groupThread, pushDestination); item.set(masterSecret, messageRecord, batchSelected, selectionClickListener,
failedIconClickHandler, groupThread, pushDestination);
} }
@Override @Override
@ -158,6 +167,18 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re
this.getCursor().close(); this.getCursor().close();
} }
public void toggleBatchSelected(MessageRecord messageRecord) {
if (batchSelected.contains(messageRecord)) {
batchSelected.remove(messageRecord);
} else {
batchSelected.add(messageRecord);
}
}
public Set<MessageRecord> getBatchSelected() {
return batchSelected;
}
@Override @Override
public void onMovedToScrapHeap(View view) { public void onMovedToScrapHeap(View view) {
((ConversationItem)view).unbind(); ((ConversationItem)view).unbind();

View File

@ -40,17 +40,23 @@ import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.Dialogs; import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.DirectoryHelper; import org.thoughtcrime.securesms.util.DirectoryHelper;
import org.thoughtcrime.securesms.util.FutureTaskListener; import org.thoughtcrime.securesms.util.FutureTaskListener;
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 java.sql.Date; import java.sql.Date;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.LinkedList;
import java.util.List;
public class ConversationFragment extends ListFragment public class ConversationFragment extends ListFragment
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 SelectionClickListener selectionClickListener = new SelectionClickListener();
private ConversationFragmentListener listener; private ConversationFragmentListener listener;
private MasterSecret masterSecret; private MasterSecret masterSecret;
@ -96,7 +102,7 @@ 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, this.setListAdapter(new ConversationAdapter(getActivity(), masterSecret, selectionClickListener,
new FailedIconClickHandler(), new FailedIconClickHandler(),
(!this.recipients.isSingleRecipient()) || this.recipients.isGroupRecipient(), (!this.recipients.isSingleRecipient()) || this.recipients.isGroupRecipient(),
DirectoryHelper.isPushDestination(getActivity(), this.recipients))); DirectoryHelper.isPushDestination(getActivity(), this.recipients)));
@ -106,49 +112,50 @@ public class ConversationFragment extends ListFragment
} }
private void initializeContextualActionBar() { private void initializeContextualActionBar() {
getListView().setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { getListView().setOnItemClickListener(selectionClickListener);
@Override getListView().setOnItemLongClickListener(selectionClickListener);
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
if (actionMode != null) {
view.setSelected(true);
return false;
}
actionMode = ((ActionBarActivity)getActivity()).startSupportActionMode(actionModeCallback);
view.setSelected(true);
return true;
}
});
getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (actionMode != null) {
view.setSelected(true);
setCorrectMenuVisibility(getMessageRecord(), actionMode.getMenu());
}
}
});
} }
private void setCorrectMenuVisibility(MessageRecord messageRecord, Menu menu) { private void setCorrectMenuVisibility(Menu menu) {
MenuItem resend = menu.findItem(R.id.menu_context_resend); ConversationAdapter adapter = (ConversationAdapter) getListAdapter();
MenuItem saveAttachment = menu.findItem(R.id.menu_context_save_attachment); List<MessageRecord> messageRecords = getSelectedMessageRecords();
if (messageRecord.isFailed()) resend.setVisible(true); if (actionMode != null && messageRecords.size() == 0) {
else resend.setVisible(false); adapter.getBatchSelected().clear();
adapter.notifyDataSetChanged();
actionMode.finish();
return;
}
if (messageRecord.isMms() && !messageRecord.isMmsNotification()) { if (messageRecords.size() > 1) {
saveAttachment.setVisible(((MediaMmsMessageRecord)messageRecord).containsMediaSlide()); menu.findItem(R.id.menu_context_forward).setVisible(false);
menu.findItem(R.id.menu_context_copy).setVisible(false);
menu.findItem(R.id.menu_context_details).setVisible(false);
menu.findItem(R.id.menu_context_save_attachment).setVisible(false);
menu.findItem(R.id.menu_context_resend).setVisible(false);
} else { } else {
saveAttachment.setVisible(false); MessageRecord messageRecord = messageRecords.get(0);
menu.findItem(R.id.menu_context_resend).setVisible(messageRecord.isFailed());
menu.findItem(R.id.menu_context_save_attachment).setVisible(messageRecord.isMms() &&
!messageRecord.isMmsNotification() &&
((MediaMmsMessageRecord)messageRecord).containsMediaSlide());
menu.findItem(R.id.menu_context_forward).setVisible(true);
menu.findItem(R.id.menu_context_details).setVisible(true);
menu.findItem(R.id.menu_context_copy).setVisible(true);
} }
} }
private MessageRecord getMessageRecord() { private MessageRecord getSelectedMessageRecord() {
Cursor cursor = ((CursorAdapter)getListAdapter()).getCursor(); List<MessageRecord> messageRecords = getSelectedMessageRecords();
ConversationItem conversationItem = (ConversationItem)(((ConversationAdapter)getListAdapter()).newView(getActivity(), cursor, null));
return conversationItem.getMessageRecord(); if (messageRecords.size() == 1) return messageRecords.get(0);
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) {
@ -177,23 +184,32 @@ public class ConversationFragment extends ListFragment
clipboard.setText(body); clipboard.setText(body);
} }
private void handleDeleteMessage(final MessageRecord message) { private void handleDeleteMessages(final List<MessageRecord> messageRecords) {
final long messageId = message.getId();
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.ConversationFragment_confirm_message_delete); builder.setTitle(R.string.ConversationFragment_confirm_message_delete);
builder.setIcon(Dialogs.resolveIcon(getActivity(), R.attr.dialog_alert_icon)); builder.setIcon(Dialogs.resolveIcon(getActivity(), R.attr.dialog_alert_icon));
builder.setCancelable(true); builder.setCancelable(true);
builder.setMessage(R.string.ConversationFragment_are_you_sure_you_want_to_permanently_delete_this_message); builder.setMessage(R.string.ConversationFragment_are_you_sure_you_want_to_permanently_delete_all_selected_messages);
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
if (message.isMms()) { new ProgressDialogAsyncTask<MessageRecord, Void, Void>(getActivity(),
DatabaseFactory.getMmsDatabase(getActivity()).delete(messageId); R.string.ConversationFragment_deleting,
} else { R.string.ConversationFragment_deleting_messages)
DatabaseFactory.getSmsDatabase(getActivity()).deleteMessage(messageId); {
} @Override
protected Void doInBackground(MessageRecord... messageRecords) {
for (MessageRecord messageRecord : messageRecords) {
if (messageRecord.isMms()) {
DatabaseFactory.getMmsDatabase(getActivity()).delete(messageRecord.getId());
} else {
DatabaseFactory.getSmsDatabase(getActivity()).deleteMessage(messageRecord.getId());
}
}
return null;
}
}.execute(messageRecords.toArray(new MessageRecord[messageRecords.size()]));
} }
}); });
@ -312,16 +328,43 @@ public class ConversationFragment extends ListFragment
public void setComposeText(String text); public void setComposeText(String text);
} }
private ActionMode.Callback actionModeCallback = new ActionMode.Callback() { public class SelectionClickListener
implements AdapterView.OnItemLongClickListener, AdapterView.OnItemClickListener
{
@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();
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();
actionMode = ((ActionBarActivity)getActivity()).startSupportActionMode(actionModeCallback);
return true;
}
return false;
}
}
private class ActionModeCallback implements ActionMode.Callback {
@Override @Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) { public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = mode.getMenuInflater(); MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.conversation_context, menu); inflater.inflate(R.menu.conversation_context, menu);
MessageRecord messageRecord = getMessageRecord(); setCorrectMenuVisibility(menu);
setCorrectMenuVisibility(messageRecord, menu);
return true; return true;
} }
@ -332,41 +375,37 @@ public class ConversationFragment extends ListFragment
@Override @Override
public void onDestroyActionMode(ActionMode mode) { public void onDestroyActionMode(ActionMode mode) {
if (getListView() != null && getListView().getChildCount() > 0) { ((ConversationAdapter)getListAdapter()).getBatchSelected().clear();
for (int i = 0; i < getListView().getChildCount(); i++){ ((ConversationAdapter)getListAdapter()).notifyDataSetChanged();
getListView().getChildAt(i).setSelected(false);
}
}
actionMode = null; actionMode = null;
} }
@Override @Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) { public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
MessageRecord messageRecord = getMessageRecord();
switch(item.getItemId()) { switch(item.getItemId()) {
case R.id.menu_context_copy: case R.id.menu_context_copy:
handleCopyMessage(messageRecord); handleCopyMessage(getSelectedMessageRecord());
actionMode.finish(); actionMode.finish();
return true; return true;
case R.id.menu_context_delete_message: case R.id.menu_context_delete_message:
handleDeleteMessage(messageRecord); handleDeleteMessages(getSelectedMessageRecords());
actionMode.finish(); actionMode.finish();
return true; return true;
case R.id.menu_context_details: case R.id.menu_context_details:
handleDisplayDetails(messageRecord); handleDisplayDetails(getSelectedMessageRecord());
actionMode.finish(); actionMode.finish();
return true; return true;
case R.id.menu_context_forward: case R.id.menu_context_forward:
handleForwardMessage(messageRecord); handleForwardMessage(getSelectedMessageRecord());
actionMode.finish(); actionMode.finish();
return true; return true;
case R.id.menu_context_resend: case R.id.menu_context_resend:
handleResendMessage(messageRecord); handleResendMessage(getSelectedMessageRecord());
actionMode.finish(); actionMode.finish();
return true; return true;
case R.id.menu_context_save_attachment: case R.id.menu_context_save_attachment:
handleSaveAttachment((MediaMmsMessageRecord)messageRecord); handleSaveAttachment((MediaMmsMessageRecord)getSelectedMessageRecord());
actionMode.finish(); actionMode.finish();
return true; return true;
} }

View File

@ -25,10 +25,8 @@ import android.content.res.TypedArray;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.os.Message; import android.os.Message;
import android.provider.Contacts.Intents;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import android.provider.ContactsContract.QuickContact; import android.provider.ContactsContract.QuickContact;
import android.util.AttributeSet; import android.util.AttributeSet;
@ -40,9 +38,10 @@ import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import org.thoughtcrime.securesms.ConversationFragment.SelectionClickListener;
import org.thoughtcrime.securesms.contacts.ContactPhotoFactory;
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.contacts.ContactPhotoFactory;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
@ -61,6 +60,8 @@ import org.thoughtcrime.securesms.util.Emoji;
import org.thoughtcrime.securesms.util.FutureTaskListener; import org.thoughtcrime.securesms.util.FutureTaskListener;
import org.thoughtcrime.securesms.util.ListenableFutureTask; import org.thoughtcrime.securesms.util.ListenableFutureTask;
import java.util.Set;
/** /**
* A view that displays an individual conversation item within a conversation * A view that displays an individual conversation item within a conversation
* thread. Used by ComposeMessageActivity's ListActivity via a ConversationAdapter. * thread. Used by ComposeMessageActivity's ListActivity via a ConversationAdapter.
@ -81,13 +82,13 @@ public class ConversationItem extends LinearLayout {
R.attr.conversation_item_sent_push_pending_background, R.attr.conversation_item_sent_push_pending_background,
R.attr.conversation_item_sent_push_pending_triangle_background}; R.attr.conversation_item_sent_push_pending_triangle_background};
private final static int SENT_PUSH = 0; private final static int SENT_PUSH = 0;
private final static int SENT_PUSH_TRIANGLE = 1; private final static int SENT_PUSH_TRIANGLE = 1;
private final static int SENT_SMS = 2; private final static int SENT_SMS = 2;
private final static int SENT_SMS_TRIANGLE = 3; private final static int SENT_SMS_TRIANGLE = 3;
private final static int SENT_SMS_PENDING = 4; private final static int SENT_SMS_PENDING = 4;
private final static int SENT_SMS_PENDING_TRIANGLE = 5; private final static int SENT_SMS_PENDING_TRIANGLE = 5;
private final static int SENT_PUSH_PENDING = 6; private final static int SENT_PUSH_PENDING = 6;
private final static int SENT_PUSH_PENDING_TRIANGLE = 7; private final static int SENT_PUSH_PENDING_TRIANGLE = 7;
private Handler failedIconHandler; private Handler failedIconHandler;
@ -108,19 +109,21 @@ public class ConversationItem extends LinearLayout {
private View triangleTick; private View triangleTick;
private ImageView pendingIndicator; private ImageView pendingIndicator;
private View mmsContainer; private Set<MessageRecord> batchSelected;
private ImageView mmsThumbnail; private SelectionClickListener selectionClickListener;
private Button mmsDownloadButton; private View mmsContainer;
private TextView mmsDownloadingLabel; private ImageView mmsThumbnail;
private ListenableFutureTask<SlideDeck> slideDeck; private Button mmsDownloadButton;
private FutureTaskListener<SlideDeck> slideDeckListener; private TextView mmsDownloadingLabel;
private TypedArray backgroundDrawables; private ListenableFutureTask<SlideDeck> slideDeck;
private FutureTaskListener<SlideDeck> slideDeckListener;
private TypedArray backgroundDrawables;
private final FailedIconClickListener failedIconClickListener = new FailedIconClickListener(); private final FailedIconClickListener failedIconClickListener = new FailedIconClickListener();
private final MmsDownloadClickListener mmsDownloadClickListener = new MmsDownloadClickListener(); private final MmsDownloadClickListener mmsDownloadClickListener = new MmsDownloadClickListener();
private final MmsPreferencesClickListener mmsPreferencesClickListener = new MmsPreferencesClickListener(); private final MmsPreferencesClickListener mmsPreferencesClickListener = new MmsPreferencesClickListener();
private final ClickListener clickListener = new ClickListener(); private final ClickListener clickListener = new ClickListener();
private final Handler handler = new Handler(); private final Handler handler = new Handler();
private final Context context; private final Context context;
public ConversationItem(Context context) { public ConversationItem(Context context) {
@ -157,19 +160,23 @@ public class ConversationItem extends LinearLayout {
setOnClickListener(clickListener); setOnClickListener(clickListener);
if (failedImage != null) failedImage.setOnClickListener(failedIconClickListener); if (failedImage != null) failedImage.setOnClickListener(failedIconClickListener);
if (mmsDownloadButton != null) mmsDownloadButton.setOnClickListener(mmsDownloadClickListener); if (mmsDownloadButton != null) mmsDownloadButton.setOnClickListener(mmsDownloadClickListener);
if (mmsThumbnail != null) mmsThumbnail.setOnLongClickListener(new MultiSelectLongClickListener());
} }
public void set(MasterSecret masterSecret, MessageRecord messageRecord, public void set(MasterSecret masterSecret, MessageRecord messageRecord,
Set<MessageRecord> batchSelected, SelectionClickListener selectionClickListener,
Handler failedIconHandler, boolean groupThread, boolean pushDestination) Handler failedIconHandler, boolean groupThread, boolean pushDestination)
{ {
this.masterSecret = masterSecret;
this.messageRecord = messageRecord;
this.batchSelected = batchSelected;
this.selectionClickListener = selectionClickListener;
this.failedIconHandler = failedIconHandler;
this.groupThread = groupThread;
this.pushDestination = pushDestination;
this.messageRecord = messageRecord; setConversationBackgroundDrawables(messageRecord);
this.masterSecret = masterSecret; setSelectionBackgroundDrawables(messageRecord);
this.failedIconHandler = failedIconHandler;
this.groupThread = groupThread;
this.pushDestination = pushDestination;
setBackgroundDrawables(messageRecord);
setBodyText(messageRecord); setBodyText(messageRecord);
if (!messageRecord.isGroupAction()) { if (!messageRecord.isGroupAction()) {
@ -211,33 +218,57 @@ public class ConversationItem extends LinearLayout {
/// MessageRecord Attribute Parsers /// MessageRecord Attribute Parsers
private void setBackgroundDrawables(MessageRecord messageRecord) { private void setConversationBackgroundDrawables(MessageRecord messageRecord) {
if (conversationParent != null && backgroundDrawables != null) { if (conversationParent != null && backgroundDrawables != null) {
if (messageRecord.isOutgoing()) { if (messageRecord.isOutgoing()) {
final int background; final int background;
final int triangleBackground; final int triangleBackground;
if (messageRecord.isPending() && pushDestination && !messageRecord.isForcedSms()) { if (messageRecord.isPending() && pushDestination && !messageRecord.isForcedSms()) {
background = SENT_PUSH_PENDING; background = SENT_PUSH_PENDING;
triangleBackground = SENT_PUSH_PENDING_TRIANGLE; triangleBackground = SENT_PUSH_PENDING_TRIANGLE;
} else if (messageRecord.isPending() || messageRecord.isPendingSmsFallback()) { } else if (messageRecord.isPending() || messageRecord.isPendingSmsFallback()) {
background = SENT_SMS_PENDING; background = SENT_SMS_PENDING;
triangleBackground = SENT_SMS_PENDING_TRIANGLE; triangleBackground = SENT_SMS_PENDING_TRIANGLE;
} else if (messageRecord.isPush()) { } else if (messageRecord.isPush()) {
background = SENT_PUSH; background = SENT_PUSH;
triangleBackground = SENT_PUSH_TRIANGLE; triangleBackground = SENT_PUSH_TRIANGLE;
} else { } else {
background = SENT_SMS; background = SENT_SMS;
triangleBackground = SENT_SMS_TRIANGLE; triangleBackground = SENT_SMS_TRIANGLE;
} }
setViewBackgroundWithoutResettingPadding(conversationParent, backgroundDrawables.getResourceId(background, -1)); setViewBackgroundWithoutResettingPadding(conversationParent, backgroundDrawables.getResourceId(background, -1));
setViewBackgroundWithoutResettingPadding(triangleTick, backgroundDrawables.getResourceId(triangleBackground, -1)); setViewBackgroundWithoutResettingPadding(triangleTick, backgroundDrawables.getResourceId(triangleBackground, -1));
} }
} }
} }
private void setSelectionBackgroundDrawables(MessageRecord messageRecord) {
int[] attributes = new int[]{R.attr.conversation_list_item_background_selected,
R.attr.conversation_item_background};
TypedArray drawables = context.obtainStyledAttributes(attributes);
if (batchSelected.contains(messageRecord)) {
setBackgroundDrawable(drawables.getDrawable(0));
} else {
setBackgroundDrawable(drawables.getDrawable(1));
}
drawables.recycle();
}
private void setBodyText(MessageRecord messageRecord) { private void setBodyText(MessageRecord messageRecord) {
bodyText.setText(Emoji.getInstance(context).emojify(messageRecord.getDisplayBody(), new Emoji.InvalidatingPageLoadedListener(bodyText)), bodyText.setClickable(false);
TextView.BufferType.SPANNABLE); bodyText.setFocusable(false);
bodyText.setText(Emoji.getInstance(context).emojify(messageRecord.getDisplayBody(),
new Emoji.InvalidatingPageLoadedListener(bodyText)),
TextView.BufferType.SPANNABLE);
if (bodyText.isClickable() && bodyText.isFocusable()) {
bodyText.setOnLongClickListener(new MultiSelectLongClickListener());
bodyText.setOnClickListener(new MultiSelectLongClickListener());
}
} }
private void setContactPhoto(MessageRecord messageRecord) { private void setContactPhoto(MessageRecord messageRecord) {
@ -365,12 +396,6 @@ public class ConversationItem extends LinearLayout {
if (slide.hasImage()) { if (slide.hasImage()) {
slide.setThumbnailOn(mmsThumbnail); slide.setThumbnailOn(mmsThumbnail);
mmsThumbnail.setOnClickListener(new ThumbnailClickListener(slide)); mmsThumbnail.setOnClickListener(new ThumbnailClickListener(slide));
mmsThumbnail.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return false;
}
});
mmsThumbnail.setVisibility(View.VISIBLE); mmsThumbnail.setVisibility(View.VISIBLE);
return; return;
} }
@ -474,7 +499,9 @@ public class ConversationItem extends LinearLayout {
} }
public void onClick(View v) { public void onClick(View v) {
if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType())) { if (!batchSelected.isEmpty()) {
selectionClickListener.onItemClick(null, ConversationItem.this, -1, -1);
} else if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType())) {
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);
intent.setDataAndType(slide.getUri(), slide.getContentType()); intent.setDataAndType(slide.getUri(), slide.getContentType());
@ -545,6 +572,19 @@ public class ConversationItem extends LinearLayout {
} }
} }
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;

View File

@ -182,4 +182,15 @@ public abstract class MessageRecord extends DisplayRecord {
return spannable; return spannable;
} }
public boolean equals(Object other) {
return other != null &&
other instanceof MessageRecord &&
((MessageRecord) other).getId() == getId() &&
((MessageRecord) other).isMms() == isMms();
}
public int hashCode() {
return (int)getId();
}
} }