1package eu.siacs.conversations.ui.util;
2
3import android.app.Activity;
4import android.preference.PreferenceManager;
5import android.text.SpannableString;
6import android.text.Spanned;
7import android.text.style.TypefaceSpan;
8import android.util.Pair;
9import android.view.ContextMenu;
10import android.view.Menu;
11import android.view.MenuItem;
12import android.view.View;
13
14import androidx.appcompat.app.AlertDialog;
15
16import java.util.ArrayList;
17
18import eu.siacs.conversations.Config;
19import eu.siacs.conversations.R;
20import eu.siacs.conversations.entities.Account;
21import eu.siacs.conversations.entities.Contact;
22import eu.siacs.conversations.entities.Conversation;
23import eu.siacs.conversations.entities.MucOptions;
24import eu.siacs.conversations.entities.MucOptions.User;
25import eu.siacs.conversations.services.XmppConnectionService;
26import eu.siacs.conversations.ui.ConferenceDetailsActivity;
27import eu.siacs.conversations.ui.ConversationFragment;
28import eu.siacs.conversations.ui.ConversationsActivity;
29import eu.siacs.conversations.ui.MucUsersActivity;
30import eu.siacs.conversations.ui.XmppActivity;
31import eu.siacs.conversations.xmpp.Jid;
32
33
34public final class MucDetailsContextMenuHelper {
35 private static final int ACTION_BAN = 0;
36 private static final int ACTION_GRANT_MEMBERSHIP = 1;
37 private static final int ACTION_REMOVE_MEMBERSHIP = 2;
38 private static final int ACTION_GRANT_ADMIN = 3;
39 private static final int ACTION_REMOVE_ADMIN = 4;
40 private static final int ACTION_GRANT_OWNER = 5;
41 private static final int ACTION_REMOVE_OWNER = 6;
42
43 public static void onCreateContextMenu(ContextMenu menu, View v) {
44 final XmppActivity activity = XmppActivity.find(v);
45 final Object tag = v.getTag();
46 if (tag instanceof MucOptions.User && activity != null) {
47 activity.getMenuInflater().inflate(R.menu.muc_details_context, menu);
48 final MucOptions.User user = (MucOptions.User) tag;
49 String name;
50 final Contact contact = user.getContact();
51 if (contact != null && contact.showInContactList()) {
52 name = contact.getDisplayName();
53 } else if (user.getRealJid() != null) {
54 name = user.getRealJid().asBareJid().toString();
55 } else {
56 name = user.getNick();
57 }
58 menu.setHeaderTitle(name);
59 MucDetailsContextMenuHelper.configureMucDetailsContextMenu(activity, menu, user.getConversation(), user);
60 }
61 }
62
63 public static Pair<CharSequence[], Integer[]> getPermissionsChoices(Activity activity, Conversation conversation, User user) {
64 ArrayList<CharSequence> items = new ArrayList<>();
65 ArrayList<Integer> actions = new ArrayList<>();
66 final User self = conversation.getMucOptions().getSelf();
67 final MucOptions mucOptions = conversation.getMucOptions();
68 final boolean isGroupChat = mucOptions.isPrivateAndNonAnonymous();
69 if ((self.getAffiliation().ranks(MucOptions.Affiliation.ADMIN) && self.getAffiliation().outranks(user.getAffiliation())) || self.getAffiliation() == MucOptions.Affiliation.OWNER) {
70 if (!Config.DISABLE_BAN) {
71 items.add(activity.getString(isGroupChat ? R.string.ban_from_conference : R.string.ban_from_channel));
72 actions.add(ACTION_BAN);
73 }
74 if (!user.getAffiliation().ranks(MucOptions.Affiliation.MEMBER)) {
75 items.add(activity.getString(R.string.grant_membership));
76 actions.add(ACTION_GRANT_MEMBERSHIP);
77 } else if (user.getAffiliation() == MucOptions.Affiliation.MEMBER) {
78 items.add(activity.getString(R.string.remove_membership));
79 actions.add(ACTION_REMOVE_MEMBERSHIP);
80 }
81 }
82 if (self.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) {
83 if (!user.getAffiliation().ranks(MucOptions.Affiliation.ADMIN)) {
84 items.add(activity.getString(R.string.grant_admin_privileges));
85 actions.add(ACTION_GRANT_ADMIN);
86 } else if (user.getAffiliation() == MucOptions.Affiliation.ADMIN) {
87 items.add(activity.getString(R.string.remove_admin_privileges));
88 actions.add(ACTION_REMOVE_ADMIN);
89 }
90 if (!user.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) {
91 items.add(activity.getString(R.string.grant_owner_privileges));
92 actions.add(ACTION_GRANT_OWNER);
93 } else if (user.getAffiliation() == MucOptions.Affiliation.OWNER){
94 items.add(activity.getString(R.string.remove_owner_privileges));
95 actions.add(ACTION_REMOVE_OWNER);
96 }
97 }
98 return new Pair<>(items.toArray(new CharSequence[items.size()]), actions.toArray(new Integer[actions.size()]));
99 }
100
101 public static void configureMucDetailsContextMenu(Activity activity, Menu menu, Conversation conversation, User user) {
102 final MucOptions mucOptions = conversation.getMucOptions();
103 final boolean advancedMode = PreferenceManager.getDefaultSharedPreferences(activity).getBoolean("advanced_muc_mode", false);
104 final boolean isGroupChat = mucOptions.isPrivateAndNonAnonymous();
105 MenuItem sendPrivateMessage = menu.findItem(R.id.send_private_message);
106
107 MenuItem blockAvatar = menu.findItem(R.id.action_block_avatar);
108 if (user != null && user.getAvatar() != null) {
109 blockAvatar.setVisible(true);
110 }
111
112 if (user != null && user.getRealJid() != null) {
113 MenuItem showContactDetails = menu.findItem(R.id.action_contact_details);
114 MenuItem startConversation = menu.findItem(R.id.start_conversation);
115 MenuItem removeFromRoom = menu.findItem(R.id.remove_from_room);
116 MenuItem managePermissions = menu.findItem(R.id.manage_permissions);
117 removeFromRoom.setTitle(isGroupChat ? R.string.remove_from_room : R.string.remove_from_channel);
118 MenuItem invite = menu.findItem(R.id.invite);
119 startConversation.setVisible(true);
120 final Contact contact = user.getContact();
121 final User self = conversation.getMucOptions().getSelf();
122 if ((contact != null && contact.showInRoster()) || mucOptions.isPrivateAndNonAnonymous()) {
123 showContactDetails.setVisible(contact == null || !contact.isSelf());
124 }
125 if ((activity instanceof ConferenceDetailsActivity || activity instanceof MucUsersActivity) && user.getRole() == MucOptions.Role.NONE) {
126 invite.setVisible(true);
127 }
128 boolean managePermissionsVisible = false;
129 if ((self.getAffiliation().ranks(MucOptions.Affiliation.ADMIN) && self.getAffiliation().outranks(user.getAffiliation())) || self.getAffiliation() == MucOptions.Affiliation.OWNER) {
130 if (!user.getAffiliation().ranks(MucOptions.Affiliation.MEMBER)) {
131 managePermissionsVisible = true;
132 } else if (user.getAffiliation() == MucOptions.Affiliation.MEMBER) {
133 managePermissionsVisible = true;
134 }
135 if (!Config.DISABLE_BAN) {
136 managePermissionsVisible = true;
137 }
138 }
139 if (self.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) {
140 if (!user.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) {
141 managePermissionsVisible = true;
142 } else if (user.getAffiliation() == MucOptions.Affiliation.OWNER){
143 managePermissionsVisible = true;
144 }
145 if (!user.getAffiliation().ranks(MucOptions.Affiliation.ADMIN)) {
146 managePermissionsVisible = true;
147 } else if (user.getAffiliation() == MucOptions.Affiliation.ADMIN) {
148 managePermissionsVisible = true;
149 }
150 }
151 managePermissions.setVisible(managePermissionsVisible);
152 sendPrivateMessage.setVisible(!isGroupChat && mucOptions.allowPm() && user.getRole().ranks(MucOptions.Role.VISITOR));
153 } else {
154 sendPrivateMessage.setVisible(true);
155 sendPrivateMessage.setEnabled(user != null && mucOptions.allowPm() && user.getRole().ranks(MucOptions.Role.VISITOR));
156 }
157 }
158
159 public static boolean onContextItemSelected(MenuItem item, User user, XmppActivity activity) {
160 return onContextItemSelected(item, user, activity, null);
161 }
162
163 public static boolean onContextItemSelected(MenuItem item, User user, XmppActivity activity, final String fingerprint) {
164 final Conversation conversation = user.getConversation();
165 final XmppConnectionService.OnAffiliationChanged onAffiliationChanged = activity instanceof XmppConnectionService.OnAffiliationChanged ? (XmppConnectionService.OnAffiliationChanged) activity : null;
166 Jid jid = user.getRealJid();
167 switch (item.getItemId()) {
168 case R.id.action_contact_details:
169 final Jid realJid = user.getRealJid();
170 final Account account = conversation.getAccount();
171 final Contact contact = realJid == null ? null : account.getRoster().getContact(realJid);
172 if (contact != null) {
173 activity.switchToContactDetails(contact, fingerprint);
174 }
175 return true;
176 case R.id.action_block_avatar:
177 new AlertDialog.Builder(activity)
178 .setTitle(R.string.block_media)
179 .setMessage("Do you really want to block this avatar?")
180 .setPositiveButton(R.string.yes, (dialog, whichButton) -> {
181 activity.xmppConnectionService.blockMedia(
182 activity.xmppConnectionService.getFileBackend().getAvatarFile(user.getAvatar())
183 );
184 activity.xmppConnectionService.getFileBackend().getAvatarFile(user.getAvatar()).delete();
185 activity.avatarService().clear(user);
186 if (user.getContact() != null) activity.avatarService().clear(user.getContact());
187 user.setAvatar(null);
188 activity.xmppConnectionService.updateConversationUi();
189 })
190 .setNegativeButton(R.string.no, null).show();
191 return true;
192 case R.id.start_conversation:
193 startConversation(user, activity);
194 return true;
195 case R.id.manage_permissions:
196 Pair<CharSequence[], Integer[]> choices = getPermissionsChoices(activity, conversation, user);
197 int[] selected = new int[] { -1 };
198 new AlertDialog.Builder(activity)
199 .setTitle(R.string.manage_permission)
200 .setSingleChoiceItems(choices.first, -1, (dialog, whichItem) -> {
201 selected[0] = whichItem;
202 })
203 .setPositiveButton(R.string.action_complete, (dialog, whichButton) -> {
204 switch (selected[0] >= 0 ? choices.second[selected[0]] : -1) {
205 case ACTION_BAN:
206 activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.OUTCAST, onAffiliationChanged);
207 if (user.getRole() != MucOptions.Role.NONE) {
208 activity.xmppConnectionService.changeRoleInConference(conversation, user.getName(), MucOptions.Role.NONE);
209 }
210 break;
211 case ACTION_GRANT_MEMBERSHIP:
212 case ACTION_REMOVE_ADMIN:
213 case ACTION_REMOVE_OWNER:
214 activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.MEMBER, onAffiliationChanged);
215 break;
216 case ACTION_GRANT_ADMIN:
217 activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.ADMIN, onAffiliationChanged);
218 break;
219 case ACTION_GRANT_OWNER:
220 activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.OWNER, onAffiliationChanged);
221 break;
222 case ACTION_REMOVE_MEMBERSHIP:
223 activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.NONE, onAffiliationChanged);
224 break;
225 }
226 })
227 .setNeutralButton(R.string.cancel, null).show();
228 return true;
229 case R.id.remove_from_room:
230 removeFromRoom(user, activity, onAffiliationChanged);
231 return true;
232 case R.id.send_private_message:
233 if (activity instanceof ConversationsActivity) {
234 ConversationFragment conversationFragment = ConversationFragment.get(activity);
235 if (conversationFragment != null) {
236 conversationFragment.privateMessageWith(user.getFullJid());
237 return true;
238 }
239 }
240 activity.privateMsgInMuc(conversation, user.getName());
241 return true;
242 case R.id.invite:
243 if (user.getAffiliation().ranks(MucOptions.Affiliation.MEMBER)) {
244 activity.xmppConnectionService.directInvite(conversation, jid.asBareJid());
245 } else {
246 activity.xmppConnectionService.invite(conversation, jid);
247 }
248 return true;
249 default:
250 return false;
251 }
252 }
253
254 private static void removeFromRoom(final User user, XmppActivity activity, XmppConnectionService.OnAffiliationChanged onAffiliationChanged) {
255 final Conversation conversation = user.getConversation();
256 if (conversation.getMucOptions().membersOnly()) {
257 activity.xmppConnectionService.changeAffiliationInConference(conversation, user.getRealJid(), MucOptions.Affiliation.NONE, onAffiliationChanged);
258 if (user.getRole() != MucOptions.Role.NONE) {
259 activity.xmppConnectionService.changeRoleInConference(conversation, user.getName(), MucOptions.Role.NONE);
260 }
261 } else {
262 AlertDialog.Builder builder = new AlertDialog.Builder(activity);
263 builder.setTitle(R.string.ban_from_conference);
264 String jid = user.getRealJid().asBareJid().toString();
265 SpannableString message = new SpannableString(activity.getString(R.string.removing_from_public_conference, jid));
266 int start = message.toString().indexOf(jid);
267 if (start >= 0) {
268 message.setSpan(new TypefaceSpan("monospace"), start, start + jid.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
269 }
270 builder.setMessage(message);
271 builder.setNegativeButton(R.string.cancel, null);
272 builder.setPositiveButton(R.string.ban_now, (dialog, which) -> {
273 activity.xmppConnectionService.changeAffiliationInConference(conversation, user.getRealJid(), MucOptions.Affiliation.OUTCAST, onAffiliationChanged);
274 if (user.getRole() != MucOptions.Role.NONE) {
275 activity.xmppConnectionService.changeRoleInConference(conversation, user.getName(), MucOptions.Role.NONE);
276 }
277 });
278 builder.create().show();
279 }
280 }
281
282 private static void startConversation(User user, XmppActivity activity) {
283 if (user.getRealJid() != null) {
284 Conversation newConversation = activity.xmppConnectionService.findOrCreateConversation(user.getAccount(), user.getRealJid().asBareJid(), false, true);
285 activity.switchToConversation(newConversation);
286 }
287 }
288}