Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Catroid-1196 Implement search history for the brick search #4304

Merged
merged 1 commit into from
Jul 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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<Brick> = mutableListOf()
private var recentlyUsedBricks: MutableList<Brick> = mutableListOf()
private var searchResults = mutableListOf<Brick>()
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
}
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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
}

Expand All @@ -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<ProgressBar>(R.id.progress_bar)?.visibility = View.VISIBLE
Expand All @@ -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()
Expand Down Expand Up @@ -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 {
Expand Down
29 changes: 22 additions & 7 deletions catroid/src/main/res/layout/fragment_brick_search.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,34 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/app_background">
<ListView
android:id="@android:id/list"
<TextView
android:id="@+id/recent_used_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:cacheColorHint="#00000000"
android:divider="@android:color/transparent">
</ListView>
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"/>
<TextView
android:id="@+id/no_recently_used"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:text="">

</TextView>
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/recent_used_header"
android:cacheColorHint="#00000000"
android:divider="@android:color/transparent"/>
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminate="true"
android:visibility="invisible"
/>
android:visibility="invisible"/>
</RelativeLayout>
4 changes: 2 additions & 2 deletions catroid/src/main/res/xml/searchable.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Catroid: An on-device visual programming system for Android devices
~ Copyright (C) 2010-2021 The Catrobat Team
~ Copyright (C) 2010-2022 The Catrobat Team
~ (<http://developer.catrobat.org/credits>)
~
~ This program is free software: you can redistribute it and/or modify
Expand All @@ -24,4 +24,4 @@

<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/app_name"
android:hint="@string/search_hint" />
android:hint="@string/search_hint"/>