From b54c36627cf2792b839b2971eac1f6d83978cd6d Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 10 Jan 2023 12:12:43 +0100 Subject: [PATCH] Search bar: Display suggestion when starting by @ or # --- .../app/fedilab/android/BaseMainActivity.java | 80 +++++++++++++++++++ .../drawer/AccountsSearchTopBarAdapter.java | 79 ++++++++++++++++++ .../ui/drawer/TagSearchTopBarAdapter.java | 72 +++++++++++++++++ app/src/main/res/layout/drawer_tag_search.xml | 1 + 4 files changed, 232 insertions(+) create mode 100644 app/src/main/java/app/fedilab/android/ui/drawer/AccountsSearchTopBarAdapter.java create mode 100644 app/src/main/java/app/fedilab/android/ui/drawer/TagSearchTopBarAdapter.java diff --git a/app/src/main/java/app/fedilab/android/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/BaseMainActivity.java index 43b4ddb3..ff7cffd9 100644 --- a/app/src/main/java/app/fedilab/android/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/BaseMainActivity.java @@ -23,11 +23,13 @@ import static app.fedilab.android.ui.drawer.StatusAdapter.sendAction; import android.Manifest; import android.annotation.SuppressLint; +import android.app.SearchManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.database.MatrixCursor; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -35,6 +37,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; +import android.provider.BaseColumns; import android.text.Editable; import android.text.Html; import android.text.TextWatcher; @@ -63,6 +66,7 @@ import androidx.appcompat.widget.SearchView; import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityOptionsCompat; import androidx.core.view.GravityCompat; +import androidx.cursoradapter.widget.CursorAdapter; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; import androidx.lifecycle.ViewModelProvider; @@ -75,7 +79,9 @@ import androidx.preference.PreferenceManager; import com.bumptech.glide.Glide; import com.bumptech.glide.load.resource.gif.GifDrawable; +import com.bumptech.glide.request.FutureTarget; import com.bumptech.glide.request.target.CustomTarget; +import com.bumptech.glide.request.target.Target; import com.bumptech.glide.request.transition.Transition; import com.google.android.material.snackbar.Snackbar; import com.google.android.material.tabs.TabLayout; @@ -91,6 +97,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Objects; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -129,6 +136,7 @@ import app.fedilab.android.client.entities.api.Filter; import app.fedilab.android.client.entities.api.Instance; import app.fedilab.android.client.entities.api.MastodonList; import app.fedilab.android.client.entities.api.Status; +import app.fedilab.android.client.entities.api.Tag; import app.fedilab.android.client.entities.app.Account; import app.fedilab.android.client.entities.app.BaseAccount; import app.fedilab.android.client.entities.app.BottomMenu; @@ -146,12 +154,15 @@ import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MastodonHelper; import app.fedilab.android.helper.PinnedTimelineHelper; import app.fedilab.android.helper.PushHelper; +import app.fedilab.android.ui.drawer.AccountsSearchTopBarAdapter; +import app.fedilab.android.ui.drawer.TagSearchTopBarAdapter; import app.fedilab.android.ui.fragment.timeline.FragmentMastodonConversation; import app.fedilab.android.ui.fragment.timeline.FragmentMastodonTimeline; import app.fedilab.android.ui.fragment.timeline.FragmentNotificationContainer; import app.fedilab.android.viewmodel.mastodon.AccountsVM; import app.fedilab.android.viewmodel.mastodon.FiltersVM; import app.fedilab.android.viewmodel.mastodon.InstancesVM; +import app.fedilab.android.viewmodel.mastodon.SearchVM; import app.fedilab.android.viewmodel.mastodon.TimelinesVM; import app.fedilab.android.viewmodel.mastodon.TopBarVM; import es.dmoral.toasty.Toasty; @@ -777,6 +788,75 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt @Override public boolean onQueryTextChange(String newText) { + String pattern = "^(@[\\w_-]+@[a-z0-9.\\-]+|@[\\w_-]+)"; + final Pattern mentionPattern = Pattern.compile(pattern); + String patternTag = "^#([\\w-]{2,})$"; + final Pattern tagPattern = Pattern.compile(patternTag); + Matcher matcherMention, matcherTag; + matcherMention = mentionPattern.matcher(newText); + matcherTag = tagPattern.matcher(newText); + if (newText.trim().isEmpty()) { + binding.toolbarSearch.setSuggestionsAdapter(null); + } + if (matcherMention.matches()) { + String[] from = new String[]{SearchManager.SUGGEST_COLUMN_ICON_1, SearchManager.SUGGEST_COLUMN_TEXT_1}; + int[] to = new int[]{R.id.account_pp, R.id.account_un}; + String searchGroup = matcherMention.group(); + AccountsVM accountsVM = new ViewModelProvider(BaseMainActivity.this).get(AccountsVM.class); + MatrixCursor cursor = new MatrixCursor(new String[]{BaseColumns._ID, + SearchManager.SUGGEST_COLUMN_ICON_1, + SearchManager.SUGGEST_COLUMN_TEXT_1}); + accountsVM.searchAccounts(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, searchGroup, 5, false, false) + .observe(BaseMainActivity.this, accounts -> { + if (accounts == null) { + return; + } + AccountsSearchTopBarAdapter cursorAdapter = new AccountsSearchTopBarAdapter(BaseMainActivity.this, accounts, R.layout.drawer_account_search, null, from, to, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER); + binding.toolbarSearch.setSuggestionsAdapter(cursorAdapter); + new Thread(() -> { + int i = 0; + for (app.fedilab.android.client.entities.api.Account account : accounts) { + FutureTarget futureTarget = Glide + .with(BaseMainActivity.this.getApplicationContext()) + .load(account.avatar_static) + .downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL); + File cacheFile; + try { + cacheFile = futureTarget.get(); + cursor.addRow(new String[]{String.valueOf(i), cacheFile.getAbsolutePath(), "@" + account.acct}); + i++; + } catch (ExecutionException | InterruptedException e) { + e.printStackTrace(); + } + } + runOnUiThread(() -> cursorAdapter.changeCursor(cursor)); + }).start(); + + }); + } else if (matcherTag.matches()) { + SearchVM searchVM = new ViewModelProvider(BaseMainActivity.this).get(SearchVM.class); + String[] from = new String[]{SearchManager.SUGGEST_COLUMN_TEXT_1}; + int[] to = new int[]{R.id.tag_name}; + String searchGroup = matcherTag.group(); + MatrixCursor cursor = new MatrixCursor(new String[]{BaseColumns._ID, + SearchManager.SUGGEST_COLUMN_TEXT_1}); + searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, searchGroup, null, + "hashtags", false, true, false, 0, + null, null, 10).observe(BaseMainActivity.this, + results -> { + if (results == null || results.hashtags == null) { + return; + } + TagSearchTopBarAdapter cursorAdapter = new TagSearchTopBarAdapter(BaseMainActivity.this, results.hashtags, R.layout.drawer_tag_search, null, from, to, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER); + binding.toolbarSearch.setSuggestionsAdapter(cursorAdapter); + int i = 0; + for (Tag tag : results.hashtags) { + cursor.addRow(new String[]{String.valueOf(i), "#" + tag.name}); + i++; + } + runOnUiThread(() -> cursorAdapter.changeCursor(cursor)); + }); + } return false; } }); diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/AccountsSearchTopBarAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/AccountsSearchTopBarAdapter.java new file mode 100644 index 00000000..6de07a1b --- /dev/null +++ b/app/src/main/java/app/fedilab/android/ui/drawer/AccountsSearchTopBarAdapter.java @@ -0,0 +1,79 @@ +package app.fedilab.android.ui.drawer; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import androidx.appcompat.widget.LinearLayoutCompat; +import androidx.core.app.ActivityOptionsCompat; +import androidx.cursoradapter.widget.SimpleCursorAdapter; + +import java.util.List; + +import app.fedilab.android.R; +import app.fedilab.android.activities.ProfileActivity; +import app.fedilab.android.client.entities.api.Account; +import app.fedilab.android.helper.Helper; + + +public class AccountsSearchTopBarAdapter extends SimpleCursorAdapter { + + private final int layout; + private final LayoutInflater inflater; + private final List accountList; + + public AccountsSearchTopBarAdapter(Context context, List accounts, int layout, Cursor c, String[] from, int[] to, int flags) { + super(context, layout, c, from, to, flags); + this.layout = layout; + this.inflater = LayoutInflater.from(context); + this.accountList = accounts; + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return inflater.inflate(layout, null); + } + + + @Override + public void bindView(View view, Context context, Cursor cursor) { + super.bindView(view, context, cursor); + + LinearLayoutCompat container = view.findViewById(R.id.account_container); + container.setTag(cursor.getPosition()); + ImageView account_pp = view.findViewById(R.id.account_pp); + container.setOnClickListener(v -> { + int position = (int) v.getTag(); + if (accountList != null && accountList.size() > position) { + Intent intent = new Intent(context, ProfileActivity.class); + Bundle b = new Bundle(); + b.putSerializable(Helper.ARG_ACCOUNT, accountList.get(position)); + intent.putExtras(b); + ActivityOptionsCompat options = ActivityOptionsCompat + .makeSceneTransitionAnimation((Activity) context, account_pp, context.getString(R.string.activity_porfile_pp)); + // start the new activity + context.startActivity(intent, options.toBundle()); + } + }); + } +} diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/TagSearchTopBarAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/TagSearchTopBarAdapter.java new file mode 100644 index 00000000..3b36f659 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/ui/drawer/TagSearchTopBarAdapter.java @@ -0,0 +1,72 @@ +package app.fedilab.android.ui.drawer; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.appcompat.widget.LinearLayoutCompat; +import androidx.cursoradapter.widget.SimpleCursorAdapter; + +import java.util.List; + +import app.fedilab.android.R; +import app.fedilab.android.activities.HashTagActivity; +import app.fedilab.android.client.entities.api.Tag; +import app.fedilab.android.helper.Helper; + + +public class TagSearchTopBarAdapter extends SimpleCursorAdapter { + + private final int layout; + private final LayoutInflater inflater; + private final List tags; + + public TagSearchTopBarAdapter(Context context, List tags, int layout, Cursor c, String[] from, int[] to, int flags) { + super(context, layout, c, from, to, flags); + this.layout = layout; + this.inflater = LayoutInflater.from(context); + this.tags = tags; + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return inflater.inflate(layout, null); + } + + + @Override + public void bindView(View view, Context context, Cursor cursor) { + super.bindView(view, context, cursor); + + LinearLayoutCompat container = view.findViewById(R.id.tag_container); + container.setTag(cursor.getPosition()); + container.setOnClickListener(v -> { + int position = (int) v.getTag(); + if (tags != null && tags.size() > position) { + Intent intent = new Intent(context, HashTagActivity.class); + Bundle b = new Bundle(); + b.putString(Helper.ARG_SEARCH_KEYWORD, tags.get(position).name.trim()); + intent.putExtras(b); + context.startActivity(intent); + } + }); + } +} diff --git a/app/src/main/res/layout/drawer_tag_search.xml b/app/src/main/res/layout/drawer_tag_search.xml index c0c284bc..03b7921f 100644 --- a/app/src/main/res/layout/drawer_tag_search.xml +++ b/app/src/main/res/layout/drawer_tag_search.xml @@ -16,6 +16,7 @@ -->