From 52d05e4261513ac2752911a73b74b9b697c8da53 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Wed, 26 Oct 2022 08:59:03 -0500 Subject: [PATCH 01/10] Store thread data on incoming messages --- .../eu/siacs/conversations/entities/Message.java | 12 ++++++++++++ .../eu/siacs/conversations/parser/MessageParser.java | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index e88bf3265506aa2815da29217b4e3c7903e8de5f..7e89357f9d69f7d9cc900218688c894bba2a169c 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -418,6 +418,18 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable this.subject = subject; } + public Element getThread() { + if (this.payloads == null) return null; + + for (Element el : this.payloads) { + if (el.getName().equals("thread") && el.getNamespace().equals("jabber:client")) { + return el; + } + } + + return null; + } + public void setMucUser(MucOptions.User user) { this.user = new WeakReference<>(user); } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 952d3d83efada6f51e6a5cb657c42589e0555e23..a6f924b28cb7e903f872c88285dfb18395fc14e0 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -610,6 +610,10 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece if (el.getName().equals("query") && el.getNamespace().equals("http://jabber.org/protocol/disco#items") && el.getAttribute("node").equals("http://jabber.org/protocol/commands")) { message.addPayload(el); } + if (el.getName().equals("thread") && (el.getNamespace() == null || el.getNamespace().equals("jabber:client"))) { + el.setAttribute("xmlns", "jabber:client"); + message.addPayload(el); + } } if (conversationMultiMode) { message.setMucUser(conversation.getMucOptions().findUserByFullJid(counterpart)); From 5b6a81cd272061fdafdeff65347a83667356c078 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Wed, 26 Oct 2022 09:00:07 -0500 Subject: [PATCH 02/10] Show identicon for thread when present --- build.gradle | 1 + .../ui/adapter/MessageAdapter.java | 17 +++++++++++++++++ .../eu/siacs/conversations/utils/UIHelper.java | 12 ++++++++++++ src/main/res/layout/account_row.xml | 1 + src/main/res/layout/message_received.xml | 9 +++++++++ 5 files changed, 40 insertions(+) diff --git a/build.gradle b/build.gradle index 72d2cbe280b9231cf530e76574e1531b527e17ed..79c4737acb03a5bfed8adcf642c1de5a634333c9 100644 --- a/build.gradle +++ b/build.gradle @@ -98,6 +98,7 @@ dependencies { implementation 'com.github.ipld:java-cid:v1.3.1' implementation 'com.splitwise:tokenautocomplete:3.0.2' implementation 'me.saket:better-link-movement-method:2.2.0' + implementation 'com.github.singpolyma:android-identicons:master-SNAPSHOT' implementation 'org.snikket:webrtc-android:107.0.0' // INSERT } 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 7e0897b271e1004ec7b47a3c1a8c701e7a8c6a96..daa170dbfaa736227bd46e5e362ff3099703d304 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -42,6 +42,8 @@ import com.cheogram.android.BobTransfer; import com.google.common.base.Strings; +import com.lelloman.identicon.view.GithubIdenticonView; + import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; @@ -733,6 +735,7 @@ public class MessageAdapter extends ArrayAdapter { viewHolder.encryption = view.findViewById(R.id.message_encryption); viewHolder.audioPlayer = view.findViewById(R.id.audio_player); viewHolder.commands_list = view.findViewById(R.id.commands_list); + viewHolder.thread_identicon = view.findViewById(R.id.thread_identicon); break; case STATUS: view = activity.getLayoutInflater().inflate(R.layout.message_status, parent, false); @@ -751,6 +754,19 @@ public class MessageAdapter extends ArrayAdapter { } } + if (viewHolder.thread_identicon != null) { + viewHolder.thread_identicon.setVisibility(View.GONE); + final Element thread = message.getThread(); + if (thread != null) { + final String threadId = thread.getContent(); + if (threadId != null) { + viewHolder.thread_identicon.setVisibility(View.VISIBLE); + viewHolder.thread_identicon.setColor(UIHelper.getColorForName(threadId)); + viewHolder.thread_identicon.setHash(UIHelper.identiconHash(threadId)); + } + } + } + boolean darkBackground = type == RECEIVED && (!isInValidSession || mUseGreenBackground) || activity.isDarkTheme(); if (type == DATE_SEPARATOR) { @@ -1028,6 +1044,7 @@ public class MessageAdapter extends ArrayAdapter { protected TextView status_message; protected TextView encryption; protected ListView commands_list; + protected GithubIdenticonView thread_identicon; } class ThumbnailTask extends AsyncTask { diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index 7692138be38c01d2268ee43e7626cdae00719e86..2bbcd08b7c5094da14ad123e9d0ce05bd850c593 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -11,8 +11,10 @@ import androidx.annotation.ColorInt; import androidx.core.content.res.ResourcesCompat; import com.google.common.base.Strings; +import com.google.common.primitives.Ints; import java.math.BigInteger; +import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.Arrays; import java.util.Calendar; @@ -229,6 +231,16 @@ public class UIHelper { } } + public static int identiconHash(String name) { + try { + MessageDigest sha1 = MessageDigest.getInstance("SHA-1"); + byte[] digest = sha1.digest(name.getBytes(StandardCharsets.UTF_8)); + return Ints.fromByteArray(digest); + } catch (Exception e) { + return 0; + } + } + public static int getColorForName(String name) { return getColorForName(name, false); } diff --git a/src/main/res/layout/account_row.xml b/src/main/res/layout/account_row.xml index 914ee1950002312e4dcfbeda64954244f17737e1..3457b68bb5311044d55c9ea709666046584f4f2f 100644 --- a/src/main/res/layout/account_row.xml +++ b/src/main/res/layout/account_row.xml @@ -22,6 +22,7 @@ android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_centerVertical="true" + android:layout_toEndOf="@+id/account_image" android:layout_toRightOf="@+id/account_image" android:orientation="vertical" android:paddingLeft="@dimen/avatar_item_distance" diff --git a/src/main/res/layout/message_received.xml b/src/main/res/layout/message_received.xml index 3cfde53b9437d0eaa151cd5a6a17b9bfb51a93c8..bbec1e533ab50a8b7d4637495ca99c5241e43638 100644 --- a/src/main/res/layout/message_received.xml +++ b/src/main/res/layout/message_received.xml @@ -93,6 +93,15 @@ android:gravity="center_vertical" android:src="@drawable/ic_mode_edit_white_18dp" /> + + Date: Wed, 26 Oct 2022 11:00:18 -0500 Subject: [PATCH 03/10] Show thread marker, send thread in stanza, allow replying and starting new thread --- .../conversations/entities/Conversation.java | 9 +++ .../siacs/conversations/entities/Message.java | 13 ++++- .../generator/MessageGenerator.java | 3 + .../ui/ConversationFragment.java | 58 ++++++++++++++----- .../ui/adapter/MessageAdapter.java | 1 + src/main/res/layout/fragment_conversation.xml | 15 ++++- src/main/res/layout/message_sent.xml | 52 ++++++++++------- 7 files changed, 113 insertions(+), 38 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index b9bdd21b2d2ecc5721b15ce66b1dfcf492904389..49cbb720f31a4ad2d7abd7e31175953d92edc1de 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -148,6 +148,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl private String mFirstMamReference = null; protected int mCurrentTab = -1; protected ConversationPagerAdapter pagerAdapter = new ConversationPagerAdapter(); + protected Element thread = null; public Conversation(final String name, final Account account, final Jid contactJid, final int mode) { @@ -628,6 +629,14 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl this.draftMessage = draftMessage; } + public Element getThread() { + return this.thread; + } + + public void setThread(Element thread) { + this.thread = thread; + } + public boolean isRead() { synchronized (this.messages) { for(final Message message : Lists.reverse(this.messages)) { diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 7e89357f9d69f7d9cc900218688c894bba2a169c..47fde1f5051683508e46a1bc2394df2730acf528 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -430,6 +430,11 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable return null; } + public void setThread(Element thread) { + payloads.removeIf(el -> el.getName().equals("thread") && el.getNamespace().equals("jabber:client")); + addPayload(thread); + } + public void setMucUser(MucOptions.User user) { this.user = new WeakReference<>(user); } @@ -919,9 +924,15 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable } public void addPayload(Element el) { + if (el == null) return; + this.payloads.add(el); } + public List getPayloads() { + return new ArrayList<>(this.payloads); + } + public Element getHtml() { if (this.payloads == null) return null; @@ -932,7 +943,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable } return null; - } + } public List getCommands() { if (this.payloads == null) return null; diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index 4b055e15883131ab61afff17482c7b4dbdec13d3..90a312204094c58cc2bda1baf3e27ed4aa7f25bf 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -62,6 +62,9 @@ public class MessageGenerator extends AbstractGenerator { if (message.edited()) { packet.addChild("replace", "urn:xmpp:message-correct:0").setAttribute("id", message.getEditedIdWireFormat()); } + for (Element el : message.getPayloads()) { + packet.addChild(el); + } return packet; } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 42e095d786da861f10e8466c08803d036c699da1..746b1ae6b642a217497e3e85f424ca2016e7a478 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -870,6 +870,7 @@ public class ConversationFragment extends XmppFragment final Message message; if (conversation.getCorrectingMessage() == null) { message = new Message(conversation, body, conversation.getNextEncryption()); + message.setThread(conversation.getThread()); Message.configurePrivateMessage(message); } else { message = conversation.getCorrectingMessage(); @@ -953,6 +954,8 @@ public class ConversationFragment extends XmppFragment this.binding.textinput.setHint(UIHelper.getMessageHint(getActivity(), conversation)); getActivity().invalidateOptionsMenu(); } + + binding.messagesView.post(this::updateThreadFromLastMessage); } public void setupIme() { @@ -1248,6 +1251,8 @@ public class ConversationFragment extends XmppFragment new EditMessageActionModeCallback(this.binding.textinput)); } + binding.threadIdenticon.setOnClickListener(v -> newThread()); + return binding.getRoot(); } @@ -1275,9 +1280,23 @@ public class ConversationFragment extends XmppFragment } private void quoteMessage(Message message) { + setThread(message.getThread()); quoteText(MessageUtils.prepareQuote(message)); } + private void setThread(Element thread) { + this.conversation.setThread(thread); + binding.threadIdenticon.setAlpha(0f); + if (thread != null) { + final String threadId = thread.getContent(); + if (threadId != null) { + binding.threadIdenticon.setAlpha(1f); + binding.threadIdenticon.setColor(UIHelper.getColorForName(threadId)); + binding.threadIdenticon.setHash(UIHelper.identiconHash(threadId)); + } + } + } + @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { // This should cancel any remaining click events that would otherwise trigger links @@ -2116,7 +2135,29 @@ public class ConversationFragment extends XmppFragment } } + private void newThread() { + Element thread = new Element("thread", "jabber:client"); + thread.setContent(UUID.randomUUID().toString()); + setThread(thread); + } + + private void updateThreadFromLastMessage() { + if (activity != null && this.conversation != null && TextUtils.isEmpty(binding.textinput.getText())) { + Message message = getLastVisibleMessage(); + if (message == null) { + newThread(); + } else { + setThread(message.getThread()); + } + } + } + private String getLastVisibleMessageUuid() { + Message message = getLastVisibleMessage(); + return message == null ? null : message.getUuid(); + } + + private Message getLastVisibleMessage() { if (binding == null) { return null; } @@ -2140,7 +2181,7 @@ public class ConversationFragment extends XmppFragment while (message.next() != null && message.next().wasMergedIntoPrevious()) { message = message.next(); } - return message.getUuid(); + return message; } } } @@ -3684,13 +3725,7 @@ public class ConversationFragment extends XmppFragment @Override public void onContactPictureClicked(Message message) { - String fingerprint; - if (message.getEncryption() == Message.ENCRYPTION_PGP - || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { - fingerprint = "pgp"; - } else { - fingerprint = message.getFingerprint(); - } + setThread(message.getThread()); final boolean received = message.getStatus() <= Message.STATUS_RECEIVED; if (received) { if (message.getConversation() instanceof Conversation @@ -3724,15 +3759,8 @@ public class ConversationFragment extends XmppFragment .show(); } } - return; - } else { - if (!message.getContact().isSelf()) { - activity.switchToContactDetails(message.getContact(), fingerprint); - return; - } } } - activity.switchToAccount(message.getConversation().getAccount(), fingerprint); } private Activity requireActivity() { 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 daa170dbfaa736227bd46e5e362ff3099703d304..a5d6c900127c6ea7e4834bd4d62962b100e744ba 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -719,6 +719,7 @@ public class MessageAdapter extends ArrayAdapter { viewHolder.subject = view.findViewById(R.id.message_subject); viewHolder.indicatorReceived = view.findViewById(R.id.indicator_received); viewHolder.audioPlayer = view.findViewById(R.id.audio_player); + viewHolder.thread_identicon = view.findViewById(R.id.thread_identicon); break; case RECEIVED: view = activity.getLayoutInflater().inflate(R.layout.message_received, parent, false); diff --git a/src/main/res/layout/fragment_conversation.xml b/src/main/res/layout/fragment_conversation.xml index 05915e0c7e88662368fdfdf60d2f635d6acae685..861e3a1e429b97d2beaf1a8a283c169967af7c50 100644 --- a/src/main/res/layout/fragment_conversation.xml +++ b/src/main/res/layout/fragment_conversation.xml @@ -60,10 +60,10 @@ + + - - - - - - - + android:layout_alignParentBottom="true"> + + + + + + + + + + Date: Wed, 26 Oct 2022 13:03:35 -0500 Subject: [PATCH 04/10] Prevent thread from changing after user selects manually --- .../eu/siacs/conversations/entities/Conversation.java | 9 +++++++++ .../siacs/conversations/ui/ConversationFragment.java | 11 +++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 49cbb720f31a4ad2d7abd7e31175953d92edc1de..0a25dd7b869020fcb2ed9babbffea391e04ff04f 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -149,6 +149,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl protected int mCurrentTab = -1; protected ConversationPagerAdapter pagerAdapter = new ConversationPagerAdapter(); protected Element thread = null; + protected boolean userSelectedThread = false; public Conversation(final String name, final Account account, final Jid contactJid, final int mode) { @@ -637,6 +638,14 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl this.thread = thread; } + public void setUserSelectedThread(boolean flag) { + this.userSelectedThread = flag; + } + + public boolean getUserSelectedThread() { + return this.userSelectedThread; + } + public boolean isRead() { synchronized (this.messages) { for(final Message message : Lists.reverse(this.messages)) { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 746b1ae6b642a217497e3e85f424ca2016e7a478..af1cfc16342bc306100f14bf1aac26ae3d555fed 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -854,6 +854,7 @@ public class ConversationFragment extends XmppFragment } private void sendMessage() { + conversation.setUserSelectedThread(false); if (mediaPreviewAdapter.hasAttachments()) { commitAttachments(); return; @@ -1251,7 +1252,10 @@ public class ConversationFragment extends XmppFragment new EditMessageActionModeCallback(this.binding.textinput)); } - binding.threadIdenticon.setOnClickListener(v -> newThread()); + binding.threadIdenticon.setOnClickListener(v -> { + newThread(); + conversation.setUserSelectedThread(true); + }); return binding.getRoot(); } @@ -1281,6 +1285,7 @@ public class ConversationFragment extends XmppFragment private void quoteMessage(Message message) { setThread(message.getThread()); + conversation.setUserSelectedThread(true); quoteText(MessageUtils.prepareQuote(message)); } @@ -2142,7 +2147,7 @@ public class ConversationFragment extends XmppFragment } private void updateThreadFromLastMessage() { - if (activity != null && this.conversation != null && TextUtils.isEmpty(binding.textinput.getText())) { + if (this.conversation != null && !this.conversation.getUserSelectedThread() && TextUtils.isEmpty(binding.textinput.getText())) { Message message = getLastVisibleMessage(); if (message == null) { newThread(); @@ -3726,6 +3731,8 @@ public class ConversationFragment extends XmppFragment @Override public void onContactPictureClicked(Message message) { setThread(message.getThread()); + conversation.setUserSelectedThread(true); + final boolean received = message.getStatus() <= Message.STATUS_RECEIVED; if (received) { if (message.getConversation() instanceof Conversation From 446e99b0cd4737618bc0502a3514e4093d3a03ff Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Fri, 28 Oct 2022 20:24:42 -0500 Subject: [PATCH 05/10] Long press to clear thread --- .../eu/siacs/conversations/ui/ConversationFragment.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index af1cfc16342bc306100f14bf1aac26ae3d555fed..0b1e0819b2f992ab13c7dcea6b8c2951677e9659 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -1257,6 +1257,12 @@ public class ConversationFragment extends XmppFragment conversation.setUserSelectedThread(true); }); + binding.threadIdenticon.setOnLongClickListener(v -> { + setThread(null); + conversation.setUserSelectedThread(true); + return true; + }); + return binding.getRoot(); } From 887edd394a714e2b85c305f5b2ca6fb3fda7fd30 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Fri, 28 Oct 2022 20:25:01 -0500 Subject: [PATCH 06/10] Tap message bubble to set thread --- .../conversations/ui/ConversationFragment.java | 5 +++++ .../ui/adapter/MessageAdapter.java | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 0b1e0819b2f992ab13c7dcea6b8c2951677e9659..59253fa1fc330a0a217fc7bf18a52348c2c4930e 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -1252,6 +1252,11 @@ public class ConversationFragment extends XmppFragment new EditMessageActionModeCallback(this.binding.textinput)); } + messageListAdapter.setOnMessageBoxClicked(message -> { + setThread(message.getThread()); + conversation.setUserSelectedThread(true); + }); + binding.threadIdenticon.setOnClickListener(v -> { newThread(); conversation.setUserSelectedThread(true); 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 a5d6c900127c6ea7e4834bd4d62962b100e744ba..1e44e08373324f97beea339a7e92eb8fd1d048b8 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -109,6 +109,7 @@ public class MessageAdapter extends ArrayAdapter { private List highlightedTerm = null; private final DisplayMetrics metrics; private OnContactPictureClicked mOnContactPictureClickedListener; + private OnContactPictureClicked mOnMessageBoxClickedListener; private OnContactPictureLongClicked mOnContactPictureLongClickedListener; private boolean mUseGreenBackground = false; private final boolean mForceNames; @@ -148,6 +149,10 @@ public class MessageAdapter extends ArrayAdapter { this.mOnContactPictureClickedListener = listener; } + public void setOnMessageBoxClicked(OnContactPictureClicked listener) { + this.mOnMessageBoxClickedListener = listener; + } + public Activity getActivity() { return activity; } @@ -839,6 +844,18 @@ public class MessageAdapter extends ArrayAdapter { resetClickListener(viewHolder.message_box, viewHolder.messageBody); + viewHolder.message_box.setOnClickListener(v -> { + if (MessageAdapter.this.mOnMessageBoxClickedListener != null) { + MessageAdapter.this.mOnMessageBoxClickedListener + .onContactPictureClicked(message); + } + }); + viewHolder.messageBody.setOnClickListener(v -> { + if (MessageAdapter.this.mOnMessageBoxClickedListener != null) { + MessageAdapter.this.mOnMessageBoxClickedListener + .onContactPictureClicked(message); + } + }); viewHolder.contact_picture.setOnClickListener(v -> { if (MessageAdapter.this.mOnContactPictureClickedListener != null) { MessageAdapter.this.mOnContactPictureClickedListener From 731b2acbb371ed9f193769fbe5054a3d4dab81f9 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Tue, 27 Dec 2022 23:07:02 -0500 Subject: [PATCH 07/10] Option to lock a specific thread --- src/cheogram/res/values/strings.xml | 1 + src/cheogram/res/values/themes.xml | 2 ++ .../conversations/entities/Conversation.java | 13 ++++++- .../ui/ConversationFragment.java | 19 ++++++++-- src/main/res/layout/fragment_conversation.xml | 36 ++++++++++++++----- src/main/res/menu/message_context.xml | 5 +++ src/main/res/values/attrs.xml | 1 + 7 files changed, 65 insertions(+), 12 deletions(-) diff --git a/src/cheogram/res/values/strings.xml b/src/cheogram/res/values/strings.xml index ed2152ab9b54fdf514f1432163029b5e24ec2f41..d4bc883598daf9b0b2d61f4d067b5ab49140155c 100644 --- a/src/cheogram/res/values/strings.xml +++ b/src/cheogram/res/values/strings.xml @@ -25,6 +25,7 @@ Go OLED Black Invite to Chat + Show only this thread Use Phone Accounts for Incoming Calls Incoming calls from phone numbers may ring with your system dialler instead of this app\'s notification settings diff --git a/src/cheogram/res/values/themes.xml b/src/cheogram/res/values/themes.xml index c7d21f641b9163938934e6958c73c2f4317a9810..55494cea1a2b4a4b56220eeabb5fe28ea84b7f18 100644 --- a/src/cheogram/res/values/themes.xml +++ b/src/cheogram/res/values/themes.xml @@ -121,6 +121,7 @@ @drawable/ic_help_white_24dp @drawable/ic_question_answer_white_24dp @drawable/ic_lock_open_white_24dp + @drawable/ic_lock_black_18dp @drawable/ic_settings_black_24dp @drawable/ic_share_white_24dp @drawable/ic_cloud_download_white_24dp @@ -275,6 +276,7 @@ @drawable/ic_help_white_24dp @drawable/ic_question_answer_white_24dp @drawable/ic_lock_open_white_24dp + @drawable/ic_lock_white_18dp @drawable/ic_settings_white_24dp @drawable/ic_share_white_24dp @drawable/ic_cloud_download_white_24dp diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 0a25dd7b869020fcb2ed9babbffea391e04ff04f..e7e098815eaf1c1541c634fb11f945770588f2e4 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -149,6 +149,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl protected int mCurrentTab = -1; protected ConversationPagerAdapter pagerAdapter = new ConversationPagerAdapter(); protected Element thread = null; + protected boolean lockThread = false; protected boolean userSelectedThread = false; public Conversation(final String name, final Account account, final Jid contactJid, @@ -531,7 +532,8 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl messages.addAll(this.messages); } for (Iterator iterator = messages.iterator(); iterator.hasNext(); ) { - if (iterator.next().wasMergedIntoPrevious()) { + Message m = iterator.next(); + if (m.wasMergedIntoPrevious() || (getLockThread() && (m.getThread() == null || !m.getThread().getContent().equals(getThread().getContent())))) { iterator.remove(); } } @@ -638,6 +640,15 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl this.thread = thread; } + public void setLockThread(boolean flag) { + this.lockThread = flag; + if (flag) setUserSelectedThread(true); + } + + public boolean getLockThread() { + return this.lockThread; + } + public void setUserSelectedThread(boolean flag) { this.userSelectedThread = flag; } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 59253fa1fc330a0a217fc7bf18a52348c2c4930e..358e7a558947856183789a3fe5e1759553c8cc3d 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -1257,14 +1257,20 @@ public class ConversationFragment extends XmppFragment conversation.setUserSelectedThread(true); }); - binding.threadIdenticon.setOnClickListener(v -> { + binding.threadIdenticonLayout.setOnClickListener(v -> { + boolean wasLocked = conversation.getLockThread(); + conversation.setLockThread(false); newThread(); conversation.setUserSelectedThread(true); + if (wasLocked) refresh(); }); - binding.threadIdenticon.setOnLongClickListener(v -> { + binding.threadIdenticonLayout.setOnLongClickListener(v -> { + boolean wasLocked = conversation.getLockThread(); + conversation.setLockThread(false); setThread(null); conversation.setUserSelectedThread(true); + if (wasLocked) refresh(); return true; }); @@ -1303,6 +1309,7 @@ public class ConversationFragment extends XmppFragment private void setThread(Element thread) { this.conversation.setThread(thread); binding.threadIdenticon.setAlpha(0f); + binding.threadIdenticonLock.setVisibility(this.conversation.getLockThread() ? View.VISIBLE : View.GONE); if (thread != null) { final String threadId = thread.getContent(); if (threadId != null) { @@ -1362,6 +1369,7 @@ public class ConversationFragment extends XmppFragment 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 onlyThisThread = menu.findItem(R.id.only_this_thread); MenuItem shareWith = menu.findItem(R.id.share_with); MenuItem sendAgain = menu.findItem(R.id.send_again); MenuItem copyUrl = menu.findItem(R.id.copy_url); @@ -1369,6 +1377,7 @@ public class ConversationFragment extends XmppFragment MenuItem cancelTransmission = menu.findItem(R.id.cancel_transmission); MenuItem deleteFile = menu.findItem(R.id.delete_file); MenuItem showErrorMessage = menu.findItem(R.id.show_error_message); + onlyThisThread.setVisible(!conversation.getLockThread() && m.getThread() != null); final boolean unInitiatedButKnownSize = MessageUtils.unInitiatedButKnownSize(m); final boolean showError = m.getStatus() == Message.STATUS_SEND_FAILED @@ -1505,6 +1514,12 @@ public class ConversationFragment extends XmppFragment case R.id.open_with: openWith(selectedMessage); return true; + case R.id.only_this_thread: + conversation.setLockThread(true); + setThread(selectedMessage.getThread()); + refresh(); + setThread(selectedMessage.getThread()); + return true; default: return super.onContextItemSelected(item); } diff --git a/src/main/res/layout/fragment_conversation.xml b/src/main/res/layout/fragment_conversation.xml index 861e3a1e429b97d2beaf1a8a283c169967af7c50..477ecf3f012a51b675de67ac4593e09424bc243b 100644 --- a/src/main/res/layout/fragment_conversation.xml +++ b/src/main/res/layout/fragment_conversation.xml @@ -62,8 +62,8 @@ android:layout_height="wrap_content" android:layout_toStartOf="@+id/textSendButton" android:layout_toLeftOf="@+id/textSendButton" - android:layout_toEndOf="@+id/thread_identicon" - android:layout_toRightOf="@+id/thread_identicon" + android:layout_toEndOf="@+id/thread_identicon_layout" + android:layout_toRightOf="@+id/thread_identicon_layout" android:orientation="vertical"> - + android:layout_marginLeft="8dp"> + + + + + + + + From f2d80e40c37f4cc03bf3668ddcf9159c9e148963 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Wed, 28 Dec 2022 19:46:20 -0500 Subject: [PATCH 08/10] Set thread from attachments and direct reply (direct reply is reply to a specific message in a notification) --- .../siacs/conversations/services/XmppConnectionService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 6091a59a61161d93429ca3805414ebfac513dc13..7eac0d2285d01bef1dc06c879e4ffc00562e99d0 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -568,6 +568,7 @@ public class XmppConnectionService extends Service { encryption = Message.ENCRYPTION_DECRYPTED; } Message message = new Message(conversation, uri.toString(), encryption); + message.setThread(conversation.getThread()); Message.configurePrivateMessage(message); if (encryption == Message.ENCRYPTION_DECRYPTED) { getPgpEngine().encrypt(message, callback); @@ -584,6 +585,7 @@ public class XmppConnectionService extends Service { } else { message = new Message(conversation, "", conversation.getNextEncryption()); } + message.setThread(conversation.getThread()); if (!Message.configurePrivateFileMessage(message)) { message.setCounterpart(conversation.getNextCounterpart()); message.setType(Message.TYPE_FILE); @@ -616,6 +618,7 @@ public class XmppConnectionService extends Service { } else { message = new Message(conversation, "", conversation.getNextEncryption()); } + message.setThread(conversation.getThread()); if (!Message.configurePrivateFileMessage(message)) { message.setCounterpart(conversation.getNextCounterpart()); message.setType(Message.TYPE_IMAGE); @@ -980,6 +983,7 @@ 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); final Message message = new Message(conversation, body, conversation.getNextEncryption()); + if (inReplyTo != null) message.setThread(inReplyTo.getThread()); if (inReplyTo != null && inReplyTo.isPrivateMessage()) { Message.configurePrivateMessage(message, inReplyTo.getCounterpart()); } From a2b43e178e6a12176d75b94bb87026bd21324e13 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Tue, 3 Jan 2023 10:02:56 -0500 Subject: [PATCH 09/10] Update default thread on scroll explicitly --- .../java/eu/siacs/conversations/ui/ConversationFragment.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 358e7a558947856183789a3fe5e1759553c8cc3d..79ea3f0dd62d69471142ee5e242a8e7ada107c88 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -264,6 +264,7 @@ public class ConversationFragment extends XmppFragment @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (AbsListView.OnScrollListener.SCROLL_STATE_IDLE == scrollState) { + updateThreadFromLastMessage(); fireReadEvent(); } } From c2a4406f4a4e2a66f6b5e8bd84d400e89b25b004 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Tue, 10 Jan 2023 13:35:07 -0500 Subject: [PATCH 10/10] When locked, just unlock --- .../siacs/conversations/ui/ConversationFragment.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 79ea3f0dd62d69471142ee5e242a8e7ada107c88..123f154b3456ea7b9011facb49e5c4f040422313 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -1261,9 +1261,14 @@ public class ConversationFragment extends XmppFragment binding.threadIdenticonLayout.setOnClickListener(v -> { boolean wasLocked = conversation.getLockThread(); conversation.setLockThread(false); - newThread(); - conversation.setUserSelectedThread(true); - if (wasLocked) refresh(); + if (wasLocked) { + conversation.setUserSelectedThread(false); + refresh(); + updateThreadFromLastMessage(); + } else { + newThread(); + conversation.setUserSelectedThread(true); + } }); binding.threadIdenticonLayout.setOnLongClickListener(v -> { @@ -1519,7 +1524,6 @@ public class ConversationFragment extends XmppFragment conversation.setLockThread(true); setThread(selectedMessage.getThread()); refresh(); - setThread(selectedMessage.getThread()); return true; default: return super.onContextItemSelected(item);