diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/test/ui/SearchTest.kt b/catroid/src/androidTest/java/org/catrobat/catroid/test/ui/SearchTest.kt new file mode 100644 index 00000000000..fea2558bc5e --- /dev/null +++ b/catroid/src/androidTest/java/org/catrobat/catroid/test/ui/SearchTest.kt @@ -0,0 +1,210 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2025 The Catrobat Team + * () + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * An additional term exception under section 7 of the GNU Affero + * General Public License, version 3, is available at + * http://developer.catrobat.org/license_additional_term + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.catrobat.catroid.test.ui +import android.view.KeyEvent +import androidx.recyclerview.widget.RecyclerView +import androidx.test.core.app.ApplicationProvider +import androidx.test.espresso.Espresso +import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.contrib.RecyclerViewActions +import androidx.test.espresso.matcher.ViewMatchers.assertThat +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.catrobat.catroid.ProjectManager +import org.catrobat.catroid.R +import org.catrobat.catroid.content.Project +import org.catrobat.catroid.content.Scene +import org.catrobat.catroid.content.Sprite +import org.catrobat.catroid.ui.FinderDataManager +import org.catrobat.catroid.ui.MainMenuActivity +import org.catrobat.catroid.uiespresso.ui.fragment.actionutils.ActionUtils +import org.hamcrest.Matchers.`is` +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.catrobat.catroid.content.Script +import org.catrobat.catroid.content.StartScript +import org.catrobat.catroid.content.bricks.FinishStageBrick + +@RunWith(AndroidJUnit4::class) +class SearchTest { + var projectName = "searchTestProject" + private val projectManager = ProjectManager.getInstance() + + @Rule + @JvmField + var baseActivityTestRule = ActivityScenarioRule(MainMenuActivity::class.java) + private lateinit var script: Script + + @Before + fun setUp() { + createProject(projectName) + } + + @Test + fun testSearchMixed() { + val queryString = "test" + val expectedResults = arrayOf("testsprite2","finish tests","testlook3","testlook4", + "testsound3","testsound4","testsprite3","testscene2", + "testsprite5","testscene3") + Espresso.onView(withId(R.id.myProjectsTextView)).perform(click()) + Espresso.onView(withId(R.id.recycler_view)).perform( + RecyclerViewActions.actionOnItemAtPosition(0, click()) + ) + + Espresso.openActionBarOverflowOrOptionsMenu(InstrumentationRegistry.getInstrumentation() + .targetContext) + Espresso.onView(withText(R.string.search)).perform(click()) + + Espresso.onView(withId(R.id.search_bar)).perform(click()) + + val viewMatcher = Espresso.onView(withId(R.id.search_bar)).perform( + ViewActions.replaceText(queryString) + ) + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + viewMatcher.perform(ViewActions.pressKey(KeyEvent.KEYCODE_ENTER)) + + for (i in expectedResults.indices) { + val currentFoundName = FinderDataManager.instance.getSearchResultsNames()?.get(i) + assertThat(currentFoundName, `is`(expectedResults[i])) + Espresso.onView(withId(R.id.find_next)).perform(click()) + } + for (i in expectedResults.indices.last downTo 0) { + val currentFoundName = FinderDataManager.instance.getSearchResultsNames()?.get(i) + assertThat(currentFoundName, `is`(expectedResults[i])) + Espresso.onView(withId(R.id.find_previous)).perform(click()) + } + } + + @Test + fun testSceneNotFindableOnLowerLevel(){ + + val queryString = "scene1" + + Espresso.onView(withId(R.id.myProjectsTextView)).perform(click()) + + Espresso.onView(withId(R.id.recycler_view)).perform( + RecyclerViewActions.actionOnItemAtPosition(0, click()) + ) + Espresso.onView(withId(R.id.recycler_view)).perform( + RecyclerViewActions.actionOnItemAtPosition(0, click()) + ) + + Espresso.openActionBarOverflowOrOptionsMenu(InstrumentationRegistry.getInstrumentation().targetContext) + Espresso.onView(withText(R.string.search)).perform(click()) + Espresso.onView(withId(R.id.search_bar)).perform(click()) + + val viewMatcher = Espresso.onView(withId(R.id.search_bar)).perform( + ViewActions.replaceText(queryString) + ) + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + viewMatcher.perform(ViewActions.pressKey(KeyEvent.KEYCODE_ENTER)) + + assertThat(FinderDataManager.instance.currentMatchIndex, `is` (-1)) + } + @Test + fun testSpriteNotFindableOnLowerLevel(){ + + val queryString = "sprite" + + Espresso.onView(withId(R.id.myProjectsTextView)).perform(click()) + + Espresso.onView(withId(R.id.recycler_view)).perform( + RecyclerViewActions.actionOnItemAtPosition(0, click()) + ) + Espresso.onView(withId(R.id.recycler_view)).perform( + RecyclerViewActions.actionOnItemAtPosition(0, click()) + ) + Espresso.onView(withId(R.id.recycler_view)).perform( + RecyclerViewActions.actionOnItemAtPosition(0, click()) + ) + + Espresso.openActionBarOverflowOrOptionsMenu(InstrumentationRegistry.getInstrumentation().targetContext) + Espresso.onView(withText(R.string.search)).perform(click()) + Espresso.onView(withId(R.id.search_bar)).perform(click()) + + val viewMatcher = Espresso.onView(withId(R.id.search_bar)).perform( + ViewActions.replaceText(queryString) + ) + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + viewMatcher.perform(ViewActions.pressKey(KeyEvent.KEYCODE_ENTER)) + + assertThat(FinderDataManager.instance.currentMatchIndex, `is` (-1)) + } + + fun createProject(projectName: String?) { + val project = Project(ApplicationProvider.getApplicationContext(), projectName) + projectManager.currentProject = project + project.sceneList.clear() + val scene1Name = "scene1" + val scene2Name = "testScene2" + val scene3Name = "testScene3" + val scene4Name = "scene4" + + val scene1 = Scene(scene1Name, project) + val scene2 = Scene(scene2Name, project) + val scene3 = Scene(scene3Name, project) + val scene4 = Scene(scene4Name, project) + val sprite1 = Sprite("background") + val sprite2 = Sprite("testSprite2") + val sprite3 = Sprite("testSprite3") + val sprite4 = Sprite("background") + val sprite5 = Sprite("testSprite5") + project.addScene(scene1) + project.addScene(scene2) + project.addScene(scene3) + project.addScene(scene4) + project.getSceneByName(scene1Name).addSprite(sprite1) + project.getSceneByName(scene1Name).addSprite(sprite2) + project.getSceneByName(scene1Name).addSprite(sprite3) + project.getSceneByName(scene2Name).addSprite(sprite4) + project.getSceneByName(scene2Name).addSprite(sprite5) + + projectManager.apply { + setCurrentSceneAndSprite(scene1Name, sprite2.name) + currentSprite = sprite2 // Force update + currentlyEditedScene = project.getSceneByName(scene1Name) + } + + script = StartScript() + script.addBrick(FinishStageBrick()) + projectManager.currentSprite.addScript(script) + + ActionUtils.addSound(projectManager,"testSound3") + ActionUtils.addSound(projectManager, "Sound3") + ActionUtils.addSound(projectManager,"testSound4") + ActionUtils.addSound(projectManager, "Sound4") + ActionUtils.addLook(projectManager,"testLook3") + ActionUtils.addLook(projectManager, "Look3") + ActionUtils.addLook(projectManager,"testLook4") + ActionUtils.addLook(projectManager, "Look4") + + ProjectManager.getInstance().currentProject = project + } +} \ No newline at end of file diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/BaseFinderFragment.kt b/catroid/src/main/java/org/catrobat/catroid/ui/BaseFinderFragment.kt new file mode 100644 index 00000000000..b7499d8641d --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/ui/BaseFinderFragment.kt @@ -0,0 +1,168 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2025 The Catrobat Team + * () + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * An additional term exception under section 7 of the GNU Affero + * General Public License, version 3, is available at + * http://developer.catrobat.org/license_additional_term + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.catrobat.catroid.ui + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager +import android.widget.TextView +import org.catrobat.catroid.ProjectManager +import org.catrobat.catroid.R +import org.catrobat.catroid.common.Nameable +import org.catrobat.catroid.content.Project +import org.catrobat.catroid.content.Scene +import org.catrobat.catroid.content.Sprite +import org.catrobat.catroid.ui.recyclerview.fragment.RecyclerViewFragment +import org.koin.android.ext.android.inject + +abstract class BaseFinderFragment : RecyclerViewFragment() { + + protected val projectManager: ProjectManager by inject() + protected lateinit var currentProject: Project + protected lateinit var currentScene: Scene + protected lateinit var currentSprite: Sprite + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val parentView = super.onCreateView(inflater, container, savedInstanceState) + activity = getActivity() as SpriteActivity? + recyclerView = parentView!!.findViewById(R.id.recycler_view) + currentProject = projectManager.currentProject + currentScene = projectManager.currentlyEditedScene + currentSprite = projectManager.currentSprite + + setupFinderListeners() + + if (FinderDataManager.instance.getInitiatingFragment() != FinderDataManager.FragmentType.NONE) { + val sceneAndSpriteName = createActionBarTitle(1) + finder?.onFragmentChanged(sceneAndSpriteName) + scrollToSearchResult() + } + + return parentView + } + + private fun setupFinderListeners() { + finder?.setOnResultFoundListener(object : Finder.OnResultFoundListener { + override fun onResultFound( + sceneIndex: Int, + spriteIndex: Int, + brickIndex: Int, + type: Int, + totalResults: Int, + textView: TextView? + ) { + currentProject = projectManager.currentProject + currentScene = currentProject.sceneList[sceneIndex] + FinderDataManager.instance.type = type + + if (type == FinderDataManager.FragmentType.SPRITE.id) { + textView?.text = createActionBarTitle(2) + } else { + currentSprite = currentScene.spriteList[spriteIndex] + textView?.text = createActionBarTitle(1) + } + + FinderDataManager.instance.currentMatchIndex = brickIndex + + when (type) { + 1 -> activity.onBackPressed() + 2 -> { + projectManager.setCurrentlyEditedScene(currentScene) + activity.onBackPressed() + } + 3 -> { + projectManager.setCurrentSceneAndSprite(currentScene.name, currentSprite.name) + activity.loadFragment(0) + } + 4 -> { + projectManager.setCurrentSceneAndSprite(currentScene.name, currentSprite.name) + activity.loadFragment(1) + } + 5 -> { + projectManager.setCurrentSceneAndSprite(currentScene.name, currentSprite.name) + activity.loadFragment(2) + } + } + + hideKeyboard() + } + }) + + finder?.setOnCloseListener(object : Finder.OnCloseListener { + override fun onClose() { + finishActionMode() + if (!activity.isFinishing) { + activity.setCurrentSceneAndSprite( + projectManager.currentlyEditedScene, + projectManager.currentSprite + ) + activity.supportActionBar?.title = createActionBarTitle(1) + activity.addTabs() + } + activity.findViewById(R.id.toolbar).visibility = View.VISIBLE + } + }) + + finder?.setOnOpenListener(object : Finder.OnOpenListener { + override fun onOpen() { + if (FinderDataManager.instance.getInitiatingFragment() == FinderDataManager.FragmentType.NONE) { + finder.setInitiatingFragment(getFragmentType()) + setInitiatingPosition() + } + activity.removeTabs() + activity.findViewById(R.id.toolbar).visibility = View.GONE + } + }) + } + + fun createActionBarTitle(flag: Int): String { + return if(flag == 1) { + if (currentProject.sceneList != null && currentProject.sceneList.size == 1) { + currentSprite.name + } else { + currentScene.name + ": " + currentSprite.name + } + } else{ + currentScene.name + } + } + + private fun hideKeyboard() { + val imm = requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(requireView().windowToken, 0) + } + + protected abstract fun getFragmentType(): FinderDataManager.FragmentType + fun setInitiatingPosition() { + val sceneIndex = ProjectManager.getInstance().currentProject.sceneList.indexOf(currentScene) + val spriteIndex = ProjectManager.getInstance() + .currentlyEditedScene + .spriteList + .indexOf(ProjectManager.getInstance().currentSprite) + + finder.setInitiatingScene(arrayOf(sceneIndex,spriteIndex,getFragmentType().id)) + }} diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/Finder.kt b/catroid/src/main/java/org/catrobat/catroid/ui/Finder.kt new file mode 100644 index 00000000000..dfbb932d07f --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/ui/Finder.kt @@ -0,0 +1,456 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2025 The Catrobat Team + * () + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * An additional term exception under section 7 of the GNU Affero + * General Public License, version 3, is available at + * http://developer.catrobat.org/license_additional_term + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.catrobat.catroid.ui + +import android.app.Activity +import android.content.Context +import android.text.Editable +import android.text.TextWatcher +import android.util.AttributeSet +import android.util.Log +import android.view.KeyEvent +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.EditorInfo +import android.view.inputmethod.InputMethodManager +import android.widget.LinearLayout +import android.widget.Spinner +import android.widget.TextView +import org.catrobat.catroid.ProjectManager +import org.catrobat.catroid.R +import org.catrobat.catroid.common.Nameable +import org.catrobat.catroid.content.Scene +import org.catrobat.catroid.content.Sprite +import org.catrobat.catroid.content.bricks.Brick +import org.catrobat.catroid.databinding.ViewScriptFinderBinding +import org.catrobat.catroid.utils.ToastUtil +import org.koin.java.KoinJavaComponent.inject +import java.util.ArrayList +import java.util.Locale + +class Finder(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) { + + private var onResultFoundListener: OnResultFoundListener? = null + private var onCloseListener: OnCloseListener? = null + private var onOpenListener: OnOpenListener? = null + private val projectManager: ProjectManager by inject(ProjectManager::class.java) + private var binding: ViewScriptFinderBinding + + fun showNavigationButtons() { + binding.find.visibility = GONE + binding.findNext.visibility = VISIBLE + binding.findPrevious.visibility = VISIBLE + binding.searchPositionIndicator.visibility = VISIBLE + } + + private fun hideNavigationButtons() { + binding.findNext.visibility = GONE + binding.findPrevious.visibility = GONE + binding.searchPositionIndicator.visibility = GONE + binding.find.visibility = VISIBLE + } + + private fun formatSearchQuery(query: CharSequence): String = query.toString().trim() + .lowercase(Locale.ROOT) + + init { + orientation = VERTICAL + val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater + binding = ViewScriptFinderBinding.inflate(inflater, this) + + binding.find.setOnClickListener { find() } + binding.findNext.setOnClickListener { findNext() } + binding.findPrevious.setOnClickListener { findPrevious() } + binding.close.setOnClickListener { close() } + + val textWatcher: TextWatcher = object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = + Unit + + override fun onTextChanged(newText: CharSequence, start: Int, before: Int, count: + Int) { + if (FinderDataManager.instance.getSearchQuery() == formatSearchQuery(newText)) { + showNavigationButtons() + } else { + hideNavigationButtons() + } + } + + override fun afterTextChanged(s: Editable) = Unit + } + binding.searchBar.addTextChangedListener(textWatcher) + binding.searchBar.setOnEditorActionListener { _, actionId, keyEvent -> + when (actionId) { + EditorInfo.IME_ACTION_SEARCH, EditorInfo.IME_ACTION_DONE -> find() + else -> if (keyEvent.action == KeyEvent.KEYCODE_ENTER) find() + } + false + } + } + + companion object { + val TAG = Finder::class.java.simpleName + + @Suppress("ComplexMethod", "TooGenericExceptionCaught") + fun searchBrickViews(v: View?, searchQuery: String): Boolean { + try { + when (v) { + is Spinner -> { + val selectedItem = v.selectedItem + if (selectedItem is Nameable && selectedItem.name.lowercase(Locale.ROOT).contains(searchQuery)) { + FinderDataManager.instance.addtoSearchResultsNames(selectedItem.name.lowercase(Locale.ROOT)) + return true + } + } + + is ViewGroup -> { + for (i in 0 until v.childCount) { + val child = v.getChildAt(i) + val queryFoundInBrick = searchBrickViews(child, searchQuery) + if (queryFoundInBrick) return true + } + } + + is TextView -> { + if (v.text.toString().lowercase(Locale.ROOT).contains(searchQuery)) { + FinderDataManager.instance.addtoSearchResultsNames(v.text.toString().lowercase(Locale.ROOT)) + return true + } + } + } + } catch (e: NullPointerException) { + Log.e(TAG, Log.getStackTraceString(e)) + } + return false + } + } + + private fun find() { + val query = formatSearchQuery(binding.searchBar.text) + if (query.isNotEmpty()) { + if (FinderDataManager.instance.getSearchQuery() != query) { + FinderDataManager.instance.setSearchQuery(query) + binding.searchBar.setText(query) + fillIndices(query) + binding.find.visibility = GONE + binding.findNext.visibility = VISIBLE + binding.findPrevious.visibility = VISIBLE + } else { + findNext() + } + } else { + ToastUtil.showError( + context, + context.getString(R.string.query_field_is_empty) + ) + } + } + + private fun findNext() { + FinderDataManager.instance.getSearchResults().let { + if (it.isNotEmpty()) { + FinderDataManager.instance.setSearchResultIndex((FinderDataManager.instance.getSearchResultIndex() + 1) % it.size) + updateUI() + } else { + binding.searchPositionIndicator.text = "0/0" + ToastUtil.showError(context, context.getString(R.string.no_results_found)) + } + } + } + + private fun findPrevious() { + FinderDataManager.instance.getSearchResults().let { + if (it.isNotEmpty()) { + FinderDataManager.instance.setSearchResultIndex(if (FinderDataManager.instance + .getSearchResultIndex() == 0) + it.size - 1 else FinderDataManager.instance.getSearchResultIndex() - 1) + updateUI() + } else { + binding.searchPositionIndicator.text = "0/0" + ToastUtil.showError(context, context.getString(R.string.no_results_found)) + } + } + } + + fun onFragmentChanged(sceneAndSpriteName:String) { + openForChangeFragment() + + binding.searchPositionIndicator.text = String.format( + Locale.ROOT, "%d/%d", FinderDataManager.instance.getSearchResultIndex() + 1, + FinderDataManager.instance.getSearchResults().size + ) + binding.sceneAndSpriteName.text = sceneAndSpriteName + } + private fun updateUI() { + val result = FinderDataManager.instance.getSearchResults().get(FinderDataManager.instance.getSearchResultIndex()) + binding.searchPositionIndicator.text = String.format( + Locale.ROOT, "%d/%d", FinderDataManager.instance.getSearchResultIndex() + 1, + FinderDataManager.instance.getSearchResults().size + ) + + result.let { + FinderDataManager.instance.getSearchResults().size.let { it1 -> + onResultFoundListener?.onResultFound( + it[0], it[1], it[2],it[3], it1, + binding.sceneAndSpriteName + ) + } + } + } + + fun fillIndices(query: String) { + FinderDataManager.instance.setSearchResultIndex(-1) + val activeScene = projectManager.currentlyEditedScene + val activeSprite: Sprite? = projectManager.currentSprite + + FinderDataManager.instance.clearSearchResults() + FinderDataManager.instance.clearSearchResultsNames() + startThreadToFillIndices(query, activeScene, activeSprite) + } + + private fun startThreadToFillIndices(query: String, activeScene: Scene, activeSprite: Sprite?) { + Thread { + val activity = context as Activity + if (!activity.isFinishing) { + activity.runOnUiThread { + binding.find.visibility = GONE + binding.findNext.visibility = GONE + binding.findPrevious.visibility = GONE + binding.progressBar.visibility = VISIBLE + } + } + val scenes = projectManager.currentProject.sceneList + + for (i in scenes.indices) { + val scene = scenes[i] + val spriteList = scene.spriteList + for (j in spriteList.indices) { + val sprite = spriteList[j] + val scriptList = sprite.scriptList + val bricks: List = ArrayList() + projectManager.setCurrentSceneAndSprite( + scene.name, + sprite.name + ) + for (script in scriptList) { + script.setParents() + script.addToFlatList(bricks) + } + for (order in FinderDataManager.instance.getSearchOrder()){ + when (order) { + 1 -> { + if (scene.name.lowercase(Locale.ROOT).contains(query)) { + val exists = FinderDataManager.instance.getSearchResults().any { res -> + res[0] == i && res[1] == i && res[2] == i && + res[3] == FinderDataManager.FragmentType.SCENE.id + } + if (!exists) { + FinderDataManager.instance.addtoSearchResults(arrayOf(i, i, i, FinderDataManager.FragmentType.SCENE.id)) + FinderDataManager.instance.addtoSearchResultsNames(scene.name.lowercase(Locale.ROOT)) + val currentFind = arrayOf(-1, -1, FinderDataManager + .FragmentType.SCENE.id) + if (FinderDataManager.instance.getInitiatingPosition() + .contentEquals(currentFind) && FinderDataManager + .instance.getSearchResultIndex() == -1){ + FinderDataManager.instance.setSearchResultIndex( + (FinderDataManager.instance.getSearchResults().size - + 2)) + } + } + } + } + 2 -> { + if (sprite.name.lowercase(Locale.ROOT).contains(query)){ + FinderDataManager.instance.addtoSearchResults(arrayOf(i, j, j, FinderDataManager.FragmentType.SPRITE.id)) + FinderDataManager.instance.addtoSearchResultsNames(sprite.name.lowercase(Locale.ROOT)) + val currentFind = arrayOf(i, -1, FinderDataManager + .FragmentType.SPRITE.id) + if (FinderDataManager.instance.getInitiatingPosition() + .contentEquals(currentFind) && FinderDataManager + .instance.getSearchResultIndex() == -1){ + FinderDataManager.instance.setSearchResultIndex( + (FinderDataManager.instance.getSearchResults().size - + 2)) + } + } + } + 3 -> { + for (k in bricks.indices) { + val brick = bricks[k] + if (searchBrickViews(brick.getView(context), query)) { + FinderDataManager.instance.addtoSearchResults(arrayOf(i, j, k, FinderDataManager.FragmentType.SCRIPT.id)) + val currentFind = arrayOf(i, j, FinderDataManager + .FragmentType.SCRIPT.id) + if (FinderDataManager.instance.getInitiatingPosition() + .contentEquals(currentFind) && FinderDataManager + .instance.getSearchResultIndex() == -1){ + FinderDataManager.instance.setSearchResultIndex( + (FinderDataManager.instance.getSearchResults() + .size) - 2) + } + } + } + } + 4 -> { + val lookList = sprite.lookList + for (k in lookList.indices) { + val look = lookList[k] + if (look.name.lowercase(Locale.ROOT).contains(query)) { + FinderDataManager.instance.addtoSearchResults(arrayOf(i, j, k, FinderDataManager.FragmentType.LOOK.id)) + FinderDataManager.instance.addtoSearchResultsNames(look.name.lowercase(Locale.ROOT)) + val currentFind = arrayOf(i, j, FinderDataManager + .FragmentType.LOOK.id) + if (FinderDataManager.instance.getInitiatingPosition() + .contentEquals(currentFind) && FinderDataManager + .instance.getSearchResultIndex() == -1){ + FinderDataManager.instance.setSearchResultIndex( + (FinderDataManager.instance.getSearchResults() + .size) - 2) + } + } + } + } + 5 -> { + val soundList = sprite.soundList + for (k in soundList.indices) { + val sound = soundList[k] + if (sound.name.lowercase(Locale.ROOT).contains(query)) { + FinderDataManager.instance.addtoSearchResults(arrayOf(i, j, k, FinderDataManager.FragmentType.SOUND.id)) + FinderDataManager.instance.addtoSearchResultsNames(sound.name.lowercase(Locale.ROOT)) + val currentFind = arrayOf(i, j, FinderDataManager + .FragmentType.SOUND.id) + if (FinderDataManager.instance.getInitiatingPosition() + .contentEquals(currentFind) && FinderDataManager + .instance.getSearchResultIndex() == -1){ + FinderDataManager.instance.setSearchResultIndex( + (FinderDataManager.instance.getSearchResults() + .size) - 2) + } + } + } + } + } + } + } + } + + if (!activity.isFinishing) { + activity.runOnUiThread { + binding.findNext.visibility = VISIBLE + binding.findPrevious.visibility = VISIBLE + binding.searchPositionIndicator.visibility = VISIBLE + binding.progressBar.visibility = GONE + if (activeSprite != null){ + projectManager.setCurrentSceneAndSprite( + activeScene.name, + activeSprite.name + ) + } + findNext() + } + } + }.start() + } + + val isOpen: Boolean + get() = visibility == VISIBLE + + fun setInitiatingFragment(fragmentEnum: FinderDataManager.FragmentType){ + FinderDataManager.instance.setInitiatingFragment(fragmentEnum) + } + fun setInitiatingScene(InitiatingSceneID: Array){ + FinderDataManager.instance.setInitiatingScene(InitiatingSceneID) + } + fun open() { + this.visibility = VISIBLE + binding.searchBar.isFocusable + val inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + inputMethodManager.toggleSoftInputFromWindow( + binding.searchBar.applicationWindowToken, + InputMethodManager.SHOW_FORCED, 0 + ) + onOpenListener?.onOpen() + binding.searchBar.requestFocus() + } + fun disableFocusSearchBar(){ + binding.searchBar.isFocusable = false + } + + private fun openForChangeFragment(){ + this.visibility = VISIBLE + showNavigationButtons() + onOpenListener?.onOpen() + binding.searchBar.setText(FinderDataManager.instance.getSearchQuery()) + binding.searchBar.isFocusable = false + } + + fun close() { + this.visibility = GONE + FinderDataManager.instance.clearSearchResults() + FinderDataManager.instance.clearSearchResultsNames() + binding.searchBar.text.clear() + binding.searchBar.isFocusableInTouchMode = true + FinderDataManager.instance.setSearchQuery(null) + FinderDataManager.instance.setInitiatingFragment(FinderDataManager.FragmentType.NONE) + FinderDataManager.instance.type = -1 + FinderDataManager.instance.currentMatchIndex = -1 + onCloseListener?.onClose() + hideNavigationButtons() + this.hideKeyboard() + } + + val isClosed: Boolean + get() = visibility == GONE + + fun setOnResultFoundListener(onResultFoundListener: OnResultFoundListener?) { + this.onResultFoundListener = onResultFoundListener + } + + fun setOnCloseListener(onCloseListener: OnCloseListener?) { + this.onCloseListener = onCloseListener + } + + fun setOnOpenListener(onOpenListener: OnOpenListener?) { + this.onOpenListener = onOpenListener + } + + interface OnResultFoundListener { + fun onResultFound( + sceneIndex: Int, + spriteIndex: Int, + brickIndex: Int, + type: Int, + totalResults: Int, + textView: TextView? + ) + } + + interface OnCloseListener { + fun onClose() + } + + interface OnOpenListener { + fun onOpen() + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/FinderDataManager.kt b/catroid/src/main/java/org/catrobat/catroid/ui/FinderDataManager.kt new file mode 100644 index 00000000000..b62f9fd0132 --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/ui/FinderDataManager.kt @@ -0,0 +1,111 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2024 The Catrobat Team + * () + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * An additional term exception under section 7 of the GNU Affero + * General Public License, version 3, is available at + * http://developer.catrobat.org/license_additional_term + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.catrobat.catroid.ui + +import java.util.ArrayList + +class FinderDataManager { + companion object { + val instance:FinderDataManager by lazy { + FinderDataManager() + } + } + enum class FragmentType(val id: Int){ + NONE(0), + SCENE(1), + SPRITE(2), + SCRIPT(3), + LOOK(4), + SOUND(5) + } + + private var initiatingfragment:FragmentType = FragmentType.NONE + private var initiatingPosition = arrayOf(-1,-1,-1) + private var searchResults = mutableListOf>() + private val searchResultsNames = mutableListOf() + private var searchResultIndex = -1 + private var searchQuery: String? = null + private var searchOrder = arrayOf(1,2,3,4,5) + var startIndex = -1 + var currentMatchIndex = -1 + var type = -1 + + fun getInitiatingPosition(): Array { + return initiatingPosition + } + + fun setInitiatingScene(initiatingSceneID:Array){ + initiatingPosition = initiatingSceneID + } + fun getSearchOrder(): Array{ + return searchOrder + } + + fun setSearchOrder(order: Array){ + searchOrder = order + } + fun setSearchQuery(searchquery: String?){ + searchQuery = searchquery + } + + fun getSearchQuery(): String? { + return searchQuery + } + fun setSearchResultIndex(searchresultIndex:Int){ + searchResultIndex = searchresultIndex + } + + fun getSearchResultIndex(): Int{ + return searchResultIndex + } + fun addtoSearchResults(array: Array){ + searchResults.add(array) + } + fun addtoSearchResultsNames(item: String){ + searchResultsNames.add(item) + } + fun getInitiatingFragment(): FragmentType { + return initiatingfragment + } + + fun setInitiatingFragment(initiatingFragment:FragmentType){ + initiatingfragment = initiatingFragment + } + + fun clearSearchResults(){ + searchResults.clear() + } + + fun getSearchResults(): MutableList> { + return searchResults + } + + fun clearSearchResultsNames(){ + searchResultsNames.clear() + } + + fun getSearchResultsNames(): MutableList { + return searchResultsNames + } +} \ No newline at end of file diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/ScriptFinder.kt b/catroid/src/main/java/org/catrobat/catroid/ui/ScriptFinder.kt deleted file mode 100644 index 37a677186ef..00000000000 --- a/catroid/src/main/java/org/catrobat/catroid/ui/ScriptFinder.kt +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Catroid: An on-device visual programming system for Android devices - * Copyright (C) 2010-2025 The Catrobat Team - * () - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * An additional term exception under section 7 of the GNU Affero - * General Public License, version 3, is available at - * http://developer.catrobat.org/license_additional_term - * - * This program 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package org.catrobat.catroid.ui - -import android.app.Activity -import android.content.Context -import android.text.Editable -import android.text.TextWatcher -import android.util.AttributeSet -import android.util.Log -import android.view.KeyEvent -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.inputmethod.EditorInfo -import android.view.inputmethod.InputMethodManager -import android.widget.LinearLayout -import android.widget.Spinner -import android.widget.TextView -import org.catrobat.catroid.ProjectManager -import org.catrobat.catroid.R -import org.catrobat.catroid.common.Nameable -import org.catrobat.catroid.content.Scene -import org.catrobat.catroid.content.Sprite -import org.catrobat.catroid.content.bricks.Brick -import org.catrobat.catroid.databinding.ViewScriptFinderBinding -import org.catrobat.catroid.utils.ToastUtil -import org.koin.java.KoinJavaComponent.inject -import java.util.ArrayList -import java.util.Locale - -class ScriptFinder(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) { - - private var onResultFoundListener: OnResultFoundListener? = null - private var onCloseListener: OnCloseListener? = null - private var onOpenListener: OnOpenListener? = null - private val projectManager: ProjectManager by inject(ProjectManager::class.java) - - private var searchResults: MutableList>? = null - private var searchResultIndex = 0 - private var searchQuery = "" - - private var binding: ViewScriptFinderBinding - - private fun showNavigationButtons() { - binding.findNext.visibility = VISIBLE - binding.findPrevious.visibility = VISIBLE - binding.searchPositionIndicator.visibility = VISIBLE - binding.find.visibility = GONE - } - - private fun hideNavigationButtons() { - binding.findNext.visibility = GONE - binding.findPrevious.visibility = GONE - binding.searchPositionIndicator.visibility = GONE - binding.find.visibility = VISIBLE - } - - private fun formatSearchQuery(query: CharSequence): String = query.toString().trim() - .toLowerCase(Locale.ROOT) - - init { - orientation = VERTICAL - val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater - binding = ViewScriptFinderBinding.inflate(inflater, this) - - binding.find.setOnClickListener { find() } - binding.findNext.setOnClickListener { findNext() } - binding.findPrevious.setOnClickListener { findPrevious() } - binding.close.setOnClickListener { close() } - - val textWatcher: TextWatcher = object : TextWatcher { - override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = - Unit - - override fun onTextChanged(newText: CharSequence, start: Int, before: Int, count: Int) { - if (searchQuery == formatSearchQuery(newText)) { - showNavigationButtons() - } else { - hideNavigationButtons() - } - } - - override fun afterTextChanged(s: Editable) = Unit - } - binding.searchBar.addTextChangedListener(textWatcher) - binding.searchBar.setOnEditorActionListener { _, actionId, keyEvent -> - when (actionId) { - EditorInfo.IME_ACTION_SEARCH, EditorInfo.IME_ACTION_DONE -> find() - else -> if (keyEvent.action == KeyEvent.KEYCODE_ENTER) find() - } - false - } - } - - companion object { - val TAG = ScriptFinder::class.java.simpleName - - @Suppress("ComplexMethod", "TooGenericExceptionCaught") - fun searchBrickViews(v: View?, searchQuery: String): Boolean { - try { - if (v is Spinner) { - val selectedItem = v.selectedItem - if (selectedItem is Nameable && selectedItem.name.toLowerCase(Locale.ROOT) - .contains(searchQuery) - ) { - return true - } - } else if (v is ViewGroup) { - for (i in 0 until v.childCount) { - val child = v.getChildAt(i) - val queryFoundInBrick = searchBrickViews(child, searchQuery) - if (queryFoundInBrick) { - return true - } - } - } else if (v is TextView && v.text.toString().toLowerCase(Locale.ROOT) - .contains(searchQuery) - ) { - return true - } - } catch (e: NullPointerException) { - Log.e(TAG, Log.getStackTraceString(e)) - } - return false - } - } - - private fun find() { - val query = formatSearchQuery(binding.searchBar.text) - if (query.isNotEmpty()) { - if (searchQuery != query) { - searchQuery = query - fillIndices(query) - binding.find.visibility = GONE - binding.findNext.visibility = VISIBLE - binding.findPrevious.visibility = VISIBLE - } else if (searchResults != null) { - findNext() - } - } else { - ToastUtil.showError( - context, - context.getString(R.string.query_field_is_empty) - ) - } - } - - private fun findNext() { - searchResults?.let { - if (it.isNotEmpty()) { - searchResultIndex = (searchResultIndex + 1) % it.size - updateUI() - } else { - binding.searchPositionIndicator.text = "0/0" - ToastUtil.showError(context, context.getString(R.string.no_results_found)) - } - } - } - - private fun findPrevious() { - searchResults?.let { - if (it.isNotEmpty()) { - searchResultIndex = - if (searchResultIndex == 0) it.size - 1 else searchResultIndex - 1 - updateUI() - } else { - binding.searchPositionIndicator.text = "0/0" - ToastUtil.showError(context, context.getString(R.string.no_results_found)) - } - } - } - - private fun updateUI() { - val result = searchResults?.get(searchResultIndex) - binding.searchPositionIndicator.text = String.format( - Locale.ROOT, "%d/%d", searchResultIndex + 1, - searchResults?.size - ) - - result?.let { - searchResults?.size?.let { it1 -> - onResultFoundListener?.onResultFound( - it[0], it[1], it[2], it1, - binding.sceneAndSpriteName - ) - } - } - } - - fun fillIndices(query: String) { - searchResultIndex = -1 - val activeScene = projectManager.currentlyEditedScene - val activeSprite = projectManager.currentSprite - - if (searchResults != null) { - searchResults?.clear() - } else { - searchResults = ArrayList() - } - startThreadToFillIndices(query, activeScene, activeSprite) - } - - private fun startThreadToFillIndices(query: String, activeScene: Scene, activeSprite: Sprite) { - Thread { - val activity = context as Activity - if (!activity.isFinishing) { - activity.runOnUiThread { - binding.find.visibility = GONE - binding.findNext.visibility = GONE - binding.findPrevious.visibility = GONE - binding.progressBar.visibility = VISIBLE - } - } - val scenes = projectManager.currentProject.sceneList - for (i in scenes.indices) { - val scene = scenes[i] - val spriteList = scene.spriteList - for (j in spriteList.indices) { - val sprite = spriteList[j] - val scriptList = sprite.scriptList - val bricks: List = ArrayList() - projectManager.setCurrentSceneAndSprite( - scene.name, - sprite.name - ) - for (script in scriptList) { - script.setParents() - script.addToFlatList(bricks) - } - for (k in bricks.indices) { - val brick = bricks[k] - if (searchBrickViews(brick.getView(context), query)) { - searchResults?.add(arrayOf(i, j, k)) - } - } - } - } - if (!activity.isFinishing) { - activity.runOnUiThread { - binding.findNext.visibility = VISIBLE - binding.findPrevious.visibility = VISIBLE - binding.searchPositionIndicator.visibility = VISIBLE - binding.progressBar.visibility = GONE - projectManager.setCurrentSceneAndSprite( - activeScene.name, - activeSprite.name - ) - findNext() - } - } - }.start() - } - - val isOpen: Boolean - get() = visibility == VISIBLE - - fun open() { - this.visibility = VISIBLE - val inputMethodManager = - context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - inputMethodManager.toggleSoftInputFromWindow( - binding.searchBar.applicationWindowToken, - InputMethodManager.SHOW_FORCED, 0 - ) - onOpenListener?.onOpen() - binding.searchBar.requestFocus() - } - - fun close() { - this.visibility = GONE - searchResults?.clear() - - binding.searchBar.setText("") - searchQuery = "" - onCloseListener?.onClose() - this.hideKeyboard() - } - - val isClosed: Boolean - get() = visibility == GONE - - fun setOnResultFoundListener(onResultFoundListener: OnResultFoundListener?) { - this.onResultFoundListener = onResultFoundListener - } - - fun setOnCloseListener(onCloseListener: OnCloseListener?) { - this.onCloseListener = onCloseListener - } - - fun setOnOpenListener(onOpenListener: OnOpenListener?) { - this.onOpenListener = onOpenListener - } - - interface OnResultFoundListener { - fun onResultFound( - sceneIndex: Int, - spriteIndex: Int, - brickIndex: Int, - totalResults: Int, - textView: TextView? - ) - } - - interface OnCloseListener { - fun onClose() - } - - interface OnOpenListener { - fun onOpen() - } -} diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/SpriteActivity.java b/catroid/src/main/java/org/catrobat/catroid/ui/SpriteActivity.java index da15cc5ba8f..c3d51c0adbc 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/SpriteActivity.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/SpriteActivity.java @@ -289,14 +289,6 @@ public void onBackPressed() { ((ScriptFragment) currentFragment).cancelMove(); return; } - if (((ScriptFragment) currentFragment).isFinderOpen()) { - ((ScriptFragment) currentFragment).closeFinder(); - return; - } - if (((ScriptFragment) currentFragment).isCurrentlyHighlighted()) { - ((ScriptFragment) currentFragment).cancelHighlighting(); - return; - } } else if (currentFragment instanceof FormulaEditorFragment) { ((FormulaEditorFragment) currentFragment).exitFormulaEditorFragment(); return; diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/adapter/LookAdapter.kt b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/adapter/LookAdapter.kt index eec771f78d9..c4166a2b976 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/adapter/LookAdapter.kt +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/adapter/LookAdapter.kt @@ -25,6 +25,7 @@ package org.catrobat.catroid.ui.recyclerview.adapter import android.view.View import org.catrobat.catroid.R import org.catrobat.catroid.common.LookData +import org.catrobat.catroid.ui.FinderDataManager import org.catrobat.catroid.ui.recyclerview.viewholder.ExtendedViewHolder import org.catrobat.catroid.utils.FileMetaDataExtractor import java.util.Locale @@ -34,6 +35,11 @@ class LookAdapter(items: List?) : ExtendedRVAdapter(items) val item = items[position] holder.title.text = item?.name holder.image.setImageBitmap(item?.thumbnailBitmap) + if (position == FinderDataManager.instance.currentMatchIndex) { + holder.itemView.setBackgroundResource(R.drawable.button_background_pressed) + } else { + holder.itemView.setBackgroundResource(R.drawable.button_background_selector) + } if (showDetails) { val measure = item?.measure val measureString = measure?.get(0).toString() + " x " + measure?.get(1) diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/adapter/MultiViewSpriteAdapter.java b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/adapter/MultiViewSpriteAdapter.java index 9808f85ea66..0f3e22a02e4 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/adapter/MultiViewSpriteAdapter.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/adapter/MultiViewSpriteAdapter.java @@ -35,6 +35,7 @@ import org.catrobat.catroid.content.GroupItemSprite; import org.catrobat.catroid.content.GroupSprite; import org.catrobat.catroid.content.Sprite; +import org.catrobat.catroid.ui.FinderDataManager; import org.catrobat.catroid.ui.recyclerview.viewholder.CheckableViewHolder; import org.catrobat.catroid.ui.recyclerview.viewholder.ExtendedViewHolder; @@ -102,15 +103,30 @@ public void onBindViewHolder(ExtendedViewHolder holder, int position) { context.getTheme()); holder.image.setImageDrawable(drawable); holder.checkBox.setVisibility(GONE); + if(isThisFoundObject(position)){ + highlightTheFoundObject(holder); + } + else{ + cancelHighlightTheFoundObject(holder); + } return; } - if (holder.getItemViewType() == BACKGROUND) { + else if (holder.getItemViewType() == BACKGROUND) { holder.itemView.setOnLongClickListener(null); holder.checkBox.setVisibility(GONE); + if (isThisFoundObject(position)) { + View viewBackgroundButton = + holder.itemView.findViewById(R.id.view_holder_background); + viewBackgroundButton.setBackgroundResource(R.drawable.button_background_pressed); + } + else{ + View viewBackgroundButton = holder.itemView.findViewById(R.id.view_holder_background); + viewBackgroundButton.setBackgroundResource(R.drawable.button_background_selector); + } } - if (holder.getItemViewType() == SPRITE_GROUP_ITEM) { + else if (holder.getItemViewType() == SPRITE_GROUP_ITEM) { LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams .MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); holder.itemView.setLayoutParams(params); @@ -118,6 +134,21 @@ public void onBindViewHolder(ExtendedViewHolder holder, int position) { params.height = 0; holder.itemView.setLayoutParams(params); } + if(isThisFoundObject(position)){ + highlightTheFoundObject(holder); + } + else{ + cancelHighlightTheFoundObject(holder); + } + } + + else{ + if(isThisFoundObject(position)){ + highlightTheFoundObject(holder); + } + else{ + cancelHighlightTheFoundObject(holder); + } } Bitmap lookData = null; @@ -126,6 +157,7 @@ public void onBindViewHolder(ExtendedViewHolder holder, int position) { } holder.image.setImageBitmap(lookData); + if (showDetails) { holder.details.setText(String.format(Locale.getDefault(), context.getString(R.string.sprite_details), @@ -137,7 +169,15 @@ public void onBindViewHolder(ExtendedViewHolder holder, int position) { holder.details.setVisibility(GONE); } } - + private boolean isThisFoundObject(int position){ + return position == FinderDataManager.Companion.getInstance().getCurrentMatchIndex(); + } + private void cancelHighlightTheFoundObject(ExtendedViewHolder holder){ + holder.itemView.setBackgroundResource(R.drawable.button_background_selector); + } + private void highlightTheFoundObject(ExtendedViewHolder holder){ + holder.itemView.setBackgroundResource(R.drawable.button_background_pressed); + } @Override public @ViewType int getItemViewType(int position) { if (position == 0) { diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/adapter/SceneAdapter.java b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/adapter/SceneAdapter.java index 0c73bc1d912..c436958796c 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/adapter/SceneAdapter.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/adapter/SceneAdapter.java @@ -23,6 +23,7 @@ package org.catrobat.catroid.ui.recyclerview.adapter; +import android.graphics.Color; import android.view.View; import org.catrobat.catroid.ProjectManager; @@ -30,12 +31,15 @@ import org.catrobat.catroid.content.Scene; import org.catrobat.catroid.content.Sprite; import org.catrobat.catroid.io.ProjectAndSceneScreenshotLoader; +import org.catrobat.catroid.ui.FinderDataManager; import org.catrobat.catroid.ui.recyclerview.viewholder.ExtendedViewHolder; import java.io.File; import java.util.List; import java.util.Locale; +import static android.view.View.GONE; + public class SceneAdapter extends ExtendedRVAdapter { public SceneAdapter(List items) { @@ -54,6 +58,12 @@ public void onBindViewHolder(ExtendedViewHolder holder, int position) { loader.loadAndShowScreenshot(projectDir.getName(), item.getDirectory().getName(), false, holder.image); + if (position == FinderDataManager.Companion.getInstance().getCurrentMatchIndex()) { + holder.itemView.setBackgroundResource(R.drawable.button_background_pressed); + } else { + holder.itemView.setBackgroundResource(R.drawable.button_background_selector); + } + if (showDetails) { holder.details.setText(String.format(Locale.getDefault(), holder.itemView.getContext().getString(R.string.scene_details), diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/adapter/SoundAdapter.kt b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/adapter/SoundAdapter.kt index 32fb683067a..0f438463748 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/adapter/SoundAdapter.kt +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/adapter/SoundAdapter.kt @@ -28,6 +28,7 @@ import android.text.format.DateUtils import android.util.Log import org.catrobat.catroid.R import org.catrobat.catroid.common.SoundInfo +import org.catrobat.catroid.ui.FinderDataManager import org.catrobat.catroid.ui.recyclerview.viewholder.ExtendedViewHolder import org.catrobat.catroid.utils.FileMetaDataExtractor import java.io.IOException @@ -47,6 +48,12 @@ class SoundAdapter(items: List?) : ExtendedRVAdapter(ite holder?.title?.text = item?.name holder?.image?.setImageResource(R.drawable.ic_media_play_dark) + if (position == FinderDataManager.instance.currentMatchIndex) { + holder?.itemView?.setBackgroundResource(R.drawable.button_background_pressed) + } else { + holder?.itemView?.setBackgroundResource(R.drawable.button_background_selector) + } + holder?.image?.setOnClickListener { if (mediaPlayer.isPlaying) { holder.image.setImageResource(R.drawable.ic_media_play_dark) diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/adapter/SpriteAdapter.java b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/adapter/SpriteAdapter.java index 271733da3bf..3b1a103ffd9 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/adapter/SpriteAdapter.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/adapter/SpriteAdapter.java @@ -31,6 +31,7 @@ import org.catrobat.catroid.content.Sprite; import org.catrobat.catroid.ui.recyclerview.viewholder.ExtendedViewHolder; + import java.util.List; import java.util.Locale; diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/LookListFragment.kt b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/LookListFragment.kt index e347551b89a..265227f9921 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/LookListFragment.kt +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/LookListFragment.kt @@ -36,6 +36,8 @@ import org.catrobat.catroid.common.Constants import org.catrobat.catroid.common.LookData import org.catrobat.catroid.common.SharedPreferenceKeys.SHOW_DETAILS_LOOKS_PREFERENCE_KEY import org.catrobat.catroid.io.StorageOperations +import org.catrobat.catroid.ui.BaseFinderFragment +import org.catrobat.catroid.ui.FinderDataManager import org.catrobat.catroid.ui.SpriteActivity import org.catrobat.catroid.ui.UiUtils import org.catrobat.catroid.ui.controller.BackpackListManager @@ -49,17 +51,19 @@ import org.koin.android.ext.android.inject import java.io.IOException import java.util.ArrayList -class LookListFragment : RecyclerViewFragment() { +class LookListFragment : BaseFinderFragment() { private val lookController = LookController() private var currentItem: LookData? = null - private val projectManager: ProjectManager by inject() - companion object { @JvmField val TAG = LookListFragment::class.java.simpleName } + override fun getFragmentType(): FinderDataManager.FragmentType { + return FinderDataManager.FragmentType.LOOK + } + override fun initializeAdapter() { sharedPreferenceDetailsKey = SHOW_DETAILS_LOOKS_PREFERENCE_KEY val items = projectManager.currentSprite.lookList @@ -72,7 +76,7 @@ class LookListFragment : RecyclerViewFragment() { super.onPrepareOptionsMenu(menu) menu.findItem(R.id.catblocks_reorder_scripts).isVisible = false menu.findItem(R.id.catblocks).isVisible = false - menu.findItem(R.id.find).isVisible = false + menu.findItem(R.id.find).isVisible = true } override fun onResume() { @@ -253,6 +257,7 @@ class LookListFragment : RecyclerViewFragment() { R.id.project_options, R.id.edit, R.id.from_local, + R.id.find ) val popupMenu = UiUtils.createSettingsPopUpMenu(view, requireContext(), R.menu .menu_project_activity, hiddenOptionMenuIds) diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/RecyclerViewFragment.java b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/RecyclerViewFragment.java index d1a72a6942c..b8da482d67a 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/RecyclerViewFragment.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/RecyclerViewFragment.java @@ -39,6 +39,8 @@ import org.catrobat.catroid.common.Nameable; import org.catrobat.catroid.merge.NewProjectNameTextWatcher; import org.catrobat.catroid.ui.BottomBar; +import org.catrobat.catroid.ui.Finder; +import org.catrobat.catroid.ui.SpriteActivity; import org.catrobat.catroid.ui.controller.BackpackListManager; import org.catrobat.catroid.ui.recyclerview.adapter.ExtendedRVAdapter; import org.catrobat.catroid.ui.recyclerview.adapter.MultiViewSpriteAdapter; @@ -50,6 +52,7 @@ import org.catrobat.catroid.ui.recyclerview.util.UniqueNameProvider; import org.catrobat.catroid.ui.recyclerview.viewholder.CheckableViewHolder; import org.catrobat.catroid.utils.ToastUtil; +import org.catrobat.catroid.ui.FinderDataManager; import org.jetbrains.annotations.NotNull; import java.lang.annotation.Retention; @@ -88,6 +91,9 @@ public abstract class RecyclerViewFragment extends Fragment protected View parentView; protected RecyclerView recyclerView; + protected Finder finder; + protected SpriteActivity activity; + protected TextView emptyView; protected ExtendedRVAdapter adapter; @@ -239,6 +245,7 @@ protected void resetActionModeParameters() { @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { parentView = inflater.inflate(R.layout.fragment_list_view, container, false); + finder = parentView.findViewById(R.id.findview); recyclerView = parentView.findViewById(R.id.recycler_view); emptyView = parentView.findViewById(R.id.empty_view); setShowProgressBar(true); @@ -349,6 +356,9 @@ public boolean onOptionsItemSelected(MenuItem item) { case R.id.merge: startActionMode(MERGE); break; + case R.id.find: + finder.open(); + break; case R.id.show_details: adapter.showDetails = !adapter.showDetails; PreferenceManager.getDefaultSharedPreferences(getActivity()) @@ -580,4 +590,20 @@ protected void mergeProjects(List selectedProjects, String mergeProjectName) ToastUtil.showSuccess(getContext(), R.string.merging_project_text); finishActionMode(); } + + protected void scrollToSearchResult() { + int indexSearch = FinderDataManager.Companion.getInstance().getSearchResultIndex(); + List searchResults = + FinderDataManager.Companion.getInstance().getSearchResults(); + + if (searchResults != null && indexSearch < searchResults.size()) { + Integer[] result = searchResults.get(indexSearch); + if (result != null && result.length > 2) { + Integer value = result[2]; + if (value != null) { + recyclerView.scrollToPosition(value); + } + } + } + } } diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/SceneListFragment.kt b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/SceneListFragment.kt index 50a2fe889ef..ba34560eec6 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/SceneListFragment.kt +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/SceneListFragment.kt @@ -22,21 +22,32 @@ */ package org.catrobat.catroid.ui.recyclerview.fragment +import android.content.Context import android.content.Intent import android.util.Log import android.view.Menu +import android.os.Bundle import android.view.View import androidx.annotation.PluralsRes import androidx.appcompat.app.AppCompatActivity import org.catrobat.catroid.ProjectManager import org.catrobat.catroid.R import org.catrobat.catroid.common.Constants +import android.view.LayoutInflater +import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager +import android.widget.TextView + import org.catrobat.catroid.common.SharedPreferenceKeys +import org.catrobat.catroid.content.Project import org.catrobat.catroid.content.Scene import org.catrobat.catroid.content.Sprite import org.catrobat.catroid.io.XstreamSerializer import org.catrobat.catroid.io.asynctask.ProjectLoader.ProjectLoadListener import org.catrobat.catroid.io.asynctask.loadProject +import org.catrobat.catroid.ui.FinderDataManager +import org.catrobat.catroid.ui.ProjectActivity +import org.catrobat.catroid.ui.Finder import org.catrobat.catroid.ui.UiUtils import org.catrobat.catroid.ui.controller.BackpackListManager import org.catrobat.catroid.ui.recyclerview.adapter.SceneAdapter @@ -53,8 +64,72 @@ class SceneListFragment : RecyclerViewFragment(), private val sceneController = SceneController() private val projectManager: ProjectManager by inject() + private lateinit var currentProject: Project + private lateinit var currentScene: Scene + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val parentView = super.onCreateView(inflater, container, savedInstanceState) + val activity = getActivity() as ProjectActivity + recyclerView = parentView!!.findViewById(R.id.recycler_view) + currentProject = ProjectManager.getInstance().currentProject + currentScene = ProjectManager.getInstance().currentlyEditedScene + + finder?.setOnResultFoundListener(object : Finder.OnResultFoundListener { + override fun onResultFound( + sceneIndex: Int, + spriteIndex: Int, + brickIndex: Int, + type: Int, + totalResults: Int, + textView: TextView? + ) { + + currentScene = currentProject.sceneList[sceneIndex] + FinderDataManager.instance.type = type + FinderDataManager.instance.currentMatchIndex = brickIndex + + if (type != FinderDataManager.FragmentType.SCENE.id) { + onItemClick(currentScene,MultiSelectionManager()) + } else { + textView?.text = createActionBarTitle() + initializeAdapter() + adapter.notifyDataSetChanged() + scrollToSearchResult() + finder.disableFocusSearchBar() + } + hideKeyboard() + } + }) + finder?.setOnCloseListener(object : Finder.OnCloseListener { + override fun onClose() { + activity.findViewById(R.id.toolbar).visibility = View.VISIBLE + finishActionMode() + FinderDataManager.instance.setSearchResultIndex(-1) + } + }) + + finder?.setOnOpenListener(object : Finder.OnOpenListener { + override fun onOpen() { + activity.findViewById(R.id.toolbar).visibility = View.GONE + finder.setInitiatingFragment(FinderDataManager.FragmentType.SCENE) + finder.setInitiatingScene(arrayOf(-1,-1,FinderDataManager + .FragmentType + .SCENE.id)) + } + }) + + return parentView + } + fun createActionBarTitle(): String { + return currentProject.name + } + private fun hideKeyboard() { + val imm = requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(requireView().windowToken, 0) + } override fun onResume() { super.onResume() + val currentProject = projectManager.currentProject if (currentProject.sceneList.size < 2) { projectManager.currentlyEditedScene = currentProject.defaultScene @@ -62,8 +137,17 @@ class SceneListFragment : RecyclerViewFragment(), } projectManager.currentlyEditedScene = currentProject.defaultScene (requireActivity() as AppCompatActivity).supportActionBar?.title = currentProject.name - } + if (FinderDataManager.instance.getInitiatingFragment() != FinderDataManager.FragmentType.NONE) { + val sceneAndSpriteName = createActionBarTitle() + finder.onFragmentChanged(sceneAndSpriteName) + scrollToSearchResult() + hideKeyboard() + } + else{ + finder.close() + } + } private fun switchToSpriteListFragment() { parentFragmentManager.beginTransaction() .replace(R.id.fragment_container, SpriteListFragment(), SpriteListFragment.TAG) @@ -232,6 +316,7 @@ class SceneListFragment : RecyclerViewFragment(), R.id.project_options, R.id.edit, R.id.from_local, + R.id.find ) val popupMenu = UiUtils.createSettingsPopUpMenu(view, requireContext(), R.menu .menu_project_activity, hiddenOptionMenuIds) diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java index 202d3c0403e..6d162bcbdfb 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java @@ -40,6 +40,7 @@ import android.widget.ListAdapter; import org.catrobat.catroid.BuildConfig; + import org.catrobat.catroid.ProjectManager; import org.catrobat.catroid.R; import org.catrobat.catroid.common.ScreenValues; @@ -64,8 +65,10 @@ import org.catrobat.catroid.io.asynctask.ProjectLoader; import org.catrobat.catroid.io.asynctask.ProjectSaver; import org.catrobat.catroid.ui.BottomBar; -import org.catrobat.catroid.ui.ScriptFinder; +import org.catrobat.catroid.ui.FinderDataManager; +import org.catrobat.catroid.ui.Finder; import org.catrobat.catroid.ui.SpriteActivity; +import org.catrobat.catroid.ui.SpriteActivityOnTabSelectedListenerKt; import org.catrobat.catroid.ui.UiUtils; import org.catrobat.catroid.ui.controller.BackpackListManager; import org.catrobat.catroid.ui.controller.RecentBrickListManager; @@ -137,7 +140,7 @@ public class ScriptFragment extends ListFragment implements private ActionMode actionMode; private BrickAdapter adapter; private BrickListView listView; - private ScriptFinder scriptFinder; + private Finder finder; private String currentSceneName; private String currentSpriteName; private int undoBrickPosition; @@ -274,7 +277,6 @@ private void resetActionModeParameters() { actionMode = null; adapter.setCheckBoxMode(BrickAdapter.NONE); } - @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = View.inflate(getActivity(), R.layout.fragment_script, null); @@ -291,29 +293,58 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa activity = (SpriteActivity) getActivity(); SettingsFragment.setToChosenLanguage(activity); - scriptFinder = view.findViewById(R.id.findview); - scriptFinder.setOnResultFoundListener((sceneIndex, spriteIndex, brickIndex, totalResults, + finder = view.findViewById(R.id.findview); + finder.setOnResultFoundListener((sceneIndex, spriteIndex, brickIndex,type, + totalResults, textView ) -> { Project currentProject = ProjectManager.getInstance().getCurrentProject(); Scene currentScene = currentProject.getSceneList().get(sceneIndex); - Sprite currentSprite = currentScene.getSpriteList().get(spriteIndex); - - textView.setText(createActionBarTitle(currentProject, - currentScene, - currentSprite)); + Sprite currentSprite = null; + if(type != FinderDataManager.FragmentType.SCENE.getId()){ + currentSprite = currentScene.getSpriteList().get(spriteIndex); + ProjectManager.getInstance().setCurrentSceneAndSprite(currentScene.getName(), currentSprite.getName()); + } + FinderDataManager.Companion.getInstance().setType(type); - ProjectManager.getInstance().setCurrentSceneAndSprite(currentScene.getName(), - currentSprite.getName()); + if(type != FinderDataManager.FragmentType.SPRITE.getId() && type != FinderDataManager.FragmentType.SCENE.getId()) { + textView.setText(createActionBarTitle(currentProject, currentScene, currentSprite,1)); + } + else{ + textView.setText(createActionBarTitle(currentProject, currentScene, currentSprite,2)); + } - adapter.updateItems(currentSprite); - adapter.notifyDataSetChanged(); - listView.smoothScrollToPosition(brickIndex); - highlightBrickAtIndex(brickIndex); - hideKeyboard(); + FinderDataManager.Companion.getInstance().setCurrentMatchIndex(brickIndex); + + if(type != FinderDataManager.FragmentType.SCRIPT.getId()){ + switch (type){ + case 1: + activity.onBackPressed(); + break; + case 2: + ProjectManager.getInstance().setCurrentlyEditedScene(currentScene); + activity.onBackPressed(); + break; + case 4: + SpriteActivityOnTabSelectedListenerKt.loadFragment(activity,1); + ProjectManager.getInstance().setCurrentSceneAndSprite(currentScene.getName(), currentSprite.getName()); + break; + case 5: + SpriteActivityOnTabSelectedListenerKt.loadFragment(activity,2); + ProjectManager.getInstance().setCurrentSceneAndSprite(currentScene.getName(), currentSprite.getName()); + break; + } + } + else{ + adapter.updateItems(currentSprite); + adapter.notifyDataSetChanged(); + listView.smoothScrollToPosition(brickIndex); + highlightBrickAtIndex(brickIndex); + hideKeyboard(); + } }); - scriptFinder.setOnCloseListener(() -> { + finder.setOnCloseListener(() -> { listView.cancelHighlighting(); finishActionMode(); if (activity != null && !activity.isFinishing()) { @@ -325,20 +356,62 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa activity.findViewById(R.id.toolbar).setVisibility(View.VISIBLE); }); - scriptFinder.setOnOpenListener(() -> { + finder.setOnOpenListener(() -> { + if (FinderDataManager.Companion.getInstance().getInitiatingFragment() == FinderDataManager.FragmentType.NONE) { + finder.setInitiatingFragment(FinderDataManager.FragmentType.SCRIPT); + setInitiatingPosition(); + } activity.removeTabs(); activity.findViewById(R.id.toolbar).setVisibility(View.GONE); }); + if (FinderDataManager.Companion.getInstance().getInitiatingFragment() != FinderDataManager.FragmentType.NONE) { + String sceneAndSpriteName = + createActionBarTitle(ProjectManager.getInstance().getCurrentProject(), + ProjectManager.getInstance().getStartScene(), + ProjectManager.getInstance().getCurrentSprite(),1); + finder.onFragmentChanged(sceneAndSpriteName); + int indexSearch = FinderDataManager.Companion.getInstance().getSearchResultIndex(); + int brickIndex = FinderDataManager.Companion.getInstance().getSearchResults().get(indexSearch)[2]; + listView.smoothScrollToPosition(brickIndex); + highlightBrickAtIndex(brickIndex); + } + setHasOptionsMenu(true); return view; } - public String createActionBarTitle(Project currentProject, Scene currentScene, Sprite currentSprite) { - if (currentProject.getSceneList().size() == 1) { - return currentSprite.getName(); - } else { - return currentScene.getName() + ": " + currentSprite.getName(); + public void setInitiatingPosition() { + ProjectManager projectManager = ProjectManager.getInstance(); + + int sceneIndex = projectManager.getCurrentProject() + .getSceneList() + .indexOf(projectManager.getCurrentlyEditedScene()); + + int spriteIndex = projectManager.getCurrentlyEditedScene() + .getSpriteList() + .indexOf(projectManager.getCurrentSprite()); + + + Integer[] initiatingPosition = new Integer[]{sceneIndex, spriteIndex, FinderDataManager + .FragmentType + .SCENE.getId()}; + + finder.setInitiatingScene(initiatingPosition); + } + + + public String createActionBarTitle(Project currentProject, Scene currentScene, + Sprite currentSprite, int flag) { + if(flag == 1) { + if (currentProject.getSceneList().size() == 1) { + return currentSprite.getName(); + } else { + return currentScene.getName() + ": " + currentSprite.getName(); + } + } + else{ + return currentScene.getName(); } } @@ -356,7 +429,7 @@ private void hideKeyboard() { @Override public void onDestroyView() { super.onDestroyView(); - if (scriptFinder.isOpen() && activity != null) { + if (finder.isOpen() && activity != null) { activity.findViewById(R.id.toolbar).setVisibility(View.VISIBLE); } } @@ -477,7 +550,7 @@ public boolean onOptionsItemSelected(MenuItem item) { switchToCatblocks(); break; case R.id.find: - scriptFinder.open(); + finder.open(); break; default: return super.onOptionsItemSelected(item); @@ -1096,12 +1169,12 @@ public int getActionModeType() { } public boolean isFinderOpen() { - return scriptFinder.isOpen(); + return finder.isOpen(); } public void closeFinder() { - if (!scriptFinder.isClosed()) { - scriptFinder.close(); + if (!finder.isClosed()) { + finder.close(); } } } diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/SoundListFragment.kt b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/SoundListFragment.kt index 4b9779a48ee..2892a0a2f9f 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/SoundListFragment.kt +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/SoundListFragment.kt @@ -33,6 +33,8 @@ import org.catrobat.catroid.R import org.catrobat.catroid.common.SharedPreferenceKeys import org.catrobat.catroid.common.SoundInfo import org.catrobat.catroid.pocketmusic.PocketMusicActivity +import org.catrobat.catroid.ui.BaseFinderFragment +import org.catrobat.catroid.ui.FinderDataManager import org.catrobat.catroid.ui.UiUtils import org.catrobat.catroid.ui.controller.BackpackListManager import org.catrobat.catroid.ui.recyclerview.adapter.SoundAdapter @@ -41,19 +43,22 @@ import org.catrobat.catroid.ui.recyclerview.backpack.BackpackActivity import org.catrobat.catroid.ui.recyclerview.controller.SoundController import org.catrobat.catroid.utils.SnackbarUtil import org.catrobat.catroid.utils.ToastUtil -import org.koin.android.ext.android.inject import java.io.IOException -class SoundListFragment : RecyclerViewFragment() { +class SoundListFragment : BaseFinderFragment() { private val soundController = SoundController() - private val projectManager: ProjectManager by inject() + companion object { @JvmField val TAG = SoundListFragment::class.java.simpleName } + override fun getFragmentType(): FinderDataManager.FragmentType { + return FinderDataManager.FragmentType.SOUND + } + override fun initializeAdapter() { sharedPreferenceDetailsKey = SharedPreferenceKeys.SHOW_DETAILS_SOUNDS_PREFERENCE_KEY val items = projectManager.currentSprite.soundList @@ -77,7 +82,7 @@ class SoundListFragment : RecyclerViewFragment() { menu.findItem(R.id.catblocks_reorder_scripts).isVisible = false menu.findItem(R.id.catblocks).isVisible = false - menu.findItem(R.id.find).isVisible = false + menu.findItem(R.id.find).isVisible = true } override fun packItems(selectedItems: List) { @@ -205,6 +210,7 @@ class SoundListFragment : RecyclerViewFragment() { R.id.project_options, R.id.edit, R.id.from_local, + R.id.find ) val popupMenu = UiUtils.createSettingsPopUpMenu(view, requireContext(), R.menu .menu_project_activity, hiddenOptionMenuIds) diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/SpriteListFragment.kt b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/SpriteListFragment.kt index ccb492c7819..139b2a6782f 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/SpriteListFragment.kt +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/SpriteListFragment.kt @@ -24,14 +24,20 @@ package org.catrobat.catroid.ui.recyclerview.fragment import android.annotation.SuppressLint import android.app.Activity +import android.content.Context import android.content.DialogInterface import android.content.Intent import android.net.Uri +import android.os.Bundle import android.preference.PreferenceManager import android.util.Log +import android.view.LayoutInflater import android.view.Menu import android.view.MenuItem import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager +import android.widget.TextView import androidx.annotation.PluralsRes import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.ItemTouchHelper @@ -41,13 +47,19 @@ import org.catrobat.catroid.R import org.catrobat.catroid.common.Constants import org.catrobat.catroid.common.Constants.TMP_IMAGE_FILE_NAME import org.catrobat.catroid.common.SharedPreferenceKeys +import org.catrobat.catroid.content.GroupItemSprite import org.catrobat.catroid.content.GroupSprite +import org.catrobat.catroid.content.Project +import org.catrobat.catroid.content.Scene import org.catrobat.catroid.content.Sprite import org.catrobat.catroid.io.StorageOperations import org.catrobat.catroid.merge.ImportProjectHelper +import org.catrobat.catroid.ui.FinderDataManager +import org.catrobat.catroid.ui.ProjectActivity import org.catrobat.catroid.ui.ProjectListActivity import org.catrobat.catroid.ui.ProjectListActivity.Companion.IMPORT_LOCAL_INTENT import org.catrobat.catroid.ui.SpriteActivity +import org.catrobat.catroid.ui.Finder import org.catrobat.catroid.ui.UiUtils import org.catrobat.catroid.ui.WebViewActivity import org.catrobat.catroid.ui.controller.BackpackListManager @@ -73,6 +85,8 @@ class SpriteListFragment : RecyclerViewFragment() { private val projectManager: ProjectManager by inject() private var currentSprite: Sprite? = null + private lateinit var currentProject: Project + private lateinit var currentScene: Scene internal inner class MultiViewTouchHelperCallback(adapterInterface: TouchHelperAdapterInterface?) : TouchHelperCallback(adapterInterface) { @@ -105,10 +119,120 @@ class SpriteListFragment : RecyclerViewFragment() { public override fun shouldShowEmptyView() = adapter.itemCount == 1 override fun onResume() { + currentScene = ProjectManager.getInstance().currentlyEditedScene + initializeAdapter() + super.onResume() SnackbarUtil.showHintSnackbar(requireActivity(), R.string.hint_objects) - val currentProject = projectManager.currentProject + makeTitle() + + if (FinderDataManager.instance.getInitiatingFragment() != FinderDataManager.FragmentType.NONE) { + handleFinderDataManagerLogic() + if (FinderDataManager.instance.type != FinderDataManager.FragmentType.SCENE.id){ + unCollapseFoundObject(FinderDataManager.instance.currentMatchIndex) + } + } + else{ + finder.close() + } + } + + private fun handleFinderDataManagerLogic() { + val activity = getActivity() as ProjectActivity + + if(FinderDataManager.instance.type == FinderDataManager.FragmentType.SCENE.id){ + activity.onBackPressed() + } + else{ + val searchIndex = FinderDataManager.instance.getSearchResultIndex() + val spriteIndex = FinderDataManager.instance.getSearchResults()[searchIndex][1] + projectManager.currentSprite = currentScene.spriteList[spriteIndex] + + when (FinderDataManager.instance.type) { + FinderDataManager.FragmentType.SCENE.id -> activity.onBackPressed() + FinderDataManager.FragmentType.SPRITE.id -> {} + else -> startActivity(createSpriteActivityIntent(FinderDataManager.instance.type)) + } + } + val sceneAndSpriteName = createActionBarTitle() + finder.onFragmentChanged(sceneAndSpriteName) + scrollToSearchResult() + hideKeyboard() + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val parentView = super.onCreateView(inflater, container, savedInstanceState) + recyclerView = parentView!!.findViewById(R.id.recycler_view) + currentProject = ProjectManager.getInstance().currentProject + currentScene = ProjectManager.getInstance().currentlyEditedScene + val activity = getActivity() as ProjectActivity + + finder?.setOnResultFoundListener(object : Finder.OnResultFoundListener { + override fun onResultFound( + sceneIndex: Int, + spriteIndex: Int, + brickIndex: Int, + type: Int, + totalResults: Int, + textView: TextView? + ) { + if (FinderDataManager.instance.startIndex != -1){ + + } + currentProject = ProjectManager.getInstance().currentProject + currentScene = currentProject.sceneList[sceneIndex] + ProjectManager.getInstance().setCurrentlyEditedScene(currentScene) + makeTitle() + FinderDataManager.instance.currentMatchIndex = brickIndex + + when (type) { + FinderDataManager.FragmentType.SPRITE.id -> { + textView?.text = createActionBarTitle() + initializeAdapter() + unCollapseFoundObject(spriteIndex) + adapter.notifyDataSetChanged() + scrollToSearchResult() + finder.disableFocusSearchBar() + + } + FinderDataManager.FragmentType.SCENE.id -> { + activity.onBackPressed() + } + else -> { + projectManager.currentSprite = currentProject.sceneList[sceneIndex].spriteList[spriteIndex] + val intent = createSpriteActivityIntent(type) + startActivity(intent) + } + } + hideKeyboard() + } + }) + + finder?.setOnCloseListener(object : Finder.OnCloseListener { + override fun onClose() { + finishActionMode() + activity.findViewById(R.id.toolbar).visibility = View.VISIBLE + FinderDataManager.instance.setSearchResultIndex(-1) + } + }) + + finder?.setOnOpenListener(object : Finder.OnOpenListener { + override fun onOpen() { + currentProject = ProjectManager.getInstance().currentProject + activity.findViewById(R.id.toolbar).visibility = View.GONE + finder.setInitiatingFragment(FinderDataManager.FragmentType.SPRITE) + val sceneIndex = ProjectManager.getInstance().currentProject.sceneList.indexOf(currentScene) + finder.setInitiatingScene(arrayOf(sceneIndex,-1,FinderDataManager + .FragmentType + .SPRITE.id)) + } + }) + return parentView + } + + private fun makeTitle(){ + currentProject = ProjectManager.getInstance().currentProject val title: String = if (currentProject.sceneList.size < 2) { currentProject.name } else { @@ -120,6 +244,40 @@ class SpriteListFragment : RecyclerViewFragment() { (requireActivity() as AppCompatActivity).supportActionBar?.title = title } + private fun unCollapseFoundObject(spriteIndex: Int) { + + val currentItem = currentScene.spriteList[spriteIndex] + if (currentItem is GroupItemSprite && currentItem.isCollapsed){ + for (i in spriteIndex downTo 1) { + val groupItem = currentScene.spriteList[i] + if (groupItem is GroupSprite) { + groupItem.isCollapsed = false + break + } + } + } + } + + private fun createSpriteActivityIntent(type: Int): Intent { + val intent = Intent(requireContext(), SpriteActivity::class.java) + intent.putExtra( + SpriteActivity.EXTRA_FRAGMENT_POSITION, + when (type) { + FinderDataManager.FragmentType.SOUND.id -> SpriteActivity.FRAGMENT_SOUNDS + FinderDataManager.FragmentType.LOOK.id -> SpriteActivity.FRAGMENT_LOOKS + else -> SpriteActivity.FRAGMENT_SCRIPTS + } + ) + return intent + } + + fun createActionBarTitle(): String { + return currentScene.name + } + private fun hideKeyboard() { + val imm = requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(requireView().windowToken, 0) + } override fun onAdapterReady() { super.onAdapterReady() val callback: ItemTouchHelper.Callback = MultiViewTouchHelperCallback(adapter) @@ -130,6 +288,7 @@ class SpriteListFragment : RecyclerViewFragment() { override fun onPrepareOptionsMenu(menu: Menu) { super.onPrepareOptionsMenu(menu) menu.findItem(R.id.new_group).isVisible = true + menu.findItem(R.id.find).isVisible = true } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -142,8 +301,8 @@ class SpriteListFragment : RecyclerViewFragment() { } private fun handleSelectedOptionItem(item: MenuItem) { - if (adapter.items.size == 1) { - ToastUtil.showError(activity, R.string.am_empty_list) + if (adapter.items.size == 1 && item.title.toString() != "Search") { + ToastUtil.showError(getActivity(), R.string.am_empty_list) resetActionModeParameters() } else { super.onOptionsItemSelected(item) @@ -321,6 +480,9 @@ class SpriteListFragment : RecyclerViewFragment() { } override fun onItemClick(item: Sprite?, selectionManager: MultiSelectionManager?) { + if (finder.isOpen){ + finder.close() + } if (item is GroupSprite) { item.isCollapsed = !item.isCollapsed adapter.notifyDataSetChanged() @@ -352,7 +514,8 @@ class SpriteListFragment : RecyclerViewFragment() { val itemList = mutableListOf() itemList.add(item) val hiddenMenuOptionIds = mutableListOf( - R.id.new_group, R.id.project_options, R.id.new_scene, R.id.show_details, R.id.edit + R.id.new_group, R.id.project_options, R.id.new_scene, R.id.show_details, R.id.edit,R + .id.find ) if (item is GroupSprite) { hiddenMenuOptionIds.add(R.id.backpack) diff --git a/catroid/src/main/res/layout/fragment_list_view.xml b/catroid/src/main/res/layout/fragment_list_view.xml index 0cf9180b67f..a073205b61f 100644 --- a/catroid/src/main/res/layout/fragment_list_view.xml +++ b/catroid/src/main/res/layout/fragment_list_view.xml @@ -1,4 +1,3 @@ - - - + android:layout_height="match_parent" + android:orientation="vertical"> - + android:visibility="gone"/> - - + android:layout_height="0dp" + android:layout_weight="1"> + + + + + + + - + diff --git a/catroid/src/main/res/layout/fragment_script.xml b/catroid/src/main/res/layout/fragment_script.xml index 7e4177c59b5..7db7aebc0f4 100644 --- a/catroid/src/main/res/layout/fragment_script.xml +++ b/catroid/src/main/res/layout/fragment_script.xml @@ -28,7 +28,7 @@ android:layout_weight="1" android:orientation="vertical" > - - + + +