From aae17593dbfd5af6731ff3048b1aee3f6704a059 Mon Sep 17 00:00:00 2001 From: Oskar Walcher Date: Fri, 6 Aug 2021 20:20:05 +0200 Subject: [PATCH] CATROID-1196 Brick Search History --- .../catroid/test/ui/BrickSearchTest.kt | 60 ++++++---- .../ui/fragment/BrickSearchFragment.kt | 104 +++++++++++++----- .../main/res/layout/fragment_brick_search.xml | 29 +++-- catroid/src/main/res/xml/searchable.xml | 4 +- 4 files changed, 144 insertions(+), 53 deletions(-) diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/test/ui/BrickSearchTest.kt b/catroid/src/androidTest/java/org/catrobat/catroid/test/ui/BrickSearchTest.kt index 335c3e69bc8..7c9c7b20f85 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/test/ui/BrickSearchTest.kt +++ b/catroid/src/androidTest/java/org/catrobat/catroid/test/ui/BrickSearchTest.kt @@ -33,6 +33,7 @@ import android.view.inputmethod.InputMethodManager import androidx.test.core.app.ApplicationProvider import androidx.test.espresso.Espresso import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.action.ViewActions.clearText import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.pressKey import androidx.test.espresso.assertion.ViewAssertions.doesNotExist @@ -118,28 +119,49 @@ class BrickSearchTest { Espresso.onView(withId(R.id.search_src_text)).perform( replaceText("if") ).perform(pressKey(KeyEvent.KEYCODE_ENTER)) - val searchAutoComplete = Espresso.onView( - Matchers.allOf( - withId(R.id.search_src_text), - ViewMatchers.withText("if"), childAtPosition( - Matchers.allOf( - withId(R.id.search_plate), - childAtPosition(withId(R.id.search_edit_frame), 1) - ), 0 - ), isDisplayed() - ) - ) - searchAutoComplete.perform(ViewActions.pressImeActionButton()) - val linearLayout = Espresso.onData(Matchers.anything()).inAdapterView( - Matchers.allOf( - withId(android.R.id.list), - childAtPosition(withId(R.id.fragment_brick_search), 0) - ) - ).atPosition(0) - linearLayout.perform(click()) + Espresso.onData(Matchers.anything()) + .inAdapterView( + Matchers.allOf( + withId(android.R.id.list), + childAtPosition( + withId(R.id.fragment_brick_search), + 2 + ) + ) + ).atPosition(0).check(matches(isDisplayed())).perform(click()) Assert.assertFalse(isKeyboardVisible()) } + @Test + fun testSearchHistory() { + Espresso.onView(withId(R.id.button_add)).perform(click()) + Espresso.onView(withId(R.id.search)).perform(click()) + Espresso.onView(withId(R.id.search_src_text)).perform(clearText(), ViewActions.typeText + ("test")).perform(pressKey(KeyEvent.KEYCODE_ENTER)) + Espresso.onData(Matchers.anything()) + .inAdapterView( + Matchers.allOf( + withId(android.R.id.list), + childAtPosition( + withId(R.id.fragment_brick_search), + 2 + ) + ) + ).atPosition(0).perform(click()) + Espresso.onView(withId(R.id.button_add)).perform(click()) + Espresso.onView(withId(R.id.search)).perform(click()) + Espresso.onData(Matchers.anything()) + .inAdapterView( + Matchers.allOf( + withId(android.R.id.list), + childAtPosition( + withId(R.id.fragment_brick_search), + 2 + ) + ) + ).atPosition(0).check(matches(isDisplayed())) + } + @Test fun testCloseKeyboardAfterSearching() { ensureKeyboardIsClosed() diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/fragment/BrickSearchFragment.kt b/catroid/src/main/java/org/catrobat/catroid/ui/fragment/BrickSearchFragment.kt index 111fa4b3c9b..0f341d4ded6 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/fragment/BrickSearchFragment.kt +++ b/catroid/src/main/java/org/catrobat/catroid/ui/fragment/BrickSearchFragment.kt @@ -23,8 +23,6 @@ package org.catrobat.catroid.ui.fragment -import android.app.SearchManager -import android.content.Context import android.os.Bundle import android.os.CountDownTimer import android.preference.PreferenceManager @@ -33,7 +31,6 @@ import android.view.Menu import android.view.MenuInflater import android.view.View import android.view.ViewGroup -import android.widget.AbsListView import android.widget.AdapterView import android.widget.ProgressBar import android.widget.TextView @@ -53,25 +50,35 @@ import org.catrobat.catroid.ui.settingsfragments.AccessibilityProfile import org.catrobat.catroid.ui.settingsfragments.SettingsFragment import org.catrobat.catroid.utils.ToastUtil import java.util.Locale +import android.widget.AbsListView +import android.database.Cursor +import org.catrobat.catroid.utils.setVisibleOrGone class BrickSearchFragment : ListFragment() { private var previousActionBarTitle: CharSequence? = null private var searchView: SearchView? = null + private var recentlyUsedTitle: TextView? = null private var queryTextListener: SearchView.OnQueryTextListener? = null + private var suggestionListener: SearchView.OnSuggestionListener? = null private var availableBricks: MutableList = mutableListOf() + private var recentlyUsedBricks: MutableList = mutableListOf() private var searchResults = mutableListOf() private var addBrickListener: AddBrickFragment.OnAddBrickListener? = null private var category: String? = null private var adapter: PrototypeBrickAdapter? = null + @Volatile private var emptyQuery: Boolean = true + @Volatile private var previousQuery: String = "" override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater.inflate(R.layout.fragment_brick_search, container, false) val actionBar = (activity as? AppCompatActivity)?.supportActionBar previousActionBarTitle = actionBar?.title + recentlyUsedTitle = view.findViewById(R.id.recent_used_header) hideBottomBar(activity) setHasOptionsMenu(true) + getRecentlyUsedBricks() category?.let { prepareBrickList(it) } return view } @@ -105,12 +112,13 @@ class BrickSearchFragment : ListFragment() { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.menu_search, menu) val searchItem = menu.findItem(R.id.search_bar).actionView - val searchManager: SearchManager = activity?.getSystemService(Context.SEARCH_SERVICE) as SearchManager (searchItem as SearchView).apply { - setSearchableInfo(searchManager.getSearchableInfo(activity?.componentName)) isIconified = false queryHint = context.getString(R.string.search_hint) } + searchResults.addAll(recentlyUsedBricks) + adapter = PrototypeBrickAdapter(searchResults) + listAdapter = adapter listView.setOnScrollListener(object : AbsListView.OnScrollListener { override fun onScrollStateChanged( view: AbsListView, @@ -133,9 +141,10 @@ class BrickSearchFragment : ListFragment() { var countDownTimer: CountDownTimer adapter = PrototypeBrickAdapter(searchResults) listAdapter = adapter - searchView?.setSearchableInfo(searchManager.getSearchableInfo(activity?.componentName)) queryTextListener = object : SearchView.OnQueryTextListener { override fun onQueryTextChange(query: String): Boolean { + previousQuery = query + recentlyUsedTitle?.setVisibleOrGone(query.isEmpty()) countDownTimer = object : CountDownTimer( PROGESSIVE_INPUT_DELAY, PROGESSIVE_INPUT_COUNTDOWN_INTERVALL @@ -145,24 +154,22 @@ class BrickSearchFragment : ListFragment() { } override fun onFinish() { - setShowProgressBar(false) - searchResults.clear() - adapter?.replaceList(searchResults) - searchBrick(query) - if (searchResults.isEmpty()) { - ToastUtil.showError( - context, - context?.getString(R.string.no_results_found) - ) - } - if (query.isEmpty()) { - searchResults.clear() - } - adapter?.replaceList(searchResults) + when (query) { + previousQuery -> searchAndFillBrickList(query) + } } } - countDownTimer.start() - setShowProgressBar(true) + emptyQuery = query.isEmpty() + if (query.isEmpty()) { + searchResults.clear() + searchResults.addAll(recentlyUsedBricks) + adapter?.replaceList(searchResults) + countDownTimer.cancel() + setShowProgressBar(false) + } else { + countDownTimer.start() + setShowProgressBar(true) + } return true } @@ -172,15 +179,32 @@ class BrickSearchFragment : ListFragment() { adapter?.replaceList(searchResults) if (searchResults.isEmpty()) { ToastUtil.showError(context, context?.getString(R.string.no_results_found)) - } else searchView?.clearFocus() + } else { + searchView?.clearFocus() + } + return true + } + } + suggestionListener = object : SearchView.OnSuggestionListener { + override fun onSuggestionSelect(position: Int): Boolean { + return false + } + + override fun onSuggestionClick(position: Int): Boolean { + val cursor: Cursor? = searchView?.suggestionsAdapter?.cursor + cursor?.moveToPosition(position) + val suggestion: String? = cursor?.getString(2) + searchView?.setQuery(suggestion, true) return true } } - searchView?.setOnQueryTextListener(queryTextListener) - searchView?.requestFocus() } + searchView?.setOnQueryTextListener(queryTextListener) + searchView?.setOnSuggestionListener(suggestionListener) + searchView?.requestFocus() super.onCreateOptionsMenu(menu, inflater) } + private fun setShowProgressBar(visible: Boolean) { if (visible) { view?.findViewById(R.id.progress_bar)?.visibility = View.VISIBLE @@ -199,6 +223,26 @@ class BrickSearchFragment : ListFragment() { private fun onlyBeginnerBricks(): Boolean = PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(AccessibilityProfile.BEGINNER_BRICKS, false) + private fun searchAndFillBrickList(query: String) { + searchResults.clear() + if (emptyQuery) { + return + } + adapter?.replaceList(searchResults) + searchBrick(query) + if (searchResults.isEmpty()) { + ToastUtil.showError( + context, + context?.getString(R.string.no_results_found) + ) + } + if (emptyQuery) { + return + } + adapter?.replaceList(searchResults) + setShowProgressBar(false) + } + private fun searchBrick(query: String) { availableBricks.forEach { brick -> val regexQuery = (".*" + query.toLowerCase(Locale.ROOT).replace("\\s".toRegex(), ".*") + ".*").toRegex() @@ -229,6 +273,16 @@ class BrickSearchFragment : ListFragment() { return wholeStringFoundInBrick } + fun getRecentlyUsedBricks() { + val categoryBricksFactory: CategoryBricksFactory = when { + onlyBeginnerBricks() -> CategoryBeginnerBricksFactory() + else -> CategoryBricksFactory() + } + val backgroundSprite = ProjectManager.getInstance().currentlyEditedScene.backgroundSprite + val sprite = ProjectManager.getInstance().currentSprite + recentlyUsedBricks.addAll(categoryBricksFactory.getBricks(requireContext().getString(R.string.category_recently_used), backgroundSprite.equals(sprite), requireContext())) + } + @SuppressWarnings("ComplexMethod") fun prepareBrickList(category: String = "") { val categoryBricksFactory: CategoryBricksFactory = when { diff --git a/catroid/src/main/res/layout/fragment_brick_search.xml b/catroid/src/main/res/layout/fragment_brick_search.xml index 90587dfee87..e01efcea6cc 100644 --- a/catroid/src/main/res/layout/fragment_brick_search.xml +++ b/catroid/src/main/res/layout/fragment_brick_search.xml @@ -28,19 +28,34 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/app_background"> - - + android:text="@string/category_recently_used" + android:textColor="@color/view_holder_item_title" + android:padding="@dimen/view_holder_padding" + android:textSize="?attr/large" + android:visibility="visible"/> + + + + + android:visibility="invisible"/> diff --git a/catroid/src/main/res/xml/searchable.xml b/catroid/src/main/res/xml/searchable.xml index fd903747719..7b32c137b2e 100644 --- a/catroid/src/main/res/xml/searchable.xml +++ b/catroid/src/main/res/xml/searchable.xml @@ -1,7 +1,7 @@