added limited private muc chat feature (messages are now properly marked) - long press on user icon will sent private messages. fixed #259

iNPUTmice created

Change summary

res/values/strings.xml                                         |  3 
src/eu/siacs/conversations/entities/Message.java               |  5 
src/eu/siacs/conversations/generator/MessageGenerator.java     |  3 
src/eu/siacs/conversations/parser/MessageParser.java           | 73 ++-
src/eu/siacs/conversations/services/XmppConnectionService.java |  2 
src/eu/siacs/conversations/ui/ConversationFragment.java        | 44 ++
src/eu/siacs/conversations/ui/adapter/MessageAdapter.java      | 55 ++
7 files changed, 155 insertions(+), 30 deletions(-)

Detailed changes

res/values/strings.xml 🔗

@@ -277,4 +277,7 @@
     <string name="error_saving_avatar">Could not save avatar to disk</string>
     <string name="or_long_press_for_default">(Or long press to bring back default)</string>
     <string name="error_publish_avatar_no_server_support">Your server does not support the publication of avatars</string>
+    <string name="private_message">in private</string>
+    <string name="private_message_to">in private to %s</string>
+    <string name="send_private_message_to">Send private message to %s</string>
 </resources>

src/eu/siacs/conversations/entities/Message.java 🔗

@@ -33,6 +33,7 @@ public class Message extends AbstractEntity {
 	public static final int TYPE_IMAGE = 1;
 	public static final int TYPE_AUDIO = 2;
 	public static final int TYPE_STATUS = 3;
+	public static final int TYPE_PRIVATE = 4;
 
 	public static String CONVERSATION = "conversationUuid";
 	public static String COUNTERPART = "counterpart";
@@ -257,4 +258,8 @@ public class Message extends AbstractEntity {
 		message.setConversation(conversation);
 		return message;
 	}
+
+	public void setCounterpart(String counterpart) {
+		this.counterpart = counterpart;
+	}
 }

src/eu/siacs/conversations/generator/MessageGenerator.java 🔗

@@ -22,6 +22,9 @@ public class MessageGenerator {
 			packet.setTo(message.getCounterpart());
 			packet.setType(MessagePacket.TYPE_CHAT);
 			packet.addChild("markable", "urn:xmpp:chat-markers:0");
+		} else if (message.getType() == Message.TYPE_PRIVATE) {
+			packet.setTo(message.getCounterpart());
+			packet.setType(MessagePacket.TYPE_CHAT);
 		} else {
 			packet.setTo(message.getCounterpart().split("/")[0]);
 			packet.setType(MessagePacket.TYPE_GROUPCHAT);

src/eu/siacs/conversations/parser/MessageParser.java 🔗

@@ -40,6 +40,14 @@ public class MessageParser extends AbstractParser implements
 					packet.getBody(), Message.ENCRYPTION_NONE,
 					Message.STATUS_RECIEVED);
 		}
+		if (conversation.getMode() == Conversation.MODE_MULTI
+				&& fromParts.length >= 2) {
+			finishedMessage.setType(Message.TYPE_PRIVATE);
+			finishedMessage.setPresence(fromParts[1]);
+			finishedMessage.setTrueCounterpart(conversation.getMucOptions()
+					.getTrueCounterpart(fromParts[1]));
+
+		}
 		finishedMessage.setTime(getTimestamp(packet));
 		return finishedMessage;
 	}
@@ -113,7 +121,8 @@ public class MessageParser extends AbstractParser implements
 	private Message parseGroupchat(MessagePacket packet, Account account) {
 		int status;
 		String[] fromParts = packet.getFrom().split("/");
-		if (mXmppConnectionService.find(account.pendingConferenceLeaves,account,fromParts[0]) != null) {
+		if (mXmppConnectionService.find(account.pendingConferenceLeaves,
+				account, fromParts[0]) != null) {
 			return null;
 		}
 		Conversation conversation = mXmppConnectionService
@@ -150,7 +159,8 @@ public class MessageParser extends AbstractParser implements
 		}
 		finishedMessage.setTime(getTimestamp(packet));
 		if (status == Message.STATUS_RECIEVED) {
-			finishedMessage.setTrueCounterpart(conversation.getMucOptions().getTrueCounterpart(counterPart));
+			finishedMessage.setTrueCounterpart(conversation.getMucOptions()
+					.getTrueCounterpart(counterPart));
 		}
 		return finishedMessage;
 	}
@@ -180,7 +190,7 @@ public class MessageParser extends AbstractParser implements
 		}
 		if (status == Message.STATUS_RECIEVED) {
 			fullJid = message.getAttribute("from");
-			if (fullJid == null ) {
+			if (fullJid == null) {
 				return null;
 			} else {
 				updateLastseen(message, account, true);
@@ -195,6 +205,7 @@ public class MessageParser extends AbstractParser implements
 		Conversation conversation = mXmppConnectionService
 				.findOrCreateConversation(account, parts[0], false);
 		conversation.setLatestMarkableMessageId(getMarkableMessageId(packet));
+		
 		String pgpBody = getPgpBody(message);
 		Message finishedMessage;
 		if (pgpBody != null) {
@@ -206,6 +217,16 @@ public class MessageParser extends AbstractParser implements
 					Message.ENCRYPTION_NONE, status);
 		}
 		finishedMessage.setTime(getTimestamp(message));
+		
+		if (conversation.getMode() == Conversation.MODE_MULTI
+				&& parts.length >= 2) {
+			finishedMessage.setType(Message.TYPE_PRIVATE);
+			finishedMessage.setPresence(parts[1]);
+			finishedMessage.setTrueCounterpart(conversation.getMucOptions()
+					.getTrueCounterpart(parts[1]));
+
+		}
+		
 		return finishedMessage;
 	}
 
@@ -216,9 +237,10 @@ public class MessageParser extends AbstractParser implements
 	}
 
 	private void parseNormal(Element packet, Account account) {
-		if (packet.hasChild("event","http://jabber.org/protocol/pubsub#event")) {
-			Element event = packet.findChild("event","http://jabber.org/protocol/pubsub#event");
-			parseEvent(event,packet.getAttribute("from"),account);
+		if (packet.hasChild("event", "http://jabber.org/protocol/pubsub#event")) {
+			Element event = packet.findChild("event",
+					"http://jabber.org/protocol/pubsub#event");
+			parseEvent(event, packet.getAttribute("from"), account);
 		}
 		if (packet.hasChild("displayed", "urn:xmpp:chat-markers:0")) {
 			String id = packet
@@ -235,8 +257,9 @@ public class MessageParser extends AbstractParser implements
 			updateLastseen(packet, account, false);
 			mXmppConnectionService.markMessage(account, fromParts[0], id,
 					Message.STATUS_SEND_RECEIVED);
-		} else if (packet.hasChild("x","http://jabber.org/protocol/muc#user")) {
-			Element x = packet.findChild("x","http://jabber.org/protocol/muc#user");
+		} else if (packet.hasChild("x", "http://jabber.org/protocol/muc#user")) {
+			Element x = packet.findChild("x",
+					"http://jabber.org/protocol/muc#user");
 			if (x.hasChild("invite")) {
 				Conversation conversation = mXmppConnectionService
 						.findOrCreateConversation(account,
@@ -244,15 +267,15 @@ public class MessageParser extends AbstractParser implements
 				if (!conversation.getMucOptions().online()) {
 					mXmppConnectionService.joinMuc(conversation);
 					mXmppConnectionService.updateConversationUi();
-				}	
+				}
 			}
 
 		} else if (packet.hasChild("x", "jabber:x:conference")) {
 			Element x = packet.findChild("x", "jabber:x:conference");
 			String jid = x.getAttribute("jid");
-			if (jid!=null) {
+			if (jid != null) {
 				Conversation conversation = mXmppConnectionService
-						.findOrCreateConversation(account,jid, true);
+						.findOrCreateConversation(account, jid, true);
 				if (!conversation.getMucOptions().online()) {
 					mXmppConnectionService.joinMuc(conversation);
 					mXmppConnectionService.updateConversationUi();
@@ -264,11 +287,12 @@ public class MessageParser extends AbstractParser implements
 	private void parseEvent(Element event, String from, Account account) {
 		Element items = event.findChild("items");
 		String node = items.getAttribute("node");
-		if (node!=null) {
+		if (node != null) {
 			if (node.equals("urn:xmpp:avatar:metadata")) {
 				Avatar avatar = Avatar.parseMetadata(items);
 				avatar.owner = from;
-				if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
+				if (mXmppConnectionService.getFileBackend().isAvatarCached(
+						avatar)) {
 					if (account.getJid().equals(from)) {
 						account.setAvatar(avatar.getFilename());
 					} else {
@@ -279,10 +303,11 @@ public class MessageParser extends AbstractParser implements
 					mXmppConnectionService.fetchAvatar(account, avatar);
 				}
 			} else {
-				Log.d("xmppService",account.getJid()+": "+node+" from "+from);
+				Log.d("xmppService", account.getJid() + ": " + node + " from "
+						+ from);
 			}
 		} else {
-			Log.d("xmppService",event.toString());
+			Log.d("xmppService", event.toString());
 		}
 	}
 
@@ -345,8 +370,7 @@ public class MessageParser extends AbstractParser implements
 					message.markUnread();
 				} else {
 					message.getConversation().markRead();
-					lastCarbonMessageReceived = SystemClock
-							.elapsedRealtime();
+					lastCarbonMessageReceived = SystemClock.elapsedRealtime();
 					notify = false;
 				}
 			}
@@ -366,11 +390,15 @@ public class MessageParser extends AbstractParser implements
 		if ((mXmppConnectionService.confirmMessages())
 				&& ((packet.getId() != null))) {
 			if (packet.hasChild("markable", "urn:xmpp:chat-markers:0")) {
-				MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account, packet, "urn:xmpp:chat-markers:0");
+				MessagePacket receipt = mXmppConnectionService
+						.getMessageGenerator().received(account, packet,
+								"urn:xmpp:chat-markers:0");
 				mXmppConnectionService.sendMessagePacket(account, receipt);
 			}
 			if (packet.hasChild("request", "urn:xmpp:receipts")) {
-				MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account, packet, "urn:xmpp:receipts");
+				MessagePacket receipt = mXmppConnectionService
+						.getMessageGenerator().received(account, packet,
+								"urn:xmpp:receipts");
 				mXmppConnectionService.sendMessagePacket(account, receipt);
 			}
 		}
@@ -383,9 +411,10 @@ public class MessageParser extends AbstractParser implements
 	}
 
 	private void parseHeadline(MessagePacket packet, Account account) {
-		if (packet.hasChild("event","http://jabber.org/protocol/pubsub#event")) {
-			Element event = packet.findChild("event","http://jabber.org/protocol/pubsub#event");
-			parseEvent(event,packet.getFrom(),account);
+		if (packet.hasChild("event", "http://jabber.org/protocol/pubsub#event")) {
+			Element event = packet.findChild("event",
+					"http://jabber.org/protocol/pubsub#event");
+			parseEvent(event, packet.getFrom(), account);
 		}
 	}
 }

src/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -528,7 +528,7 @@ public class XmppConnectionService extends Service {
 				} else {
 					message.getConversation().endOtrIfNeeded();
 					failWaitingOtrMessages(message.getConversation());
-					if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
+					if (message.getConversation().getMode() == Conversation.MODE_SINGLE || message.getType() == Message.TYPE_PRIVATE) {
 						message.setStatus(Message.STATUS_SEND);
 					}
 					packet = mMessageGenerator.generateChat(message);

src/eu/siacs/conversations/ui/ConversationFragment.java 🔗

@@ -17,6 +17,7 @@ import eu.siacs.conversations.ui.EditMessage.OnEnterPressed;
 import eu.siacs.conversations.ui.XmppActivity.OnPresenceSelected;
 import eu.siacs.conversations.ui.adapter.MessageAdapter;
 import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureClicked;
+import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureLongClicked;
 import eu.siacs.conversations.utils.UIHelper;
 import android.app.AlertDialog;
 import android.app.Fragment;
@@ -160,10 +161,22 @@ public class ConversationFragment extends Fragment {
 	
 	
 	private void sendMessage() {
-		if (mEditMessage.getText().length() < 1)
+		if (mEditMessage.getText().length() < 1) {
+			if (this.conversation.getMode() == Conversation.MODE_MULTI) {
+				conversation.setNextPresence(null);
+				updateChatMsgHint();
+			}
 			return;
+		}
 		Message message = new Message(conversation, mEditMessage.getText()
 				.toString(), conversation.getNextEncryption());
+		if (conversation.getMode() == Conversation.MODE_MULTI) {
+			if (conversation.getNextPresence() != null) {
+				message.setPresence(conversation.getNextPresence());
+				message.setType(Message.TYPE_PRIVATE);
+				conversation.setNextPresence(null);
+			}
+		}
 		if (conversation.getNextEncryption() == Message.ENCRYPTION_OTR) {
 			sendOtrMessage(message);
 		} else if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
@@ -230,7 +243,24 @@ public class ConversationFragment extends Fragment {
 			@Override
 			public void onContactPictureClicked(Message message) {
 				if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
-					highlightInConference(message.getCounterpart());
+					if (message.getPresence() != null) {
+						highlightInConference(message.getPresence());
+					} else {
+						highlightInConference(message.getCounterpart());
+					}
+				}
+			}
+		});
+		messageListAdapter.setOnContactPictureLongClicked(new OnContactPictureLongClicked() {
+			
+			@Override
+			public void onContactPictureLongClicked(Message message) {
+				if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
+					if (message.getPresence() != null) {
+						privateMessageWith(message.getPresence());
+					} else {
+						privateMessageWith(message.getCounterpart());
+					}
 				}
 			}
 		});
@@ -238,6 +268,12 @@ public class ConversationFragment extends Fragment {
 
 		return view;
 	}
+	
+	protected void privateMessageWith(String counterpart) {
+		this.mEditMessage.setHint(getString(R.string.send_private_message_to,counterpart));
+		this.mEditMessage.setText("");
+		this.conversation.setNextPresence(counterpart);
+	}
 
 	protected void highlightInConference(String nick) {
 		String oldString = mEditMessage.getText().toString().trim();
@@ -303,6 +339,9 @@ public class ConversationFragment extends Fragment {
 				activity.invalidateOptionsMenu();
 			}
 		}
+		if (this.conversation.getMode() == Conversation.MODE_MULTI) {
+			conversation.setNextPresence(null);
+		}
 	}
 
 	private void decryptMessage(Message message) {
@@ -406,6 +445,7 @@ public class ConversationFragment extends Fragment {
 			messagesView.setSelection(size - 1);
 		}
 		mEditMessage.setText("");
+		updateChatMsgHint();
 	}
 
 	protected void updateStatusMessages() {

src/eu/siacs/conversations/ui/adapter/MessageAdapter.java 🔗

@@ -14,6 +14,7 @@ import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.Typeface;
+import android.text.Html;
 import android.util.DisplayMetrics;
 import android.view.View;
 import android.view.ViewGroup;
@@ -40,6 +41,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
 	private DisplayMetrics metrics;
 
 	private OnContactPictureClicked mOnContactPictureClickedListener;
+	private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
 
 	public MessageAdapter(ConversationActivity activity, List<Message> messages) {
 		super(activity, 0, messages);
@@ -61,6 +63,10 @@ public class MessageAdapter extends ArrayAdapter<Message> {
 	public void setOnContactPictureClicked(OnContactPictureClicked listener) {
 		this.mOnContactPictureClickedListener = listener;
 	}
+	
+	public void setOnContactPictureLongClicked(OnContactPictureLongClicked listener) {
+		this.mOnContactPictureLongClickedListener = listener;
+	}
 
 	@Override
 	public int getViewTypeCount() {
@@ -120,7 +126,11 @@ public class MessageAdapter extends ArrayAdapter<Message> {
 				if (contact != null) {
 					info = contact.getDisplayName();
 				} else {
-					info = message.getCounterpart();
+					if (message.getPresence() != null) {
+						info = message.getPresence();
+					} else {
+						info = message.getCounterpart();
+					}
 				}
 			}
 			break;
@@ -190,14 +200,33 @@ public class MessageAdapter extends ArrayAdapter<Message> {
 		viewHolder.messageBody.setTextIsSelectable(false);
 	}
 
-	private void displayTextMessage(ViewHolder viewHolder, String text) {
+	private void displayTextMessage(ViewHolder viewHolder, Message message) {
 		if (viewHolder.download_button != null) {
 			viewHolder.download_button.setVisibility(View.GONE);
 		}
 		viewHolder.image.setVisibility(View.GONE);
 		viewHolder.messageBody.setVisibility(View.VISIBLE);
-		if (text != null) {
-			viewHolder.messageBody.setText(text.trim());
+		if (message.getBody() != null) {
+			if (message.getType() != Message.TYPE_PRIVATE) {
+				viewHolder.messageBody.setText(message.getBody().trim());
+			} else {
+				StringBuilder builder = new StringBuilder();
+				builder.append(message.getBody().trim());
+				builder.append("&nbsp;<b><i>(");
+				if (message.getStatus() <= Message.STATUS_RECIEVED) {
+					builder.append(activity.getString(R.string.private_message));
+				} else {
+					String to;
+					if (message.getPresence() != null) {
+						to = message.getPresence();
+					} else {
+						to = message.getCounterpart();
+					}
+					builder.append(activity.getString(R.string.private_message_to, to));
+				}
+				builder.append(")</i></b>");
+				viewHolder.messageBody.setText(Html.fromHtml(builder.toString()));
+			}
 		} else {
 			viewHolder.messageBody.setText("");
 		}
@@ -376,6 +405,18 @@ public class MessageAdapter extends ArrayAdapter<Message> {
 
 							}
 						});
+				viewHolder.contact_picture.setOnLongClickListener(new OnLongClickListener() {
+					
+					@Override
+					public boolean onLongClick(View v) {
+						if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
+							MessageAdapter.this.mOnContactPictureLongClickedListener.onContactPictureLongClicked(item);
+							return true;
+						} else {
+							return false;
+						}
+					}
+				});
 			}
 		}
 
@@ -426,7 +467,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
 			} else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
 				displayDecryptionFailed(viewHolder);
 			} else {
-				displayTextMessage(viewHolder, item.getBody());
+				displayTextMessage(viewHolder, item);
 			}
 		}
 
@@ -474,4 +515,8 @@ public class MessageAdapter extends ArrayAdapter<Message> {
 	public interface OnContactPictureClicked {
 		public void onContactPictureClicked(Message message);
 	}
+	
+	public interface OnContactPictureLongClicked {
+		public void onContactPictureLongClicked(Message message);
+	}
 }