And emoji reply is a reaction

Stephen Paul Weber created

Retracting an emoji reply retracts the reaction

Change summary

src/main/java/eu/siacs/conversations/entities/Conversation.java   | 25 
src/main/java/eu/siacs/conversations/entities/Message.java        | 36 
src/main/java/eu/siacs/conversations/ui/ConversationFragment.java | 17 
3 files changed, 75 insertions(+), 3 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/entities/Conversation.java 🔗

@@ -69,10 +69,12 @@ import org.json.JSONObject;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
+import java.util.HashSet;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
+import java.util.Set;
 import java.util.Timer;
 import java.util.TimerTask;
 
@@ -541,6 +543,29 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
         return false;
     }
 
+    public Set<String> findOwnReactionsTo(String id) {
+        Set<String> reactionEmoji = new HashSet<>();
+        Element reactions = null;
+        synchronized (this.messages) {
+            for (Message message : this.messages) {
+                if (message.getStatus() < Message.STATUS_SEND) continue;
+
+                final Element r = message.getReactions();
+                if (r != null && r.getAttribute("id") != null && id.equals(r.getAttribute("id"))) {
+                    reactions = r;
+                }
+            }
+        }
+        if (reactions != null) {
+            for (Element el : reactions.getChildren()) {
+                if (el.getName().equals("reaction") && el.getNamespace().equals("urn:xmpp:reactions:0")) {
+                    reactionEmoji.add(el.getContent());
+                }
+            }
+        }
+        return reactionEmoji;
+    }
+
     public void populateWithMessages(final List<Message> messages) {
         synchronized (this.messages) {
             messages.clear();

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

@@ -33,6 +33,7 @@ import java.time.Duration;
 import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
@@ -380,13 +381,17 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
         return values;
     }
 
+    public String replyId() {
+        return conversation.getMode() == Conversation.MODE_MULTI ? getServerMsgId() : getRemoteMsgId();
+	 }
+
     public Message reply() {
         Message m = new Message(conversation, QuoteHelper.quote(MessageUtils.prepareQuote(this)) + "\n", ENCRYPTION_NONE);
         m.setThread(getThread());
         m.addPayload(
             new Element("reply", "urn:xmpp:reply:0")
                 .setAttribute("to", getCounterpart())
-                .setAttribute("id", conversation.getMode() == Conversation.MODE_MULTI ? getServerMsgId() : getRemoteMsgId())
+                .setAttribute("id", replyId())
         );
         final Element fallback = new Element("fallback", "urn:xmpp:fallback:0").setAttribute("for", "urn:xmpp:reply:0");
         fallback.addChild("body", "urn:xmpp:fallback:0")
@@ -396,6 +401,35 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
         return m;
     }
 
+    public Message react(String emoji) {
+        Set<String> emojis = new HashSet<>();
+        if (conversation instanceof Conversation) emojis = ((Conversation) conversation).findOwnReactionsTo(replyId());
+        emojis.add(emoji);
+        final Message m = reply();
+        m.appendBody(emoji);
+        final Element fallback = new Element("fallback", "urn:xmpp:fallback:0").setAttribute("for", "urn:xmpp:reactions:0");
+        fallback.addChild("body", "urn:xmpp:fallback:0");
+        m.addPayload(fallback);
+        final Element reactions = new Element("reactions", "urn:xmpp:reactions:0").setAttribute("id", replyId());
+        for (String oneEmoji : emojis) {
+            reactions.addChild("reaction", "urn:xmpp:reactions:0").setContent(oneEmoji);
+        }
+        m.addPayload(reactions);
+        return m;
+    }
+
+    public Element getReactions() {
+        if (this.payloads == null) return null;
+
+        for (Element el : this.payloads) {
+            if (el.getName().equals("reactions") && el.getNamespace().equals("urn:xmpp:reactions:0")) {
+                return el;
+            }
+        }
+
+        return null;
+    }
+
     public String getConversationUuid() {
         return conversationUuid;
     }

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

@@ -137,6 +137,7 @@ import eu.siacs.conversations.ui.util.ViewUtil;
 import eu.siacs.conversations.ui.widget.EditMessage;
 import eu.siacs.conversations.utils.AccountUtils;
 import eu.siacs.conversations.utils.Compatibility;
+import eu.siacs.conversations.utils.Emoticons;
 import eu.siacs.conversations.utils.GeoHelper;
 import eu.siacs.conversations.utils.MessageUtils;
 import eu.siacs.conversations.utils.MimeUtils;
@@ -895,8 +896,12 @@ public class ConversationFragment extends XmppFragment
         final Message message;
         if (conversation.getCorrectingMessage() == null) {
             if (conversation.getReplyTo() != null) {
-                message = conversation.getReplyTo().reply();
-                message.appendBody(body);
+                if (Emoticons.isEmoji(body)) {
+                    message = conversation.getReplyTo().react(body);
+                } else {
+                    message = conversation.getReplyTo().reply();
+                    message.appendBody(body);
+                }
                 message.setEncryption(conversation.getNextEncryption());
             } else {
                 message = new Message(conversation, body, conversation.getNextEncryption());
@@ -1568,6 +1573,14 @@ public class ConversationFragment extends XmppFragment
                         while (message.mergeable(message.next())) {
                             message = message.next();
                         }
+                        Element reactions = message.getReactions();
+                        if (reactions != null) {
+                            for (Element el : reactions.getChildren()) {
+                                if (message.getQuoteableBody().endsWith(el.getContent())) {
+                                    reactions.removeChild(el);
+                                }
+                            }
+                        }
                         message.setBody(" ");
                         message.putEdited(message.getUuid(), message.getServerMsgId());
                         message.setServerMsgId(null);