Re-use existing forked thread when exists

Stephen Paul Weber created

When replying to or mentioning a null-thread message, re-use the thread
id of any existing forked thread from a previous reply to that message
if there is one, so that not everyone replying to a null thread message
ends up generating a new thread when most of the time they mean to all
end up in the same new thread.

Of course user may tap thread icon at any time if they actually want a
second fork.

Mentions still do not generate a new thread since their association is
not tracked, but will follow the forked thread if it exists.

Change summary

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

Detailed changes

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

@@ -599,6 +599,23 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
         return reactionEmoji;
     }
 
+    public Set<Message> findReplies(String id) {
+        Set<Message> replies = new HashSet<>();
+
+        synchronized (this.messages) {
+            for (int i = this.messages.size() - 1; i >= 0; --i) {
+                final Message message = messages.get(i);
+                if (id.equals(message.getServerMsgId())) break;
+                if (id.equals(message.getUuid())) break;
+                final Element r = message.getReply();
+                if (r != null && r.getAttribute("id") != null && id.equals(r.getAttribute("id"))) {
+                    replies.add(message);
+                }
+            }
+        }
+        return replies;
+    }
+
     public long loadMoreTimestamp() {
         if (messages.size() < 1) return 0;
         if (getLockThread() && messages.size() > 5000) return 0;

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

@@ -1500,10 +1500,23 @@ public class ConversationFragment extends XmppFragment
         if (message.isPrivateMessage()) privateMessageWith(message.getCounterpart());
         setThread(message.getThread());
         conversation.setUserSelectedThread(true);
-        if (message.getThread() == null && conversation.getMode() == Conversation.MODE_MULTI) newThread();
+        if (!forkNullThread(message)) newThread();
         setupReply(message);
     }
 
+    private boolean forkNullThread(Message message) {
+        if (message.getThread() != null || conversation.getMode() != Conversation.MODE_MULTI) return true;
+        for (final Message m : conversation.findReplies(message.getServerMsgId())) {
+            final Element thread = m.getThread();
+            if (thread != null) {
+                setThread(thread);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
     private void setupReply(Message message) {
         conversation.setReplyTo(message);
         if (message == null) {
@@ -4188,8 +4201,12 @@ public class ConversationFragment extends XmppFragment
 
     @Override
     public void onContactPictureClicked(Message message) {
-        if (message.isPrivateMessage()) privateMessageWith(message.getCounterpart());
         setThread(message.getThread());
+        if (message.isPrivateMessage()) {
+            privateMessageWith(message.getCounterpart());
+            return;
+        }
+        forkNullThread(message);
         conversation.setUserSelectedThread(true);
 
         final boolean received = message.getStatus() <= Message.STATUS_RECEIVED;