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 final User self = mConversation.getMucOptions().getSelf();
422 this.mSelectedUser = user;
423 String name;
424 final Contact contact = user.getContact();
425 if (contact != null && contact.showInRoster()) {
426 name = contact.getDisplayName();
427 } else if (user.getRealJid() != null) {
428 name = user.getRealJid().asBareJid().toString();
429 } else {
430 name = user.getName();
431 }
432 menu.setHeaderTitle(name);
433 MucDetailsContextMenuHelper.configureMucDetailsContextMenu(menu, mConversation, user, mAdvancedMode);
434 }
435 super.onCreateContextMenu(menu, v, menuInfo);
436 }
437
438 @Override
439 public boolean onContextItemSelected(MenuItem item) {
440 if (!MucDetailsContextMenuHelper.onContextItemSelected(item, mSelectedUser, mConversation, this, this, this)) {
441 return super.onContextItemSelected(item);
442 }
443 return true;
444 }
445
446 private void removeFromRoom(final User user) {
447 MucDetailsContextMenuHelper.removeFromRoom(user, mConversation, this, this, this);
448 }
449
450 protected void startConversation(User user) {
451 MucDetailsContextMenuHelper.startConversation(user, this.mConversation, this);
452 }
453
454 protected void saveAsBookmark() {
455 xmppConnectionService.saveConversationAsBookmark(mConversation, mConversation.getMucOptions().getName());
456 }
457
458 protected void deleteBookmark() {
459 Account account = mConversation.getAccount();
460 Bookmark bookmark = mConversation.getBookmark();
461 account.getBookmarks().remove(bookmark);
462 bookmark.setConversation(null);
463 xmppConnectionService.pushBookmarks(account);
464 updateView();
465 }
466
467 @Override
468 void onBackendConnected() {
469 if (mPendingConferenceInvite != null) {
470 mPendingConferenceInvite.execute(this);
471 mPendingConferenceInvite = null;
472 }
473 if (getIntent().getAction().equals(ACTION_VIEW_MUC)) {
474 this.uuid = getIntent().getExtras().getString("uuid");
475 }
476 if (uuid != null) {
477 this.mConversation = xmppConnectionService.findConversationByUuid(uuid);
478 if (this.mConversation != null) {
479 updateView();
480 }
481 }
482 }
483
484 @Override
485 public void onBackPressed() {
486 if (this.binding.mucEditor.getVisibility() == View.VISIBLE) {
487 hideEditor();
488 } else {
489 super.onBackPressed();
490 }
491 }
492
493 private void updateView() {
494 invalidateOptionsMenu();
495 final MucOptions mucOptions = mConversation.getMucOptions();
496 final User self = mucOptions.getSelf();
497 String account;
498 if (Config.DOMAIN_LOCK != null) {
499 account = mConversation.getAccount().getJid().getLocal();
500 } else {
501 account = mConversation.getAccount().getJid().asBareJid().toString();
502 }
503 this.binding.editMucNameButton.setVisibility((self.getAffiliation().ranks(MucOptions.Affiliation.OWNER) || mucOptions.canChangeSubject()) ? View.VISIBLE : View.GONE);
504 this.binding.detailsAccount.setText(getString(R.string.using_account, account));
505 this.binding.jid.setText(mConversation.getJid().asBareJid().toEscapedString());
506 this.binding.yourPhoto.setImageBitmap(avatarService().get(mConversation,(int) getResources().getDimension(R.dimen.avatar_on_details_screen_size)));
507 String roomName = mucOptions.getName();
508 String subject = mucOptions.getSubject();
509 final boolean hasTitle;
510 if (printableValue(roomName)) {
511 this.binding.mucTitle.setText(EmojiWrapper.transform(roomName));
512 this.binding.mucTitle.setVisibility(View.VISIBLE);
513 hasTitle = true;
514 } else if (!printableValue(subject)) {
515 this.binding.mucTitle.setText(EmojiWrapper.transform(mConversation.getName()));
516 hasTitle = true;
517 this.binding.mucTitle.setVisibility(View.VISIBLE);
518 } else {
519 hasTitle = false;
520 this.binding.mucTitle.setVisibility(View.GONE);
521 }
522 if (printableValue(subject)) {
523 SpannableStringBuilder spannable = new SpannableStringBuilder(subject);
524 StylingHelper.format(spannable, this.binding.mucSubject.getCurrentTextColor());
525 MyLinkify.addLinks(spannable, false);
526 this.binding.mucSubject.setText(EmojiWrapper.transform(spannable));
527 this.binding.mucSubject.setTextAppearance(this,subject.length() > (hasTitle ? 128 : 196) ? R.style.TextAppearance_Conversations_Body1_Linkified : R.style.TextAppearance_Conversations_Subhead);
528 this.binding.mucSubject.setAutoLinkMask(0);
529 this.binding.mucSubject.setVisibility(View.VISIBLE);
530 } else {
531 this.binding.mucSubject.setVisibility(View.GONE);
532 }
533 this.binding.mucYourNick.setText(mucOptions.getActualNick());
534 if (mucOptions.online()) {
535 this.binding.mucMoreDetails.setVisibility(View.VISIBLE);
536 this.binding.mucSettings.setVisibility(View.VISIBLE);
537 this.binding.mucInfoMore.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE);
538 this.binding.mucRole.setVisibility(View.VISIBLE);
539 this.binding.mucRole.setText(getStatus(self));
540 if (mucOptions.membersOnly()) {
541 this.binding.mucConferenceType.setText(R.string.private_conference);
542 } else {
543 this.binding.mucConferenceType.setText(R.string.public_conference);
544 }
545 if (mucOptions.mamSupport()) {
546 this.binding.mucInfoMam.setText(R.string.server_info_available);
547 } else {
548 this.binding.mucInfoMam.setText(R.string.server_info_unavailable);
549 }
550 if (self.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) {
551 this.binding.changeConferenceButton.setVisibility(View.VISIBLE);
552 } else {
553 this.binding.changeConferenceButton.setVisibility(View.INVISIBLE);
554 }
555 } else {
556 this.binding.mucMoreDetails.setVisibility(View.GONE);
557 this.binding.mucInfoMore.setVisibility(View.GONE);
558 this.binding.mucSettings.setVisibility(View.GONE);
559 }
560
561 int ic_notifications = getThemeResource(R.attr.icon_notifications, R.drawable.ic_notifications_black_24dp);
562 int ic_notifications_off = getThemeResource(R.attr.icon_notifications_off, R.drawable.ic_notifications_off_black_24dp);
563 int ic_notifications_paused = getThemeResource(R.attr.icon_notifications_paused, R.drawable.ic_notifications_paused_black_24dp);
564 int ic_notifications_none = getThemeResource(R.attr.icon_notifications_none, R.drawable.ic_notifications_none_black_24dp);
565
566 long mutedTill = mConversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL, 0);
567 if (mutedTill == Long.MAX_VALUE) {
568 this.binding.notificationStatusText.setText(R.string.notify_never);
569 this.binding.notificationStatusButton.setImageResource(ic_notifications_off);
570 } else if (System.currentTimeMillis() < mutedTill) {
571 this.binding.notificationStatusText.setText(R.string.notify_paused);
572 this.binding.notificationStatusButton.setImageResource(ic_notifications_paused);
573 } else if (mConversation.alwaysNotify()) {
574 this.binding.notificationStatusText.setText(R.string.notify_on_all_messages);
575 this.binding.notificationStatusButton.setImageResource(ic_notifications);
576 } else {
577 this.binding.notificationStatusText.setText(R.string.notify_only_when_highlighted);
578 this.binding.notificationStatusButton.setImageResource(ic_notifications_none);
579 }
580
581 final LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
582 this.binding.mucMembers.removeAllViews();
583 if (inflater == null) {
584 return;
585 }
586 final ArrayList<User> users = mucOptions.getUsers();
587 Collections.sort(users);
588 for (final User user : users) {
589 ContactBinding binding = DataBindingUtil.inflate(inflater, R.layout.contact, this.binding.mucMembers, false);
590 this.setListItemBackgroundOnView(binding.getRoot());
591 binding.getRoot().setOnClickListener(view1 -> highlightInMuc(mConversation, user.getName()));
592 registerForContextMenu(binding.getRoot());
593 binding.getRoot().setTag(user);
594 if (mAdvancedMode && user.getPgpKeyId() != 0) {
595 binding.key.setVisibility(View.VISIBLE);
596 binding.key.setOnClickListener(v -> viewPgpKey(user));
597 binding.key.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId()));
598 }
599 Contact contact = user.getContact();
600 String name = user.getName();
601 if (contact != null) {
602 binding.contactDisplayName.setText(contact.getDisplayName());
603 binding.contactJid.setText((name != null ? name + " \u2022 " : "") + getStatus(user));
604 } else {
605 binding.contactDisplayName.setText(name == null ? "" : name);
606 binding.contactJid.setText(getStatus(user));
607
608 }
609 loadAvatar(user, binding.contactPhoto);
610 if (user.getRole() == MucOptions.Role.NONE) {
611 binding.contactJid.setAlpha(INACTIVE_ALPHA);
612 binding.key.setAlpha(INACTIVE_ALPHA);
613 binding.contactDisplayName.setAlpha(INACTIVE_ALPHA);
614 binding.contactPhoto.setAlpha(INACTIVE_ALPHA);
615 }
616 this.binding.mucMembers.addView(binding.getRoot());
617 if (mConversation.getMucOptions().canInvite()) {
618 this.binding.invite.setVisibility(View.VISIBLE);
619 } else {
620 this.binding.invite.setVisibility(View.GONE);
621 }
622 }
623 }
624
625 private String getStatus(User user) {
626 if (mAdvancedMode) {
627 return getString(user.getAffiliation().getResId()) +
628 " (" + getString(user.getRole().getResId()) + ')';
629 } else {
630 return getString(user.getAffiliation().getResId());
631 }
632 }
633
634 private void viewPgpKey(User user) {
635 PgpEngine pgp = xmppConnectionService.getPgpEngine();
636 if (pgp != null) {
637 PendingIntent intent = pgp.getIntentForKey(user.getPgpKeyId());
638 if (intent != null) {
639 try {
640 startIntentSenderForResult(intent.getIntentSender(), 0, null, 0, 0, 0);
641 } catch (SendIntentException ignored) {
642
643 }
644 }
645 }
646 }
647
648 @Override
649 public void onAffiliationChangedSuccessful(Jid jid) {
650 refreshUi();
651 }
652
653 @Override
654 public void onAffiliationChangeFailed(Jid jid, int resId) {
655 displayToast(getString(resId, jid.asBareJid().toString()));
656 }
657
658 @Override
659 public void onRoleChangedSuccessful(String nick) {
660
661 }
662
663 @Override
664 public void onRoleChangeFailed(String nick, int resId) {
665 displayToast(getString(resId, nick));
666 }
667
668 @Override
669 public void onPushSucceeded() {
670 displayToast(getString(R.string.modified_conference_options));
671 }
672
673 @Override
674 public void onPushFailed() {
675 displayToast(getString(R.string.could_not_modify_conference_options));
676 }
677
678 private void displayToast(final String msg) {
679 runOnUiThread(() -> Toast.makeText(ConferenceDetailsActivity.this, msg, Toast.LENGTH_SHORT).show());
680 }
681
682 public void loadAvatar(User user, ImageView imageView) {
683 if (cancelPotentialWork(user, imageView)) {
684 final Bitmap bm = avatarService().get(user, getPixel(48), true);
685 if (bm != null) {
686 cancelPotentialWork(user, imageView);
687 imageView.setImageBitmap(bm);
688 imageView.setBackgroundColor(0x00000000);
689 } else {
690 String seed = user.getRealJid() != null ? user.getRealJid().asBareJid().toString() : null;
691 imageView.setBackgroundColor(UIHelper.getColorForName(seed == null ? user.getName() : seed));
692 imageView.setImageDrawable(null);
693 final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
694 final AsyncDrawable asyncDrawable = new AsyncDrawable(getResources(), null, task);
695 imageView.setImageDrawable(asyncDrawable);
696 try {
697 task.execute(user);
698 } catch (final RejectedExecutionException ignored) {
699 }
700 }
701 }
702 }
703
704 @Override
705 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
706
707 }
708
709 @Override
710 public void onTextChanged(CharSequence s, int start, int before, int count) {
711
712 }
713
714 @Override
715 public void afterTextChanged(Editable s) {
716 if (mConversation == null) {
717 return;
718 }
719 final MucOptions mucOptions = mConversation.getMucOptions();
720 if (this.binding.mucEditor.getVisibility() == View.VISIBLE) {
721 boolean subjectChanged = changed(binding.mucEditSubject.getEditableText().toString(), mucOptions.getSubject());
722 boolean nameChanged = changed(binding.mucEditTitle.getEditableText().toString(), mucOptions.getName());
723 if (subjectChanged || nameChanged) {
724 this.binding.editMucNameButton.setImageResource(getThemeResource(R.attr.icon_save, R.drawable.ic_save_black_24dp));
725 } else {
726 this.binding.editMucNameButton.setImageResource(getThemeResource(R.attr.icon_cancel, R.drawable.ic_cancel_black_24dp));
727 }
728 }
729 }
730
731 static class AsyncDrawable extends BitmapDrawable {
732 private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
733
734 AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
735 super(res, bitmap);
736 bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
737 }
738
739 BitmapWorkerTask getBitmapWorkerTask() {
740 return bitmapWorkerTaskReference.get();
741 }
742 }
743
744 class BitmapWorkerTask extends AsyncTask<User, Void, Bitmap> {
745 private final WeakReference<ImageView> imageViewReference;
746 private User o = null;
747
748 private BitmapWorkerTask(ImageView imageView) {
749 imageViewReference = new WeakReference<>(imageView);
750 }
751
752 @Override
753 protected Bitmap doInBackground(User... params) {
754 this.o = params[0];
755 if (imageViewReference.get() == null) {
756 return null;
757 }
758 return avatarService().get(this.o, getPixel(48), isCancelled());
759 }
760
761 @Override
762 protected void onPostExecute(Bitmap bitmap) {
763 if (bitmap != null && !isCancelled()) {
764 final ImageView imageView = imageViewReference.get();
765 if (imageView != null) {
766 imageView.setImageBitmap(bitmap);
767 imageView.setBackgroundColor(0x00000000);
768 }
769 }
770 }
771 }
772
773}