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