ConferenceDetailsActivity.java

  1package eu.siacs.conversations.ui;
  2
  3import android.app.Activity;
  4import android.app.PendingIntent;
  5import android.content.Context;
  6import android.content.Intent;
  7import android.os.Bundle;
  8import android.text.Editable;
  9import android.text.SpannableStringBuilder;
 10import android.text.TextWatcher;
 11import android.text.method.LinkMovementMethod;
 12import android.view.LayoutInflater;
 13import android.view.Menu;
 14import android.view.MenuItem;
 15import android.view.View;
 16import android.view.View.OnClickListener;
 17import android.widget.ArrayAdapter;
 18import android.widget.TextView;
 19import android.widget.Toast;
 20
 21import androidx.appcompat.app.AlertDialog;
 22import androidx.databinding.DataBindingUtil;
 23
 24import java.util.ArrayList;
 25import java.util.Collections;
 26import java.util.Comparator;
 27import java.util.List;
 28import java.util.Map;
 29import java.util.concurrent.atomic.AtomicInteger;
 30import java.util.stream.Collectors;
 31
 32import eu.siacs.conversations.Config;
 33import eu.siacs.conversations.R;
 34import eu.siacs.conversations.databinding.ActivityMucDetailsBinding;
 35import eu.siacs.conversations.entities.Account;
 36import eu.siacs.conversations.entities.Bookmark;
 37import eu.siacs.conversations.entities.Contact;
 38import eu.siacs.conversations.entities.Conversation;
 39import eu.siacs.conversations.entities.ListItem;
 40import eu.siacs.conversations.entities.MucOptions;
 41import eu.siacs.conversations.entities.MucOptions.User;
 42import eu.siacs.conversations.services.XmppConnectionService;
 43import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdate;
 44import eu.siacs.conversations.services.XmppConnectionService.OnMucRosterUpdate;
 45import eu.siacs.conversations.ui.adapter.MediaAdapter;
 46import eu.siacs.conversations.ui.adapter.UserPreviewAdapter;
 47import eu.siacs.conversations.ui.interfaces.OnMediaLoaded;
 48import eu.siacs.conversations.ui.util.Attachment;
 49import eu.siacs.conversations.ui.util.AvatarWorkerTask;
 50import eu.siacs.conversations.ui.util.GridManager;
 51import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
 52import eu.siacs.conversations.ui.util.MucConfiguration;
 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.AccountUtils;
 57import eu.siacs.conversations.utils.Compatibility;
 58import eu.siacs.conversations.utils.StringUtils;
 59import eu.siacs.conversations.utils.StylingHelper;
 60import eu.siacs.conversations.utils.XmppUri;
 61import eu.siacs.conversations.xmpp.Jid;
 62import me.drakeet.support.toast.ToastCompat;
 63
 64import static eu.siacs.conversations.entities.Bookmark.printableValue;
 65import static eu.siacs.conversations.utils.StringUtils.changed;
 66
 67public class ConferenceDetailsActivity extends XmppActivity implements OnConversationUpdate, OnMucRosterUpdate, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnConfigurationPushed, XmppConnectionService.OnRoomDestroy, TextWatcher, OnMediaLoaded {
 68    public static final String ACTION_VIEW_MUC = "view_muc";
 69
 70    private Conversation mConversation;
 71    private ActivityMucDetailsBinding binding;
 72    private MediaAdapter mMediaAdapter;
 73    private UserPreviewAdapter mUserPreviewAdapter;
 74    private String uuid = null;
 75
 76    private boolean mAdvancedMode = false;
 77
 78    private final UiCallback<Conversation> renameCallback = new UiCallback<Conversation>() {
 79        @Override
 80        public void success(Conversation object) {
 81            displayToast(getString(R.string.your_nick_has_been_changed));
 82            runOnUiThread(() -> {
 83                updateView();
 84            });
 85
 86        }
 87
 88        @Override
 89        public void error(final int errorCode, Conversation object) {
 90            displayToast(getString(errorCode));
 91        }
 92
 93        @Override
 94        public void userInputRequired(PendingIntent pi, Conversation object) {
 95
 96        }
 97    };
 98
 99    public static void open(final Activity activity, final Conversation conversation) {
100        Intent intent = new Intent(activity, ConferenceDetailsActivity.class);
101        intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC);
102        intent.putExtra("uuid", conversation.getUuid());
103        activity.startActivity(intent);
104    }
105
106    private final 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 final 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            MucConfiguration configuration = MucConfiguration.get(ConferenceDetailsActivity.this, mAdvancedMode, mucOptions);
144            builder.setTitle(configuration.title);
145            final boolean[] values = configuration.values;
146            builder.setMultiChoiceItems(configuration.names, values, (dialog, which, isChecked) -> values[which] = isChecked);
147            builder.setNegativeButton(R.string.cancel, null);
148            builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
149                Bundle options = configuration.toBundle(values);
150                options.putString("muc#roomconfig_persistentroom", "1");
151                xmppConnectionService.pushConferenceConfiguration(mConversation,
152                        options,
153                        ConferenceDetailsActivity.this);
154            });
155            builder.create().show();
156        }
157    };
158
159
160    @Override
161    public void onConversationUpdate() {
162        refreshUi();
163    }
164
165    @Override
166    public void onMucRosterUpdate() {
167        refreshUi();
168    }
169
170    @Override
171    protected void refreshUiReal() {
172        updateView();
173    }
174
175    @Override
176    protected void onCreate(Bundle savedInstanceState) {
177        super.onCreate(savedInstanceState);
178        this.binding = DataBindingUtil.setContentView(this, R.layout.activity_muc_details);
179        this.binding.changeConferenceButton.setOnClickListener(this.mChangeConferenceSettings);
180        setSupportActionBar(binding.toolbar);
181        configureActionBar(getSupportActionBar());
182        this.binding.editNickButton.setOnClickListener(v -> quickEdit(mConversation.getMucOptions().getActualNick(),
183                R.string.nickname,
184                value -> {
185                    if (xmppConnectionService.renameInMuc(mConversation, value, renameCallback)) {
186                        return null;
187                    } else {
188                        return getString(R.string.invalid_muc_nick);
189                    }
190                }));
191        this.mAdvancedMode = getPreferences().getBoolean("advanced_muc_mode", false);
192        this.binding.mucInfoMore.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE);
193        this.binding.notificationStatusButton.setOnClickListener(this.mNotifyStatusClickListener);
194        this.binding.yourPhoto.setOnClickListener(v -> {
195            final MucOptions mucOptions = mConversation.getMucOptions();
196            if (!mucOptions.hasVCards()) {
197                Toast.makeText(this, R.string.host_does_not_support_group_chat_avatars, Toast.LENGTH_SHORT).show();
198                return;
199            }
200            if (!mucOptions.getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER)) {
201                Toast.makeText(this, R.string.only_the_owner_can_change_group_chat_avatar, Toast.LENGTH_SHORT).show();
202                return;
203            }
204            final Intent intent = new Intent(this, PublishGroupChatProfilePictureActivity.class);
205            intent.putExtra("uuid", mConversation.getUuid());
206            startActivity(intent);
207        });
208        this.binding.editMucNameButton.setOnClickListener(this::onMucEditButtonClicked);
209        this.binding.mucEditTitle.addTextChangedListener(this);
210        this.binding.mucEditSubject.addTextChangedListener(this);
211        this.binding.mucEditSubject.addTextChangedListener(new StylingHelper.MessageEditorStyler(this.binding.mucEditSubject));
212        this.binding.editTags.addTextChangedListener(this);
213        this.mMediaAdapter = new MediaAdapter(this, R.dimen.media_size);
214        this.mUserPreviewAdapter = new UserPreviewAdapter();
215        this.binding.media.setAdapter(mMediaAdapter);
216        this.binding.users.setAdapter(mUserPreviewAdapter);
217        GridManager.setupLayoutManager(this, this.binding.media, R.dimen.media_size);
218        GridManager.setupLayoutManager(this, this.binding.users, R.dimen.media_size);
219        this.binding.invite.setOnClickListener(v -> inviteToConversation(mConversation));
220        this.binding.showUsers.setOnClickListener(v -> {
221            Intent intent = new Intent(this, MucUsersActivity.class);
222            intent.putExtra("uuid", mConversation.getUuid());
223            startActivity(intent);
224        });
225    }
226
227    @Override
228    protected void onStart() {
229        super.onStart();
230        final int theme = findTheme();
231        if (this.mTheme != theme) {
232            recreate();
233        }
234        binding.mediaWrapper.setVisibility(Compatibility.hasStoragePermission(this) ? View.VISIBLE : View.GONE);
235    }
236
237    @Override
238    public boolean onOptionsItemSelected(MenuItem menuItem) {
239        if (MenuDoubleTabUtil.shouldIgnoreTap()) {
240            return false;
241        }
242        switch (menuItem.getItemId()) {
243            case android.R.id.home:
244                finish();
245                break;
246            case R.id.action_share_http:
247                shareLink(true);
248                break;
249            case R.id.action_share_uri:
250                shareLink(false);
251                break;
252            case R.id.action_save_as_bookmark:
253                saveAsBookmark();
254                break;
255            case R.id.action_delete_bookmark:
256                deleteBookmark();
257                break;
258            case R.id.action_destroy_room:
259                destroyRoom();
260                break;
261            case R.id.action_advanced_mode:
262                this.mAdvancedMode = !menuItem.isChecked();
263                menuItem.setChecked(this.mAdvancedMode);
264                getPreferences().edit().putBoolean("advanced_muc_mode", mAdvancedMode).apply();
265                final boolean online = mConversation != null && mConversation.getMucOptions().online();
266                this.binding.mucInfoMore.setVisibility(this.mAdvancedMode && online ? View.VISIBLE : View.GONE);
267                invalidateOptionsMenu();
268                updateView();
269                break;
270        }
271        return super.onOptionsItemSelected(menuItem);
272    }
273
274    @Override
275    public boolean onContextItemSelected(MenuItem item) {
276        final User user = mUserPreviewAdapter.getSelectedUser();
277        if (user == null) {
278            Toast.makeText(this, R.string.unable_to_perform_this_action, Toast.LENGTH_SHORT).show();
279            return true;
280        }
281        if (!MucDetailsContextMenuHelper.onContextItemSelected(item, mUserPreviewAdapter.getSelectedUser(), this)) {
282            return super.onContextItemSelected(item);
283        }
284        return true;
285    }
286
287    public void onMucEditButtonClicked(View v) {
288        if (this.binding.mucEditor.getVisibility() == View.GONE) {
289            final MucOptions mucOptions = mConversation.getMucOptions();
290            this.binding.mucEditor.setVisibility(View.VISIBLE);
291            this.binding.mucDisplay.setVisibility(View.GONE);
292            this.binding.editMucNameButton.setImageResource(getThemeResource(R.attr.icon_cancel, R.drawable.ic_cancel_black_24dp));
293            final String name = mucOptions.getName();
294            this.binding.mucEditTitle.setText("");
295            final boolean owner = mucOptions.getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER);
296            if (owner || printableValue(name)) {
297                this.binding.mucEditTitle.setVisibility(View.VISIBLE);
298                if (name != null) {
299                    this.binding.mucEditTitle.append(name);
300                }
301            } else {
302                this.binding.mucEditTitle.setVisibility(View.GONE);
303            }
304            this.binding.mucEditTitle.setEnabled(owner);
305            final String subject = mucOptions.getSubject();
306            this.binding.mucEditSubject.setText("");
307            if (subject != null) {
308                this.binding.mucEditSubject.append(subject);
309            }
310            this.binding.mucEditSubject.setEnabled(mucOptions.canChangeSubject());
311            if (!owner) {
312                this.binding.mucEditSubject.requestFocus();
313            }
314
315            final Bookmark bookmark = mConversation.getBookmark();
316            if (bookmark != null && mConversation.getAccount().getXmppConnection().getFeatures().bookmarks2()) {
317                for (final ListItem.Tag group : bookmark.getGroupTags()) {
318                    binding.editTags.addObjectSync(group);
319                }
320                ArrayList<ListItem.Tag> tags = new ArrayList<>();
321                for (final Account account : xmppConnectionService.getAccounts()) {
322                    for (Contact contact : account.getRoster().getContacts()) {
323                        tags.addAll(contact.getTags(this));
324                    }
325                    for (Bookmark bmark : account.getBookmarks()) {
326                        tags.addAll(bmark.getTags(this));
327                    }
328                }
329                Comparator<Map.Entry<ListItem.Tag,Integer>> sortTagsBy = Map.Entry.comparingByValue(Comparator.reverseOrder());
330                sortTagsBy = sortTagsBy.thenComparing(entry -> entry.getKey().getName());
331
332                ArrayAdapter<ListItem.Tag> adapter = new ArrayAdapter<>(
333                    this,
334                    android.R.layout.simple_list_item_1,
335                    tags.stream()
336                    .collect(Collectors.toMap((x) -> x, (t) -> 1, (c1, c2) -> c1 + c2))
337                    .entrySet().stream()
338                    .sorted(sortTagsBy)
339                    .map(e -> e.getKey()).collect(Collectors.toList())
340                );
341                binding.editTags.setAdapter(adapter);
342                this.binding.editTags.setVisibility(View.VISIBLE);
343            } else {
344                this.binding.editTags.setVisibility(View.GONE);
345            }
346        } else {
347            String subject = this.binding.mucEditSubject.isEnabled() ? this.binding.mucEditSubject.getEditableText().toString().trim() : null;
348            String name = this.binding.mucEditTitle.isEnabled() ? this.binding.mucEditTitle.getEditableText().toString().trim() : null;
349            onMucInfoUpdated(subject, name);
350
351            final Bookmark bookmark = mConversation.getBookmark();
352            if (bookmark != null && mConversation.getAccount().getXmppConnection().getFeatures().bookmarks2()) {
353                bookmark.setGroups(binding.editTags.getObjects().stream().map(tag -> tag.getName()).collect(Collectors.toList()));
354                xmppConnectionService.createBookmark(bookmark.getAccount(), bookmark);
355            }
356
357            SoftKeyboardUtils.hideSoftKeyboard(this);
358            hideEditor();
359            updateView();
360        }
361    }
362
363    private void hideEditor() {
364        this.binding.mucEditor.setVisibility(View.GONE);
365        this.binding.mucDisplay.setVisibility(View.VISIBLE);
366        this.binding.editMucNameButton.setImageResource(getThemeResource(R.attr.icon_edit_body, R.drawable.ic_edit_black_24dp));
367    }
368
369    private void onMucInfoUpdated(String subject, String name) {
370        final MucOptions mucOptions = mConversation.getMucOptions();
371        if (mucOptions.canChangeSubject() && changed(mucOptions.getSubject(), subject)) {
372            xmppConnectionService.pushSubjectToConference(mConversation, subject);
373        }
374        if (mucOptions.getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER) && changed(mucOptions.getName(), name)) {
375            Bundle options = new Bundle();
376            options.putString("muc#roomconfig_persistentroom", "1");
377            options.putString("muc#roomconfig_roomname", StringUtils.nullOnEmpty(name));
378            xmppConnectionService.pushConferenceConfiguration(mConversation, options, this);
379        }
380    }
381
382
383    @Override
384    protected String getShareableUri(boolean http) {
385        if (mConversation != null) {
386            if (http) {
387                return "https://conversations.im/j/" + XmppUri.lameUrlEncode(mConversation.getJid().asBareJid().toEscapedString());
388            } else {
389                return "xmpp:" + mConversation.getJid().asBareJid() + "?join";
390            }
391        } else {
392            return null;
393        }
394    }
395
396    @Override
397    public boolean onPrepareOptionsMenu(Menu menu) {
398        MenuItem menuItemSaveBookmark = menu.findItem(R.id.action_save_as_bookmark);
399        MenuItem menuItemDeleteBookmark = menu.findItem(R.id.action_delete_bookmark);
400        MenuItem menuItemAdvancedMode = menu.findItem(R.id.action_advanced_mode);
401        MenuItem menuItemDestroyRoom = menu.findItem(R.id.action_destroy_room);
402        menuItemAdvancedMode.setChecked(mAdvancedMode);
403        if (mConversation == null) {
404            return true;
405        }
406        if (mConversation.getBookmark() != null) {
407            menuItemSaveBookmark.setVisible(false);
408            menuItemDeleteBookmark.setVisible(true);
409        } else {
410            menuItemDeleteBookmark.setVisible(false);
411            menuItemSaveBookmark.setVisible(true);
412        }
413        menuItemDestroyRoom.setVisible(mConversation.getMucOptions().getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER));
414        return true;
415    }
416
417    @Override
418    public boolean onCreateOptionsMenu(Menu menu) {
419        final boolean groupChat = mConversation != null && mConversation.isPrivateAndNonAnonymous();
420        getMenuInflater().inflate(R.menu.muc_details, menu);
421        final MenuItem share = menu.findItem(R.id.action_share);
422        share.setVisible(!groupChat);
423        final MenuItem destroy = menu.findItem(R.id.action_destroy_room);
424        destroy.setTitle(groupChat ? R.string.destroy_room : R.string.destroy_channel);
425        AccountUtils.showHideMenuItems(menu);
426        return super.onCreateOptionsMenu(menu);
427    }
428
429    @Override
430    public void onMediaLoaded(List<Attachment> attachments) {
431        runOnUiThread(() -> {
432            int limit = GridManager.getCurrentColumnCount(binding.media);
433            mMediaAdapter.setAttachments(attachments.subList(0, Math.min(limit, attachments.size())));
434            binding.mediaWrapper.setVisibility(attachments.size() > 0 ? View.VISIBLE : View.GONE);
435        });
436
437    }
438
439
440    protected void saveAsBookmark() {
441        xmppConnectionService.saveConversationAsBookmark(mConversation, mConversation.getMucOptions().getName());
442    }
443
444    protected void deleteBookmark() {
445        final Account account = mConversation.getAccount();
446        final Bookmark bookmark = mConversation.getBookmark();
447        bookmark.setConversation(null);
448        xmppConnectionService.deleteBookmark(account, bookmark);
449        updateView();
450    }
451
452    protected void destroyRoom() {
453        final boolean groupChat = mConversation != null && mConversation.isPrivateAndNonAnonymous();
454        AlertDialog.Builder builder = new AlertDialog.Builder(this);
455        builder.setTitle(groupChat ? R.string.destroy_room : R.string.destroy_channel);
456        builder.setMessage(groupChat ? R.string.destroy_room_dialog : R.string.destroy_channel_dialog);
457        builder.setPositiveButton(R.string.ok, (dialog, which) -> {
458            xmppConnectionService.destroyRoom(mConversation, ConferenceDetailsActivity.this);
459        });
460        builder.setNegativeButton(R.string.cancel, null);
461        final AlertDialog dialog = builder.create();
462        dialog.setCanceledOnTouchOutside(false);
463        dialog.show();
464    }
465
466    @Override
467    void onBackendConnected() {
468        if (mPendingConferenceInvite != null) {
469            mPendingConferenceInvite.execute(this);
470            mPendingConferenceInvite = null;
471        }
472        if (getIntent().getAction().equals(ACTION_VIEW_MUC)) {
473            this.uuid = getIntent().getExtras().getString("uuid");
474        }
475        if (uuid != null) {
476            this.mConversation = xmppConnectionService.findConversationByUuid(uuid);
477            if (this.mConversation != null) {
478                if (Compatibility.hasStoragePermission(this)) {
479                    final int limit = GridManager.getCurrentColumnCount(this.binding.media);
480                    xmppConnectionService.getAttachments(this.mConversation, limit, this);
481                    this.binding.showMedia.setOnClickListener((v) -> MediaBrowserActivity.launch(this, mConversation));
482                }
483                updateView();
484            }
485        }
486    }
487
488    @Override
489    public void onBackPressed() {
490        if (this.binding.mucEditor.getVisibility() == View.VISIBLE) {
491            hideEditor();
492        } else {
493            super.onBackPressed();
494        }
495    }
496
497    private void updateView() {
498        invalidateOptionsMenu();
499        if (mConversation == null) {
500            return;
501        }
502        final MucOptions mucOptions = mConversation.getMucOptions();
503        final User self = mucOptions.getSelf();
504        String account;
505        if (Config.DOMAIN_LOCK != null) {
506            account = mConversation.getAccount().getJid().getEscapedLocal();
507        } else {
508            account = mConversation.getAccount().getJid().asBareJid().toEscapedString();
509        }
510        setTitle(mucOptions.isPrivateAndNonAnonymous() ? R.string.action_muc_details : R.string.channel_details);
511        final Bookmark bookmark = mConversation.getBookmark();
512        this.binding.editMucNameButton.setVisibility((self.getAffiliation().ranks(MucOptions.Affiliation.OWNER) || mucOptions.canChangeSubject() || (bookmark != null && mConversation.getAccount().getXmppConnection().getFeatures().bookmarks2())) ? View.VISIBLE : View.GONE);
513        this.binding.detailsAccount.setText(getString(R.string.using_account, account));
514        this.binding.truejid.setVisibility(View.GONE);
515        if (mConversation.isPrivateAndNonAnonymous()) {
516            this.binding.jid.setText(getString(R.string.hosted_on, mConversation.getJid().getDomain()));
517            this.binding.truejid.setText(mConversation.getJid().asBareJid().toEscapedString());
518            if (mAdvancedMode) this.binding.truejid.setVisibility(View.VISIBLE);
519        } else {
520            this.binding.jid.setText(mConversation.getJid().asBareJid().toEscapedString());
521        }
522        AvatarWorkerTask.loadAvatar(mConversation, binding.yourPhoto, R.dimen.avatar_on_details_screen_size);
523        String roomName = mucOptions.getName();
524        String subject = mucOptions.getSubject();
525        final boolean hasTitle;
526        if (printableValue(roomName)) {
527            this.binding.mucTitle.setText(roomName);
528            this.binding.mucTitle.setVisibility(View.VISIBLE);
529            hasTitle = true;
530        } else if (!printableValue(subject)) {
531            this.binding.mucTitle.setText(mConversation.getName());
532            hasTitle = true;
533            this.binding.mucTitle.setVisibility(View.VISIBLE);
534        } else {
535            hasTitle = false;
536            this.binding.mucTitle.setVisibility(View.GONE);
537        }
538        if (printableValue(subject)) {
539            SpannableStringBuilder spannable = new SpannableStringBuilder(subject);
540            StylingHelper.format(spannable, this.binding.mucSubject.getCurrentTextColor());
541            MyLinkify.addLinks(spannable, false);
542            this.binding.mucSubject.setText(spannable);
543            this.binding.mucSubject.setTextAppearance(this, subject.length() > (hasTitle ? 128 : 196) ? R.style.TextAppearance_Conversations_Body1_Linkified : R.style.TextAppearance_Conversations_Subhead);
544            this.binding.mucSubject.setAutoLinkMask(0);
545            this.binding.mucSubject.setVisibility(View.VISIBLE);
546            this.binding.mucSubject.setMovementMethod(LinkMovementMethod.getInstance());
547        } else {
548            this.binding.mucSubject.setVisibility(View.GONE);
549        }
550        this.binding.mucYourNick.setText(mucOptions.getActualNick());
551        if (mucOptions.online()) {
552            this.binding.usersWrapper.setVisibility(View.VISIBLE);
553            this.binding.mucInfoMore.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE);
554            this.binding.mucRole.setVisibility(View.VISIBLE);
555            this.binding.mucRole.setText(getStatus(self));
556            if (mucOptions.getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER)) {
557                this.binding.mucSettings.setVisibility(View.VISIBLE);
558                this.binding.mucConferenceType.setText(MucConfiguration.describe(this, mucOptions));
559            } else if (!mucOptions.isPrivateAndNonAnonymous() && mucOptions.nonanonymous()) {
560                this.binding.mucSettings.setVisibility(View.VISIBLE);
561                this.binding.mucConferenceType.setText(R.string.group_chat_will_make_your_jabber_id_public);
562            } else {
563                this.binding.mucSettings.setVisibility(View.GONE);
564            }
565            if (mucOptions.mamSupport()) {
566                this.binding.mucInfoMam.setText(R.string.server_info_available);
567            } else {
568                this.binding.mucInfoMam.setText(R.string.server_info_unavailable);
569            }
570            if (self.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) {
571                this.binding.changeConferenceButton.setVisibility(View.VISIBLE);
572            } else {
573                this.binding.changeConferenceButton.setVisibility(View.INVISIBLE);
574            }
575        } else {
576            this.binding.usersWrapper.setVisibility(View.GONE);
577            this.binding.mucInfoMore.setVisibility(View.GONE);
578            this.binding.mucSettings.setVisibility(View.GONE);
579        }
580
581        int ic_notifications = getThemeResource(R.attr.icon_notifications, R.drawable.ic_notifications_black_24dp);
582        int ic_notifications_off = getThemeResource(R.attr.icon_notifications_off, R.drawable.ic_notifications_off_black_24dp);
583        int ic_notifications_paused = getThemeResource(R.attr.icon_notifications_paused, R.drawable.ic_notifications_paused_black_24dp);
584        int ic_notifications_none = getThemeResource(R.attr.icon_notifications_none, R.drawable.ic_notifications_none_black_24dp);
585
586        long mutedTill = mConversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL, 0);
587        if (mutedTill == Long.MAX_VALUE) {
588            this.binding.notificationStatusText.setText(R.string.notify_never);
589            this.binding.notificationStatusButton.setImageResource(ic_notifications_off);
590        } else if (System.currentTimeMillis() < mutedTill) {
591            this.binding.notificationStatusText.setText(R.string.notify_paused);
592            this.binding.notificationStatusButton.setImageResource(ic_notifications_paused);
593        } else if (mConversation.alwaysNotify()) {
594            this.binding.notificationStatusText.setText(R.string.notify_on_all_messages);
595            this.binding.notificationStatusButton.setImageResource(ic_notifications);
596        } else {
597            this.binding.notificationStatusText.setText(R.string.notify_only_when_highlighted);
598            this.binding.notificationStatusButton.setImageResource(ic_notifications_none);
599        }
600        final List<User> users = mucOptions.getUsers();
601        Collections.sort(users, (a, b) -> {
602            if (b.getAffiliation().outranks(a.getAffiliation())) {
603                return 1;
604            } else if (a.getAffiliation().outranks(b.getAffiliation())) {
605                return -1;
606            } else {
607                if (a.getAvatar() != null && b.getAvatar() == null) {
608                    return -1;
609                } else if (a.getAvatar() == null && b.getAvatar() != null) {
610                    return 1;
611                } else {
612                    return a.getComparableName().compareToIgnoreCase(b.getComparableName());
613                }
614            }
615        });
616        this.mUserPreviewAdapter.submitList(MucOptions.sub(users, GridManager.getCurrentColumnCount(binding.users)));
617        this.binding.invite.setVisibility(mucOptions.canInvite() ? View.VISIBLE : View.GONE);
618        this.binding.showUsers.setVisibility(users.size() > 0 ? View.VISIBLE : View.GONE);
619        this.binding.showUsers.setText(getResources().getQuantityString(R.plurals.view_users, users.size(), users.size()));
620        this.binding.usersWrapper.setVisibility(users.size() > 0 || mucOptions.canInvite() ? View.VISIBLE : View.GONE);
621        if (users.size() == 0) {
622            this.binding.noUsersHints.setText(mucOptions.isPrivateAndNonAnonymous() ? R.string.no_users_hint_group_chat : R.string.no_users_hint_channel);
623            this.binding.noUsersHints.setVisibility(View.VISIBLE);
624        } else {
625            this.binding.noUsersHints.setVisibility(View.GONE);
626        }
627
628        if (bookmark == null) {
629            binding.tags.setVisibility(View.GONE);
630            return;
631        }
632
633        List<ListItem.Tag> tagList = bookmark.getTags(this);
634        if (tagList.size() == 0) {
635            binding.tags.setVisibility(View.GONE);
636        } else {
637            final LayoutInflater inflater = getLayoutInflater();
638            binding.tags.setVisibility(View.VISIBLE);
639            binding.tags.removeAllViewsInLayout();
640            for (final ListItem.Tag tag : tagList) {
641                final TextView tv = (TextView) inflater.inflate(R.layout.list_item_tag, binding.tags, false);
642                tv.setText(tag.getName());
643                tv.setBackgroundColor(tag.getColor());
644                binding.tags.addView(tv);
645            }
646        }
647    }
648
649    public static String getStatus(Context context, User user, final boolean advanced) {
650        if (advanced) {
651            return String.format("%s (%s)", context.getString(user.getAffiliation().getResId()), context.getString(user.getRole().getResId()));
652        } else {
653            return context.getString(user.getAffiliation().getResId());
654        }
655    }
656
657    private String getStatus(User user) {
658        return getStatus(this, user, mAdvancedMode);
659    }
660
661
662    @Override
663    public void onAffiliationChangedSuccessful(Jid jid) {
664        refreshUi();
665    }
666
667    @Override
668    public void onAffiliationChangeFailed(Jid jid, int resId) {
669        displayToast(getString(resId, jid.asBareJid().toEscapedString()));
670    }
671
672    @Override
673    public void onRoomDestroySucceeded() {
674        finish();
675    }
676
677    @Override
678    public void onRoomDestroyFailed() {
679        final boolean groupChat = mConversation != null && mConversation.isPrivateAndNonAnonymous();
680        displayToast(getString(groupChat ? R.string.could_not_destroy_room : R.string.could_not_destroy_channel));
681    }
682
683    @Override
684    public void onPushSucceeded() {
685        displayToast(getString(R.string.modified_conference_options));
686    }
687
688    @Override
689    public void onPushFailed() {
690        displayToast(getString(R.string.could_not_modify_conference_options));
691    }
692
693    private void displayToast(final String msg) {
694        runOnUiThread(() -> {
695            if (isFinishing()) {
696                return;
697            }
698            ToastCompat.makeText(this, msg, Toast.LENGTH_SHORT).show();
699        });
700    }
701
702    @Override
703    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
704
705    }
706
707    @Override
708    public void onTextChanged(CharSequence s, int start, int before, int count) {
709
710    }
711
712    @Override
713    public void afterTextChanged(Editable s) {
714        if (mConversation == null) {
715            return;
716        }
717        final MucOptions mucOptions = mConversation.getMucOptions();
718        if (this.binding.mucEditor.getVisibility() == View.VISIBLE) {
719            boolean subjectChanged = changed(binding.mucEditSubject.getEditableText().toString(), mucOptions.getSubject());
720            boolean nameChanged = changed(binding.mucEditTitle.getEditableText().toString(), mucOptions.getName());
721            final Bookmark bookmark = mConversation.getBookmark();
722            if (subjectChanged || nameChanged || (bookmark != null && mConversation.getAccount().getXmppConnection().getFeatures().bookmarks2())) {
723                this.binding.editMucNameButton.setImageResource(getThemeResource(R.attr.icon_save, R.drawable.ic_save_black_24dp));
724            } else {
725                this.binding.editMucNameButton.setImageResource(getThemeResource(R.attr.icon_cancel, R.drawable.ic_cancel_black_24dp));
726            }
727        }
728    }
729
730}