From 3c5224251c1590f57e23ae339cb65cc8bdb54170 Mon Sep 17 00:00:00 2001 From: Sam Whited Date: Sun, 11 Jan 2015 10:22:29 -0500 Subject: [PATCH] Add XEP-0245 (/me command) support --- docs/XEPs.md | 1 + .../siacs/conversations/entities/Message.java | 47 ++- .../ui/ConversationFragment.java | 10 +- .../ui/adapter/ConversationAdapter.java | 19 +- .../ui/adapter/MessageAdapter.java | 284 ++++++++++-------- 5 files changed, 204 insertions(+), 157 deletions(-) diff --git a/docs/XEPs.md b/docs/XEPs.md index 35c7de455..c64f1f579 100644 --- a/docs/XEPs.md +++ b/docs/XEPs.md @@ -9,6 +9,7 @@ * XEP-0198: Stream Management * XEP-0234: Jingle File Transfer * XEP-0237: Roster Versioning +* XEP-0245: The /me Command * XEP-0249: Direct MUC Invitations (receiving only) * XEP-0260: Jingle SOCKS5 Bytestreams Transport Method * XEP-0261: Jingle In-Band Bytestreams Transport Method diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 8cb486356..e8e579abe 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -36,17 +36,18 @@ public class Message extends AbstractEntity { public static final int TYPE_STATUS = 3; public static final int TYPE_PRIVATE = 4; - public static String CONVERSATION = "conversationUuid"; - public static String COUNTERPART = "counterpart"; - public static String TRUE_COUNTERPART = "trueCounterpart"; - public static String BODY = "body"; - public static String TIME_SENT = "timeSent"; - public static String ENCRYPTION = "encryption"; - public static String STATUS = "status"; - public static String TYPE = "type"; - public static String REMOTE_MSG_ID = "remoteMsgId"; - public static String SERVER_MSG_ID = "serverMsgId"; - public static String RELATIVE_FILE_PATH = "relativeFilePath"; + public static final String CONVERSATION = "conversationUuid"; + public static final String COUNTERPART = "counterpart"; + public static final String TRUE_COUNTERPART = "trueCounterpart"; + public static final String BODY = "body"; + public static final String TIME_SENT = "timeSent"; + public static final String ENCRYPTION = "encryption"; + public static final String STATUS = "status"; + public static final String TYPE = "type"; + public static final String REMOTE_MSG_ID = "remoteMsgId"; + public static final String SERVER_MSG_ID = "serverMsgId"; + public static final String RELATIVE_FILE_PATH = "relativeFilePath"; + public boolean markable = false; protected String conversationUuid; protected Jid counterpart; @@ -348,17 +349,35 @@ public class Message extends AbstractEntity { } public boolean mergeable(final Message message) { - return message != null && (message.getType() == Message.TYPE_TEXT && this.getDownloadable() == null && message.getDownloadable() == null && message.getEncryption() != Message.ENCRYPTION_PGP && this.getType() == message.getType() && this.getStatus() == message.getStatus() && this.getEncryption() == message.getEncryption() && this.getCounterpart() != null && this.getCounterpart().equals(message.getCounterpart()) && (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) && !message.bodyContainsDownloadable() && !this.bodyContainsDownloadable()); + return message != null && + (message.getType() == Message.TYPE_TEXT && + this.getDownloadable() == null && + message.getDownloadable() == null && + message.getEncryption() != Message.ENCRYPTION_PGP && + this.getType() == message.getType() && + this.getStatus() == message.getStatus() && + this.getEncryption() == message.getEncryption() && + this.getCounterpart() != null && + this.getCounterpart().equals(message.getCounterpart()) && + (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) && + !message.bodyContainsDownloadable() && + !this.bodyContainsDownloadable() && + !this.body.startsWith("/me ") + ); } public String getMergedBody() { - Message next = this.next(); + final Message next = this.next(); if (this.mergeable(next)) { - return body.trim() + '\n' + next.getMergedBody(); + return getBody() + '\n' + next.getMergedBody(); } return body.trim(); } + public boolean hasMeCommand() { + return getMergedBody().startsWith("/me "); + } + public int getMergedStatus() { return getStatus(); } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 5eb81e64e..bd6fda987 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -425,16 +425,16 @@ public class ConversationFragment extends Fragment { if ((m.getType() != Message.TYPE_IMAGE && m.getDownloadable() == null) || m.getImageParams().url == null) { copyUrl.setVisible(false); - } + } if (m.getType() != Message.TYPE_TEXT || m.getDownloadable() != null || !m.bodyContainsDownloadable()) { downloadImage.setVisible(false); - } + } if (!((m.getDownloadable() != null && !(m.getDownloadable() instanceof DownloadablePlaceholder)) - || (m.isFileOrImage() && m.getStatus() == Message.STATUS_WAITING))) { + || (m.isFileOrImage() && m.getStatus() == Message.STATUS_WAITING))) { cancelTransmission.setVisible(false); - } + } } } @@ -657,7 +657,7 @@ public class ConversationFragment extends Fragment { } } conversation.populateWithMessages(ConversationFragment.this.messageList); - for (Message message : this.messageList) { + for (final Message message : this.messageList) { if (message.getEncryption() == Message.ENCRYPTION_PGP && (message.getStatus() == Message.STATUS_RECEIVED || message .getStatus() >= Message.STATUS_SEND) diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java index 2465380fd..9c606c3e4 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java @@ -1,5 +1,15 @@ package eu.siacs.conversations.ui.adapter; +import android.content.Context; +import android.graphics.Color; +import android.graphics.Typeface; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + import java.util.List; import eu.siacs.conversations.R; @@ -10,15 +20,6 @@ import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.ui.ConversationActivity; import eu.siacs.conversations.ui.XmppActivity; import eu.siacs.conversations.utils.UIHelper; -import android.content.Context; -import android.graphics.Color; -import android.graphics.Typeface; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.TextView; public class ConversationAdapter extends ArrayAdapter { diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 5b5664c6e..9a69db119 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -52,14 +52,14 @@ public class MessageAdapter extends ArrayAdapter { private OnContactPictureLongClicked mOnContactPictureLongClickedListener; private OnLongClickListener openContextMenu = new OnLongClickListener() { - + @Override public boolean onLongClick(View v) { v.showContextMenu(); return true; } }; - + public MessageAdapter(ConversationActivity activity, List messages) { super(activity, 0, messages); this.activity = activity; @@ -73,7 +73,7 @@ public class MessageAdapter extends ArrayAdapter { public void setOnContactPictureLongClicked( OnContactPictureLongClicked listener) { this.mOnContactPictureLongClickedListener = listener; - } + } @Override public int getViewTypeCount() { @@ -101,7 +101,7 @@ public class MessageAdapter extends ArrayAdapter { viewHolder.indicatorReceived.setVisibility(View.GONE); } boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI - && message.getMergedStatus() <= Message.STATUS_RECEIVED; + && message.getMergedStatus() <= Message.STATUS_RECEIVED; if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getDownloadable() != null) { ImageParams params = message.getImageParams(); if (params.size > (1.5 * 1024 * 1024)) { @@ -114,44 +114,39 @@ public class MessageAdapter extends ArrayAdapter { } } switch (message.getMergedStatus()) { - case Message.STATUS_WAITING: - info = getContext().getString(R.string.waiting); - break; - case Message.STATUS_UNSEND: - Downloadable d = message.getDownloadable(); - if (d!=null) { - info = getContext().getString(R.string.sending_file,d.getProgress()); - } else { - info = getContext().getString(R.string.sending); - } - break; - case Message.STATUS_OFFERED: - info = getContext().getString(R.string.offering); - break; - case Message.STATUS_SEND_RECEIVED: - if (activity.indicateReceived()) { - viewHolder.indicatorReceived.setVisibility(View.VISIBLE); - } - break; - case Message.STATUS_SEND_DISPLAYED: - if (activity.indicateReceived()) { - viewHolder.indicatorReceived.setVisibility(View.VISIBLE); - } - break; - case Message.STATUS_SEND_FAILED: - info = getContext().getString(R.string.send_failed); - error = true; - break; - default: - if (multiReceived) { - Contact contact = message.getContact(); - if (contact != null) { - info = contact.getDisplayName(); + case Message.STATUS_WAITING: + info = getContext().getString(R.string.waiting); + break; + case Message.STATUS_UNSEND: + Downloadable d = message.getDownloadable(); + if (d!=null) { + info = getContext().getString(R.string.sending_file,d.getProgress()); } else { - info = getDisplayedMucCounterpart(message.getCounterpart()); + info = getContext().getString(R.string.sending); } - } - break; + break; + case Message.STATUS_OFFERED: + info = getContext().getString(R.string.offering); + break; + case Message.STATUS_SEND_RECEIVED: + if (activity.indicateReceived()) { + viewHolder.indicatorReceived.setVisibility(View.VISIBLE); + } + break; + case Message.STATUS_SEND_DISPLAYED: + if (activity.indicateReceived()) { + viewHolder.indicatorReceived.setVisibility(View.VISIBLE); + } + break; + case Message.STATUS_SEND_FAILED: + info = getContext().getString(R.string.send_failed); + error = true; + break; + default: + if (multiReceived) { + info = getMessageDisplayName(message); + } + break; } if (error) { viewHolder.time.setTextColor(activity.getWarningTextColor()); @@ -212,26 +207,53 @@ public class MessageAdapter extends ArrayAdapter { viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.VISIBLE); viewHolder.messageBody.setText(getContext().getString( - R.string.decryption_failed)); + R.string.decryption_failed)); viewHolder.messageBody.setTextColor(activity.getWarningTextColor()); viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); viewHolder.messageBody.setTextIsSelectable(false); } - private void displayTextMessage(ViewHolder viewHolder, Message message) { + private String getMessageDisplayName(final Message message) { + if (message.getStatus() == Message.STATUS_RECEIVED) { + if (message.getConversation().getMode() == Conversation.MODE_MULTI) { + return getDisplayedMucCounterpart(message.getCounterpart()); + } else { + final Contact contact = message.getContact(); + return contact != null ? contact.getDisplayName() : ""; + } + } else { + if (message.getConversation().getMode() == Conversation.MODE_MULTI) { + return getDisplayedMucCounterpart(message.getConversation().getJid()); + } else { + final Jid jid = message.getConversation().getAccount().getJid(); + return jid.hasLocalpart() ? jid.getLocalpart() : jid.toDomainJid().toString(); + } + } + } + + private void displayTextMessage(final ViewHolder viewHolder, final Message message) { if (viewHolder.download_button != null) { viewHolder.download_button.setVisibility(View.GONE); } viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.VISIBLE); if (message.getBody() != null) { + final String nick = getMessageDisplayName(message); + final String formattedBody = message.getMergedBody().replaceAll("^/me ", nick + " "); if (message.getType() != Message.TYPE_PRIVATE) { - viewHolder.messageBody.setText(message.getMergedBody()); + if (message.hasMeCommand()) { + final Spannable span = new SpannableString(formattedBody); + span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + viewHolder.messageBody.setText(span); + } else { + viewHolder.messageBody.setText(message.getMergedBody()); + } } else { String privateMarker; if (message.getStatus() <= Message.STATUS_RECEIVED) { privateMarker = activity - .getString(R.string.private_message); + .getString(R.string.private_message); } else { final String to; if (message.getCounterpart() != null) { @@ -241,15 +263,19 @@ public class MessageAdapter extends ArrayAdapter { } privateMarker = activity.getString(R.string.private_message_to, to); } - SpannableString span = new SpannableString(privateMarker + " " - + message.getBody()); - span.setSpan( - new ForegroundColorSpan(activity - .getSecondaryTextColor()), 0, privateMarker - .length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - span.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, + final Spannable span = new SpannableString(privateMarker + " " + + formattedBody); + span.setSpan(new ForegroundColorSpan(activity + .getSecondaryTextColor()), 0, privateMarker + .length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + span.setSpan(new StyleSpan(Typeface.BOLD), 0, privateMarker.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + if (message.hasMeCommand()) { + span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), privateMarker.length() + 1, + privateMarker.length() + 1 + nick.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } viewHolder.messageBody.setText(span); } } else { @@ -281,7 +307,7 @@ public class MessageAdapter extends ArrayAdapter { viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.GONE); viewHolder.download_button.setVisibility(View.VISIBLE); - viewHolder.download_button.setText(activity.getString(R.string.open_file,file.getMimeType())); + viewHolder.download_button.setText(activity.getString(R.string.open_file, file.getMimeType())); viewHolder.download_button.setOnClickListener(new OnClickListener() { @Override @@ -311,7 +337,7 @@ public class MessageAdapter extends ArrayAdapter { scalledH = (int) (params.height / ((double) params.width / target)); } viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams( - scalledW, scalledH)); + scalledW, scalledH)); activity.loadBitmap(message, viewHolder.image); viewHolder.image.setOnClickListener(new OnClickListener() { @@ -346,59 +372,59 @@ public class MessageAdapter extends ArrayAdapter { if (view == null) { viewHolder = new ViewHolder(); switch (type) { - case NULL: - view = activity.getLayoutInflater().inflate( - R.layout.message_null, parent, false); - break; - case SENT: - view = activity.getLayoutInflater().inflate( - R.layout.message_sent, parent, false); - viewHolder.message_box = (LinearLayout) view + case NULL: + view = activity.getLayoutInflater().inflate( + R.layout.message_null, parent, false); + break; + case SENT: + view = activity.getLayoutInflater().inflate( + R.layout.message_sent, parent, false); + viewHolder.message_box = (LinearLayout) view .findViewById(R.id.message_box); - viewHolder.contact_picture = (ImageView) view + viewHolder.contact_picture = (ImageView) view .findViewById(R.id.message_photo); - viewHolder.download_button = (Button) view + viewHolder.download_button = (Button) view .findViewById(R.id.download_button); - viewHolder.indicator = (ImageView) view + viewHolder.indicator = (ImageView) view .findViewById(R.id.security_indicator); - viewHolder.image = (ImageView) view + viewHolder.image = (ImageView) view .findViewById(R.id.message_image); - viewHolder.messageBody = (TextView) view + viewHolder.messageBody = (TextView) view .findViewById(R.id.message_body); - viewHolder.time = (TextView) view + viewHolder.time = (TextView) view .findViewById(R.id.message_time); - viewHolder.indicatorReceived = (ImageView) view + viewHolder.indicatorReceived = (ImageView) view .findViewById(R.id.indicator_received); - break; - case RECEIVED: - view = activity.getLayoutInflater().inflate( - R.layout.message_received, parent, false); - viewHolder.message_box = (LinearLayout) view + break; + case RECEIVED: + view = activity.getLayoutInflater().inflate( + R.layout.message_received, parent, false); + viewHolder.message_box = (LinearLayout) view .findViewById(R.id.message_box); - viewHolder.contact_picture = (ImageView) view + viewHolder.contact_picture = (ImageView) view .findViewById(R.id.message_photo); - viewHolder.download_button = (Button) view + viewHolder.download_button = (Button) view .findViewById(R.id.download_button); - viewHolder.indicator = (ImageView) view + viewHolder.indicator = (ImageView) view .findViewById(R.id.security_indicator); - viewHolder.image = (ImageView) view + viewHolder.image = (ImageView) view .findViewById(R.id.message_image); - viewHolder.messageBody = (TextView) view + viewHolder.messageBody = (TextView) view .findViewById(R.id.message_body); - viewHolder.time = (TextView) view + viewHolder.time = (TextView) view .findViewById(R.id.message_time); - viewHolder.indicatorReceived = (ImageView) view + viewHolder.indicatorReceived = (ImageView) view .findViewById(R.id.indicator_received); - break; - case STATUS: - view = activity.getLayoutInflater().inflate( - R.layout.message_status, parent, false); - viewHolder.contact_picture = (ImageView) view + break; + case STATUS: + view = activity.getLayoutInflater().inflate( + R.layout.message_status, parent, false); + viewHolder.contact_picture = (ImageView) view .findViewById(R.id.message_photo); - break; - default: - viewHolder = null; - break; + break; + default: + viewHolder = null; + break; } view.setTag(viewHolder); } else { @@ -412,22 +438,22 @@ public class MessageAdapter extends ArrayAdapter { if (conversation.getMode() == Conversation.MODE_SINGLE) { viewHolder.contact_picture.setImageBitmap(activity .avatarService().get(conversation.getContact(), - activity.getPixel(32))); + activity.getPixel(32))); viewHolder.contact_picture.setAlpha(0.5f); viewHolder.contact_picture - .setOnClickListener(new OnClickListener() { + .setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - String name = conversation.getName(); - String read = getContext() - .getString( - R.string.contact_has_read_up_to_this_point, - name); - Toast.makeText(getContext(), read, - Toast.LENGTH_SHORT).show(); - } - }); + @Override + public void onClick(View v) { + String name = conversation.getName(); + String read = getContext() + .getString( + R.string.contact_has_read_up_to_this_point, + name); + Toast.makeText(getContext(), read, + Toast.LENGTH_SHORT).show(); + } + }); } return view; @@ -456,38 +482,38 @@ public class MessageAdapter extends ArrayAdapter { viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(contact, activity.getPixel(48))); } else if (conversation.getMode() == Conversation.MODE_MULTI) { viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(getDisplayedMucCounterpart(message.getCounterpart()), - activity.getPixel(48))); + activity.getPixel(48))); } } else if (type == SENT) { viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(account, activity.getPixel(48))); } viewHolder.contact_picture - .setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - if (MessageAdapter.this.mOnContactPictureClickedListener != null) { - MessageAdapter.this.mOnContactPictureClickedListener - .onContactPictureClicked(message); - } + .setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (MessageAdapter.this.mOnContactPictureClickedListener != null) { + MessageAdapter.this.mOnContactPictureClickedListener + .onContactPictureClicked(message); } - }); + + } + }); viewHolder.contact_picture - .setOnLongClickListener(new OnLongClickListener() { + .setOnLongClickListener(new OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) { - MessageAdapter.this.mOnContactPictureLongClickedListener - .onContactPictureLongClicked(message); - return true; - } else { - return false; - } + @Override + public boolean onLongClick(View v) { + if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) { + MessageAdapter.this.mOnContactPictureLongClickedListener + .onContactPictureLongClicked(message); + return true; + } else { + return false; } - }); + } + }); if (message.getDownloadable() != null && message.getDownloadable().getStatus() != Downloadable.STATUS_UPLOADING) { Downloadable d = message.getDownloadable(); @@ -536,13 +562,13 @@ public class MessageAdapter extends ArrayAdapter { activity.getString(R.string.install_openkeychain)); if (viewHolder != null) { viewHolder.message_box - .setOnClickListener(new OnClickListener() { + .setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - activity.showInstallPgpDialog(); - } - }); + @Override + public void onClick(View v) { + activity.showInstallPgpDialog(); + } + }); } } } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {