From f342c31f78214b7457730911c2acf1aa5c87b4d5 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Fri, 16 Feb 2024 00:19:59 -0500 Subject: [PATCH] Track occupant ID and allow local muting --- .../conversations/entities/Conversation.java | 4 +- .../siacs/conversations/entities/Message.java | 22 +++++++- .../conversations/entities/MucOptions.java | 32 +++++++++-- .../conversations/parser/AbstractParser.java | 6 +-- .../conversations/parser/MessageParser.java | 2 + .../conversations/parser/PresenceParser.java | 5 +- .../persistance/DatabaseBackend.java | 54 +++++++++++++++++++ .../services/XmppConnectionService.java | 21 ++++++++ .../ui/ConversationFragment.java | 15 ++++-- .../ui/adapter/MessageAdapter.java | 12 +++-- .../ui/util/MucDetailsContextMenuHelper.java | 27 +++++++++- src/main/res/menu/muc_details_context.xml | 8 +++ 12 files changed, 186 insertions(+), 22 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 91a0f4818e23b2f63f19387cf89db4eaa04d6e46..ccc700de099dadeeddb2439689f00d56989c4a42 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -653,7 +653,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl } } - public void populateWithMessages(final List messages) { + public void populateWithMessages(final List messages, XmppConnectionService xmppConnectionService) { synchronized (this.messages) { messages.clear(); messages.addAll(this.messages); @@ -676,7 +676,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl thread.first = m; } } - if (m.wasMergedIntoPrevious() || (m.getSubject() != null && !m.isOOb() && (m.getRawBody() == null || m.getRawBody().length() == 0)) || (getLockThread() && !extraIds.contains(m.replyId()) && (mthread == null || !mthread.getContent().equals(getThread() == null ? "" : getThread().getContent())))) { + if (m.wasMergedIntoPrevious(xmppConnectionService) || (m.getSubject() != null && !m.isOOb() && (m.getRawBody() == null || m.getRawBody().length() == 0)) || (getLockThread() && !extraIds.contains(m.replyId()) && (mthread == null || !mthread.getContent().equals(getThread() == null ? "" : getThread().getContent())))) { iterator.remove(); } else if (getLockThread() && mthread != null) { Element reply = m.getReply(); diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 2b9bf6f51a04d4dbc14c2bc6f787edc4b1da5c13..a9561d34ab233b2ee6a3714a56143a310e86c434 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -59,6 +59,7 @@ import eu.siacs.conversations.utils.MessageUtils; import eu.siacs.conversations.utils.MimeUtils; import eu.siacs.conversations.utils.StringUtils; import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; @@ -125,6 +126,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable protected String conversationUuid; protected Jid counterpart; protected Jid trueCounterpart; + protected String occupantId = null; protected String body; protected SpannableStringBuilder spannableBody = null; protected String subject; @@ -273,7 +275,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable } } - return new Message(conversation, + Message m = new Message(conversation, cursor.getString(cursor.getColumnIndex(UUID)), cursor.getString(cursor.getColumnIndex(CONVERSATION)), fromString(cursor.getString(cursor.getColumnIndex(COUNTERPART))), @@ -301,6 +303,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable cursor.getString(cursor.getColumnIndex("fileParams")), payloads ); + m.setOccupantId(cursor.getString(cursor.getColumnIndex("occupant_id"))); + return m; } private static Jid fromString(String value) { @@ -344,6 +348,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable } } values.put("payloads", payloads.size() < 1 ? null : payloads.stream().map(Object::toString).collect(Collectors.joining())); + values.put("occupant_id", occupantId); return values; } @@ -640,8 +645,17 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable addPayload(thread); } + public void setOccupantId(final String id) { + occupantId = id; + } + + public String getOccupantId() { + return occupantId; + } + public void setMucUser(MucOptions.User user) { this.user = new WeakReference<>(user); + if (user != null && user.getOccupantId() != null) setOccupantId(user.getOccupantId()); } public boolean sameMucUser(Message otherMessage) { @@ -1096,9 +1110,13 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable return time; } - public boolean wasMergedIntoPrevious() { + public boolean wasMergedIntoPrevious(XmppConnectionService xmppConnectionService) { Message prev = this.prev(); if (prev != null && getModerated() != null && prev.getModerated() != null) return true; + if (getOccupantId() != null && xmppConnectionService != null) { + final boolean muted = getStatus() == Message.STATUS_RECEIVED && conversation.getMode() == Conversation.MODE_MULTI && xmppConnectionService.isMucUserMuted(new MucOptions.User(null, conversation.getJid(), getOccupantId(), null, null)); + if (prev != null && muted && getOccupantId().equals(prev.getOccupantId())) return true; + } return prev != null && prev.mergeable(this); } diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index ff3e9a00da8d138d304adc024fcd9bd84594a98d..27ab3624ae7a5e1091348dd3529bba5a472376eb 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -56,7 +56,7 @@ public class MucOptions { this.account = conversation.getAccount(); this.conversation = conversation; final String nick = getProposedNick(conversation.getAttribute("mucNick")); - this.self = new User(this, createJoinJid(nick), nick, new HashSet<>()); + this.self = new User(this, createJoinJid(nick), null, nick, new HashSet<>()); this.self.affiliation = Affiliation.of(conversation.getAttribute("affiliation")); this.self.role = Role.of(conversation.getAttribute("role")); } @@ -342,10 +342,24 @@ public class MucOptions { return null; } + public User findUserByOccupantId(final String id) { + if (id == null) { + return null; + } + synchronized (users) { + for (User user : users) { + if (id.equals(user.getOccupantId())) { + return user; + } + } + } + return null; + } + public User findOrCreateUserByRealJid(Jid jid, Jid fullJid) { User user = findUserByRealJid(jid); if (user == null) { - user = new User(this, fullJid, null, new HashSet<>()); + user = new User(this, fullJid, null, null, new HashSet<>()); user.setRealJid(jid); } return user; @@ -544,7 +558,7 @@ public class MucOptions { private List getFallbackUsersFromCryptoTargets() { List users = new ArrayList<>(); for (Jid jid : conversation.getAcceptedCryptoTargets()) { - User user = new User(this, null, null, new HashSet<>()); + User user = new User(this, null, null, null, new HashSet<>()); user.setRealJid(jid); users.add(user); } @@ -832,10 +846,12 @@ public class MucOptions { private final MucOptions options; private ChatState chatState = Config.DEFAULT_CHAT_STATE; protected Set hats; + protected String occupantId; - public User(MucOptions options, Jid fullJid, final String nick, final Set hats) { + public User(MucOptions options, Jid fullJid, final String occupantId, final String nick, final Set hats) { this.options = options; this.fullJid = fullJid; + this.occupantId = occupantId; this.nick = nick; this.hats = hats; } @@ -844,6 +860,14 @@ public class MucOptions { return fullJid == null ? null : fullJid.getResource(); } + public Jid getMuc() { + return fullJid == null ? null : fullJid.asBareJid(); + } + + public String getOccupantId() { + return occupantId; + } + public String getNick() { return nick == null ? getName() : nick; } diff --git a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java index c2d999f2ce3407bdf3c064d3f4d4112a639a8847..02f1b91c1d63137d2619d975f3847c56dbb34384 100644 --- a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java +++ b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java @@ -134,10 +134,10 @@ public abstract class AbstractParser { } public static MucOptions.User parseItem(Conversation conference, Element item) { - return parseItem(conference,item,null,null,new Element("hats", "urn:xmpp:hats:0")); + return parseItem(conference,item,null,null,null,new Element("hats", "urn:xmpp:hats:0")); } - public static MucOptions.User parseItem(Conversation conference, Element item, Jid fullJid, final String nicknameIn, final Element hatsEl) { + public static MucOptions.User parseItem(Conversation conference, Element item, Jid fullJid, final Element occupantId, final String nicknameIn, final Element hatsEl) { final String local = conference.getJid().getLocal(); final String domain = conference.getJid().getDomain().toEscapedString(); String affiliation = item.getAttribute("affiliation"); @@ -165,7 +165,7 @@ public abstract class AbstractParser { hats.add(new MucOptions.Hat(hat)); } } - MucOptions.User user = new MucOptions.User(conference.getMucOptions(), fullJid, nickname, hatsEl == null ? null : hats); + MucOptions.User user = new MucOptions.User(conference.getMucOptions(), fullJid, occupantId == null ? null : occupantId.getAttribute("id"), nickname, hatsEl == null ? null : hats); if (InvalidJid.isValid(realJid)) { user.setRealJid(realJid); } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 78ce55d312d62da5b9164ead76bf77b8bac186dd..a732bac94beb119a936170a509f28c1df04ad3f4 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -728,6 +728,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } if (conversationMultiMode) { message.setMucUser(conversation.getMucOptions().findUserByFullJid(counterpart)); + final Element occupantId = packet.findChild("occupant-id", "urn:xmpp:occupant-id:0"); + if (occupantId != null) message.setOccupantId(occupantId.getAttribute("id")); final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart); Jid trueCounterpart; if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java index 289f2a992ba223f2e0de3d378069137bf51625db..de0f4fbea6e6b7b2c66a7e7a612e09cec9f902d8 100644 --- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java +++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java @@ -69,6 +69,7 @@ public class PresenceParser extends AbstractParser implements hats = packet.findChild("hats", "xmpp:prosody.im/protocol/hats:1"); } if (hats == null) hats = new Element("hats", "urn:xmpp:hats:0"); + final Element occupantId = packet.findChild("occupant-id", "urn:xmpp:occupant-id:0"); Avatar avatar = Avatar.parsePresence(packet.findChild("x", "vcard-temp:x:update")); final List codes = getStatusCodes(x); if (type == null) { @@ -76,7 +77,7 @@ public class PresenceParser extends AbstractParser implements Element item = x.findChild("item"); if (item != null && !from.isBareJid()) { mucOptions.setError(MucOptions.Error.NONE); - MucOptions.User user = parseItem(conversation, item, from, nick == null ? null : nick.getContent(), hats); + MucOptions.User user = parseItem(conversation, item, from, occupantId, nick == null ? null : nick.getContent(), hats); if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE) || (codes.contains(MucOptions.STATUS_CODE_ROOM_CREATED) && jid.equals(InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid"))))) { if (mucOptions.setOnline()) { mXmppConnectionService.getAvatarService().clear(mucOptions); @@ -180,7 +181,7 @@ public class PresenceParser extends AbstractParser implements } else if (!from.isBareJid()){ Element item = x.findChild("item"); if (item != null) { - mucOptions.updateUser(parseItem(conversation, item, from, nick == null ? null : nick.getContent(), hats)); + mucOptions.updateUser(parseItem(conversation, item, from, occupantId, nick == null ? null : nick.getContent(), hats)); } MucOptions.User user = mucOptions.deleteUser(from); if (user != null) { diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 14c01f578fd85308b7c645de588c8af32086f3f5..e2b5361c2e7f0141b1cbf9db45b18edd5513d1f8 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -14,6 +14,8 @@ import android.util.Log; import com.cheogram.android.WebxdcUpdate; import com.google.common.base.Stopwatch; +import com.google.common.collect.Multimap; +import com.google.common.collect.HashMultimap; import org.json.JSONException; import org.json.JSONObject; @@ -52,6 +54,7 @@ import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.PresenceTemplate; import eu.siacs.conversations.entities.Roster; import eu.siacs.conversations.entities.ServiceDiscoveryResult; @@ -320,6 +323,22 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL("PRAGMA cheogram.user_version = 9"); } + if(cheogramVersion < 10) { + db.execSQL( + "CREATE TABLE cheogram.muted_participants (" + + "muc_jid TEXT NOT NULL, " + + "occupant_id TEXT NOT NULL, " + + "nick TEXT NOT NULL," + + "PRIMARY KEY (muc_jid, occupant_id)" + + ")" + ); + db.execSQL( + "ALTER TABLE cheogram." + Message.TABLENAME + " " + + "ADD COLUMN occupant_id TEXT" + ); + db.execSQL("PRAGMA cheogram.user_version = 10"); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); @@ -863,6 +882,41 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL("DELETE FROM cheogram.blocked_media"); } + public Multimap loadMutedMucUsers() { + Multimap result = HashMultimap.create(); + SQLiteDatabase db = this.getReadableDatabase(); + Cursor cursor = db.query("cheogram.muted_participants", new String[]{"muc_jid", "occupant_id"}, null, null, null, null, null); + while (cursor.moveToNext()) { + result.put(cursor.getString(0), cursor.getString(1)); + } + cursor.close(); + return result; + } + + public boolean muteMucUser(MucOptions.User user) { + if (user.getMuc() == null || user.getOccupantId() == null) return false; + + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues cv = new ContentValues(); + cv.put("muc_jid", user.getMuc().toString()); + cv.put("occupant_id", user.getOccupantId()); + cv.put("nick", user.getNick()); + db.insertWithOnConflict("cheogram.muted_participants", null, cv, SQLiteDatabase.CONFLICT_REPLACE); + + return true; + } + + public boolean unmuteMucUser(MucOptions.User user) { + if (user.getMuc() == null || user.getOccupantId() == null) return false; + + SQLiteDatabase db = this.getWritableDatabase(); + String where = "muc_jid=? AND occupant_id=?"; + String[] whereArgs = {user.getMuc().toString(), user.getOccupantId()}; + db.delete("cheogram.muted_participants", where, whereArgs); + + return true; + } + public void insertWebxdcUpdate(final WebxdcUpdate update) { SQLiteDatabase db = this.getWritableDatabase(); db.insertWithOnConflict("cheogram.webxdc_updates", null, update.getContentValues(), SQLiteDatabase.CONFLICT_IGNORE); diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index ebe189f9092bc16d6584e87f6cfa0d8913af8315..03eb47dfc2a7f879372bb174a5d57f471fc37b14 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -65,6 +65,7 @@ import com.cheogram.android.WebxdcUpdate; import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.base.Strings; +import com.google.common.collect.Multimap; import com.google.common.io.Files; import com.kedia.ogparser.JsoupProxy; @@ -258,6 +259,7 @@ public class XmppConnectionService extends Service { } }; public DatabaseBackend databaseBackend; + private Multimap mutedMucUsers; private final ReplacingSerialSingleThreadExecutor mContactMergerExecutor = new ReplacingSerialSingleThreadExecutor("ContactMerger"); private final ReplacingSerialSingleThreadExecutor mStickerScanExecutor = new ReplacingSerialSingleThreadExecutor("StickerScan"); private long mLastActivity = 0; @@ -621,6 +623,24 @@ public class XmppConnectionService extends Service { this.databaseBackend.saveCid(cid, file, url); } + public boolean muteMucUser(MucOptions.User user) { + boolean muted = databaseBackend.muteMucUser(user); + if (!muted) return false; + mutedMucUsers.put(user.getMuc().toString(), user.getOccupantId()); + return true; + } + + public boolean unmuteMucUser(MucOptions.User user) { + boolean unmuted = databaseBackend.unmuteMucUser(user); + if (!unmuted) return false; + mutedMucUsers.remove(user.getMuc().toString(), user.getOccupantId()); + return true; + } + + public boolean isMucUserMuted(MucOptions.User user) { + return mutedMucUsers.containsEntry("" + user.getMuc(), user.getOccupantId()); + } + public void blockMedia(File f) { try { Cid[] cids = getFileBackend().calculateCids(new FileInputStream(f)); @@ -2408,6 +2428,7 @@ public class XmppConnectionService extends Service { if (DatabaseBackend.requiresMessageIndexRebuild()) { DatabaseBackend.getInstance(this).rebuildMessagesIndex(); } + mutedMucUsers = databaseBackend.loadMutedMucUsers(); final long deletionDate = getAutomaticMessageDeletionDate(); mLastExpiryRun.set(SystemClock.elapsedRealtime()); if (deletionDate > 0) { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index c6525c6312328e93995e06f73a02c82e78e73ab4..cf7877ca380074fba218b54c533fbed53167141d 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -387,7 +387,7 @@ public class ConversationFragment extends XmppFragment .populateWithMessages( ConversationFragment .this - .messageList); + .messageList, activity.xmppConnectionService); try { updateStatusMessages(); } catch (IllegalStateException e) { @@ -772,7 +772,7 @@ public class ConversationFragment extends XmppFragment return i; } else { Message next = messages.get(i); - while (next != null && next.wasMergedIntoPrevious()) { + while (next != null && next.wasMergedIntoPrevious(activity.xmppConnectionService)) { if (uuid.equals(next.getUuid())) { return i; } @@ -2657,7 +2657,7 @@ public class ConversationFragment extends XmppFragment } } if (message != null) { - while (message.next() != null && message.next().wasMergedIntoPrevious()) { + while (message.next() != null && message.next().wasMergedIntoPrevious(activity.xmppConnectionService)) { message = message.next(); } return message; @@ -3567,7 +3567,7 @@ public class ConversationFragment extends XmppFragment if (messageListAdapter.hasSelection()) { if (notifyConversationRead) binding.messagesView.postDelayed(this::refresh, 1000L); } else { - conversation.populateWithMessages(this.messageList); + conversation.populateWithMessages(this.messageList, activity.xmppConnectionService); updateStatusMessages(); this.messageListAdapter.notifyDataSetChanged(); } @@ -4293,10 +4293,15 @@ public class ConversationFragment extends XmppFragment tcp != null ? conversation.getMucOptions().findOrCreateUserByRealJid(tcp, cp) : null; + final String occupantId = message.getOccupantId(); + final User userByOccupantId = + occupantId != null + ? conversation.getMucOptions().findUserByOccupantId(occupantId) + : null; final User user = userByRealJid != null ? userByRealJid - : conversation.getMucOptions().findUserByFullJid(cp); + : (userByOccupantId != null ? userByOccupantId : conversation.getMucOptions().findUserByFullJid(cp)); if (user == null) return; popupMenu.inflate(R.menu.muc_details_context); final Menu menu = popupMenu.getMenu(); 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 4b056a94442b9a5a57190c2ca48b9dfa1536a6ca..b24b5d1f45c9ead154fa4e53af172b9cd3e34048 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -79,6 +79,7 @@ import eu.siacs.conversations.entities.Conversational; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message.FileParams; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.Roster; import eu.siacs.conversations.entities.RtpSessionStatus; import eu.siacs.conversations.entities.Transferable; @@ -1081,7 +1082,12 @@ public class MessageAdapter extends ArrayAdapter { final Transferable transferable = message.getTransferable(); final boolean unInitiatedButKnownSize = MessageUtils.unInitiatedButKnownSize(message); - if (unInitiatedButKnownSize || message.isDeleted() || (transferable != null && transferable.getStatus() != Transferable.STATUS_UPLOADING)) { + + final boolean muted = message.getStatus() == Message.STATUS_RECEIVED && conversation.getMode() == Conversation.MODE_MULTI && activity.xmppConnectionService.isMucUserMuted(new MucOptions.User(null, conversation.getJid(), message.getOccupantId(), null, null)); + if (muted) { + // Muted MUC participant + displayInfoMessage(viewHolder, "Muted", darkBackground); + } else if (unInitiatedButKnownSize || message.isDeleted() || (transferable != null && transferable.getStatus() != Transferable.STATUS_UPLOADING)) { if (unInitiatedButKnownSize || (message.isDeleted() && message.getModerated() == null) || transferable != null && transferable.getStatus() == Transferable.STATUS_OFFER) { displayDownloadableMessage(viewHolder, message, activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, message)), darkBackground, type); } else if (transferable != null && transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) { @@ -1152,7 +1158,7 @@ public class MessageAdapter extends ArrayAdapter { viewHolder.status_line.setLayoutParams(statusParams); if (type == RECEIVED) { - if (commands != null && conversation instanceof Conversation) { + if (!muted && commands != null && conversation instanceof Conversation) { CommandButtonAdapter adapter = new CommandButtonAdapter(activity); adapter.addAll(commands); viewHolder.commands_list.setAdapter(adapter); @@ -1194,7 +1200,7 @@ public class MessageAdapter extends ArrayAdapter { if (subject == null && message.getThread() != null) { subject = ((Conversation) message.getConversation()).getThread(message.getThread().getContent()).getSubject(); } - if (subject == null) { + if (muted || subject == null) { viewHolder.subject.setVisibility(View.GONE); } else { viewHolder.subject.setVisibility(View.VISIBLE); diff --git a/src/main/java/eu/siacs/conversations/ui/util/MucDetailsContextMenuHelper.java b/src/main/java/eu/siacs/conversations/ui/util/MucDetailsContextMenuHelper.java index f8798f6722b66fd10dc3393234fe06d2cbf59f58..8e4ee18bb4b038f136ef44f703cad11bc6e509b2 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/MucDetailsContextMenuHelper.java +++ b/src/main/java/eu/siacs/conversations/ui/util/MucDetailsContextMenuHelper.java @@ -10,6 +10,7 @@ import android.view.ContextMenu; import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.widget.Toast; import androidx.appcompat.app.AlertDialog; import androidx.databinding.DataBindingUtil; @@ -102,7 +103,7 @@ public final class MucDetailsContextMenuHelper { return new Pair<>(items.toArray(new CharSequence[items.size()]), actions.toArray(new Integer[actions.size()])); } - public static void configureMucDetailsContextMenu(Activity activity, Menu menu, Conversation conversation, User user) { + public static void configureMucDetailsContextMenu(XmppActivity activity, Menu menu, Conversation conversation, User user) { final MucOptions mucOptions = conversation.getMucOptions(); final boolean advancedMode = PreferenceManager.getDefaultSharedPreferences(activity).getBoolean("advanced_muc_mode", false); final boolean isGroupChat = mucOptions.isPrivateAndNonAnonymous(); @@ -113,6 +114,16 @@ public final class MucDetailsContextMenuHelper { blockAvatar.setVisible(true); } + MenuItem muteParticipant = menu.findItem(R.id.action_mute_participant); + MenuItem unmuteParticipant = menu.findItem(R.id.action_unmute_participant); + if (user != null && user.getOccupantId() != null) { + if (activity.xmppConnectionService.isMucUserMuted(user)) { + unmuteParticipant.setVisible(true); + } else { + muteParticipant.setVisible(true); + } + } + if (user != null && user.getRealJid() != null) { MenuItem showContactDetails = menu.findItem(R.id.action_contact_details); MenuItem startConversation = menu.findItem(R.id.start_conversation); @@ -210,6 +221,20 @@ public final class MucDetailsContextMenuHelper { }) .setNegativeButton(R.string.no, null).show(); return true; + case R.id.action_mute_participant: + if (activity.xmppConnectionService.muteMucUser(user)) { + activity.xmppConnectionService.updateConversationUi(); + } else { + Toast.makeText(activity, "Failed to mute", Toast.LENGTH_SHORT).show(); + } + return true; + case R.id.action_unmute_participant: + if (activity.xmppConnectionService.unmuteMucUser(user)) { + activity.xmppConnectionService.updateConversationUi(); + } else { + Toast.makeText(activity, "Failed to unmute", Toast.LENGTH_SHORT).show(); + } + return true; case R.id.start_conversation: startConversation(user, activity); return true; diff --git a/src/main/res/menu/muc_details_context.xml b/src/main/res/menu/muc_details_context.xml index 955c78e412492a2c35063d91e7c5040a34490a6b..c0ff644cf14f7f3ceb015cab426583db0967861c 100644 --- a/src/main/res/menu/muc_details_context.xml +++ b/src/main/res/menu/muc_details_context.xml @@ -12,6 +12,14 @@ android:id="@+id/action_block_avatar" android:title="Block Avatar" android:visible="false" /> + +