diff --git a/src/main/java/eu/siacs/conversations/entities/Bookmark.java b/src/main/java/eu/siacs/conversations/entities/Bookmark.java index 6ab62f6ea38da3345cd5888d0cd265e79aa02d5d..c9e39bafb4ac9ae134bb605bf0b23e72e812db77 100644 --- a/src/main/java/eu/siacs/conversations/entities/Bookmark.java +++ b/src/main/java/eu/siacs/conversations/entities/Bookmark.java @@ -196,10 +196,20 @@ public class Bookmark extends Element implements ListItem { return true; } needle = needle.toLowerCase(Locale.US); - final Jid jid = getJid(); - return (jid != null && jid.toString().contains(needle)) || - getDisplayName().toLowerCase(Locale.US).contains(needle) || - matchInTag(context, needle); + String[] parts = needle.split("[,\\s]+"); + if (parts.length > 1) { + for (String part : parts) { + if (!match(context, part)) { + return false; + } + } + return true; + } else { + final Jid jid = getJid(); + return (jid != null && jid.toString().contains(parts[0])) || + getDisplayName().toLowerCase(Locale.US).contains(parts[0]) || + matchInTag(context, parts[0]); + } } private boolean matchInTag(Context context, String needle) { diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index cb66538c55ea8ffb13968334997d26cfe23cc8f7..bcac60f933f9545b53d7e5646bfad0d8a214c862 100644 --- a/src/main/java/eu/siacs/conversations/entities/Contact.java +++ b/src/main/java/eu/siacs/conversations/entities/Contact.java @@ -216,7 +216,7 @@ public class Contact implements ListItem, Blockable { return true; } needle = needle.toLowerCase(Locale.US).trim(); - String[] parts = needle.split("\\s+"); + String[] parts = needle.split("[,\\s]+"); if (parts.length > 1) { for (String part : parts) { if (!match(context, part)) { @@ -225,9 +225,9 @@ public class Contact implements ListItem, Blockable { } return true; } else { - return jid.toString().contains(needle) || - getDisplayName().toLowerCase(Locale.US).contains(needle) || - matchInTag(context, needle); + return jid.toString().contains(parts[0]) || + getDisplayName().toLowerCase(Locale.US).contains(parts[0]) || + matchInTag(context, parts[0]); } } diff --git a/src/main/java/eu/siacs/conversations/entities/ListItem.java b/src/main/java/eu/siacs/conversations/entities/ListItem.java index adc7666ce0be2fa545b5438afc2fb28bd8fcc445..ee27dff5da4005b903951af7832a88bfc6361a80 100644 --- a/src/main/java/eu/siacs/conversations/entities/ListItem.java +++ b/src/main/java/eu/siacs/conversations/entities/ListItem.java @@ -3,6 +3,7 @@ package eu.siacs.conversations.entities; import android.content.Context; import java.util.List; +import java.util.Locale; import eu.siacs.conversations.services.AvatarService; import eu.siacs.conversations.xmpp.Jid; @@ -31,6 +32,20 @@ public interface ListItem extends Comparable, AvatarService.Avatarable public String getName() { return this.name; } + + public String toString() { + return getName(); + } + + public boolean equals(Object o) { + if (!(o instanceof Tag)) return false; + Tag ot = (Tag) o; + return name.toLowerCase(Locale.US).equals(ot.getName().toLowerCase(Locale.US)) && color == ot.getColor(); + } + + public int hashCode() { + return name.toLowerCase(Locale.US).hashCode(); + } } boolean match(Context context, final String needle); diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index 830ff888460072126b9934c65f5cbb6b2daeb3ac..75381aadac18b3c3f4cfb9012b7c4607651f2be3 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -21,6 +21,7 @@ import android.util.Pair; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.KeyEvent; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -49,6 +50,8 @@ import androidx.databinding.DataBindingUtil; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.LinearLayoutManager; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.ViewPager; @@ -57,10 +60,16 @@ import com.google.android.material.textfield.TextInputLayout; import com.leinardi.android.speeddial.SpeedDialActionItem; import com.leinardi.android.speeddial.SpeedDialView; +import java.util.Arrays; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; @@ -83,6 +92,7 @@ import eu.siacs.conversations.ui.util.PendingItem; import eu.siacs.conversations.ui.util.SoftKeyboardUtils; import eu.siacs.conversations.ui.widget.SwipeRefreshListFragment; import eu.siacs.conversations.utils.AccountUtils; +import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; @@ -101,6 +111,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne private ListPagerAdapter mListPagerAdapter; private final List contacts = new ArrayList<>(); private ListItemAdapter mContactsAdapter; + private TagsAdapter mTagsAdapter = new TagsAdapter(); private final List conferences = new ArrayList<>(); private ListItemAdapter mConferenceAdapter; private final List mActivatedAccounts = new ArrayList<>(); @@ -668,6 +679,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne mSearchEditText = mSearchView.findViewById(R.id.search_field); mSearchEditText.addTextChangedListener(mSearchTextWatcher); mSearchEditText.setOnEditorActionListener(mSearchDone); + RecyclerView tags = mSearchView.findViewById(R.id.tags); + tags.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)); + tags.setAdapter(mTagsAdapter); String initialSearchValue = mInitialSearchValue.pop(); if (initialSearchValue != null) { mMenuSearchView.expandActionView(); @@ -959,6 +973,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne protected void filterContacts(String needle) { this.contacts.clear(); + ArrayList tags = new ArrayList<>(); final List accounts = xmppConnectionService.getAccounts(); boolean foundSopranica = false; for (Account account : accounts) { @@ -970,6 +985,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne || (needle != null && !needle.trim().isEmpty()) || s.compareTo(Presence.Status.OFFLINE) < 0)) { this.contacts.add(contact); + tags.addAll(contact.getTags(this)); } } @@ -979,11 +995,22 @@ public class StartConversationActivity extends XmppActivity implements XmppConne foundSopranica = true; } this.contacts.add(bookmark); + tags.addAll(bookmark.getTags(this)); } } } } + Comparator> sortTagsBy = Map.Entry.comparingByValue(Comparator.reverseOrder()); + sortTagsBy = sortTagsBy.thenComparing(entry -> entry.getKey().getName()); + + mTagsAdapter.setTags( + tags.stream() + .collect(Collectors.toMap((x) -> x, (t) -> 1, (c1, c2) -> c1 + c2)) + .entrySet().stream() + .sorted(sortTagsBy) + .map(e -> e.getKey()).collect(Collectors.toList()) + ); Collections.sort(this.contacts); final boolean sopranicaDeleted = getPreferences().getBoolean("cheogram_sopranica_bookmark_deleted", false); @@ -1385,4 +1412,64 @@ public class StartConversationActivity extends XmppActivity implements XmppConne return false; } } + + class TagsAdapter extends RecyclerView.Adapter { + class ViewHolder extends RecyclerView.ViewHolder { + protected TextView tv; + + public ViewHolder(View v) { + super(v); + tv = (TextView) v; + tv.setOnClickListener(view -> { + String needle = mSearchEditText.getText().toString(); + String tag = tv.getText().toString(); + String[] parts = needle.split("[,\\s]+"); + if(needle.isEmpty()) { + needle = tag; + } else if (tag.toLowerCase(Locale.US).contains(parts[parts.length-1])) { + needle = needle.replace(parts[parts.length-1], tag); + } else { + needle += ", " + tag; + } + mSearchEditText.setText(""); + mSearchEditText.append(needle); + filter(needle); + }); + } + + public void setTag(ListItem.Tag tag) { + tv.setText(tag.getName()); + tv.setBackgroundColor(tag.getColor()); + } + } + + protected List tags = new ArrayList<>(); + + @Override + public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { + View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.list_item_tag, null); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(ViewHolder viewHolder, int i) { + viewHolder.setTag(tags.get(i)); + } + + @Override + public int getItemCount() { + return tags.size(); + } + + public void setTags(final List tags) { + ListItem.Tag channelTag = new ListItem.Tag("Channel", UIHelper.getColorForName("Channel", true)); + String needle = mSearchEditText == null ? "" : mSearchEditText.getText().toString().toLowerCase(Locale.US).trim(); + HashSet parts = new HashSet<>(Arrays.asList(needle.split("[,\\s]+"))); + this.tags = tags.stream().filter( + tag -> !tag.equals(channelTag) && !parts.contains(tag.getName().toLowerCase(Locale.US)) + ).collect(Collectors.toList()); + if (!parts.contains("channel") && tags.contains(channelTag)) this.tags.add(0, channelTag); + notifyDataSetChanged(); + } + } } diff --git a/src/main/res/layout/actionview_search.xml b/src/main/res/layout/actionview_search.xml index 90783b776285ad1cafc52e1990fb41ad5294c67d..5ef204372af8d1f9704fdb1a147094a1760d5a23 100644 --- a/src/main/res/layout/actionview_search.xml +++ b/src/main/res/layout/actionview_search.xml @@ -15,4 +15,10 @@ android:imeOptions="flagNoExtractUi|actionSearch" android:inputType="textEmailAddress|textNoSuggestions"/> - \ No newline at end of file + + +