reactions
This commit is contained in:
parent
9467fc1789
commit
305ae7a288
|
@ -79,10 +79,12 @@ import java.time.format.DateTimeParseException;
|
|||
import java.time.format.FormatStyle;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
@ -518,6 +520,23 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
return null;
|
||||
}
|
||||
|
||||
public Message findMessageWithRemoteIdAndCounterpart(String id, Jid counterpart) {
|
||||
synchronized (this.messages) {
|
||||
for (int i = this.messages.size() - 1; i >= 0; --i) {
|
||||
final Message message = messages.get(i);
|
||||
final Jid mcp = message.getCounterpart();
|
||||
if (mcp == null && counterpart != null) {
|
||||
continue;
|
||||
}
|
||||
if (counterpart == null || mcp.equals(counterpart) || mcp.asBareJid().equals(counterpart)) {
|
||||
final boolean idMatch = id.equals(message.getUuid()) || id.equals(message.getRemoteMsgId()) || message.remoteMsgIdMatchInEdit(id) || (getMode() == MODE_MULTI && id.equals(message.getServerMsgId()));
|
||||
if (idMatch) return message;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Message findMessageWithRemoteIdAndCounterpart(String id, Jid counterpart, boolean received, boolean carbon) {
|
||||
synchronized (this.messages) {
|
||||
for (int i = this.messages.size() - 1; i >= 0; --i) {
|
||||
|
@ -585,6 +604,40 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
return false;
|
||||
}
|
||||
|
||||
public Message findMessageReactingTo(String id, Jid reactor) {
|
||||
if (id == null) return null;
|
||||
|
||||
synchronized (this.messages) {
|
||||
for (int i = this.messages.size() - 1; i >= 0; --i) {
|
||||
final Message message = messages.get(i);
|
||||
if (reactor == null && message.getStatus() < Message.STATUS_SEND) continue;
|
||||
if (reactor != null && message.getCounterpart() == null) continue;
|
||||
if (reactor != null && !(message.getCounterpart().equals(reactor) || message.getCounterpart().asBareJid().equals(reactor))) continue;
|
||||
|
||||
final Element r = message.getReactions();
|
||||
if (r != null && r.getAttribute("id") != null && id.equals(r.getAttribute("id"))) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Set<String> findReactionsTo(String id, Jid reactor) {
|
||||
Set<String> reactionEmoji = new HashSet<>();
|
||||
Message reactM = findMessageReactingTo(id, reactor);
|
||||
Element reactions = reactM == null ? null : reactM.getReactions();
|
||||
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) {
|
||||
if (historyPartMessages.size() > 0) {
|
||||
messages.clear();
|
||||
|
|
|
@ -16,6 +16,7 @@ import java.io.IOException;
|
|||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
@ -362,6 +363,42 @@ 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).findReactionsTo(replyId(), null);
|
||||
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 void setReactions(Element reactions) {
|
||||
if (this.payloads != null) {
|
||||
this.payloads.remove(getReactions());
|
||||
}
|
||||
addPayload(reactions);
|
||||
}
|
||||
|
||||
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 Element getReply() {
|
||||
if (this.payloads == null) return null;
|
||||
|
||||
|
@ -863,6 +900,31 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
|||
return new ArrayList<>(this.payloads);
|
||||
}
|
||||
|
||||
public List<Element> getFallbacks(String... includeFor) {
|
||||
List<Element> fallbacks = new ArrayList<>();
|
||||
|
||||
if (this.payloads == null) return fallbacks;
|
||||
|
||||
for (Element el : this.payloads) {
|
||||
if (el.getName().equals("fallback") && el.getNamespace().equals("urn:xmpp:fallback:0")) {
|
||||
final String fallbackFor = el.getAttribute("for");
|
||||
if (fallbackFor == null) continue;
|
||||
for (String includeOne : includeFor) {
|
||||
if (fallbackFor.equals(includeOne)) {
|
||||
fallbacks.add(el);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fallbacks;
|
||||
}
|
||||
|
||||
public synchronized void clearFallbacks(String... includeFor) {
|
||||
this.payloads.removeAll(getFallbacks(includeFor));
|
||||
}
|
||||
|
||||
public void setOob(boolean isOob) {
|
||||
this.oob = isOob;
|
||||
}
|
||||
|
|
|
@ -410,13 +410,21 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
|||
if (timestamp == null) {
|
||||
timestamp = AbstractParser.parseTimestamp(original, AbstractParser.parseTimestamp(packet));
|
||||
}
|
||||
final LocalizedContent body = packet.getBody();
|
||||
|
||||
LocalizedContent body = packet.getBody();
|
||||
final Element mucUserElement = packet.findChild("x", Namespace.MUC_USER);
|
||||
final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted");
|
||||
final Element replaceElement = packet.findChild("replace", "urn:xmpp:message-correct:0");
|
||||
final Element oob = packet.findChild("x", Namespace.OOB);
|
||||
final String oobUrl = oob != null ? oob.findChildContent("url") : null;
|
||||
final String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id");
|
||||
String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id");
|
||||
if (replacementId == null) {
|
||||
Element fasten = packet.findChild("apply-to", "urn:xmpp:fasten:0");
|
||||
if (fasten != null && fasten.findChild("retract", "urn:xmpp:message-retract:0") != null) {
|
||||
replacementId = fasten.getAttribute("id");
|
||||
packet.setBody("");
|
||||
}
|
||||
}
|
||||
final Element axolotlEncrypted = packet.findChildEnsureSingle(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
|
||||
int status;
|
||||
final Jid counterpart;
|
||||
|
@ -479,6 +487,27 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
|||
nextCounterpart = counterpart;
|
||||
}
|
||||
|
||||
final Element reactions = packet.findChild("reactions", "urn:xmpp:reactions:0");
|
||||
if (body == null) {
|
||||
if (reactions != null && reactions.getAttribute("id") != null) {
|
||||
final Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid(), nextCounterpart);
|
||||
if (conversation != null) {
|
||||
final Message reactionTo = conversation.findMessageWithRemoteIdAndCounterpart(reactions.getAttribute("id"), null);
|
||||
if (reactionTo != null) {
|
||||
String bodyS = reactionTo.reply().getBody();
|
||||
for (Element el : reactions.getChildren()) {
|
||||
if (el.getName().equals("reaction") && el.getNamespace().equals("urn:xmpp:reactions:0")) {
|
||||
bodyS += el.getContent();
|
||||
}
|
||||
}
|
||||
body = new LocalizedContent(bodyS, "en", 1);
|
||||
final Message previousReaction = conversation.findMessageReactingTo(reactions.getAttribute("id"), counterpart);
|
||||
if (previousReaction != null) replacementId = previousReaction.replyId();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nextCounterpart != null && mXmppConnectionService.checkIsArchived(account, counterpart.asBareJid(), nextCounterpart)) {
|
||||
return;
|
||||
}
|
||||
|
@ -605,7 +634,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
|||
}
|
||||
}
|
||||
message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
|
||||
|
||||
if (reactions != null) message.addPayload(reactions);
|
||||
for (Element el : packet.getChildren()) {
|
||||
String name = el.getName();
|
||||
String ns = el.getNamespace();
|
||||
|
|
|
@ -1083,7 +1083,12 @@ public class XmppConnectionService extends Service {
|
|||
private void directReply(final Conversation conversation, final String body, final String lastMessageUuid, final boolean dismissAfterReply) {final Message inReplyTo = lastMessageUuid == null ? null : conversation.findMessageWithUuid(lastMessageUuid);
|
||||
Message message = new Message(conversation, body, conversation.getNextEncryption());
|
||||
if (inReplyTo != null) {
|
||||
if (Emoticons.isEmoji(body.toString().replaceAll("\\s", ""))) {
|
||||
message = conversation.getReplyTo().react(body.toString().replaceAll("\\s", ""));
|
||||
} else {
|
||||
message = inReplyTo.reply();
|
||||
}
|
||||
message.clearFallbacks("urn:xmpp:reply:0");
|
||||
message.setBody(body);
|
||||
message.setEncryption(conversation.getNextEncryption());
|
||||
}
|
||||
|
|
|
@ -146,6 +146,7 @@ import eu.siacs.conversations.ui.widget.HighlighterView;
|
|||
import eu.siacs.conversations.ui.widget.TabLayout;
|
||||
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.NickValidityChecker;
|
||||
|
@ -1046,8 +1047,12 @@ public class ConversationFragment extends XmppFragment
|
|||
final Message message;
|
||||
if (conversation.getCorrectingMessage() == null) {
|
||||
if (conversation.getReplyTo() != null) {
|
||||
if (Emoticons.isEmoji(body.toString().replaceAll("\\s", ""))) {
|
||||
message = conversation.getReplyTo().react(body.toString().replaceAll("\\s", ""));
|
||||
} else {
|
||||
message = conversation.getReplyTo().reply();
|
||||
message.appendBody(body);
|
||||
}
|
||||
message.setEncryption(conversation.getNextEncryption());
|
||||
} else {
|
||||
message = new Message(conversation, body, conversation.getNextEncryption());
|
||||
|
@ -1708,6 +1713,7 @@ public class ConversationFragment extends XmppFragment
|
|||
MenuItem quoteMessage = menu.findItem(R.id.quote_message);
|
||||
MenuItem retryDecryption = menu.findItem(R.id.retry_decryption);
|
||||
MenuItem correctMessage = menu.findItem(R.id.correct_message);
|
||||
MenuItem retractMessage = menu.findItem(R.id.retract_message);
|
||||
MenuItem shareWith = menu.findItem(R.id.share_with);
|
||||
MenuItem sendAgain = menu.findItem(R.id.send_again);
|
||||
MenuItem copyUrl = menu.findItem(R.id.copy_url);
|
||||
|
@ -1752,6 +1758,7 @@ public class ConversationFragment extends XmppFragment
|
|||
if (m.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED && !deleted) {
|
||||
retryDecryption.setVisible(true);
|
||||
}
|
||||
|
||||
if (!showError
|
||||
&& m.getType() == Message.TYPE_TEXT
|
||||
&& !m.isGeoUri()
|
||||
|
@ -1759,6 +1766,24 @@ public class ConversationFragment extends XmppFragment
|
|||
&& m.getConversation() instanceof Conversation) {
|
||||
correctMessage.setVisible(true);
|
||||
}
|
||||
|
||||
if (!showError
|
||||
&& m.getType() == Message.TYPE_TEXT
|
||||
&& !m.isGeoUri()
|
||||
&& m.isLastCorrectableMessage()
|
||||
&& m.getConversation() instanceof Conversation) {
|
||||
correctMessage.setVisible(true);
|
||||
|
||||
if (!m.getBody().equals("") && !m.getBody().equals(" ")) {
|
||||
retractMessage.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (m.getReactions() != null) {
|
||||
correctMessage.setVisible(false);
|
||||
retractMessage.setVisible(true);
|
||||
}
|
||||
|
||||
if ((m.isFileOrImage() && !deleted && !receiving)
|
||||
|| (m.getType() == Message.TYPE_TEXT && !m.treatAsDownloadable())
|
||||
&& !unInitiatedButKnownSize
|
||||
|
@ -1830,6 +1855,36 @@ public class ConversationFragment extends XmppFragment
|
|||
case R.id.correct_message:
|
||||
correctMessage(selectedMessage);
|
||||
return true;
|
||||
case R.id.retract_message:
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.retract_message)
|
||||
.setMessage(R.string.retract_message_alert_title)
|
||||
.setPositiveButton(R.string.yes, (dialog, whichButton) -> {
|
||||
Message message = selectedMessage;
|
||||
|
||||
Element reactions = message.getReactions();
|
||||
if (reactions != null) {
|
||||
final Message previousReaction = conversation.findMessageReactingTo(reactions.getAttribute("id"), null);
|
||||
if (previousReaction != null) reactions = previousReaction.getReactions();
|
||||
for (Element el : reactions.getChildren()) {
|
||||
if (message.getBody().endsWith(el.getContent())) {
|
||||
reactions.removeChild(el);
|
||||
}
|
||||
}
|
||||
message.setReactions(reactions);
|
||||
if (previousReaction != null) {
|
||||
previousReaction.setReactions(reactions);
|
||||
activity.xmppConnectionService.updateMessage(previousReaction);
|
||||
}
|
||||
}
|
||||
message.setBody(" ");
|
||||
message.putEdited(message.getUuid(), message.getServerMsgId());
|
||||
message.setServerMsgId(null);
|
||||
message.setUuid(UUID.randomUUID().toString());
|
||||
sendMessage(message);
|
||||
})
|
||||
.setNegativeButton(R.string.no, null).show();
|
||||
return true;
|
||||
case R.id.copy_message:
|
||||
ShareUtil.copyToClipboard(activity, selectedMessage);
|
||||
return true;
|
||||
|
|
|
@ -14,7 +14,7 @@ public class LocalizedContent {
|
|||
public final String language;
|
||||
public final int count;
|
||||
|
||||
private LocalizedContent(String content, String language, int count) {
|
||||
public LocalizedContent(String content, String language, int count) {
|
||||
this.content = content;
|
||||
this.language = language;
|
||||
this.count = count;
|
||||
|
@ -23,14 +23,14 @@ public class LocalizedContent {
|
|||
public static LocalizedContent get(final Element element, String name) {
|
||||
final HashMap<String, String> contents = new HashMap<>();
|
||||
final String parentLanguage = element.getAttribute("xml:lang");
|
||||
for(Element child : element.children) {
|
||||
for(Element child : element.getChildren()) {
|
||||
if (name.equals(child.getName())) {
|
||||
final String namespace = child.getNamespace();
|
||||
final String childLanguage = child.getAttribute("xml:lang");
|
||||
final String lang = childLanguage == null ? parentLanguage : childLanguage;
|
||||
final String content = child.getContent();
|
||||
if (content != null && (namespace == null || Namespace.JABBER_CLIENT.equals(namespace))) {
|
||||
if (contents.put(lang, content) != null) {
|
||||
if (namespace == null || "jabber:client".equals(namespace)) {
|
||||
if (contents.put(lang, content == null ? "" : content) != null) {
|
||||
//anything that has multiple contents for the same language is invalid
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
android:visible="false" />
|
||||
<item
|
||||
android:id="@+id/quote_message"
|
||||
android:title="@string/quote"
|
||||
android:title="@string/reply"
|
||||
android:visible="false" />
|
||||
|
||||
<item
|
||||
|
@ -38,6 +38,10 @@
|
|||
android:id="@+id/correct_message"
|
||||
android:title="@string/correct_message"
|
||||
android:visible="false" />
|
||||
<item
|
||||
android:id="@+id/retract_message"
|
||||
android:title="@string/retract_message"
|
||||
android:visible="false" />
|
||||
<item
|
||||
android:id="@+id/copy_url"
|
||||
android:title="@string/copy_original_url"
|
||||
|
|
|
@ -544,6 +544,7 @@
|
|||
<string name="no_accounts">(No activated accounts)</string>
|
||||
<string name="this_field_is_required">This field is required</string>
|
||||
<string name="correct_message">Correct message</string>
|
||||
<string name="retract_message">Retract message</string>
|
||||
<string name="send_corrected_message">Send corrected message</string>
|
||||
<string name="no_keys_just_confirm">You have already trusted this persons fingerprint. By selecting “Done” you are just confirming that %s is part of this group chat.</string>
|
||||
<string name="this_account_is_disabled">You have disabled this account</string>
|
||||
|
@ -1072,4 +1073,5 @@
|
|||
<string name="contact_tag_general">General</string>
|
||||
<string name="contact_tag_with_total">%1$s (%2$d)</string>
|
||||
<string name="refresh_feature_discovery">Refresh Feature Discovery</string>
|
||||
<string name="retract_message_alert_title">Do you really want to retract this message?</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue