diff --git a/src/main/java/eu/siacs/conversations/entities/Bookmark.java b/src/main/java/eu/siacs/conversations/entities/Bookmark.java index dca510750..c148223cf 100644 --- a/src/main/java/eu/siacs/conversations/entities/Bookmark.java +++ b/src/main/java/eu/siacs/conversations/entities/Bookmark.java @@ -25,6 +25,7 @@ public class Bookmark extends Element implements ListItem { private final Account account; private WeakReference conversation; private Jid jid; + protected Element extensions = new Element("extensions", Namespace.BOOKMARKS2); public Bookmark(final Account account, final Jid jid) { super("conference"); @@ -101,9 +102,48 @@ public class Bookmark extends Element implements ListItem { bookmark.setBookmarkName(conference.getAttribute("name")); bookmark.setAutojoin(conference.getAttributeAsBoolean("autojoin")); bookmark.setNick(conference.findChildContent("nick")); + bookmark.setPassword(conference.findChildContent("password")); + final Element extensions = conference.findChild("extensions", Namespace.BOOKMARKS2); + if (extensions != null) { + for (final Element ext : extensions.getChildren()) { + if (ext.getName().equals("group") && ext.getNamespace().equals("jabber:iq:roster")) { + bookmark.addGroup(ext.getContent()); + } + } + bookmark.extensions = extensions; + } return bookmark; } + public Element getExtensions() { + return extensions; + } + + public void addGroup(final String group) { + addChild("group", "jabber:iq:roster").setContent(group); + extensions.addChild("group", "jabber:iq:roster").setContent(group); + } + + public void setGroups(List groups) { + final List children = new ArrayList<>(getChildren()); + for (final Element el : children) { + if (el.getName().equals("group")) { + removeChild(el); + } + } + + final List extChildren = new ArrayList<>(extensions.getChildren()); + for (final Element el : extChildren) { + if (el.getName().equals("group")) { + extensions.removeChild(el); + } + } + + for (final String group : groups) { + addGroup(group); + } + } + public void setAutojoin(boolean autojoin) { if (autojoin) { this.setAttribute("autojoin", "true"); @@ -150,15 +190,24 @@ public class Bookmark extends Element implements ListItem { return jid == null || nick == null || nick.trim().isEmpty() ? jid : jid.withResource(nick); } - @Override - public List getTags(Context context) { + public List getGroupTags() { ArrayList tags = new ArrayList<>(); + for (Element element : getChildren()) { if (element.getName().equals("group") && element.getContent() != null) { String group = element.getContent(); - tags.add(new Tag(group, UIHelper.getColorForName(group,true))); + tags.add(new Tag(group, UIHelper.getColorForName(group, true))); } } + + return tags; + } + + @Override + public List getTags(Context context) { + ArrayList tags = new ArrayList<>(); + tags.add(new Tag("Channel", UIHelper.getColorForName("Channel",true))); + tags.addAll(getGroupTags()); return tags; } @@ -203,15 +252,11 @@ public class Bookmark extends Element implements ListItem { } } return true; - } else if (parts.length > 0) { + } else { final Jid jid = getJid(); return (jid != null && jid.toString().contains(parts[0])) || getDisplayName().toLowerCase(Locale.US).contains(parts[0]) || matchInTag(context, parts[0]); - } else { - final Jid jid = getJid(); - return (jid != null && jid.toString().contains(needle)) || - getDisplayName().toLowerCase(Locale.US).contains(needle); } } diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 52a19eaa4..501c6d0c7 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -247,6 +247,7 @@ public class IqGenerator extends AbstractGenerator { public Element publishBookmarkItem(final Bookmark bookmark) { final String name = bookmark.getBookmarkName(); final String nick = bookmark.getNick(); + final String password = bookmark.getPassword(); final boolean autojoin = bookmark.autojoin(); final Element conference = new Element("conference", Namespace.BOOKMARKS2); if (name != null) { @@ -255,7 +256,11 @@ public class IqGenerator extends AbstractGenerator { if (nick != null) { conference.addChild("nick").setContent(nick); } + if (password != null) { + conference.addChild("password").setContent(password); + } conference.setAttribute("autojoin",String.valueOf(autojoin)); + conference.addChild(bookmark.getExtensions()); return conference; } diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index fb716044c..ec4206ac7 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -9,25 +9,34 @@ import android.text.Editable; import android.text.SpannableStringBuilder; import android.text.TextWatcher; import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; +import android.widget.ArrayAdapter; +import android.widget.TextView; import android.widget.Toast; import androidx.appcompat.app.AlertDialog; import androidx.databinding.DataBindingUtil; +import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityMucDetailsBinding; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Bookmark; +import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.ListItem; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.MucOptions.User; import eu.siacs.conversations.services.XmppConnectionService; @@ -200,6 +209,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers this.binding.mucEditTitle.addTextChangedListener(this); this.binding.mucEditSubject.addTextChangedListener(this); this.binding.mucEditSubject.addTextChangedListener(new StylingHelper.MessageEditorStyler(this.binding.mucEditSubject)); + this.binding.editTags.addTextChangedListener(this); this.mMediaAdapter = new MediaAdapter(this, R.dimen.media_size); this.mUserPreviewAdapter = new UserPreviewAdapter(); this.binding.media.setAdapter(mMediaAdapter); @@ -301,12 +311,52 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers if (!owner) { this.binding.mucEditSubject.requestFocus(); } + + final Bookmark bookmark = mConversation.getBookmark(); + if (bookmark != null && mConversation.getAccount().getXmppConnection().getFeatures().bookmarks2()) { + for (final ListItem.Tag group : bookmark.getGroupTags()) { + binding.editTags.addObjectSync(group); + } + ArrayList tags = new ArrayList<>(); + for (final Account account : xmppConnectionService.getAccounts()) { + for (Contact contact : account.getRoster().getContacts()) { + tags.addAll(contact.getTags(this)); + } + for (Bookmark bmark : account.getBookmarks()) { + tags.addAll(bmark.getTags(this)); + } + } + Comparator> sortTagsBy = Map.Entry.comparingByValue(Comparator.reverseOrder()); + sortTagsBy = sortTagsBy.thenComparing(entry -> entry.getKey().getName()); + + ArrayAdapter adapter = new ArrayAdapter<>( + this, + android.R.layout.simple_list_item_1, + tags.stream() + .collect(Collectors.toMap((x) -> x, (t) -> 1, (c1, c2) -> c1 + c2)) + .entrySet().stream() + .sorted(sortTagsBy) + .map(e -> e.getKey()).collect(Collectors.toList()) + ); + binding.editTags.setAdapter(adapter); + this.binding.editTags.setVisibility(View.VISIBLE); + } else { + this.binding.editTags.setVisibility(View.GONE); + } + } else { String subject = this.binding.mucEditSubject.isEnabled() ? this.binding.mucEditSubject.getEditableText().toString().trim() : null; String name = this.binding.mucEditTitle.isEnabled() ? this.binding.mucEditTitle.getEditableText().toString().trim() : null; onMucInfoUpdated(subject, name); + final Bookmark bookmark = mConversation.getBookmark(); + if (bookmark != null && mConversation.getAccount().getXmppConnection().getFeatures().bookmarks2()) { + bookmark.setGroups(binding.editTags.getObjects().stream().map(tag -> tag.getName()).collect(Collectors.toList())); + xmppConnectionService.createBookmark(bookmark.getAccount(), bookmark); + } + SoftKeyboardUtils.hideSoftKeyboard(this); hideEditor(); + updateView(); } } @@ -458,7 +508,8 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers account = mConversation.getAccount().getJid().asBareJid().toEscapedString(); } setTitle(mucOptions.isPrivateAndNonAnonymous() ? R.string.action_muc_details : R.string.channel_details); - this.binding.editMucNameButton.setVisibility((self.getAffiliation().ranks(MucOptions.Affiliation.OWNER) || mucOptions.canChangeSubject()) ? View.VISIBLE : View.GONE); + final Bookmark bookmark = mConversation.getBookmark(); + this.binding.editMucNameButton.setVisibility((self.getAffiliation().ranks(MucOptions.Affiliation.OWNER) || mucOptions.canChangeSubject() || (bookmark != null && mConversation.getAccount().getXmppConnection().getFeatures().bookmarks2())) ? View.VISIBLE : View.GONE); this.binding.detailsAccount.setText(getString(R.string.using_account, account)); if (mConversation.isPrivateAndNonAnonymous()) { this.binding.jid.setText(getString(R.string.hosted_on, mConversation.getJid().getDomain())); @@ -571,6 +622,25 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers this.binding.noUsersHints.setVisibility(View.GONE); } + if (bookmark == null) { + binding.tags.setVisibility(View.GONE); + return; + } + + List tagList = bookmark.getTags(this); + if (tagList.size() == 0) { + binding.tags.setVisibility(View.GONE); + } else { + final LayoutInflater inflater = getLayoutInflater(); + binding.tags.setVisibility(View.VISIBLE); + binding.tags.removeAllViewsInLayout(); + for (final ListItem.Tag tag : tagList) { + final TextView tv = (TextView) inflater.inflate(R.layout.list_item_tag, binding.tags, false); + tv.setText(tag.getName()); + tv.setBackgroundColor(tag.getColor()); + binding.tags.addView(tv); + } + } } public static String getStatus(Context context, User user, final boolean advanced) { @@ -645,7 +715,8 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers if (this.binding.mucEditor.getVisibility() == View.VISIBLE) { boolean subjectChanged = changed(binding.mucEditSubject.getEditableText().toString(), mucOptions.getSubject()); boolean nameChanged = changed(binding.mucEditTitle.getEditableText().toString(), mucOptions.getName()); - if (subjectChanged || nameChanged) { + final Bookmark bookmark = mConversation.getBookmark(); + if (subjectChanged || nameChanged || (bookmark != null && mConversation.getAccount().getXmppConnection().getFeatures().bookmarks2())) { this.binding.editMucNameButton.setImageResource(getThemeResource(R.attr.icon_save, R.drawable.ic_save_black_24dp)); } else { this.binding.editMucNameButton.setImageResource(getThemeResource(R.attr.icon_cancel, R.drawable.ic_cancel_black_24dp)); diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index c0a0f5dbc..24326f048 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -206,7 +206,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne int pos = binding.startConversationViewPager.getCurrentItem(); if (pos == 0) { if (contacts.size() == 1) { - openConversationForContact((Contact) contacts.get(0)); + openConversation((Contact) contacts.get(0)); return true; } else if (contacts.size() == 0 && conferences.size() == 1) { openConversationsForBookmark((Bookmark) conferences.get(0)); @@ -217,7 +217,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne openConversationsForBookmark((Bookmark) conferences.get(0)); return true; } else if (conferences.size() == 0 && contacts.size() == 1) { - openConversationForContact((Contact) contacts.get(0)); + openConversation((Contact) contacts.get(0)); return true; } } @@ -418,8 +418,15 @@ public class StartConversationActivity extends XmppActivity implements XmppConne } protected void openConversationForContact(int position) { - Contact contact = (Contact) contacts.get(position); - openConversationForContact(contact); + openConversation(contacts.get(position)); + } + + protected void openConversation(ListItem item) { + if (item instanceof Contact) { + openConversationForContact((Contact) item); + } else { + openConversationsForBookmark((Bookmark) item); + } } protected void openConversationForContact(Contact contact) { diff --git a/src/main/java/eu/siacs/conversations/xml/Element.java b/src/main/java/eu/siacs/conversations/xml/Element.java index fe5ad57b5..062d24a9b 100644 --- a/src/main/java/eu/siacs/conversations/xml/Element.java +++ b/src/main/java/eu/siacs/conversations/xml/Element.java @@ -50,6 +50,12 @@ public class Element { return child; } + public void removeChild(Element element) { + if (name == null) return; + this.children.remove(element); + } + + public Element setContent(String content) { this.content = content; this.children.clear(); diff --git a/src/main/java/eu/siacs/conversations/xml/Namespace.java b/src/main/java/eu/siacs/conversations/xml/Namespace.java index b614251bd..4570033e4 100644 --- a/src/main/java/eu/siacs/conversations/xml/Namespace.java +++ b/src/main/java/eu/siacs/conversations/xml/Namespace.java @@ -59,7 +59,7 @@ public final class Namespace { public static final String PUSH = "urn:xmpp:push:0"; public static final String COMMANDS = "http://jabber.org/protocol/commands"; public static final String MUC_USER = "http://jabber.org/protocol/muc#user"; - public static final String BOOKMARKS2 = "urn:xmpp:bookmarks:0"; + public static final String BOOKMARKS2 = "urn:xmpp:bookmarks:1"; public static final String BOOKMARKS2_COMPAT = BOOKMARKS2 + "#compat"; public static final String INVITE = "urn:xmpp:invite"; public static final String PARS = "urn:xmpp:pars:0"; diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 40828ef79..4c52ddb9f 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -2795,8 +2795,7 @@ public class XmppConnection implements Runnable { } public boolean bookmarks2() { - return Config - .USE_BOOKMARKS2 /* || hasDiscoFeature(account.getJid().asBareJid(), Namespace.BOOKMARKS2_COMPAT)*/; + return Config.USE_BOOKMARKS2 || hasDiscoFeature(account.getJid().asBareJid(), Namespace.BOOKMARKS2_COMPAT); } public boolean externalServiceDiscovery() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java b/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java index 62f65ee1f..71ba3d86a 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java +++ b/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java @@ -30,7 +30,7 @@ public class PublishOptions { options.putString("pubsub#persist_items", "true"); options.putString("pubsub#access_model", "whitelist"); options.putString("pubsub#send_last_published_item", "never"); - options.putString("pubsub#max_items", "128"); //YOLO! + options.putString("pubsub#max_items", "max"); //YOLO! options.putString("pubsub#notify_delete", "true"); options.putString("pubsub#notify_retract", "true"); //one could also set notify=true on the retract diff --git a/src/main/res/layout/activity_muc_details.xml b/src/main/res/layout/activity_muc_details.xml index eb3898319..774df9b93 100644 --- a/src/main/res/layout/activity_muc_details.xml +++ b/src/main/res/layout/activity_muc_details.xml @@ -86,6 +86,16 @@ android:layout_height="wrap_content" android:autoLink="web" android:textAppearance="@style/TextAppearance.Conversations.Subhead"/> + + + @@ -128,6 +138,13 @@ android:textAppearance="@style/Widget.Conversations.EditText"/> + +