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