skyline/app/src/main/java/emu/skyline/settings/SettingsActivity.kt
PabloG02 44cbf26b72 Add search functionality to settings
Multiple terms search is also available by separating individual terms with commas.
2023-03-25 22:54:27 +00:00

230 lines
9.9 KiB
Kotlin

/*
* SPDX-License-Identifier: MPL-2.0
* Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline.settings
import android.annotation.SuppressLint
import android.os.Bundle
import android.text.TextUtils
import android.view.KeyEvent
import android.view.Menu
import android.view.ViewTreeObserver
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.WindowCompat
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.forEach
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.internal.ToolbarUtils
import emu.skyline.R
import emu.skyline.data.AppItemTag
import emu.skyline.databinding.SettingsActivityBinding
import emu.skyline.preference.IntegerListPreference
import emu.skyline.preference.dialog.EditTextPreferenceMaterialDialogFragmentCompat
import emu.skyline.preference.dialog.IntegerListPreferenceMaterialDialogFragmentCompat
import emu.skyline.preference.dialog.ListPreferenceMaterialDialogFragmentCompat
import emu.skyline.utils.WindowInsetsHelper
private const val PREFERENCE_DIALOG_FRAGMENT_TAG = "androidx.preference.PreferenceFragment.DIALOG"
class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPreferenceDisplayDialogCallback {
val binding by lazy { SettingsActivityBinding.inflate(layoutInflater) }
val creditsCategories = arrayOf("category_credits", "category_licenses")
/**
* The instance of [PreferenceFragmentCompat] that is shown inside [R.id.settings]
* Retrieves extras from the intent if any and instantiates the appropriate fragment
*/
private val preferenceFragment by lazy {
if (intent.hasExtra(AppItemTag))
GameSettingsFragment().apply { arguments = intent.extras }
else
GlobalSettingsFragment()
}
/**
* This initializes all of the elements in the activity and displays the settings fragment
*/
override fun onCreate(savedInstanceState : Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
WindowCompat.setDecorFitsSystemWindows(window, false)
WindowInsetsHelper.applyToActivity(binding.root)
setSupportActionBar(binding.titlebar.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
var layoutDone = false // Tracks if the layout is complete to avoid retrieving invalid attributes
binding.coordinatorLayout.viewTreeObserver.addOnTouchModeChangeListener { isTouchMode ->
val layoutUpdate = {
val params = binding.settings.layoutParams as CoordinatorLayout.LayoutParams
if (!isTouchMode) {
binding.titlebar.appBarLayout.setExpanded(true)
params.height = binding.coordinatorLayout.height - binding.titlebar.toolbar.height
} else {
params.height = CoordinatorLayout.LayoutParams.MATCH_PARENT
}
binding.settings.layoutParams = params
binding.settings.requestLayout()
}
if (!layoutDone) {
binding.coordinatorLayout.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
// We need to wait till the layout is done to get the correct height of the toolbar
binding.coordinatorLayout.viewTreeObserver.removeOnGlobalLayoutListener(this)
layoutUpdate()
layoutDone = true
}
})
} else {
layoutUpdate()
}
}
fun enableMarquee(textView : TextView) {
textView.ellipsize = TextUtils.TruncateAt.MARQUEE
textView.isSelected = true
textView.marqueeRepeatLimit = -1
}
// Set a temporary subtitle because the retrieval of the subtitle TextView fails if subtitle is null
supportActionBar?.subtitle = "sub"
@SuppressLint("RestrictedApi")
val toolbarTitleTextView = ToolbarUtils.getTitleTextView(binding.titlebar.toolbar)
@SuppressLint("RestrictedApi")
val toolbarSubtitleTextView = ToolbarUtils.getSubtitleTextView(binding.titlebar.toolbar)
toolbarTitleTextView?.let { enableMarquee(it) }
toolbarSubtitleTextView?.let { enableMarquee(it) }
// Reset the subtitle to null
supportActionBar?.subtitle = null
supportFragmentManager
.beginTransaction()
.replace(R.id.settings, preferenceFragment)
.commit()
}
override fun onCreateOptionsMenu(menu : Menu?) : Boolean {
menuInflater.inflate(R.menu.settings_menu, menu)
val menuItem = menu!!.findItem(R.id.app_bar_search)
val searchView = menuItem.actionView as SearchView
searchView.queryHint = getString(R.string.search)
searchView.setOnQueryTextFocusChangeListener { _, focus ->
(binding.titlebar.toolbar.layoutParams as AppBarLayout.LayoutParams).scrollFlags =
if (focus)
AppBarLayout.LayoutParams.SCROLL_FLAG_NO_SCROLL
else
AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL
}
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query : String) : Boolean {
return false
}
override fun onQueryTextChange(newText : String) : Boolean {
val queries = newText.split(",")
if (newText.isNotEmpty()) {
preferenceFragment.preferenceScreen.forEach { preferenceCategory ->
if (creditsCategories.contains(preferenceCategory.key)) {
preferenceCategory.isVisible = false
return@forEach
}
val queryMatchesCategory = queries.any { preferenceCategory.title?.contains(it, true) ?: false }
// Tracks whether all preferences under this category are hidden
var areAllPrefsHidden = true
(preferenceCategory as PreferenceCategory).forEach { preference ->
preference.isVisible = queryMatchesCategory || queries.any { preference.title?.contains(it, true) ?: false }
if (preference.isVisible && areAllPrefsHidden)
areAllPrefsHidden = false
}
// Hide PreferenceCategory if none of its preferences match the search and neither the category title
preferenceCategory.isVisible = !areAllPrefsHidden || queryMatchesCategory
}
} else { // If user input is empty, show all preferences
preferenceFragment.preferenceScreen.forEach { preferenceCategory ->
preferenceCategory.isVisible = true
(preferenceCategory as PreferenceCategory).forEach { preference ->
preference.isVisible = true
}
}
}
return true
}
})
return super.onCreateOptionsMenu(menu)
}
/**
* This handles on calling [onBackPressed] when [KeyEvent.KEYCODE_BUTTON_B] is lifted
*/
override fun onKeyUp(keyCode : Int, event : KeyEvent?) : Boolean {
if (keyCode == KeyEvent.KEYCODE_BUTTON_B) {
onBackPressedDispatcher.onBackPressed()
return true
}
return super.onKeyUp(keyCode, event)
}
override fun finish() {
setResult(RESULT_OK)
super.finish()
}
override fun onPreferenceDisplayDialog(caller : PreferenceFragmentCompat, pref : Preference) : Boolean {
when (pref) {
is IntegerListPreference -> {
// Check if dialog is already showing
if (supportFragmentManager.findFragmentByTag(PREFERENCE_DIALOG_FRAGMENT_TAG) != null)
return true
val dialogFragment = IntegerListPreferenceMaterialDialogFragmentCompat.newInstance(pref.getKey())
@Suppress("DEPRECATION")
dialogFragment.setTargetFragment(caller, 0) // androidx.preference.PreferenceDialogFragmentCompat depends on the target fragment being set correctly even though it's deprecated
dialogFragment.show(supportFragmentManager, PREFERENCE_DIALOG_FRAGMENT_TAG)
return true
}
is EditTextPreference -> {
if (supportFragmentManager.findFragmentByTag(PREFERENCE_DIALOG_FRAGMENT_TAG) != null)
return true
val dialogFragment = EditTextPreferenceMaterialDialogFragmentCompat.newInstance(pref.getKey())
@Suppress("DEPRECATION")
dialogFragment.setTargetFragment(caller, 0)
dialogFragment.show(supportFragmentManager, PREFERENCE_DIALOG_FRAGMENT_TAG)
return true
}
is ListPreference -> {
if (supportFragmentManager.findFragmentByTag(PREFERENCE_DIALOG_FRAGMENT_TAG) != null)
return true
val dialogFragment = ListPreferenceMaterialDialogFragmentCompat.newInstance(pref.getKey())
@Suppress("DEPRECATION")
dialogFragment.setTargetFragment(caller, 0)
dialogFragment.show(supportFragmentManager, PREFERENCE_DIALOG_FRAGMENT_TAG)
return true
}
else -> return false
}
}
}