@@ -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) {
@@ -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]);
}
}
@@ -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<ListItem>, 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);
@@ -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<ListItem> contacts = new ArrayList<>();
private ListItemAdapter mContactsAdapter;
+ private TagsAdapter mTagsAdapter = new TagsAdapter();
private final List<ListItem> conferences = new ArrayList<>();
private ListItemAdapter mConferenceAdapter;
private final List<String> 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<ListItem.Tag> tags = new ArrayList<>();
final List<Account> 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<Map.Entry<ListItem.Tag,Integer>> 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<TagsAdapter.ViewHolder> {
+ 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<ListItem.Tag> 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<ListItem.Tag> tags) {
+ ListItem.Tag channelTag = new ListItem.Tag("Channel", UIHelper.getColorForName("Channel", true));
+ String needle = mSearchEditText == null ? "" : mSearchEditText.getText().toString().toLowerCase(Locale.US).trim();
+ HashSet<String> 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();
+ }
+ }
}
@@ -15,4 +15,10 @@
android:imeOptions="flagNoExtractUi|actionSearch"
android:inputType="textEmailAddress|textNoSuggestions"/>
-</RelativeLayout>
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/tags"
+ android:layout_below="@+id/search_field"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+</RelativeLayout>