1package eu.siacs.conversations.ui;
2
3import android.app.PendingIntent;
4import android.content.Context;
5import android.content.Intent;
6import android.content.IntentSender.SendIntentException;
7import android.content.res.Resources;
8import android.databinding.DataBindingUtil;
9import android.graphics.Bitmap;
10import android.graphics.drawable.BitmapDrawable;
11import android.graphics.drawable.Drawable;
12import android.os.AsyncTask;
13import android.os.Bundle;
14import android.support.v4.content.ContextCompat;
15import android.support.v7.app.AlertDialog;
16import android.support.v7.widget.Toolbar;
17import android.text.Editable;
18import android.text.SpannableStringBuilder;
19import android.text.TextWatcher;
20import android.util.Log;
21import android.view.ContextMenu;
22import android.view.LayoutInflater;
23import android.view.Menu;
24import android.view.MenuItem;
25import android.view.View;
26import android.view.View.OnClickListener;
27import android.view.WindowManager;
28import android.widget.ImageView;
29import android.widget.Toast;
30
31import org.openintents.openpgp.util.OpenPgpUtils;
32
33import java.lang.ref.WeakReference;
34import java.util.ArrayList;
35import java.util.Collections;
36import java.util.List;
37import java.util.concurrent.RejectedExecutionException;
38import java.util.concurrent.atomic.AtomicInteger;
39
40import eu.siacs.conversations.Config;
41import eu.siacs.conversations.R;
42import eu.siacs.conversations.crypto.PgpEngine;
43import eu.siacs.conversations.databinding.ActivityMucDetailsBinding;
44import eu.siacs.conversations.databinding.ContactBinding;
45import eu.siacs.conversations.entities.Account;
46import eu.siacs.conversations.entities.Bookmark;
47import eu.siacs.conversations.entities.Contact;
48import eu.siacs.conversations.entities.Conversation;
49import eu.siacs.conversations.entities.MucOptions;
50import eu.siacs.conversations.entities.MucOptions.User;
51import eu.siacs.conversations.services.XmppConnectionService;
52import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdate;
53import eu.siacs.conversations.services.XmppConnectionService.OnMucRosterUpdate;
54import eu.siacs.conversations.ui.adapter.MediaAdapter;
55import eu.siacs.conversations.ui.interfaces.OnMediaLoaded;
56import eu.siacs.conversations.ui.util.Attachment;
57import eu.siacs.conversations.ui.util.GridManager;
58import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
59import eu.siacs.conversations.ui.util.MucDetailsContextMenuHelper;
60import eu.siacs.conversations.ui.util.MyLinkify;
61import eu.siacs.conversations.ui.util.SoftKeyboardUtils;
62import eu.siacs.conversations.utils.AccountUtils;
63import eu.siacs.conversations.utils.Compatibility;
64import eu.siacs.conversations.utils.EmojiWrapper;
65import eu.siacs.conversations.utils.StringUtils;
66import eu.siacs.conversations.utils.StylingHelper;
67import eu.siacs.conversations.utils.UIHelper;
68import eu.siacs.conversations.utils.XmppUri;
69import me.drakeet.support.toast.ToastCompat;
70import rocks.xmpp.addr.Jid;
71
72import static eu.siacs.conversations.entities.Bookmark.printableValue;
73import static eu.siacs.conversations.utils.StringUtils.changed;
74
75public class ConferenceDetailsActivity extends XmppActivity implements OnConversationUpdate, OnMucRosterUpdate, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnRoleChanged, XmppConnectionService.OnConfigurationPushed, TextWatcher, OnMediaLoaded {
76 public static final String ACTION_VIEW_MUC = "view_muc";
77
78 private static final float INACTIVE_ALPHA = 0.4684f; //compromise between dark and light theme
79
80 private Conversation mConversation;
81 private OnClickListener inviteListener = new OnClickListener() {
82
83 @Override
84 public void onClick(View v) {
85 inviteToConversation(mConversation);
86 }
87 };
88 private ActivityMucDetailsBinding binding;
89 private MediaAdapter mMediaAdapter;
90 private String uuid = null;
91 private User mSelectedUser = null;
92
93 private boolean mAdvancedMode = false;
94
95 private UiCallback<Conversation> renameCallback = new UiCallback<Conversation>() {
96 @Override
97 public void success(Conversation object) {
98 displayToast(getString(R.string.your_nick_has_been_changed));
99 runOnUiThread(() -> {
100 updateView();
101 });
102
103 }
104
105 @Override
106 public void error(final int errorCode, Conversation object) {
107 displayToast(getString(errorCode));
108 }
109
110 @Override
111 public void userInputRequried(PendingIntent pi, Conversation object) {
112
113 }
114 };
115
116 private OnClickListener mNotifyStatusClickListener = new OnClickListener() {
117 @Override
118 public void onClick(View v) {
119 AlertDialog.Builder builder = new AlertDialog.Builder(ConferenceDetailsActivity.this);
120 builder.setTitle(R.string.pref_notification_settings);
121 String[] choices = {
122 getString(R.string.notify_on_all_messages),
123 getString(R.string.notify_only_when_highlighted),
124 getString(R.string.notify_never)
125 };
126 final AtomicInteger choice;
127 if (mConversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL, 0) == Long.MAX_VALUE) {
128 choice = new AtomicInteger(2);
129 } else {
130 choice = new AtomicInteger(mConversation.alwaysNotify() ? 0 : 1);
131 }
132 builder.setSingleChoiceItems(choices, choice.get(), (dialog, which) -> choice.set(which));
133 builder.setNegativeButton(R.string.cancel, null);
134 builder.setPositiveButton(R.string.ok, (dialog, which) -> {
135 if (choice.get() == 2) {
136 mConversation.setMutedTill(Long.MAX_VALUE);
137 } else {
138 mConversation.setMutedTill(0);
139 mConversation.setAttribute(Conversation.ATTRIBUTE_ALWAYS_NOTIFY, String.valueOf(choice.get() == 0));
140 }
141 xmppConnectionService.updateConversation(mConversation);
142 updateView();
143 });
144 builder.create().show();
145 }
146 };
147
148 private OnClickListener mChangeConferenceSettings = new OnClickListener() {
149 @Override
150 public void onClick(View v) {
151 final MucOptions mucOptions = mConversation.getMucOptions();
152 AlertDialog.Builder builder = new AlertDialog.Builder(ConferenceDetailsActivity.this);
153 builder.setTitle(R.string.conference_options);
154 final String[] options;
155 final boolean[] values;
156 if (mAdvancedMode) {
157 options = new String[]{
158 getString(R.string.members_only),
159 getString(R.string.moderated),
160 getString(R.string.non_anonymous)
161 };
162 values = new boolean[]{
163 mucOptions.membersOnly(),
164 mucOptions.moderated(),
165 mucOptions.nonanonymous()
166 };
167 } else {
168 options = new String[]{
169 getString(R.string.members_only),
170 getString(R.string.non_anonymous)
171 };
172 values = new boolean[]{
173 mucOptions.membersOnly(),
174 mucOptions.nonanonymous()
175 };
176 }
177 builder.setMultiChoiceItems(options, values, (dialog, which, isChecked) -> values[which] = isChecked);
178 builder.setNegativeButton(R.string.cancel, null);
179 builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
180 if (!mucOptions.membersOnly() && values[0]) {
181 xmppConnectionService.changeAffiliationsInConference(mConversation,
182 MucOptions.Affiliation.NONE,
183 MucOptions.Affiliation.MEMBER);
184 }
185 Bundle options1 = new Bundle();
186 options1.putString("muc#roomconfig_membersonly", values[0] ? "1" : "0");
187 if (values.length == 2) {
188 options1.putString("muc#roomconfig_whois", values[1] ? "anyone" : "moderators");
189 } else if (values.length == 3) {
190 options1.putString("muc#roomconfig_moderatedroom", values[1] ? "1" : "0");
191 options1.putString("muc#roomconfig_whois", values[2] ? "anyone" : "moderators");
192 }
193 options1.putString("muc#roomconfig_persistentroom", "1");
194 final boolean whois = values.length == 2 ? values[1] : values[2];
195 if (values[0] == whois) {
196 options1.putString("muc#roomconfig_publicroom", whois ? "0" : "1");
197 }
198 xmppConnectionService.pushConferenceConfiguration(mConversation,
199 options1,
200 ConferenceDetailsActivity.this);
201 });
202 builder.create().show();
203 }
204 };
205
206 public static boolean cancelPotentialWork(User user, ImageView imageView) {
207 final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
208
209 if (bitmapWorkerTask != null) {
210 final User old = bitmapWorkerTask.o;
211 if (old == null || user != old) {
212 bitmapWorkerTask.cancel(true);
213 } else {
214 return false;
215 }
216 }
217 return true;
218 }
219
220 private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
221 if (imageView != null) {
222 final Drawable drawable = imageView.getDrawable();
223 if (drawable instanceof AsyncDrawable) {
224 final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
225 return asyncDrawable.getBitmapWorkerTask();
226 }
227 }
228 return null;
229 }
230
231
232 @Override
233 public void onConversationUpdate() {
234 refreshUi();
235 }
236
237 @Override
238 public void onMucRosterUpdate() {
239 refreshUi();
240 }
241
242 @Override
243 protected void refreshUiReal() {
244 updateView();
245 }
246
247 @Override
248 protected void onCreate(Bundle savedInstanceState) {
249 super.onCreate(savedInstanceState);
250 this.binding = DataBindingUtil.setContentView(this, R.layout.activity_muc_details);
251 this.binding.mucMoreDetails.setVisibility(View.GONE);
252 this.binding.changeConferenceButton.setOnClickListener(this.mChangeConferenceSettings);
253 this.binding.invite.setOnClickListener(inviteListener);
254 setSupportActionBar((Toolbar) binding.toolbar);
255 configureActionBar(getSupportActionBar());
256 this.binding.editNickButton.setOnClickListener(v -> quickEdit(mConversation.getMucOptions().getActualNick(),
257 R.string.nickname,
258 value -> {
259 if (xmppConnectionService.renameInMuc(mConversation, value, renameCallback)) {
260 return null;
261 } else {
262 return getString(R.string.invalid_muc_nick);
263 }
264 }));
265 this.mAdvancedMode = getPreferences().getBoolean("advanced_muc_mode", false);
266 this.binding.mucInfoMore.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE);
267 this.binding.notificationStatusButton.setOnClickListener(this.mNotifyStatusClickListener);
268 this.binding.yourPhoto.setOnClickListener(v -> {
269 final MucOptions mucOptions = mConversation.getMucOptions();
270 if (!mucOptions.hasVCards()) {
271 Toast.makeText(this, R.string.host_does_not_support_group_chat_avatars, Toast.LENGTH_SHORT).show();
272 return;
273 }
274 if (!mucOptions.getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER)) {
275 Toast.makeText(this, R.string.only_the_owner_can_change_group_chat_avatar, Toast.LENGTH_SHORT).show();
276 return;
277 }
278 final Intent intent = new Intent(this, PublishGroupChatProfilePictureActivity.class);
279 intent.putExtra("uuid", mConversation.getUuid());
280 startActivity(intent);
281 });
282 this.binding.editMucNameButton.setOnClickListener(this::onMucEditButtonClicked);
283 this.binding.mucEditTitle.addTextChangedListener(this);
284 this.binding.mucEditSubject.addTextChangedListener(this);
285 this.binding.mucEditSubject.addTextChangedListener(new StylingHelper.MessageEditorStyler(this.binding.mucEditSubject));
286 mMediaAdapter = new MediaAdapter(this,R.dimen.media_size);
287 this.binding.media.setAdapter(mMediaAdapter);
288 GridManager.setupLayoutManager(this, this.binding.media, R.dimen.media_size);
289 }
290
291 @Override
292 protected void onStart() {
293 super.onStart();
294 final int theme = findTheme();
295 if (this.mTheme != theme) {
296 recreate();
297 }
298 binding.mediaWrapper.setVisibility(Compatibility.hasStoragePermission(this) ? View.VISIBLE : View.GONE);
299 }
300
301 @Override
302 public boolean onOptionsItemSelected(MenuItem menuItem) {
303 if (MenuDoubleTabUtil.shouldIgnoreTap()) {
304 return false;
305 }
306 switch (menuItem.getItemId()) {
307 case android.R.id.home:
308 finish();
309 break;
310 case R.id.action_share_http:
311 shareLink(true);
312 break;
313 case R.id.action_share_uri:
314 shareLink(false);
315 break;
316 case R.id.action_save_as_bookmark:
317 saveAsBookmark();
318 break;
319 case R.id.action_delete_bookmark:
320 deleteBookmark();
321 break;
322 case R.id.action_advanced_mode:
323 this.mAdvancedMode = !menuItem.isChecked();
324 menuItem.setChecked(this.mAdvancedMode);
325 getPreferences().edit().putBoolean("advanced_muc_mode", mAdvancedMode).apply();
326 final boolean online = mConversation != null && mConversation.getMucOptions().online();
327 this.binding.mucInfoMore.setVisibility(this.mAdvancedMode && online ? View.VISIBLE : View.GONE);
328 invalidateOptionsMenu();
329 updateView();
330 break;
331 }
332 return super.onOptionsItemSelected(menuItem);
333 }
334
335 public void onMucEditButtonClicked(View v) {
336 if (this.binding.mucEditor.getVisibility() == View.GONE) {
337 final MucOptions mucOptions = mConversation.getMucOptions();
338 this.binding.mucEditor.setVisibility(View.VISIBLE);
339 this.binding.mucDisplay.setVisibility(View.GONE);
340 this.binding.editMucNameButton.setImageResource(getThemeResource(R.attr.icon_cancel, R.drawable.ic_cancel_black_24dp));
341 final String name = mucOptions.getName();
342 this.binding.mucEditTitle.setText("");
343 final boolean owner = mucOptions.getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER);
344 if (owner || printableValue(name)) {
345 this.binding.mucEditTitle.setVisibility(View.VISIBLE);
346 if (name != null) {
347 this.binding.mucEditTitle.append(name);
348 }
349 } else {
350 this.binding.mucEditTitle.setVisibility(View.GONE);
351 }
352 this.binding.mucEditTitle.setEnabled(owner);
353 final String subject = mucOptions.getSubject();
354 this.binding.mucEditSubject.setText("");
355 if (subject != null) {
356 this.binding.mucEditSubject.append(subject);
357 }
358 this.binding.mucEditSubject.setEnabled(mucOptions.canChangeSubject());
359 if (!owner) {
360 this.binding.mucEditSubject.requestFocus();
361 }
362 } else {
363 String subject = this.binding.mucEditSubject.isEnabled() ? this.binding.mucEditSubject.getEditableText().toString().trim() : null;
364 String name = this.binding.mucEditTitle.isEnabled() ? this.binding.mucEditTitle.getEditableText().toString().trim() : null;
365 onMucInfoUpdated(subject, name);
366 SoftKeyboardUtils.hideSoftKeyboard(this);
367 hideEditor();
368 }
369 }
370
371 private void hideEditor() {
372 this.binding.mucEditor.setVisibility(View.GONE);
373 this.binding.mucDisplay.setVisibility(View.VISIBLE);
374 this.binding.editMucNameButton.setImageResource(getThemeResource(R.attr.icon_edit_body, R.drawable.ic_edit_black_24dp));
375 }
376
377 private void onMucInfoUpdated(String subject, String name) {
378 final MucOptions mucOptions = mConversation.getMucOptions();
379 if (mucOptions.canChangeSubject() && changed(mucOptions.getSubject(), subject)) {
380 xmppConnectionService.pushSubjectToConference(mConversation, subject);
381 }
382 if (mucOptions.getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER) && changed(mucOptions.getName(), name)) {
383 Bundle options = new Bundle();
384 options.putString("muc#roomconfig_persistentroom", "1");
385 options.putString("muc#roomconfig_roomname", StringUtils.nullOnEmpty(name));
386 xmppConnectionService.pushConferenceConfiguration(mConversation, options, this);
387 }
388 }
389
390
391 @Override
392 protected String getShareableUri(boolean http) {
393 if (mConversation != null) {
394 if (http) {
395 return "https://conversations.im/j/" + XmppUri.lameUrlEncode(mConversation.getJid().asBareJid().toEscapedString());
396 } else {
397 return "xmpp:" + mConversation.getJid().asBareJid() + "?join";
398 }
399 } else {
400 return null;
401 }
402 }
403
404 @Override
405 public boolean onPrepareOptionsMenu(Menu menu) {
406 MenuItem menuItemSaveBookmark = menu.findItem(R.id.action_save_as_bookmark);
407 MenuItem menuItemDeleteBookmark = menu.findItem(R.id.action_delete_bookmark);
408 MenuItem menuItemAdvancedMode = menu.findItem(R.id.action_advanced_mode);
409 menuItemAdvancedMode.setChecked(mAdvancedMode);
410 if (mConversation == null) {
411 return true;
412 }
413 if (mConversation.getBookmark() != null) {
414 menuItemSaveBookmark.setVisible(false);
415 menuItemDeleteBookmark.setVisible(true);
416 } else {
417 menuItemDeleteBookmark.setVisible(false);
418 menuItemSaveBookmark.setVisible(true);
419 }
420 return true;
421 }
422
423 @Override
424 public boolean onCreateOptionsMenu(Menu menu) {
425 getMenuInflater().inflate(R.menu.muc_details, menu);
426 AccountUtils.showHideMenuItems(menu);
427 return super.onCreateOptionsMenu(menu);
428 }
429
430 @Override
431 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
432 Object tag = v.getTag();
433 if (tag instanceof User) {
434 getMenuInflater().inflate(R.menu.muc_details_context, menu);
435 final User user = (User) tag;
436 this.mSelectedUser = user;
437 String name;
438 final Contact contact = user.getContact();
439 if (contact != null && contact.showInContactList()) {
440 name = contact.getDisplayName();
441 } else if (user.getRealJid() != null) {
442 name = user.getRealJid().asBareJid().toString();
443 } else {
444 name = user.getName();
445 }
446 menu.setHeaderTitle(name);
447 MucDetailsContextMenuHelper.configureMucDetailsContextMenu(this, menu, mConversation, user);
448 }
449 super.onCreateContextMenu(menu, v, menuInfo);
450 }
451
452 @Override
453 public boolean onContextItemSelected(MenuItem item) {
454 if (!MucDetailsContextMenuHelper.onContextItemSelected(item, mSelectedUser, mConversation, this)) {
455 return super.onContextItemSelected(item);
456 }
457 return true;
458 }
459
460 @Override
461 public void onMediaLoaded(List<Attachment> attachments) {
462 runOnUiThread(() -> {
463 int limit = GridManager.getCurrentColumnCount(binding.media);
464 mMediaAdapter.setAttachments(attachments.subList(0, Math.min(limit,attachments.size())));
465 binding.mediaWrapper.setVisibility(attachments.size() > 0 ? View.VISIBLE : View.GONE);
466 });
467
468 }
469
470
471 protected void saveAsBookmark() {
472 xmppConnectionService.saveConversationAsBookmark(mConversation, mConversation.getMucOptions().getName());
473 }
474
475 protected void deleteBookmark() {
476 Account account = mConversation.getAccount();
477 Bookmark bookmark = mConversation.getBookmark();
478 account.getBookmarks().remove(bookmark);
479 bookmark.setConversation(null);
480 xmppConnectionService.pushBookmarks(account);
481 updateView();
482 }
483
484 @Override
485 void onBackendConnected() {
486 if (mPendingConferenceInvite != null) {
487 mPendingConferenceInvite.execute(this);
488 mPendingConferenceInvite = null;
489 }
490 if (getIntent().getAction().equals(ACTION_VIEW_MUC)) {
491 this.uuid = getIntent().getExtras().getString("uuid");
492 }
493 if (uuid != null) {
494 this.mConversation = xmppConnectionService.findConversationByUuid(uuid);
495 if (this.mConversation != null) {
496 if (Compatibility.hasStoragePermission(this)) {
497 final int limit = GridManager.getCurrentColumnCount(this.binding.media);
498 xmppConnectionService.getAttachments(this.mConversation, limit, this);
499 this.binding.showMedia.setOnClickListener((v) -> MediaBrowserActivity.launch(this, mConversation));
500 }
501 updateView();
502 }
503 }
504 }
505
506 @Override
507 public void onBackPressed() {
508 if (this.binding.mucEditor.getVisibility() == View.VISIBLE) {
509 hideEditor();
510 } else {
511 super.onBackPressed();
512 }
513 }
514
515 private void updateView() {
516 invalidateOptionsMenu();
517 final MucOptions mucOptions = mConversation.getMucOptions();
518 final User self = mucOptions.getSelf();
519 String account;
520 if (Config.DOMAIN_LOCK != null) {
521 account = mConversation.getAccount().getJid().getLocal();
522 } else {
523 account = mConversation.getAccount().getJid().asBareJid().toString();
524 }
525 this.binding.editMucNameButton.setVisibility((self.getAffiliation().ranks(MucOptions.Affiliation.OWNER) || mucOptions.canChangeSubject()) ? View.VISIBLE : View.GONE);
526 this.binding.detailsAccount.setText(getString(R.string.using_account, account));
527 this.binding.jid.setText(mConversation.getJid().asBareJid().toEscapedString());
528 this.binding.yourPhoto.setImageBitmap(avatarService().get(mConversation,(int) getResources().getDimension(R.dimen.avatar_on_details_screen_size)));
529 String roomName = mucOptions.getName();
530 String subject = mucOptions.getSubject();
531 final boolean hasTitle;
532 if (printableValue(roomName)) {
533 this.binding.mucTitle.setText(EmojiWrapper.transform(roomName));
534 this.binding.mucTitle.setVisibility(View.VISIBLE);
535 hasTitle = true;
536 } else if (!printableValue(subject)) {
537 this.binding.mucTitle.setText(EmojiWrapper.transform(mConversation.getName()));
538 hasTitle = true;
539 this.binding.mucTitle.setVisibility(View.VISIBLE);
540 } else {
541 hasTitle = false;
542 this.binding.mucTitle.setVisibility(View.GONE);
543 }
544 if (printableValue(subject)) {
545 SpannableStringBuilder spannable = new SpannableStringBuilder(subject);
546 StylingHelper.format(spannable, this.binding.mucSubject.getCurrentTextColor());
547 MyLinkify.addLinks(spannable, false);
548 this.binding.mucSubject.setText(EmojiWrapper.transform(spannable));
549 this.binding.mucSubject.setTextAppearance(this,subject.length() > (hasTitle ? 128 : 196) ? R.style.TextAppearance_Conversations_Body1_Linkified : R.style.TextAppearance_Conversations_Subhead);
550 this.binding.mucSubject.setAutoLinkMask(0);
551 this.binding.mucSubject.setVisibility(View.VISIBLE);
552 } else {
553 this.binding.mucSubject.setVisibility(View.GONE);
554 }
555 this.binding.mucYourNick.setText(mucOptions.getActualNick());
556 if (mucOptions.online()) {
557 this.binding.mucMoreDetails.setVisibility(View.VISIBLE);
558 this.binding.mucSettings.setVisibility(View.VISIBLE);
559 this.binding.mucInfoMore.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE);
560 this.binding.mucRole.setVisibility(View.VISIBLE);
561 this.binding.mucRole.setText(getStatus(self));
562 if (mucOptions.membersOnly()) {
563 this.binding.mucConferenceType.setText(R.string.private_conference);
564 } else {
565 this.binding.mucConferenceType.setText(R.string.public_conference);
566 }
567 if (mucOptions.mamSupport()) {
568 this.binding.mucInfoMam.setText(R.string.server_info_available);
569 } else {
570 this.binding.mucInfoMam.setText(R.string.server_info_unavailable);
571 }
572 if (self.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) {
573 this.binding.changeConferenceButton.setVisibility(View.VISIBLE);
574 } else {
575 this.binding.changeConferenceButton.setVisibility(View.INVISIBLE);
576 }
577 } else {
578 this.binding.mucMoreDetails.setVisibility(View.GONE);
579 this.binding.mucInfoMore.setVisibility(View.GONE);
580 this.binding.mucSettings.setVisibility(View.GONE);
581 }
582
583 int ic_notifications = getThemeResource(R.attr.icon_notifications, R.drawable.ic_notifications_black_24dp);
584 int ic_notifications_off = getThemeResource(R.attr.icon_notifications_off, R.drawable.ic_notifications_off_black_24dp);
585 int ic_notifications_paused = getThemeResource(R.attr.icon_notifications_paused, R.drawable.ic_notifications_paused_black_24dp);
586 int ic_notifications_none = getThemeResource(R.attr.icon_notifications_none, R.drawable.ic_notifications_none_black_24dp);
587
588 long mutedTill = mConversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL, 0);
589 if (mutedTill == Long.MAX_VALUE) {
590 this.binding.notificationStatusText.setText(R.string.notify_never);
591 this.binding.notificationStatusButton.setImageResource(ic_notifications_off);
592 } else if (System.currentTimeMillis() < mutedTill) {
593 this.binding.notificationStatusText.setText(R.string.notify_paused);
594 this.binding.notificationStatusButton.setImageResource(ic_notifications_paused);
595 } else if (mConversation.alwaysNotify()) {
596 this.binding.notificationStatusText.setText(R.string.notify_on_all_messages);
597 this.binding.notificationStatusButton.setImageResource(ic_notifications);
598 } else {
599 this.binding.notificationStatusText.setText(R.string.notify_only_when_highlighted);
600 this.binding.notificationStatusButton.setImageResource(ic_notifications_none);
601 }
602
603 final LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
604 this.binding.mucMembers.removeAllViews();
605 if (inflater == null) {
606 return;
607 }
608 final ArrayList<User> users = mucOptions.getUsers();
609 Collections.sort(users);
610 for (final User user : users) {
611 ContactBinding binding = DataBindingUtil.inflate(inflater, R.layout.contact, this.binding.mucMembers, false);
612 this.setListItemBackgroundOnView(binding.getRoot());
613 binding.getRoot().setOnClickListener(view1 -> highlightInMuc(mConversation, user.getName()));
614 registerForContextMenu(binding.getRoot());
615 binding.getRoot().setTag(user);
616 if (mAdvancedMode && user.getPgpKeyId() != 0) {
617 binding.key.setVisibility(View.VISIBLE);
618 binding.key.setOnClickListener(v -> viewPgpKey(user));
619 binding.key.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId()));
620 }
621 Contact contact = user.getContact();
622 String name = user.getName();
623 if (contact != null) {
624 binding.contactDisplayName.setText(contact.getDisplayName());
625 binding.contactJid.setText((name != null ? name + " \u2022 " : "") + getStatus(user));
626 } else {
627 binding.contactDisplayName.setText(name == null ? "" : name);
628 binding.contactJid.setText(getStatus(user));
629
630 }
631 loadAvatar(user, binding.contactPhoto);
632 if (user.getRole() == MucOptions.Role.NONE) {
633 binding.contactJid.setAlpha(INACTIVE_ALPHA);
634 binding.key.setAlpha(INACTIVE_ALPHA);
635 binding.contactDisplayName.setAlpha(INACTIVE_ALPHA);
636 binding.contactPhoto.setAlpha(INACTIVE_ALPHA);
637 }
638 this.binding.mucMembers.addView(binding.getRoot());
639 if (mConversation.getMucOptions().canInvite()) {
640 this.binding.invite.setVisibility(View.VISIBLE);
641 } else {
642 this.binding.invite.setVisibility(View.GONE);
643 }
644 }
645 }
646
647 private String getStatus(User user) {
648 if (mAdvancedMode) {
649 return getString(user.getAffiliation().getResId()) +
650 " (" + getString(user.getRole().getResId()) + ')';
651 } else {
652 return getString(user.getAffiliation().getResId());
653 }
654 }
655
656 private void viewPgpKey(User user) {
657 PgpEngine pgp = xmppConnectionService.getPgpEngine();
658 if (pgp != null) {
659 PendingIntent intent = pgp.getIntentForKey(user.getPgpKeyId());
660 if (intent != null) {
661 try {
662 startIntentSenderForResult(intent.getIntentSender(), 0, null, 0, 0, 0);
663 } catch (SendIntentException ignored) {
664
665 }
666 }
667 }
668 }
669
670 @Override
671 public void onAffiliationChangedSuccessful(Jid jid) {
672 refreshUi();
673 }
674
675 @Override
676 public void onAffiliationChangeFailed(Jid jid, int resId) {
677 displayToast(getString(resId, jid.asBareJid().toString()));
678 }
679
680 @Override
681 public void onRoleChangedSuccessful(String nick) {
682
683 }
684
685 @Override
686 public void onRoleChangeFailed(String nick, int resId) {
687 displayToast(getString(resId, nick));
688 }
689
690 @Override
691 public void onPushSucceeded() {
692 displayToast(getString(R.string.modified_conference_options));
693 }
694
695 @Override
696 public void onPushFailed() {
697 displayToast(getString(R.string.could_not_modify_conference_options));
698 }
699
700 private void displayToast(final String msg) {
701 runOnUiThread(() -> {
702 if (isFinishing()) {
703 return;
704 }
705 ToastCompat.makeText(this, msg, Toast.LENGTH_SHORT).show();
706 });
707 }
708
709 public void loadAvatar(User user, ImageView imageView) {
710 if (cancelPotentialWork(user, imageView)) {
711 final Bitmap bm = avatarService().get(user, getPixel(48), true);
712 if (bm != null) {
713 cancelPotentialWork(user, imageView);
714 imageView.setImageBitmap(bm);
715 imageView.setBackgroundColor(0x00000000);
716 } else {
717 String seed = user.getRealJid() != null ? user.getRealJid().asBareJid().toString() : null;
718 imageView.setBackgroundColor(UIHelper.getColorForName(seed == null ? user.getName() : seed));
719 imageView.setImageDrawable(null);
720 final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
721 final AsyncDrawable asyncDrawable = new AsyncDrawable(getResources(), null, task);
722 imageView.setImageDrawable(asyncDrawable);
723 try {
724 task.execute(user);
725 } catch (final RejectedExecutionException ignored) {
726 }
727 }
728 }
729 }
730
731 @Override
732 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
733
734 }
735
736 @Override
737 public void onTextChanged(CharSequence s, int start, int before, int count) {
738
739 }
740
741 @Override
742 public void afterTextChanged(Editable s) {
743 if (mConversation == null) {
744 return;
745 }
746 final MucOptions mucOptions = mConversation.getMucOptions();
747 if (this.binding.mucEditor.getVisibility() == View.VISIBLE) {
748 boolean subjectChanged = changed(binding.mucEditSubject.getEditableText().toString(), mucOptions.getSubject());
749 boolean nameChanged = changed(binding.mucEditTitle.getEditableText().toString(), mucOptions.getName());
750 if (subjectChanged || nameChanged) {
751 this.binding.editMucNameButton.setImageResource(getThemeResource(R.attr.icon_save, R.drawable.ic_save_black_24dp));
752 } else {
753 this.binding.editMucNameButton.setImageResource(getThemeResource(R.attr.icon_cancel, R.drawable.ic_cancel_black_24dp));
754 }
755 }
756 }
757
758 static class AsyncDrawable extends BitmapDrawable {
759 private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
760
761 AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
762 super(res, bitmap);
763 bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
764 }
765
766 BitmapWorkerTask getBitmapWorkerTask() {
767 return bitmapWorkerTaskReference.get();
768 }
769 }
770
771 class BitmapWorkerTask extends AsyncTask<User, Void, Bitmap> {
772 private final WeakReference<ImageView> imageViewReference;
773 private User o = null;
774
775 private BitmapWorkerTask(ImageView imageView) {
776 imageViewReference = new WeakReference<>(imageView);
777 }
778
779 @Override
780 protected Bitmap doInBackground(User... params) {
781 this.o = params[0];
782 if (imageViewReference.get() == null) {
783 return null;
784 }
785 return avatarService().get(this.o, getPixel(48), isCancelled());
786 }
787
788 @Override
789 protected void onPostExecute(Bitmap bitmap) {
790 if (bitmap != null && !isCancelled()) {
791 final ImageView imageView = imageViewReference.get();
792 if (imageView != null) {
793 imageView.setImageBitmap(bitmap);
794 imageView.setBackgroundColor(0x00000000);
795 }
796 }
797 }
798 }
799
800}