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, XmppConnectionService.OnRoomDestroy, 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_destroy_room:
323 destroyRoom();
324 break;
325 case R.id.action_advanced_mode:
326 this.mAdvancedMode = !menuItem.isChecked();
327 menuItem.setChecked(this.mAdvancedMode);
328 getPreferences().edit().putBoolean("advanced_muc_mode", mAdvancedMode).apply();
329 final boolean online = mConversation != null && mConversation.getMucOptions().online();
330 this.binding.mucInfoMore.setVisibility(this.mAdvancedMode && online ? View.VISIBLE : View.GONE);
331 invalidateOptionsMenu();
332 updateView();
333 break;
334 }
335 return super.onOptionsItemSelected(menuItem);
336 }
337
338 public void onMucEditButtonClicked(View v) {
339 if (this.binding.mucEditor.getVisibility() == View.GONE) {
340 final MucOptions mucOptions = mConversation.getMucOptions();
341 this.binding.mucEditor.setVisibility(View.VISIBLE);
342 this.binding.mucDisplay.setVisibility(View.GONE);
343 this.binding.editMucNameButton.setImageResource(getThemeResource(R.attr.icon_cancel, R.drawable.ic_cancel_black_24dp));
344 final String name = mucOptions.getName();
345 this.binding.mucEditTitle.setText("");
346 final boolean owner = mucOptions.getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER);
347 if (owner || printableValue(name)) {
348 this.binding.mucEditTitle.setVisibility(View.VISIBLE);
349 if (name != null) {
350 this.binding.mucEditTitle.append(name);
351 }
352 } else {
353 this.binding.mucEditTitle.setVisibility(View.GONE);
354 }
355 this.binding.mucEditTitle.setEnabled(owner);
356 final String subject = mucOptions.getSubject();
357 this.binding.mucEditSubject.setText("");
358 if (subject != null) {
359 this.binding.mucEditSubject.append(subject);
360 }
361 this.binding.mucEditSubject.setEnabled(mucOptions.canChangeSubject());
362 if (!owner) {
363 this.binding.mucEditSubject.requestFocus();
364 }
365 } else {
366 String subject = this.binding.mucEditSubject.isEnabled() ? this.binding.mucEditSubject.getEditableText().toString().trim() : null;
367 String name = this.binding.mucEditTitle.isEnabled() ? this.binding.mucEditTitle.getEditableText().toString().trim() : null;
368 onMucInfoUpdated(subject, name);
369 SoftKeyboardUtils.hideSoftKeyboard(this);
370 hideEditor();
371 }
372 }
373
374 private void hideEditor() {
375 this.binding.mucEditor.setVisibility(View.GONE);
376 this.binding.mucDisplay.setVisibility(View.VISIBLE);
377 this.binding.editMucNameButton.setImageResource(getThemeResource(R.attr.icon_edit_body, R.drawable.ic_edit_black_24dp));
378 }
379
380 private void onMucInfoUpdated(String subject, String name) {
381 final MucOptions mucOptions = mConversation.getMucOptions();
382 if (mucOptions.canChangeSubject() && changed(mucOptions.getSubject(), subject)) {
383 xmppConnectionService.pushSubjectToConference(mConversation, subject);
384 }
385 if (mucOptions.getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER) && changed(mucOptions.getName(), name)) {
386 Bundle options = new Bundle();
387 options.putString("muc#roomconfig_persistentroom", "1");
388 options.putString("muc#roomconfig_roomname", StringUtils.nullOnEmpty(name));
389 xmppConnectionService.pushConferenceConfiguration(mConversation, options, this);
390 }
391 }
392
393
394 @Override
395 protected String getShareableUri(boolean http) {
396 if (mConversation != null) {
397 if (http) {
398 return "https://conversations.im/j/" + XmppUri.lameUrlEncode(mConversation.getJid().asBareJid().toEscapedString());
399 } else {
400 return "xmpp:" + mConversation.getJid().asBareJid() + "?join";
401 }
402 } else {
403 return null;
404 }
405 }
406
407 @Override
408 public boolean onPrepareOptionsMenu(Menu menu) {
409 MenuItem menuItemSaveBookmark = menu.findItem(R.id.action_save_as_bookmark);
410 MenuItem menuItemDeleteBookmark = menu.findItem(R.id.action_delete_bookmark);
411 MenuItem menuItemAdvancedMode = menu.findItem(R.id.action_advanced_mode);
412 MenuItem menuItemDestroyRoom = menu.findItem(R.id.action_destroy_room);
413 menuItemAdvancedMode.setChecked(mAdvancedMode);
414 if (mConversation == null) {
415 return true;
416 }
417 if (mConversation.getBookmark() != null) {
418 menuItemSaveBookmark.setVisible(false);
419 menuItemDeleteBookmark.setVisible(true);
420 } else {
421 menuItemDeleteBookmark.setVisible(false);
422 menuItemSaveBookmark.setVisible(true);
423 }
424 menuItemDestroyRoom.setVisible(mConversation.getMucOptions().getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER));
425 return true;
426 }
427
428 @Override
429 public boolean onCreateOptionsMenu(Menu menu) {
430 getMenuInflater().inflate(R.menu.muc_details, menu);
431 AccountUtils.showHideMenuItems(menu);
432 return super.onCreateOptionsMenu(menu);
433 }
434
435 @Override
436 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
437 Object tag = v.getTag();
438 if (tag instanceof User) {
439 getMenuInflater().inflate(R.menu.muc_details_context, menu);
440 final User user = (User) tag;
441 this.mSelectedUser = user;
442 String name;
443 final Contact contact = user.getContact();
444 if (contact != null && contact.showInContactList()) {
445 name = contact.getDisplayName();
446 } else if (user.getRealJid() != null) {
447 name = user.getRealJid().asBareJid().toString();
448 } else {
449 name = user.getName();
450 }
451 menu.setHeaderTitle(name);
452 MucDetailsContextMenuHelper.configureMucDetailsContextMenu(this, menu, mConversation, user);
453 }
454 super.onCreateContextMenu(menu, v, menuInfo);
455 }
456
457 @Override
458 public boolean onContextItemSelected(MenuItem item) {
459 if (!MucDetailsContextMenuHelper.onContextItemSelected(item, mSelectedUser, mConversation, this)) {
460 return super.onContextItemSelected(item);
461 }
462 return true;
463 }
464
465 @Override
466 public void onMediaLoaded(List<Attachment> attachments) {
467 runOnUiThread(() -> {
468 int limit = GridManager.getCurrentColumnCount(binding.media);
469 mMediaAdapter.setAttachments(attachments.subList(0, Math.min(limit,attachments.size())));
470 binding.mediaWrapper.setVisibility(attachments.size() > 0 ? View.VISIBLE : View.GONE);
471 });
472
473 }
474
475
476 protected void saveAsBookmark() {
477 xmppConnectionService.saveConversationAsBookmark(mConversation, mConversation.getMucOptions().getName());
478 }
479
480 protected void deleteBookmark() {
481 Account account = mConversation.getAccount();
482 Bookmark bookmark = mConversation.getBookmark();
483 account.getBookmarks().remove(bookmark);
484 bookmark.setConversation(null);
485 xmppConnectionService.pushBookmarks(account);
486 updateView();
487 }
488
489 protected void destroyRoom() {
490 AlertDialog.Builder builder = new AlertDialog.Builder(this);
491 builder.setTitle(R.string.destroy_room);
492 builder.setMessage(R.string.destroy_room_dialog);
493 builder.setPositiveButton(R.string.ok, (dialog, which) -> {
494 xmppConnectionService.destroyRoom(mConversation, ConferenceDetailsActivity.this);
495 });
496 builder.setNegativeButton(R.string.cancel, null);
497 final AlertDialog dialog = builder.create();
498 dialog.setCanceledOnTouchOutside(false);
499 dialog.show();
500 }
501
502 @Override
503 void onBackendConnected() {
504 if (mPendingConferenceInvite != null) {
505 mPendingConferenceInvite.execute(this);
506 mPendingConferenceInvite = null;
507 }
508 if (getIntent().getAction().equals(ACTION_VIEW_MUC)) {
509 this.uuid = getIntent().getExtras().getString("uuid");
510 }
511 if (uuid != null) {
512 this.mConversation = xmppConnectionService.findConversationByUuid(uuid);
513 if (this.mConversation != null) {
514 if (Compatibility.hasStoragePermission(this)) {
515 final int limit = GridManager.getCurrentColumnCount(this.binding.media);
516 xmppConnectionService.getAttachments(this.mConversation, limit, this);
517 this.binding.showMedia.setOnClickListener((v) -> MediaBrowserActivity.launch(this, mConversation));
518 }
519 updateView();
520 }
521 }
522 }
523
524 @Override
525 public void onBackPressed() {
526 if (this.binding.mucEditor.getVisibility() == View.VISIBLE) {
527 hideEditor();
528 } else {
529 super.onBackPressed();
530 }
531 }
532
533 private void updateView() {
534 invalidateOptionsMenu();
535 final MucOptions mucOptions = mConversation.getMucOptions();
536 final User self = mucOptions.getSelf();
537 String account;
538 if (Config.DOMAIN_LOCK != null) {
539 account = mConversation.getAccount().getJid().getLocal();
540 } else {
541 account = mConversation.getAccount().getJid().asBareJid().toString();
542 }
543 this.binding.editMucNameButton.setVisibility((self.getAffiliation().ranks(MucOptions.Affiliation.OWNER) || mucOptions.canChangeSubject()) ? View.VISIBLE : View.GONE);
544 this.binding.detailsAccount.setText(getString(R.string.using_account, account));
545 this.binding.jid.setText(mConversation.getJid().asBareJid().toEscapedString());
546 this.binding.yourPhoto.setImageBitmap(avatarService().get(mConversation,(int) getResources().getDimension(R.dimen.avatar_on_details_screen_size)));
547 String roomName = mucOptions.getName();
548 String subject = mucOptions.getSubject();
549 final boolean hasTitle;
550 if (printableValue(roomName)) {
551 this.binding.mucTitle.setText(EmojiWrapper.transform(roomName));
552 this.binding.mucTitle.setVisibility(View.VISIBLE);
553 hasTitle = true;
554 } else if (!printableValue(subject)) {
555 this.binding.mucTitle.setText(EmojiWrapper.transform(mConversation.getName()));
556 hasTitle = true;
557 this.binding.mucTitle.setVisibility(View.VISIBLE);
558 } else {
559 hasTitle = false;
560 this.binding.mucTitle.setVisibility(View.GONE);
561 }
562 if (printableValue(subject)) {
563 SpannableStringBuilder spannable = new SpannableStringBuilder(subject);
564 StylingHelper.format(spannable, this.binding.mucSubject.getCurrentTextColor());
565 MyLinkify.addLinks(spannable, false);
566 this.binding.mucSubject.setText(EmojiWrapper.transform(spannable));
567 this.binding.mucSubject.setTextAppearance(this,subject.length() > (hasTitle ? 128 : 196) ? R.style.TextAppearance_Conversations_Body1_Linkified : R.style.TextAppearance_Conversations_Subhead);
568 this.binding.mucSubject.setAutoLinkMask(0);
569 this.binding.mucSubject.setVisibility(View.VISIBLE);
570 } else {
571 this.binding.mucSubject.setVisibility(View.GONE);
572 }
573 this.binding.mucYourNick.setText(mucOptions.getActualNick());
574 if (mucOptions.online()) {
575 this.binding.mucMoreDetails.setVisibility(View.VISIBLE);
576 this.binding.mucSettings.setVisibility(View.VISIBLE);
577 this.binding.mucInfoMore.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE);
578 this.binding.mucRole.setVisibility(View.VISIBLE);
579 this.binding.mucRole.setText(getStatus(self));
580 if (mucOptions.membersOnly()) {
581 this.binding.mucConferenceType.setText(R.string.private_conference);
582 } else {
583 this.binding.mucConferenceType.setText(R.string.public_conference);
584 }
585 if (mucOptions.mamSupport()) {
586 this.binding.mucInfoMam.setText(R.string.server_info_available);
587 } else {
588 this.binding.mucInfoMam.setText(R.string.server_info_unavailable);
589 }
590 if (self.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) {
591 this.binding.changeConferenceButton.setVisibility(View.VISIBLE);
592 } else {
593 this.binding.changeConferenceButton.setVisibility(View.INVISIBLE);
594 }
595 } else {
596 this.binding.mucMoreDetails.setVisibility(View.GONE);
597 this.binding.mucInfoMore.setVisibility(View.GONE);
598 this.binding.mucSettings.setVisibility(View.GONE);
599 }
600
601 int ic_notifications = getThemeResource(R.attr.icon_notifications, R.drawable.ic_notifications_black_24dp);
602 int ic_notifications_off = getThemeResource(R.attr.icon_notifications_off, R.drawable.ic_notifications_off_black_24dp);
603 int ic_notifications_paused = getThemeResource(R.attr.icon_notifications_paused, R.drawable.ic_notifications_paused_black_24dp);
604 int ic_notifications_none = getThemeResource(R.attr.icon_notifications_none, R.drawable.ic_notifications_none_black_24dp);
605
606 long mutedTill = mConversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL, 0);
607 if (mutedTill == Long.MAX_VALUE) {
608 this.binding.notificationStatusText.setText(R.string.notify_never);
609 this.binding.notificationStatusButton.setImageResource(ic_notifications_off);
610 } else if (System.currentTimeMillis() < mutedTill) {
611 this.binding.notificationStatusText.setText(R.string.notify_paused);
612 this.binding.notificationStatusButton.setImageResource(ic_notifications_paused);
613 } else if (mConversation.alwaysNotify()) {
614 this.binding.notificationStatusText.setText(R.string.notify_on_all_messages);
615 this.binding.notificationStatusButton.setImageResource(ic_notifications);
616 } else {
617 this.binding.notificationStatusText.setText(R.string.notify_only_when_highlighted);
618 this.binding.notificationStatusButton.setImageResource(ic_notifications_none);
619 }
620
621 final LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
622 this.binding.mucMembers.removeAllViews();
623 if (inflater == null) {
624 return;
625 }
626 final ArrayList<User> users = mucOptions.getUsers();
627 Collections.sort(users);
628 for (final User user : users) {
629 ContactBinding binding = DataBindingUtil.inflate(inflater, R.layout.contact, this.binding.mucMembers, false);
630 this.setListItemBackgroundOnView(binding.getRoot());
631 binding.getRoot().setOnClickListener(view1 -> highlightInMuc(mConversation, user.getName()));
632 registerForContextMenu(binding.getRoot());
633 binding.getRoot().setTag(user);
634 if (mAdvancedMode && user.getPgpKeyId() != 0) {
635 binding.key.setVisibility(View.VISIBLE);
636 binding.key.setOnClickListener(v -> viewPgpKey(user));
637 binding.key.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId()));
638 }
639 Contact contact = user.getContact();
640 String name = user.getName();
641 if (contact != null) {
642 binding.contactDisplayName.setText(contact.getDisplayName());
643 binding.contactJid.setText((name != null ? name + " \u2022 " : "") + getStatus(user));
644 } else {
645 binding.contactDisplayName.setText(name == null ? "" : name);
646 binding.contactJid.setText(getStatus(user));
647
648 }
649 loadAvatar(user, binding.contactPhoto);
650 if (user.getRole() == MucOptions.Role.NONE) {
651 binding.contactJid.setAlpha(INACTIVE_ALPHA);
652 binding.key.setAlpha(INACTIVE_ALPHA);
653 binding.contactDisplayName.setAlpha(INACTIVE_ALPHA);
654 binding.contactPhoto.setAlpha(INACTIVE_ALPHA);
655 }
656 this.binding.mucMembers.addView(binding.getRoot());
657 if (mConversation.getMucOptions().canInvite()) {
658 this.binding.invite.setVisibility(View.VISIBLE);
659 } else {
660 this.binding.invite.setVisibility(View.GONE);
661 }
662 }
663 }
664
665 private String getStatus(User user) {
666 if (mAdvancedMode) {
667 return getString(user.getAffiliation().getResId()) +
668 " (" + getString(user.getRole().getResId()) + ')';
669 } else {
670 return getString(user.getAffiliation().getResId());
671 }
672 }
673
674 private void viewPgpKey(User user) {
675 PgpEngine pgp = xmppConnectionService.getPgpEngine();
676 if (pgp != null) {
677 PendingIntent intent = pgp.getIntentForKey(user.getPgpKeyId());
678 if (intent != null) {
679 try {
680 startIntentSenderForResult(intent.getIntentSender(), 0, null, 0, 0, 0);
681 } catch (SendIntentException ignored) {
682
683 }
684 }
685 }
686 }
687
688 @Override
689 public void onAffiliationChangedSuccessful(Jid jid) {
690 refreshUi();
691 }
692
693 @Override
694 public void onAffiliationChangeFailed(Jid jid, int resId) {
695 displayToast(getString(resId, jid.asBareJid().toString()));
696 }
697
698 @Override
699 public void onRoleChangedSuccessful(String nick) {
700
701 }
702
703 @Override
704 public void onRoleChangeFailed(String nick, int resId) {
705 displayToast(getString(resId, nick));
706 }
707
708 @Override
709 public void onRoomDestroySucceeded() {
710 finish();
711 }
712 @Override
713 public void onRoomDestroyFailed() {
714 displayToast(getString(R.string.could_not_destroy_room));
715 }
716
717 @Override
718 public void onPushSucceeded() {
719 displayToast(getString(R.string.modified_conference_options));
720 }
721
722 @Override
723 public void onPushFailed() {
724 displayToast(getString(R.string.could_not_modify_conference_options));
725 }
726
727 private void displayToast(final String msg) {
728 runOnUiThread(() -> {
729 if (isFinishing()) {
730 return;
731 }
732 ToastCompat.makeText(this, msg, Toast.LENGTH_SHORT).show();
733 });
734 }
735
736 public void loadAvatar(User user, ImageView imageView) {
737 if (cancelPotentialWork(user, imageView)) {
738 final Bitmap bm = avatarService().get(user, getPixel(48), true);
739 if (bm != null) {
740 cancelPotentialWork(user, imageView);
741 imageView.setImageBitmap(bm);
742 imageView.setBackgroundColor(0x00000000);
743 } else {
744 String seed = user.getRealJid() != null ? user.getRealJid().asBareJid().toString() : null;
745 imageView.setBackgroundColor(UIHelper.getColorForName(seed == null ? user.getName() : seed));
746 imageView.setImageDrawable(null);
747 final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
748 final AsyncDrawable asyncDrawable = new AsyncDrawable(getResources(), null, task);
749 imageView.setImageDrawable(asyncDrawable);
750 try {
751 task.execute(user);
752 } catch (final RejectedExecutionException ignored) {
753 }
754 }
755 }
756 }
757
758 @Override
759 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
760
761 }
762
763 @Override
764 public void onTextChanged(CharSequence s, int start, int before, int count) {
765
766 }
767
768 @Override
769 public void afterTextChanged(Editable s) {
770 if (mConversation == null) {
771 return;
772 }
773 final MucOptions mucOptions = mConversation.getMucOptions();
774 if (this.binding.mucEditor.getVisibility() == View.VISIBLE) {
775 boolean subjectChanged = changed(binding.mucEditSubject.getEditableText().toString(), mucOptions.getSubject());
776 boolean nameChanged = changed(binding.mucEditTitle.getEditableText().toString(), mucOptions.getName());
777 if (subjectChanged || nameChanged) {
778 this.binding.editMucNameButton.setImageResource(getThemeResource(R.attr.icon_save, R.drawable.ic_save_black_24dp));
779 } else {
780 this.binding.editMucNameButton.setImageResource(getThemeResource(R.attr.icon_cancel, R.drawable.ic_cancel_black_24dp));
781 }
782 }
783 }
784
785 static class AsyncDrawable extends BitmapDrawable {
786 private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
787
788 AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
789 super(res, bitmap);
790 bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
791 }
792
793 BitmapWorkerTask getBitmapWorkerTask() {
794 return bitmapWorkerTaskReference.get();
795 }
796 }
797
798 class BitmapWorkerTask extends AsyncTask<User, Void, Bitmap> {
799 private final WeakReference<ImageView> imageViewReference;
800 private User o = null;
801
802 private BitmapWorkerTask(ImageView imageView) {
803 imageViewReference = new WeakReference<>(imageView);
804 }
805
806 @Override
807 protected Bitmap doInBackground(User... params) {
808 this.o = params[0];
809 if (imageViewReference.get() == null) {
810 return null;
811 }
812 return avatarService().get(this.o, getPixel(48), isCancelled());
813 }
814
815 @Override
816 protected void onPostExecute(Bitmap bitmap) {
817 if (bitmap != null && !isCancelled()) {
818 final ImageView imageView = imageViewReference.get();
819 if (imageView != null) {
820 imageView.setImageBitmap(bitmap);
821 imageView.setBackgroundColor(0x00000000);
822 }
823 }
824 }
825 }
826
827}