From 7ce273ec67124bf03700f3bed2e0f71502577cdb Mon Sep 17 00:00:00 2001 From: tarsin Date: Wed, 7 Dec 2022 13:47:10 +0800 Subject: [PATCH] Initial migrate GalleryListScene to New SearchBar, #397 --- .../ehviewer/ui/scene/GalleryListScene.java | 240 ++++---------- .../hippo/ehviewer/ui/scene/SearchBarScene.kt | 294 ++++++++++++++++++ .../main/res/layout/scene_gallery_list.xml | 12 +- app/src/main/res/layout/scene_searchbar.xml | 49 +++ .../scene_gallery_list_searchbar_menu.xml | 11 + 5 files changed, 408 insertions(+), 198 deletions(-) create mode 100644 app/src/main/java/com/hippo/ehviewer/ui/scene/SearchBarScene.kt create mode 100644 app/src/main/res/layout/scene_searchbar.xml create mode 100644 app/src/main/res/menu/scene_gallery_list_searchbar_menu.xml diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java index 95c35416d..d9789b248 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java @@ -32,6 +32,7 @@ import android.text.TextUtils; import android.text.style.ImageSpan; import android.view.Gravity; import android.view.LayoutInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; @@ -92,7 +93,6 @@ import com.hippo.ehviewer.ui.GalleryActivity; import com.hippo.ehviewer.ui.MainActivity; import com.hippo.ehviewer.ui.dialog.SelectItemWithIconAdapter; import com.hippo.ehviewer.widget.GalleryInfoContentHelper; -import com.hippo.ehviewer.widget.SearchBar; import com.hippo.ehviewer.widget.SearchLayout; import com.hippo.scene.Announcer; import com.hippo.scene.SceneFragment; @@ -102,7 +102,6 @@ import com.hippo.view.BringOutTransition; import com.hippo.view.ViewTransition; import com.hippo.widget.ContentLayout; import com.hippo.widget.FabLayout; -import com.hippo.widget.SearchBarMover; import com.hippo.yorozuya.AnimationUtils; import com.hippo.yorozuya.AssertUtils; import com.hippo.yorozuya.MathUtils; @@ -123,9 +122,9 @@ import java.util.List; import rikka.core.res.ResourcesKt; @SuppressLint("RtlHardcoded") -public final class GalleryListScene extends BaseScene - implements SearchBar.Helper, SearchBar.OnStateChangeListener, FastScroller.OnDragHandlerListener, - SearchLayout.Helper, SearchBarMover.Helper, View.OnClickListener, FabLayout.OnClickFabListener, +public final class GalleryListScene extends SearchBarScene + implements FastScroller.OnDragHandlerListener, + SearchLayout.Helper, FabLayout.OnClickFabListener, FabLayout.OnExpandListener { public final static String KEY_ACTION = "action"; @@ -137,7 +136,6 @@ public final class GalleryListScene extends BaseScene public final static String KEY_LIST_URL_BUILDER = "list_url_builder"; public final static String KEY_HAS_FIRST_REFRESH = "has_first_refresh"; public final static String KEY_STATE = "state"; - private static final int BACK_PRESSED_INTERVAL = 2000; private final static int STATE_NORMAL = 0; private final static int STATE_SIMPLE_SEARCH = 1; private final static int STATE_SEARCH = 2; @@ -165,8 +163,6 @@ public final class GalleryListScene extends BaseScene new ActivityResultContracts.PickVisualMedia(), result -> mSearchLayout.setImageUri(result)); @Nullable - private SearchBar mSearchBar; - @Nullable private View mSearchFab; @Nullable private final Animator.AnimatorListener mSearchFabAnimatorListener = new SimpleAnimatorListener() { @@ -199,10 +195,6 @@ public final class GalleryListScene extends BaseScene @Nullable private DrawerArrowDrawable mLeftDrawable; @Nullable - private AddDeleteDrawable mRightDrawable; - @Nullable - private SearchBarMover mSearchBarMover; - @Nullable private AddDeleteDrawable mActionFabDrawable; @Nullable private List mQuickSearchList; @@ -278,6 +270,11 @@ public final class GalleryListScene extends BaseScene } } + @Override + public int getMenuResId() { + return R.menu.scene_gallery_list_searchbar_menu; + } + public static void startScene(SceneFragment scene, ListUrlBuilder lub) { scene.startScene(getStartAnnouncer(lub)); } @@ -328,9 +325,7 @@ public final class GalleryListScene extends BaseScene mHelper.refresh(); } setState(STATE_NORMAL); - if (null != mSearchBarMover) { - mSearchBarMover.showSearchBar(); - } + showSearchBar(); } @Override @@ -441,14 +436,14 @@ public final class GalleryListScene extends BaseScene mFavouriteStatusRouter.removeListener(mFavouriteStatusRouterListener); } - private void setSearchBarHint(SearchBar searchBar) { - searchBar.setEditTextHint(getString(EhUrl.SITE_EX == Settings.getGallerySite() ? + private void setSearchBarHint() { + setEditTextHint(getString(EhUrl.SITE_EX == Settings.getGallerySite() ? R.string.gallery_list_search_bar_hint_exhentai : R.string.gallery_list_search_bar_hint_e_hentai)); } - private void setSearchBarSuggestionProvider(SearchBar searchBar) { - searchBar.setSuggestionProvider(text -> { + private void setSearchBarSuggestionProvider() { + setSuggestionProvider(text -> { GalleryDetailUrlParser.Result result1 = GalleryDetailUrlParser.parse(text, false); if (result1 != null) { return Collections.singletonList(new GalleryDetailUrlSuggestion(result1.gid, result1.token)); @@ -508,12 +503,11 @@ public final class GalleryListScene extends BaseScene : R.id.search_normal_search); // Update search edit text - if (!TextUtils.isEmpty(keyword) && null != mSearchBar && !mIsTopList) { + if (!TextUtils.isEmpty(keyword) && !mIsTopList) { if (builder.getMode() == ListUrlBuilder.MODE_TAG) { keyword = wrapTagKeyword(keyword); } - mSearchBar.setText(keyword); - mSearchBar.cursorToEnd(); + setSearchBarText(keyword); } // Update title @@ -521,9 +515,7 @@ public final class GalleryListScene extends BaseScene if (null == title) { title = resources.getString(R.string.search); } - if (null != mSearchBar) { - mSearchBar.setTitle(title); - } + setSearchBarHint(title); // Update nav checked item int checkedItemId; @@ -546,8 +538,8 @@ public final class GalleryListScene extends BaseScene @NonNull @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { + public View onCreateViewWithToolbar(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.scene_gallery_list, container, false); Context context = getContext(); @@ -562,7 +554,6 @@ public final class GalleryListScene extends BaseScene mRecyclerView = mContentLayout.getRecyclerView(); FastScroller fastScroller = mContentLayout.getFastScroller(); mSearchLayout = (SearchLayout) ViewUtils.$$(mainLayout, R.id.search_layout); - mSearchBar = (SearchBar) ViewUtils.$$(mainLayout, R.id.search_bar); mFabLayout = (FabLayout) ViewUtils.$$(mainLayout, R.id.fab_layout); mSearchFab = ViewUtils.$$(mainLayout, R.id.search_fab); ViewCompat.setWindowInsetsAnimationCallback(view, new WindowInsetsAnimationHelper( @@ -570,6 +561,8 @@ public final class GalleryListScene extends BaseScene mFabLayout, (View) mSearchFab.getParent() )); + ((ViewGroup)mFabLayout.getParent()).removeView(mFabLayout); + container.addView(mFabLayout); int paddingTopSB = resources.getDimensionPixelOffset(R.dimen.gallery_padding_top_search_bar); int paddingBottomFab = resources.getDimensionPixelOffset(R.dimen.gallery_padding_bottom_fab); @@ -591,13 +584,13 @@ public final class GalleryListScene extends BaseScene fastScroller.getPaddingRight(), fastScroller.getPaddingBottom()); mLeftDrawable = new DrawerArrowDrawable(context, ResourcesKt.resolveColor(getTheme(), android.R.attr.colorControlNormal)); - mRightDrawable = new AddDeleteDrawable(context, ResourcesKt.resolveColor(getTheme(), android.R.attr.colorControlNormal)); - mSearchBar.setLeftDrawable(mLeftDrawable); - mSearchBar.setRightDrawable(mRightDrawable); - mSearchBar.setHelper(this); - mSearchBar.setOnStateChangeListener(this); - setSearchBarHint(mSearchBar); - setSearchBarSuggestionProvider(mSearchBar); + setOnApplySearch((query) -> { + onApplySearch(query); + return null; + }); + setNavigationDrawable(mLeftDrawable); + setSearchBarHint(); + setSearchBarSuggestionProvider(); mSearchLayout.setHelper(this); mSearchLayout.setPadding(mSearchLayout.getPaddingLeft(), mSearchLayout.getPaddingTop() + paddingTopSB, @@ -614,10 +607,6 @@ public final class GalleryListScene extends BaseScene mActionFabDrawable = new AddDeleteDrawable(context, colorID); mFabLayout.getPrimaryFab().setImageDrawable(mActionFabDrawable); - mSearchFab.setOnClickListener(this); - - mSearchBarMover = new SearchBarMover(this, mSearchBar, mRecyclerView, mSearchLayout); - // Update list url builder onUpdateUrlBuilder(); @@ -639,10 +628,6 @@ public final class GalleryListScene extends BaseScene public void onDestroyView() { super.onDestroyView(); - if (null != mSearchBarMover) { - mSearchBarMover.cancelAnimation(); - mSearchBarMover = null; - } if (null != mHelper) { mHelper.destroy(); if (1 == mHelper.getShownViewIndex()) { @@ -654,17 +639,15 @@ public final class GalleryListScene extends BaseScene mRecyclerView = null; } if (null != mFabLayout) { + ((ViewGroup)mFabLayout.getParent()).removeView(mFabLayout); removeAboveSnackView(mFabLayout); mFabLayout = null; } mAdapter = null; mSearchLayout = null; - mSearchBar = null; - mSearchFab = null; mViewTransition = null; mLeftDrawable = null; - mRightDrawable = null; mActionFabDrawable = null; } @@ -793,6 +776,8 @@ public final class GalleryListScene extends BaseScene if (null != mFabLayout && mFabLayout.isExpanded()) { return false; } + if (isSearchViewShown()) + return false; switch (mState) { case STATE_NORMAL: return getStackIndex() == 0; @@ -806,6 +791,10 @@ public final class GalleryListScene extends BaseScene @Override public void onBackPressed() { + if (isSearchViewShown()) { + hideSearchView(); + return; + } if (null != mFabLayout && mFabLayout.isExpanded()) { mFabLayout.setExpanded(false); return; @@ -846,14 +835,6 @@ public final class GalleryListScene extends BaseScene return true; } - @Override - public void onClick(View v) { - if (STATE_NORMAL != mState && null != mSearchBar) { - mSearchBar.applySearch(); - hideSoftInput(); - } - } - @Override public void onClickPrimaryFab(FabLayout view, FloatingActionButton fab) { if (STATE_NORMAL == mState) { @@ -1157,8 +1138,7 @@ public final class GalleryListScene extends BaseScene } private void setState(@State int state, boolean animation) { - if (null == mSearchBar || null == mSearchBarMover || - null == mViewTransition || null == mSearchLayout) { + if (null == mViewTransition || null == mSearchLayout) { return; } @@ -1166,71 +1146,49 @@ public final class GalleryListScene extends BaseScene int oldState = mState; mState = state; + showSearchBar(); + onStateChange(state); switch (oldState) { case STATE_NORMAL: if (state == STATE_SIMPLE_SEARCH) { - mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation); - mSearchBarMover.returnSearchBarPosition(); selectSearchFab(animation); } else if (state == STATE_SEARCH) { mViewTransition.showView(1, animation); mSearchLayout.scrollSearchContainerToTop(); - mSearchBar.setState(SearchBar.STATE_SEARCH, animation); - mSearchBarMover.returnSearchBarPosition(); selectSearchFab(animation); } else if (state == STATE_SEARCH_SHOW_LIST) { mViewTransition.showView(1, animation); mSearchLayout.scrollSearchContainerToTop(); - mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation); - mSearchBarMover.returnSearchBarPosition(); selectSearchFab(animation); } break; case STATE_SIMPLE_SEARCH: if (state == STATE_NORMAL) { - mSearchBar.setState(SearchBar.STATE_NORMAL, animation); - mSearchBarMover.returnSearchBarPosition(); selectActionFab(animation); } else if (state == STATE_SEARCH) { mViewTransition.showView(1, animation); mSearchLayout.scrollSearchContainerToTop(); - mSearchBar.setState(SearchBar.STATE_SEARCH, animation); - mSearchBarMover.returnSearchBarPosition(); } else if (state == STATE_SEARCH_SHOW_LIST) { mViewTransition.showView(1, animation); mSearchLayout.scrollSearchContainerToTop(); - mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation); - mSearchBarMover.returnSearchBarPosition(); } break; case STATE_SEARCH: if (state == STATE_NORMAL) { mViewTransition.showView(0, animation); - mSearchBar.setState(SearchBar.STATE_NORMAL, animation); - mSearchBarMover.returnSearchBarPosition(); selectActionFab(animation); } else if (state == STATE_SIMPLE_SEARCH) { mViewTransition.showView(0, animation); - mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation); - mSearchBarMover.returnSearchBarPosition(); } else if (state == STATE_SEARCH_SHOW_LIST) { - mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation); - mSearchBarMover.returnSearchBarPosition(); } break; case STATE_SEARCH_SHOW_LIST: if (state == STATE_NORMAL) { mViewTransition.showView(0, animation); - mSearchBar.setState(SearchBar.STATE_NORMAL, animation); - mSearchBarMover.returnSearchBarPosition(); selectActionFab(animation); } else if (state == STATE_SIMPLE_SEARCH) { mViewTransition.showView(0, animation); - mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation); - mSearchBarMover.returnSearchBarPosition(); } else if (state == STATE_SEARCH) { - mSearchBar.setState(SearchBar.STATE_SEARCH, animation); - mSearchBarMover.returnSearchBarPosition(); } break; } @@ -1238,7 +1196,6 @@ public final class GalleryListScene extends BaseScene ((StageActivity) requireActivity()).updateBackPressCallBackStatus(); } - @Override public void onClickTitle() { if (mState == STATE_NORMAL) { setState(STATE_SIMPLE_SEARCH); @@ -1246,44 +1203,15 @@ public final class GalleryListScene extends BaseScene } @Override - public void onClickLeftIcon() { - if (null == mSearchBar) { - return; - } - - if (mSearchBar.getState() == SearchBar.STATE_NORMAL) { - toggleDrawer(Gravity.LEFT); + public boolean onMenuItemClick(@NonNull MenuItem item) { + if (mState == STATE_NORMAL) { + setState(STATE_SEARCH); } else { setState(STATE_NORMAL); } + return true; } - @Override - public void onClickRightIcon() { - if (null == mSearchBar) { - return; - } - - if (mSearchBar.getState() == SearchBar.STATE_NORMAL) { - setState(STATE_SEARCH); - } else { - if (mSearchBar.getEditText().length() == 0) { - setState(STATE_NORMAL); - } else { - // Clear - mSearchBar.setText(""); - } - } - } - - @Override - public void onSearchEditTextClick() { - if (mState == STATE_SEARCH) { - setState(STATE_SEARCH_SHOW_LIST); - } - } - - @Override public void onApplySearch(String query) { if (null == mUrlBuilder || null == mHelper || null == mSearchLayout) { return; @@ -1311,12 +1239,6 @@ public final class GalleryListScene extends BaseScene setState(STATE_NORMAL); } - @Override - public void onSearchEditTextBackPressed() { - onBackPressed(); - } - - @Override public void onReceiveContent(Uri uri) { if (mSearchLayout == null || uri == null) { return; @@ -1336,38 +1258,10 @@ public final class GalleryListScene extends BaseScene public void onEndDragHandler() { // Restore right drawer setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT); - - if (null != mSearchBarMover) { - mSearchBarMover.returnSearchBarPosition(); - } + showSearchBar(); } - @Override - public void onStateChange(SearchBar searchBar, int newState, int oldState, boolean animation) { - if (null == mLeftDrawable || null == mRightDrawable) { - return; - } - - switch (oldState) { - default: - case SearchBar.STATE_NORMAL: - mLeftDrawable.setArrow(animation ? ANIMATE_TIME : 0); - mRightDrawable.setDelete(animation ? ANIMATE_TIME : 0); - break; - case SearchBar.STATE_SEARCH: - if (newState == SearchBar.STATE_NORMAL) { - mLeftDrawable.setMenu(animation ? ANIMATE_TIME : 0); - mRightDrawable.setAdd(animation ? ANIMATE_TIME : 0); - } - break; - case SearchBar.STATE_SEARCH_LIST: - if (newState == STATE_NORMAL) { - mLeftDrawable.setMenu(animation ? ANIMATE_TIME : 0); - mRightDrawable.setAdd(animation ? ANIMATE_TIME : 0); - } - break; - } - + public void onStateChange(int newState) { if (newState == STATE_NORMAL || newState == STATE_SIMPLE_SEARCH) { setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.LEFT); setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT); @@ -1379,9 +1273,7 @@ public final class GalleryListScene extends BaseScene @Override public void onChangeSearchMode() { - if (null != mSearchBarMover) { - mSearchBarMover.showSearchBar(); - } + showSearchBar(); } @Override @@ -1396,32 +1288,9 @@ public final class GalleryListScene extends BaseScene } } - // SearchBarMover.Helper - @Override - public boolean isValidView(RecyclerView recyclerView) { - return (mState == STATE_NORMAL && recyclerView == mRecyclerView) || - (mState == STATE_SEARCH && recyclerView == mSearchLayout); - } - - // SearchBarMover.Helper - @Override - public RecyclerView getValidRecyclerView() { - if (mState == STATE_NORMAL || mState == STATE_SIMPLE_SEARCH) { - return mRecyclerView; - } else { - return mSearchLayout; - } - } - - // SearchBarMover.Helper - @Override - public boolean forceShowSearchBar() { - return mState == STATE_SIMPLE_SEARCH || mState == STATE_SEARCH_SHOW_LIST; - } private void onGetGalleryListSuccess(GalleryListParser.Result result, int taskId) { - if (mHelper != null && mSearchBarMover != null && - mHelper.isCurrentTask(taskId)) { + if (mHelper != null && mHelper.isCurrentTask(taskId)) { String emptyString = getString(mUrlBuilder.getMode() == ListUrlBuilder.MODE_SUBSCRIPTION && result.noWatchedTags ? R.string.gallery_list_empty_hit_subscription : R.string.gallery_list_empty_hit); @@ -1440,8 +1309,7 @@ public final class GalleryListScene extends BaseScene } private void onGetGalleryListFailure(Exception e, int taskId) { - if (mHelper != null && mSearchBarMover != null && - mHelper.isCurrentTask(taskId)) { + if (mHelper != null && mHelper.isCurrentTask(taskId)) { mHelper.onGetException(taskId, e); } } @@ -1651,7 +1519,7 @@ public final class GalleryListScene extends BaseScene } } - private abstract class UrlSuggestion extends SearchBar.Suggestion { + private abstract class UrlSuggestion extends Suggestion { @Override public CharSequence getText(TextView textView) { if (textView.getId() == android.R.id.text1) { @@ -1835,9 +1703,7 @@ public final class GalleryListScene extends BaseScene @Override public void onShowView(View hiddenView, View shownView) { - if (null != mSearchBarMover) { - mSearchBarMover.showSearchBar(); - } + showSearchBar(); showActionFab(); } @@ -1847,11 +1713,9 @@ public final class GalleryListScene extends BaseScene } @Override - protected void onScrollToPosition(int postion) { - if (0 == postion) { - if (null != mSearchBarMover) { - mSearchBarMover.showSearchBar(); - } + protected void onScrollToPosition(int position) { + if (0 == position) { + showSearchBar(); showActionFab(); } } diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/SearchBarScene.kt b/app/src/main/java/com/hippo/ehviewer/ui/scene/SearchBarScene.kt new file mode 100644 index 000000000..7ed16115f --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/SearchBarScene.kt @@ -0,0 +1,294 @@ +package com.hippo.ehviewer.ui.scene + +import android.annotation.SuppressLint +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.text.TextUtils +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.core.widget.addTextChangedListener +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.search.SearchBar +import com.google.android.material.search.SearchView +import com.hippo.app.BaseDialogBuilder +import com.hippo.ehviewer.R +import com.hippo.ehviewer.client.EhTagDatabase +import com.hippo.ehviewer.widget.SearchDatabase +import com.hippo.scene.StageActivity + + +abstract class SearchBarScene : ToolbarScene() { + private var mSearchView: SearchView? = null + private var mRecyclerView: RecyclerView? = null + private var mSuggestionList: List? = null + private var mSuggestionAdapter: SuggestionAdapter? = null + private var mSuggestionProvider: SuggestionProvider? = null + private var mAllowEmptySearch = true + private val mSearchDatabase by lazy { SearchDatabase.getInstance(context) } + private var onApplySearch: (String) -> Unit = {} + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + val view = + inflater.inflate(R.layout.scene_searchbar, container, false) as ViewGroup + mToolbar = view.findViewById(R.id.toolbar) + mAppBarLayout = view.findViewById(R.id.appbar) + mSearchView = view.findViewById(R.id.searchview) + mRecyclerView = view.findViewById(R.id.search_bar_list) + mSearchView?.editText?.addTextChangedListener { + updateSuggestions() + } + mSearchView?.editText?.setOnEditorActionListener { _, _, _ -> + (mToolbar as SearchBar).text = mSearchView!!.text + mSearchView?.hide() + onApplySearch() + false + } + mSuggestionList = ArrayList() + mSuggestionAdapter = SuggestionAdapter(LayoutInflater.from(context)) + mRecyclerView?.adapter = mSuggestionAdapter + val layoutManager = LinearLayoutManager(context) + mRecyclerView?.layoutManager = layoutManager + mSearchView?.addTransitionListener { _, _, _ -> + (requireActivity() as StageActivity).updateBackPressCallBackStatus() + updateSuggestions() + } + val contentView = onCreateViewWithToolbar(inflater, view, savedInstanceState) + return view.apply { addView(contentView, 0) } + } + + fun setSearchBarHint(hint: String?) { + (mToolbar as? SearchBar)?.hint = hint + } + + fun setSearchBarText(text: String?) { + (mToolbar as? SearchBar)?.text = text + } + + fun setEditTextHint(hint: String?) { + mSearchView?.editText?.hint = hint + } + + fun setEditTextText(text: String?) { + mSearchView?.setText(text) + } + + fun setNavigationDrawable(drawable: Drawable?) { + (mToolbar as? SearchBar)?.navigationIcon = drawable + } + + override fun onNavigationClick() { + toggleDrawer(Gravity.START) + } + + fun setOnApplySearch(lambda: (String) -> Unit) { + onApplySearch = lambda + } + + private fun onApplySearch() { + val query = mSearchView?.text.toString().trim() + if (!mAllowEmptySearch && query.isEmpty()) { + return + } + mSearchDatabase.addQuery(query) + onApplySearch(query) + } + + fun setAllowEmptySearch(allowEmptySearch: Boolean) { + mAllowEmptySearch = allowEmptySearch + } + + fun isSearchViewShown(): Boolean { + return mSearchView?.isShowing ?: false + } + + fun hideSearchView() { + mSearchView?.hide() + } + + private fun wrapTagKeyword(keyword: String): String { + var keyword = keyword + keyword = keyword.trim { it <= ' ' } + + val index1 = keyword.indexOf(':') + if (index1 == -1 || index1 >= keyword.length - 1) { + // Can't find :, or : is the last char + return keyword + } + if (keyword[index1 + 1] == '"') { + // The char after : is ", the word must be quoted + return keyword + } + val index2 = keyword.indexOf(' ') + return if (index2 <= index1) { + // Can't find space, or space is before : + keyword + } else keyword.substring(0, index1 + 1) + "\"" + keyword.substring(index1 + 1) + "$\"" + + } + + interface SuggestionProvider { + + fun providerSuggestions(text: String): List? + } + + abstract class Suggestion { + + abstract fun getText(textView: TextView): CharSequence? + + abstract fun onClick() + + open fun onLongClick(): Boolean { + return false + } + + } + + private class SuggestionHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val text1 = itemView.findViewById(android.R.id.text1) as TextView + val text2 = itemView.findViewById(android.R.id.text2) as TextView + } + + private inner class SuggestionAdapter constructor(private val mInflater: LayoutInflater) : + RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SuggestionHolder { + return SuggestionHolder(mInflater.inflate(R.layout.item_simple_list_2, parent, false)) + } + + override fun onBindViewHolder(holder: SuggestionHolder, position: Int) { + val suggestion = mSuggestionList?.get(position) + val text1 = suggestion?.getText(holder.text1) + val text2 = suggestion?.getText(holder.text2) + holder.text1.text = text1 + if (text2 == null) { + holder.text2.visibility = View.GONE + holder.text2.text = "" + } else { + holder.text2.visibility = View.VISIBLE + holder.text2.text = text2 + } + + holder.itemView.setOnClickListener { + mSuggestionList?.run { + if (position < size) { + this[position].onClick() + } + } + } + holder.itemView.setOnLongClickListener { + mSuggestionList?.run { + if (position < size) { + return@setOnLongClickListener this[position].onLongClick() + } + } + return@setOnLongClickListener false + } + } + + override fun getItemId(position: Int): Long { + return position.toLong() + } + + override fun getItemCount(): Int { + return mSuggestionList?.size ?: 0 + } + + } + + inner class TagSuggestion constructor(var mHint: String, var mKeyword: String) : Suggestion() { + + override fun getText(textView: TextView): CharSequence { + return if (textView.id == android.R.id.text1) { + mKeyword + } else { + mHint + } + } + + override fun onClick() { + val edittext = mSearchView?.editText + edittext?.let { + val text = it.text.toString() + var temp = wrapTagKeyword(mKeyword) + " " + if (text.contains(" ")) { + temp = text.substring(0, text.lastIndexOf(" ")) + " " + temp + } + it.setText(temp) + it.setSelection(text.length) + } + } + } + + inner class KeywordSuggestion constructor(private val mKeyword: String) : Suggestion() { + + override fun getText(textView: TextView): CharSequence? { + return if (textView.id == android.R.id.text1) { + mKeyword + } else { + null + } + } + + override fun onClick() { + mSearchView?.editText?.run { + setText(mKeyword) + setSelection(length()) + } + } + + override fun onLongClick(): Boolean { + BaseDialogBuilder(requireContext()) + .setMessage(requireContext().getString(R.string.delete_search_history, mKeyword)) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.delete) { _, _ -> + mSearchDatabase.deleteQuery(mKeyword) + updateSuggestions(false) + } + .show() + return true + } + } + + @SuppressLint("NotifyDataSetChanged") + private fun updateSuggestions(scrollToTop: Boolean = true) { + val suggestions = arrayListOf() + val text = mSearchView?.editText?.text?.toString() ?: return + mSuggestionProvider?.run { + providerSuggestions(text)?.let { suggestions.addAll(it) } + } + mSearchDatabase.getSuggestions(text, 128).forEach { suggestions.add(KeywordSuggestion(it)) } + val ehTagDatabase = EhTagDatabase.getInstance(context) + if (!TextUtils.isEmpty(text) && ehTagDatabase != null && !text.endsWith(" ")) { + val s = text.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + if (s.isNotEmpty()) { + val keyword = s[s.size - 1] + val searchHints = ehTagDatabase.suggest(keyword) + + for (searchHint in searchHints) { + suggestions.add(TagSuggestion(searchHint.first, searchHint.second)) + } + + } + } + mSuggestionList = suggestions + mSuggestionAdapter?.notifyDataSetChanged() + if (scrollToTop) { + mRecyclerView?.scrollToPosition(0) + } + } + + fun setSuggestionProvider(suggestionProvider: SuggestionProvider) { + mSuggestionProvider = suggestionProvider + } + + fun showSearchBar() { + mAppBarLayout?.setExpanded(true) + } +} diff --git a/app/src/main/res/layout/scene_gallery_list.xml b/app/src/main/res/layout/scene_gallery_list.xml index 89ea9abbc..7bc3e9ada 100644 --- a/app/src/main/res/layout/scene_gallery_list.xml +++ b/app/src/main/res/layout/scene_gallery_list.xml @@ -19,7 +19,8 @@ android:id="@+id/main_layout" android:layout_width="match_parent" android:layout_height="match_parent" - android:clipChildren="false"> + android:paddingTop="16dp" + app:layout_behavior="@string/searchbar_scrolling_view_behavior"> - - + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/scene_gallery_list_searchbar_menu.xml b/app/src/main/res/menu/scene_gallery_list_searchbar_menu.xml new file mode 100644 index 000000000..950d17fad --- /dev/null +++ b/app/src/main/res/menu/scene_gallery_list_searchbar_menu.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file