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