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