tags navigation and self contact

This commit is contained in:
kosyak 2023-08-15 01:21:39 +02:00
parent 18c41eb05e
commit a2a943c36a
6 changed files with 183 additions and 12 deletions

View file

@ -99,7 +99,7 @@ android {
compileSdkVersion 33 compileSdkVersion 33
defaultConfig { defaultConfig {
minSdkVersion 21 minSdkVersion 24
targetSdkVersion 33 targetSdkVersion 33
versionCode 42058 versionCode 42058
versionName "2.12.5" versionName "2.12.5"

View file

@ -109,6 +109,11 @@ public class Contact implements ListItem, Blockable {
this.keys = new JSONObject(); this.keys = new JSONObject();
} }
public Contact(Contact other) {
this(null, other.systemName, other.serverName, other.presenceName, other.jid, other.subscription, other.photoUri, other.systemAccount, other.keys == null ? null : other.keys.toString(), other.getAvatar() == null ? null : other.getAvatar().sha1sum, other.mLastseen, other.mLastPresence, other.groups == null ? null : other.groups.toString(), other.rtpCapability);
setAccount(other.getAccount());
}
public static Contact fromCursor(final Cursor cursor) { public static Contact fromCursor(final Cursor cursor) {
final Jid jid; final Jid jid;
try { try {

View file

@ -27,9 +27,10 @@ import eu.siacs.conversations.ui.adapter.ListItemAdapter;
public abstract class AbstractSearchableListItemActivity extends XmppActivity implements TextView.OnEditorActionListener { public abstract class AbstractSearchableListItemActivity extends XmppActivity implements TextView.OnEditorActionListener {
protected ActivityChooseContactBinding binding; protected ActivityChooseContactBinding binding;
private final List<ListItem> listItems = new ArrayList<>(); private final List<ListItem> listItems = new ArrayList<>();
private ArrayAdapter<ListItem> mListItemsAdapter; private ListItemAdapter mListItemsAdapter;
private EditText mSearchEditText; protected MenuItem mMenuSearchView;
protected EditText mSearchEditText;
private final MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() { private final MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() {
@ -84,7 +85,7 @@ public abstract class AbstractSearchableListItemActivity extends XmppActivity im
return mSearchEditText; return mSearchEditText;
} }
public ArrayAdapter<ListItem> getListItemAdapter() { public ListItemAdapter getListItemAdapter() {
return mListItemsAdapter; return mListItemsAdapter;
} }
@ -102,13 +103,13 @@ public abstract class AbstractSearchableListItemActivity extends XmppActivity im
@Override @Override
public boolean onCreateOptionsMenu(final Menu menu) { public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.choose_contact, menu); getMenuInflater().inflate(R.menu.choose_contact, menu);
final MenuItem menuSearchView = menu.findItem(R.id.action_search); mMenuSearchView = menu.findItem(R.id.action_search);
final View mSearchView = menuSearchView.getActionView(); final View mSearchView = mMenuSearchView.getActionView();
mSearchEditText = mSearchView.findViewById(R.id.search_field); mSearchEditText = mSearchView.findViewById(R.id.search_field);
mSearchEditText.addTextChangedListener(mSearchTextWatcher); mSearchEditText.addTextChangedListener(mSearchTextWatcher);
mSearchEditText.setHint(R.string.search_contacts); mSearchEditText.setHint(R.string.search_contacts);
mSearchEditText.setOnEditorActionListener(this); mSearchEditText.setOnEditorActionListener(this);
menuSearchView.setOnActionExpandListener(mOnActionExpandListener); mMenuSearchView.setOnActionExpandListener(mOnActionExpandListener);
return true; return true;
} }

View file

@ -120,7 +120,7 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
multiple = intent.getBooleanExtra(EXTRA_SELECT_MULTIPLE, false); multiple = intent.getBooleanExtra(EXTRA_SELECT_MULTIPLE, false);
if (multiple) { if (multiple) {
getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
getListView().setMultiChoiceModeListener(this); getListView().setMultiChoiceModeListener(this);
} }
@ -136,6 +136,15 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
final SharedPreferences preferences = getPreferences(); final SharedPreferences preferences = getPreferences();
this.startSearching = intent.getBooleanExtra("direct_search", false) && preferences.getBoolean("start_searching", getResources().getBoolean(R.bool.start_searching)); this.startSearching = intent.getBooleanExtra("direct_search", false) && preferences.getBoolean("start_searching", getResources().getBoolean(R.bool.start_searching));
getListItemAdapter().refreshSettings();
getListItemAdapter().setOnTagClickedListener((tag) -> {
if (mMenuSearchView != null) {
mMenuSearchView.expandActionView();
mSearchEditText.setText("");
mSearchEditText.append(tag);
filterContacts(tag);
}
});
} }
private void onFabClicked(View v) { private void onFabClicked(View v) {
@ -278,10 +287,19 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
getListItems().add(contact); getListItems().add(contact);
} }
} }
final Contact self = new Contact(account.getSelfContact());
self.setSystemName("Note to Self");
if (self.match(this, needle)) {
getListItems().add(self);
}
} }
} }
Collections.sort(getListItems()); Collections.sort(getListItems());
getListItemAdapter().notifyDataSetChanged(); getListItemAdapter().notifyDataSetChanged();
for (int i = 0; i < getListItemAdapter().getCount(); i++) {
getListView().setItemChecked(i, selected.contains(getListItemAdapter().getItem(i).getJid().toString()));
}
} }
private String[] getSelectedContactJids() { private String[] getSelectedContactJids() {
@ -390,8 +408,24 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
@Override @Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (multiple) { if (multiple) {
startActionMode(this); if (getListView().isItemChecked(position)) {
getListView().setItemChecked(position, true); selected.add(getListItemAdapter().getItem(position).getJid().toString());
} else {
selected.remove(getListItemAdapter().getItem(position).getJid().toString());
}
if (selected.isEmpty()) {
this.binding.fab.setImageResource(R.drawable.ic_person_add_white_24dp);
if (this.showEnterJid) {
this.binding.fab.show();
} else {
this.binding.fab.hide();
}
} else {
binding.fab.setImageResource(R.drawable.ic_forward_white_24dp);
binding.fab.show();
}
return; return;
} }
final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);

View file

@ -12,6 +12,7 @@ import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Editable; import android.text.Editable;
import android.text.Html; import android.text.Html;
import android.text.TextWatcher; import android.text.TextWatcher;
@ -21,6 +22,7 @@ import android.util.Pair;
import android.view.ContextMenu; import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextMenu.ContextMenuInfo;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@ -49,6 +51,8 @@ import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager; import androidx.viewpager.widget.ViewPager;
@ -58,9 +62,15 @@ import com.leinardi.android.speeddial.SpeedDialActionItem;
import com.leinardi.android.speeddial.SpeedDialView; import com.leinardi.android.speeddial.SpeedDialView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
@ -83,6 +93,7 @@ import eu.siacs.conversations.ui.util.PendingItem;
import eu.siacs.conversations.ui.util.SoftKeyboardUtils; import eu.siacs.conversations.ui.util.SoftKeyboardUtils;
import eu.siacs.conversations.ui.widget.SwipeRefreshListFragment; import eu.siacs.conversations.ui.widget.SwipeRefreshListFragment;
import eu.siacs.conversations.utils.AccountUtils; import eu.siacs.conversations.utils.AccountUtils;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.utils.XmppUri;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
@ -102,6 +113,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
private ListPagerAdapter mListPagerAdapter; private ListPagerAdapter mListPagerAdapter;
private final List<ListItem> contacts = new ArrayList<>(); private final List<ListItem> contacts = new ArrayList<>();
private ListItemAdapter mContactsAdapter; private ListItemAdapter mContactsAdapter;
private TagsAdapter mTagsAdapter = new TagsAdapter();
private final List<ListItem> conferences = new ArrayList<>(); private final List<ListItem> conferences = new ArrayList<>();
private ListItemAdapter mConferenceAdapter; private ListItemAdapter mConferenceAdapter;
private final List<String> mActivatedAccounts = new ArrayList<>(); private final List<String> mActivatedAccounts = new ArrayList<>();
@ -703,6 +715,15 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
mSearchEditText = mSearchView.findViewById(R.id.search_field); mSearchEditText = mSearchView.findViewById(R.id.search_field);
mSearchEditText.addTextChangedListener(mSearchTextWatcher); mSearchEditText.addTextChangedListener(mSearchTextWatcher);
mSearchEditText.setOnEditorActionListener(mSearchDone); mSearchEditText.setOnEditorActionListener(mSearchDone);
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
boolean showDynamicTags = preferences.getBoolean(SettingsActivity.SHOW_DYNAMIC_TAGS, getResources().getBoolean(R.bool.show_dynamic_tags));
if (showDynamicTags) {
RecyclerView tags = mSearchView.findViewById(R.id.tags);
tags.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
tags.setAdapter(mTagsAdapter);
}
String initialSearchValue = mInitialSearchValue.pop(); String initialSearchValue = mInitialSearchValue.pop();
if (initialSearchValue != null) { if (initialSearchValue != null) {
mMenuSearchView.expandActionView(); mMenuSearchView.expandActionView();
@ -996,7 +1017,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
protected void filterContacts(String needle) { protected void filterContacts(String needle) {
this.contacts.clear(); this.contacts.clear();
ArrayList<ListItem.Tag> tags = new ArrayList<>();
final List<Account> accounts = xmppConnectionService.getAccounts(); final List<Account> accounts = xmppConnectionService.getAccounts();
boolean foundSopranica = false;
for (Account account : accounts) { for (Account account : accounts) {
if (account.getStatus() != Account.State.DISABLED) { if (account.getStatus() != Account.State.DISABLED) {
for (Contact contact : account.getRoster().getContacts()) { for (Contact contact : account.getRoster().getContacts()) {
@ -1006,11 +1029,52 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|| (needle != null && !needle.trim().isEmpty()) || (needle != null && !needle.trim().isEmpty())
|| s.compareTo(Presence.Status.OFFLINE) < 0)) { || s.compareTo(Presence.Status.OFFLINE) < 0)) {
this.contacts.add(contact); this.contacts.add(contact);
tags.addAll(contact.getTags(this));
}
}
final Contact self = new Contact(account.getSelfContact());
self.setSystemName("Note to Self");
if (self.match(this, needle)) {
this.contacts.add(self);
}
for (Bookmark bookmark : account.getBookmarks()) {
if (bookmark.match(this, needle)) {
if (bookmark.getJid().toString().equals("discuss@conference.soprani.ca")) {
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); Collections.sort(this.contacts);
final boolean sopranicaDeleted = getPreferences().getBoolean("cheogram_sopranica_bookmark_deleted", false);
if (!sopranicaDeleted && !foundSopranica && (needle == null || needle.equals("")) && xmppConnectionService.getAccounts().size() > 0) {
Bookmark bookmark = new Bookmark(
xmppConnectionService.getAccounts().get(0),
Jid.of("discuss@conference.soprani.ca")
);
bookmark.setBookmarkName("Soprani.ca / Cheogram Discussion");
bookmark.addChild("group").setContent("support");
this.contacts.add(0, bookmark);
}
mContactsAdapter.notifyDataSetChanged(); mContactsAdapter.notifyDataSetChanged();
} }
@ -1393,4 +1457,65 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
return false; 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();
}
}
} }

View file

@ -15,4 +15,10 @@
android:imeOptions="flagNoExtractUi|actionSearch" android:imeOptions="flagNoExtractUi|actionSearch"
android:inputType="textEmailAddress|textNoSuggestions"/> android:inputType="textEmailAddress|textNoSuggestions"/>
<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> </RelativeLayout>