summaryrefslogtreecommitdiff
path: root/Source/Android/app/src/main/java
diff options
context:
space:
mode:
authorSimonx22 <simon@oatmealdome.me>2026-07-18 15:45:12 -0400
committerSimonx22 <simon@oatmealdome.me>2026-07-25 11:36:57 -0400
commit893be0996e6c58901f2169ca0162e5252a52e13b (patch)
treee901b83109c1a80882279c1f5550e073eaab14c8 /Source/Android/app/src/main/java
parent707d3c7a732634ad8d522bd27e52b075f54ac4f3 (diff)
Android: Add global settings search
Diffstat (limited to 'Source/Android/app/src/main/java')
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsItem.kt1
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsSearchResult.kt19
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.kt231
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityPresenter.kt130
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityView.kt29
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt35
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt136
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt763
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentView.kt14
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingViewHolder.kt43
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingsSearchResultViewHolder.kt29
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ThemeHelper.kt5
12 files changed, 983 insertions, 452 deletions
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsItem.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsItem.kt
index 4d41676a14..20cfdcb2d0 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsItem.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsItem.kt
@@ -86,5 +86,6 @@ abstract class SettingsItem {
const val TYPE_STRING = 12
const val TYPE_HYPERLINK_HEADER = 13
const val TYPE_DATETIME_CHOICE = 14
+ const val TYPE_SEARCH_RESULT = 15
}
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsSearchResult.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsSearchResult.kt
new file mode 100644
index 0000000000..dc786851ac
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsSearchResult.kt
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.settings.model.view
+
+import android.os.Bundle
+import org.dolphinemu.dolphinemu.features.settings.model.AbstractSetting
+import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag
+
+class SettingsSearchResult(
+ name: CharSequence,
+ description: CharSequence,
+ val menuKey: MenuTag,
+ val settingPosition: Int,
+ val navigationExtras: Bundle?
+) : SettingsItem(name, description) {
+ override val type: Int = TYPE_SEARCH_RESULT
+
+ override val setting: AbstractSetting? = null
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.kt
index 68f957a626..1135dca463 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.kt
@@ -7,14 +7,17 @@ import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.view.KeyEvent
-import android.view.Menu
import android.view.MotionEvent
import android.view.View
+import android.view.animation.PathInterpolator
import android.widget.Toast
+import androidx.activity.OnBackPressedCallback
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.SearchView
import androidx.core.view.ViewCompat
+import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.ViewModelProvider
@@ -39,6 +42,17 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView, ThemeProvide
private var dialog: AlertDialog? = null
private var toolbarLayout: CollapsingToolbarLayout? = null
private var binding: ActivitySettingsBinding? = null
+ private lateinit var searchView: SearchView
+ private var expandedToolbarHeight = 0
+ private var toolbarStateGeneration = 0
+ private var currentToolbarTitle: String? = null
+ private var currentToolbarShowsHeadline = false
+ private var currentToolbarShowsSearch = false
+ private var currentToolbarShowsSearchMode = false
+ override val settingsSearchQuery: String
+ get() = presenter!!.settingsSearchQuery
+ override val isSettingsSearchActive: Boolean
+ get() = presenter!!.isSettingsSearchActive
override var themeId: Int = 0
override var isMappingAllDevices = false
@@ -76,8 +90,11 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView, ThemeProvide
presenter = SettingsActivityPresenter(this, settings)
presenter!!.onCreate(savedInstanceState, menuTag, gameID, revision, isWii, this)
toolbarLayout = binding!!.toolbarSettingsLayout
+ expandedToolbarHeight = toolbarLayout!!.layoutParams.height
setSupportActionBar(binding!!.toolbarSettings)
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
+ setUpSettingsSearch()
+ setUpBackNavigation()
// TODO: Remove this when CollapsingToolbarLayouts are fixed by Google
// https://github.com/material-components/material-components-android/issues/1310
@@ -86,16 +103,84 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView, ThemeProvide
enableScrollTint(this, binding!!.toolbarSettings, binding!!.appbarSettings)
}
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- val inflater = menuInflater
- inflater.inflate(R.menu.menu_settings, menu)
- return true
+ private fun setUpSettingsSearch() {
+ searchView = binding!!.settingsSearch
+ searchView.setQuery(settingsSearchQuery, false)
+ searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
+ override fun onQueryTextSubmit(query: String?): Boolean {
+ searchView.clearFocus()
+ return true
+ }
+
+ override fun onQueryTextChange(newText: String?): Boolean {
+ presenter!!.onSettingsSearchQueryChanged(newText.orEmpty())
+ return true
+ }
+ })
+ binding!!.settingsSearchPreview.setOnClickListener { enterSettingsSearch() }
+ binding!!.settingsSearchToolbar.setNavigationOnClickListener { exitSettingsSearch() }
+ }
+
+ private fun enterSettingsSearch() {
+ if (!presenter!!.enterSettingsSearch()) {
+ return
+ }
+
+ refreshToolbarState()
+ val focusDelay =
+ if (areSystemAnimationsEnabled()) SEARCH_FOCUS_DELAY_MS else 0L
+ searchView.postDelayed({
+ if (!isSettingsSearchActive) {
+ return@postDelayed
+ }
+ searchView.requestFocus()
+ WindowCompat.getInsetsController(window, searchView)
+ .show(WindowInsetsCompat.Type.ime())
+ }, focusDelay)
+ }
+
+ private fun exitSettingsSearch() {
+ if (!presenter!!.exitSettingsSearch()) {
+ return
+ }
+
+ searchView.setQuery("", false)
+ searchView.clearFocus()
+ WindowCompat.getInsetsController(window, searchView).hide(WindowInsetsCompat.Type.ime())
+ refreshToolbarState()
+ }
+
+ private fun refreshToolbarState() {
+ val title = currentToolbarTitle ?: getString(R.string.settings)
+ setToolbarState(
+ title,
+ currentToolbarShowsHeadline,
+ currentToolbarShowsSearch
+ )
+ }
+
+ private fun setUpBackNavigation() {
+ onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ if (supportFragmentManager.backStackEntryCount == 0 &&
+ isSettingsSearchActive
+ ) {
+ exitSettingsSearch()
+ return
+ }
+
+ isEnabled = false
+ onBackPressedDispatcher.onBackPressed()
+ isEnabled = true
+ }
+ })
}
override fun onSaveInstanceState(outState: Bundle) {
// Critical: If super method is not called, rotations will be busted.
super.onSaveInstanceState(outState)
outState.putBoolean(KEY_MAPPING_ALL_DEVICES, isMappingAllDevices)
+ presenter!!.onSaveInstanceState(outState)
}
override fun onStart() {
@@ -128,10 +213,17 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView, ThemeProvide
}
override fun showSettingsFragment(
+ menuTag: MenuTag, extras: Bundle?, addToStack: Boolean, gameId: String
+ ) {
+ replaceSettingsFragment(menuTag, extras, addToStack, gameId, false)
+ }
+
+ private fun replaceSettingsFragment(
menuTag: MenuTag,
extras: Bundle?,
addToStack: Boolean,
- gameId: String
+ gameId: String,
+ isSearchResult: Boolean
) {
if (!addToStack && fragment != null) return
val transaction = supportFragmentManager.beginTransaction()
@@ -140,15 +232,18 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView, ThemeProvide
transaction.setCustomAnimations(
R.anim.anim_settings_fragment_in,
R.anim.anim_settings_fragment_out,
- 0,
- R.anim.anim_pop_settings_fragment_out
+ if (isSearchResult) R.anim.anim_settings_search_pop_in else 0,
+ if (isSearchResult) {
+ R.anim.anim_settings_search_pop_out
+ } else {
+ R.anim.anim_pop_settings_fragment_out
+ }
)
}
transaction.addToBackStack(null)
}
transaction.replace(
- R.id.frame_content_settings,
- newInstance(menuTag, gameId, extras), FRAGMENT_TAG
+ R.id.frame_content_settings, newInstance(menuTag, gameId, extras), FRAGMENT_TAG
)
transaction.commit()
}
@@ -157,16 +252,22 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView, ThemeProvide
fragment.show(supportFragmentManager, FRAGMENT_DIALOG_TAG)
}
+ override fun showSearchResult(
+ menuTag: MenuTag, settingPosition: Int, gameId: String, extras: Bundle?
+ ) {
+ val navigationExtras = extras?.let(::Bundle) ?: Bundle()
+ navigationExtras.putInt(
+ SettingsFragment.ARGUMENT_SCROLL_TO_SETTING_POSITION, settingPosition
+ )
+ replaceSettingsFragment(menuTag, navigationExtras, true, gameId, true)
+ }
+
private fun areSystemAnimationsEnabled(): Boolean {
val duration = android.provider.Settings.Global.getFloat(
- contentResolver,
- android.provider.Settings.Global.ANIMATOR_DURATION_SCALE,
- 1f
+ contentResolver, android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, 1f
)
val transition = android.provider.Settings.Global.getFloat(
- contentResolver,
- android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE,
- 1f
+ contentResolver, android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, 1f
)
return duration != 0f && transition != 0f
}
@@ -183,10 +284,8 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView, ThemeProvide
override fun showLoading() {
if (dialog == null) {
- dialog = MaterialAlertDialogBuilder(this)
- .setTitle(getString(R.string.load_settings))
- .setView(R.layout.dialog_indeterminate_progress)
- .create()
+ dialog = MaterialAlertDialogBuilder(this).setTitle(getString(R.string.load_settings))
+ .setView(R.layout.dialog_indeterminate_progress).create()
}
dialog!!.show()
}
@@ -196,12 +295,10 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView, ThemeProvide
}
override fun showGameIniJunkDeletionQuestion() {
- MaterialAlertDialogBuilder(this)
- .setTitle(getString(R.string.game_ini_junk_title))
+ MaterialAlertDialogBuilder(this).setTitle(getString(R.string.game_ini_junk_title))
.setMessage(getString(R.string.game_ini_junk_question))
.setPositiveButton(R.string.yes) { _: DialogInterface?, _: Int -> presenter!!.clearGameSettings() }
- .setNegativeButton(R.string.no, null)
- .show()
+ .setNegativeButton(R.string.no, null).show()
}
override fun onSettingsFileLoaded(settings: Settings) {
@@ -229,13 +326,78 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView, ThemeProvide
return presenter!!.hasMenuTagActionForValue(menuTag, value)
}
+ override fun getMenuTagActionExtras(menuTag: MenuTag, value: Int): Bundle? {
+ return presenter!!.getMenuTagActionExtras(menuTag, value)
+ }
+
+ override fun filterSettings(query: String) {
+ fragment?.filterSettings(query)
+ }
+
override fun onSupportNavigateUp(): Boolean {
- onBackPressed()
+ onBackPressedDispatcher.onBackPressed()
return true
}
- override fun setToolbarTitle(title: String) {
- binding!!.toolbarSettingsLayout.title = title
+ override fun setToolbarState(title: String, showHeadline: Boolean, showSearch: Boolean) {
+ val appBar = binding!!.appbarSettings
+ val generation = ++toolbarStateGeneration
+ val showSearchMode = showSearch && isSettingsSearchActive
+ val stateChanged =
+ currentToolbarTitle != title || currentToolbarShowsHeadline != showHeadline || currentToolbarShowsSearch != showSearch || currentToolbarShowsSearchMode != showSearchMode
+ appBar.animate().cancel()
+
+ if (!appBar.isLaidOut || !stateChanged) {
+ applyToolbarState(title, showHeadline, showSearch)
+ appBar.alpha = 1f
+ return
+ }
+
+ if (!showSearch) {
+ searchView.clearFocus()
+ }
+
+ appBar.animate().alpha(0f).setDuration(APP_BAR_FADE_OUT_DURATION_MS)
+ .setInterpolator(APP_BAR_FADE_OUT_INTERPOLATOR).withEndAction {
+ if (generation != toolbarStateGeneration) {
+ return@withEndAction
+ }
+
+ applyToolbarState(title, showHeadline, showSearch)
+ appBar.post {
+ if (generation != toolbarStateGeneration) {
+ return@post
+ }
+
+ appBar.animate().alpha(1f).setDuration(APP_BAR_FADE_IN_DURATION_MS)
+ .setInterpolator(APP_BAR_FADE_IN_INTERPOLATOR).start()
+ }
+ }.start()
+ }
+
+ private fun applyToolbarState(title: String, showHeadline: Boolean, showSearch: Boolean) {
+ val showSearchMode = showSearch && isSettingsSearchActive
+ toolbarLayout!!.isTitleEnabled = showHeadline
+ supportActionBar!!.title = title
+ if (showHeadline) {
+ toolbarLayout!!.title = title
+ }
+ toolbarLayout!!.layoutParams = toolbarLayout!!.layoutParams.apply {
+ height = if (showHeadline) {
+ expandedToolbarHeight
+ } else {
+ binding!!.toolbarSettings.layoutParams.height
+ }
+ }
+ toolbarLayout!!.visibility = if (showSearchMode) View.GONE else View.VISIBLE
+ binding!!.settingsSearchContainer.visibility =
+ if (showSearch && !showSearchMode) View.VISIBLE else View.GONE
+ binding!!.settingsSearchModeContainer.visibility =
+ if (showSearchMode) View.VISIBLE else View.GONE
+ currentToolbarTitle = title
+ currentToolbarShowsHeadline = showHeadline
+ currentToolbarShowsSearch = showSearch
+ currentToolbarShowsSearchMode = showSearchMode
}
override fun setOldControllerSettingsWarningVisibility(visible: Boolean): Int {
@@ -274,14 +436,16 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView, ThemeProvide
private const val KEY_MAPPING_ALL_DEVICES = "all_devices"
private const val FRAGMENT_TAG = "settings"
private const val FRAGMENT_DIALOG_TAG = "settings_dialog"
+ private const val APP_BAR_FADE_OUT_DURATION_MS = 90L
+ private const val APP_BAR_FADE_IN_DURATION_MS = 180L
+ private const val SEARCH_FOCUS_DELAY_MS =
+ APP_BAR_FADE_OUT_DURATION_MS + APP_BAR_FADE_IN_DURATION_MS
+ private val APP_BAR_FADE_OUT_INTERPOLATOR = PathInterpolator(0.4f, 0f, 1f, 1f)
+ private val APP_BAR_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0.2f, 1f)
@JvmStatic
fun launch(
- context: Context,
- menuTag: MenuTag?,
- gameId: String?,
- revision: Int,
- isWii: Boolean
+ context: Context, menuTag: MenuTag?, gameId: String?, revision: Int, isWii: Boolean
) {
val settings = Intent(context, SettingsActivity::class.java)
settings.putExtra(ARG_MENU_TAG, menuTag)
@@ -296,8 +460,7 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView, ThemeProvide
val settings = Intent(context, SettingsActivity::class.java)
settings.putExtra(ARG_MENU_TAG, menuTag)
settings.putExtra(
- ARG_IS_WII,
- !NativeLibrary.IsRunning() || NativeLibrary.IsEmulatingWii()
+ ARG_IS_WII, !NativeLibrary.IsRunning() || NativeLibrary.IsEmulatingWii()
)
context.startActivity(settings)
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityPresenter.kt
index 7627a7b5c6..1615911fb6 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityPresenter.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityPresenter.kt
@@ -18,6 +18,10 @@ class SettingsActivityPresenter(
private var revision = 0
private var isWii = false
private lateinit var activity: AppCompatActivity
+ var settingsSearchQuery = ""
+ private set
+ var isSettingsSearchActive = false
+ private set
fun onCreate(
savedInstanceState: Bundle?,
@@ -32,6 +36,43 @@ class SettingsActivityPresenter(
this.revision = revision
this.isWii = isWii
this.activity = activity
+ if (savedInstanceState != null) {
+ isSettingsSearchActive =
+ savedInstanceState.getBoolean(KEY_SETTINGS_SEARCH_ACTIVE)
+ settingsSearchQuery =
+ savedInstanceState.getString(KEY_SETTINGS_SEARCH_QUERY).orEmpty()
+ }
+ }
+
+ fun onSaveInstanceState(outState: Bundle) {
+ outState.putBoolean(KEY_SETTINGS_SEARCH_ACTIVE, isSettingsSearchActive)
+ outState.putString(KEY_SETTINGS_SEARCH_QUERY, settingsSearchQuery)
+ }
+
+ fun onSettingsSearchQueryChanged(query: String) {
+ settingsSearchQuery = query
+ activityView.filterSettings(query)
+ }
+
+ fun enterSettingsSearch(): Boolean {
+ if (isSettingsSearchActive) {
+ return false
+ }
+
+ isSettingsSearchActive = true
+ activityView.filterSettings(settingsSearchQuery)
+ return true
+ }
+
+ fun exitSettingsSearch(): Boolean {
+ if (!isSettingsSearchActive) {
+ return false
+ }
+
+ isSettingsSearchActive = false
+ settingsSearchQuery = ""
+ activityView.filterSettings("")
+ return true
}
fun onDestroy() {
@@ -85,55 +126,58 @@ class SettingsActivityPresenter(
}
fun onMenuTagAction(menuTag: MenuTag, value: Int) {
- if (menuTag.isSerialPort1Menu) {
- // Not disabled or dummy
- if (value != 0 && value != 255) {
- val bundle = Bundle()
- bundle.putInt(SettingsFragmentPresenter.ARG_SERIALPORT1_TYPE, value)
- activityView.showSettingsFragment(menuTag, bundle, true, gameId!!)
- }
- }
- if (menuTag.isGCPadMenu) {
- // Not disabled
- if (value != 0)
- {
- val bundle = Bundle()
- bundle.putInt(SettingsFragmentPresenter.ARG_CONTROLLER_TYPE, value)
- activityView.showSettingsFragment(menuTag, bundle, true, gameId!!)
- }
- }
- if (menuTag.isWiimoteMenu) {
- // Emulated Wii Remote
- if (value == 1) {
- activityView.showSettingsFragment(menuTag, null, true, gameId!!)
- }
- }
- if (menuTag.isWiimoteExtensionMenu) {
- // Not disabled
- if (value != 0) {
- val bundle = Bundle()
- bundle.putInt(SettingsFragmentPresenter.ARG_CONTROLLER_TYPE, value)
- activityView.showSettingsFragment(menuTag, bundle, true, gameId!!)
- }
- }
+ val action = getMenuTagAction(menuTag, value) ?: return
+ activityView.showSettingsFragment(action.menuTag, action.extras, true, gameId!!)
}
fun hasMenuTagActionForValue(menuTag: MenuTag, value: Int): Boolean {
- if (menuTag.isSerialPort1Menu) {
+ return getMenuTagAction(menuTag, value) != null
+ }
+
+ fun getMenuTagActionExtras(menuTag: MenuTag, value: Int): Bundle? {
+ return getMenuTagAction(menuTag, value)?.extras
+ }
+
+ private fun getMenuTagAction(menuTag: MenuTag, value: Int): MenuTagAction? {
+ return when {
// Not disabled or dummy
- return value != 0 && value != 255
- }
- if (menuTag.isGCPadMenu) {
+ menuTag.isSerialPort1Menu && value != 0 && value != 255 ->
+ MenuTagAction(
+ menuTag,
+ Bundle().apply {
+ putInt(SettingsFragmentPresenter.ARG_SERIALPORT1_TYPE, value)
+ }
+ )
+
// Not disabled
- return value != 0
- }
- if (menuTag.isWiimoteMenu) {
+ menuTag.isGCPadMenu && value != 0 ->
+ MenuTagAction(
+ menuTag,
+ Bundle().apply {
+ putInt(SettingsFragmentPresenter.ARG_CONTROLLER_TYPE, value)
+ }
+ )
+
// Emulated Wii Remote
- return value == 1
- }
- return if (menuTag.isWiimoteExtensionMenu) {
+ menuTag.isWiimoteMenu && value == 1 -> MenuTagAction(menuTag, null)
+
// Not disabled
- value != 0
- } else false
+ menuTag.isWiimoteExtensionMenu && value != 0 ->
+ MenuTagAction(
+ menuTag,
+ Bundle().apply {
+ putInt(SettingsFragmentPresenter.ARG_CONTROLLER_TYPE, value)
+ }
+ )
+
+ else -> null
+ }
+ }
+
+ private data class MenuTagAction(val menuTag: MenuTag, val extras: Bundle?)
+
+ companion object {
+ private const val KEY_SETTINGS_SEARCH_ACTIVE = "settings_search_active"
+ private const val KEY_SETTINGS_SEARCH_QUERY = "settings_search_query"
}
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityView.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityView.kt
index 1cd39f6fe4..44f02bc3f8 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityView.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityView.kt
@@ -11,6 +11,16 @@ import org.dolphinemu.dolphinemu.features.settings.model.Settings
*/
interface SettingsActivityView {
/**
+ * The query currently displayed in the settings search view.
+ */
+ val settingsSearchQuery: String
+
+ /**
+ * Whether the dedicated settings search screen is active.
+ */
+ val isSettingsSearchActive: Boolean
+
+ /**
* Show a new SettingsFragment.
*
* @param menuTag Identifier for the settings group that should be displayed.
@@ -24,6 +34,16 @@ interface SettingsActivityView {
)
/**
+ * Opens the settings screen containing a search result and scrolls to the result.
+ */
+ fun showSearchResult(menuTag: MenuTag, settingPosition: Int, gameId: String, extras: Bundle?)
+
+ /**
+ * Filters the root settings screen using the current search query.
+ */
+ fun filterSettings(query: String)
+
+ /**
* Shows a DialogFragment.
*
* Only one can be shown at a time.
@@ -87,6 +107,11 @@ interface SettingsActivityView {
fun hasMenuTagActionForValue(menuTag: MenuTag, value: Int): Boolean
/**
+ * Returns the arguments used when opening a navigable setting's associated screen.
+ */
+ fun getMenuTagActionExtras(menuTag: MenuTag, value: Int): Bundle?
+
+ /**
* Show loading dialog while loading the settings
*/
fun showLoading()
@@ -102,9 +127,9 @@ interface SettingsActivityView {
fun showGameIniJunkDeletionQuestion()
/**
- * Accesses the material toolbar layout and changes the title
+ * Updates the settings app bar as a single state change.
*/
- fun setToolbarTitle(title: String)
+ fun setToolbarState(title: String, showHeadline: Boolean, showSearch: Boolean)
/**
* Returns whether the input mapping dialog should detect inputs from all devices,
* not just the device configured for the controller.
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt
index ae236d3764..2492a0113a 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt
@@ -112,11 +112,15 @@ class SettingsAdapter(
SettingsItem.TYPE_DATETIME_CHOICE -> DateTimeSettingViewHolder(
ListItemSettingBinding.inflate(inflater, parent, false), this
)
+ SettingsItem.TYPE_SEARCH_RESULT -> SettingsSearchResultViewHolder(
+ ListItemSearchResultBinding.inflate(inflater, parent, false), this
+ )
else -> throw IllegalArgumentException("Invalid view type: $viewType")
}
}
override fun onBindViewHolder(holder: SettingViewHolder, position: Int) {
+ holder.clearSearchResultHighlight()
holder.bind(getItem(position))
}
@@ -143,7 +147,7 @@ class SettingsAdapter(
fun clearSetting(item: SettingsItem) {
item.clear(settings!!)
- fragmentView.onSettingChanged()
+ fragmentView.onSettingChanged(item)
}
fun notifyAllSettingsChanged() {
@@ -153,7 +157,7 @@ class SettingsAdapter(
fun onBooleanClick(item: SwitchSetting, checked: Boolean) {
item.setChecked(settings!!, checked)
- fragmentView.onSettingChanged()
+ fragmentView.onSettingChanged(item)
}
fun onInputStringClick(item: InputStringSetting, position: Int) {
@@ -168,7 +172,7 @@ class SettingsAdapter(
val editTextInput = input.text.toString()
if (item.selectedValue != editTextInput) {
notifyItemChanged(position)
- fragmentView.onSettingChanged()
+ fragmentView.onSettingChanged(item)
}
item.setSelectedValue(fragmentView.settings!!, editTextInput)
}
@@ -271,6 +275,10 @@ class SettingsAdapter(
fragmentView.loadSubMenu(item.menuKey)
}
+ fun onSearchResultClick(item: SettingsSearchResult) {
+ fragmentView.loadSearchResult(item.menuKey, item.settingPosition, item.navigationExtras)
+ }
+
fun onInputMappingClick(item: InputMappingControlSetting, position: Int) {
if (item.controller.getDefaultDevice().isEmpty() && !fragmentView.isMappingAllDevices) {
MaterialAlertDialogBuilder(fragmentView.fragmentActivity)
@@ -307,7 +315,7 @@ class SettingsAdapter(
) { _: DialogInterface?, _: Int -> item.clearValue() }
dialog.setOnDismissListener {
notifyItemChanged(position)
- fragmentView.onSettingChanged()
+ fragmentView.onSettingChanged(item)
}
dialog.setCanceledOnTouchOutside(false)
dialog.show()
@@ -338,7 +346,7 @@ class SettingsAdapter(
) { _: DialogInterface?, _: Int ->
item.value = dialog.expression
notifyItemChanged(position)
- fragmentView.onSettingChanged()
+ fragmentView.onSettingChanged(item)
}
dialog.setButton(AlertDialog.BUTTON_NEGATIVE, context.getString(R.string.cancel), this)
dialog.setButton(
@@ -435,7 +443,7 @@ class SettingsAdapter(
val rtcString = "0x" + java.lang.Long.toHexString(epochTime)
if (item.getSelectedValue() != rtcString) {
notifyItemChanged(clickedPosition)
- fragmentView.onSettingChanged()
+ fragmentView.onSettingChanged(item)
}
item.setSelectedValue(fragmentView.settings!!, rtcString)
clickedItem = null
@@ -448,7 +456,7 @@ class SettingsAdapter(
if (filePicker.getSelectedValue() != selectedFile) {
notifyItemChanged(clickedPosition)
- fragmentView.onSettingChanged()
+ fragmentView.onSettingChanged(filePicker)
}
filePicker.setSelectedValue(fragmentView.settings!!, selectedFile)
@@ -470,7 +478,7 @@ class SettingsAdapter(
val scSetting = clickedItem as SingleChoiceSetting
val value = getValueForSingleChoiceSelection(scSetting, which)
- if (scSetting.selectedValue != value) fragmentView.onSettingChanged()
+ if (scSetting.selectedValue != value) fragmentView.onSettingChanged(scSetting)
scSetting.setSelectedValue(settings!!, value)
@@ -480,7 +488,7 @@ class SettingsAdapter(
val scSetting = clickedItem as SingleChoiceSettingDynamicDescriptions
val value = getValueForSingleChoiceDynamicDescriptionsSelection(scSetting, which)
- if (scSetting.selectedValue != value) fragmentView.onSettingChanged()
+ if (scSetting.selectedValue != value) fragmentView.onSettingChanged(scSetting)
scSetting.setSelectedValue(settings!!, value)
@@ -490,7 +498,7 @@ class SettingsAdapter(
val scSetting = clickedItem as StringSingleChoiceSetting
val value = scSetting.getValueAt(which)
- if (scSetting.selectedValue != value) fragmentView.onSettingChanged()
+ if (scSetting.selectedValue != value) fragmentView.onSettingChanged(scSetting)
scSetting.setSelectedValue(settings!!, value)
@@ -499,7 +507,7 @@ class SettingsAdapter(
is IntSliderSetting -> {
val sliderSetting = clickedItem as IntSliderSetting
if (sliderSetting.selectedValue != seekbarProgress.toInt()) {
- fragmentView.onSettingChanged()
+ fragmentView.onSettingChanged(sliderSetting)
}
sliderSetting.setSelectedValue(settings!!, seekbarProgress.toInt())
closeDialog()
@@ -507,7 +515,9 @@ class SettingsAdapter(
is FloatSliderSetting -> {
val sliderSetting = clickedItem as FloatSliderSetting
- if (sliderSetting.selectedValue != seekbarProgress) fragmentView.onSettingChanged()
+ if (sliderSetting.selectedValue != seekbarProgress) {
+ fragmentView.onSettingChanged(sliderSetting)
+ }
sliderSetting.setSelectedValue(settings!!, seekbarProgress)
@@ -540,6 +550,7 @@ class SettingsAdapter(
override fun onViewRecycled(holder: SettingViewHolder) {
super.onViewRecycled(holder)
+ holder.clearSearchResultHighlight()
holder.onViewRecycled()
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt
index 1289d30f61..7e6f646478 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt
@@ -24,13 +24,19 @@ import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
import org.dolphinemu.dolphinemu.R
import org.dolphinemu.dolphinemu.databinding.FragmentSettingsBinding
import org.dolphinemu.dolphinemu.features.settings.model.Settings
import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem
+import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SettingViewHolder
import org.dolphinemu.dolphinemu.utils.GpuDriverInstallResult
import org.dolphinemu.dolphinemu.utils.SerializableHelper.serializable
import java.util.*
@@ -51,6 +57,11 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
SettingsActivityResultLaunchers(this) { adapter }
private var oldControllerSettingsWarningHeight = 0
+ private var hasScrolledToSearchResult = false
+ private var highlightedSearchResult: SettingsItem? = null
+ private var highlightedSearchResultPosition = RecyclerView.NO_POSITION
+ private var searchIndexWarmupJob: Job? = null
+ private var searchJob: Job? = null
private var binding: FragmentSettingsBinding? = null
@@ -92,7 +103,11 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
if (titles.containsKey(menuTag)) {
- activityView!!.setToolbarTitle(getString(titles[menuTag]!!))
+ activityView!!.setToolbarState(
+ getString(titles[menuTag]!!),
+ menuTag != MenuTag.SETTINGS,
+ menuTag == MenuTag.SETTINGS
+ )
}
val manager = LinearLayoutManager(activity)
@@ -107,10 +122,13 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
setInsets()
val activity = requireActivity() as SettingsActivityView
+ presenter.invalidateSearchIndex()
presenter.onViewCreated(menuTag, activity.settings)
}
override fun onDestroyView() {
+ clearSearchResultHighlight()
+ searchJob?.cancel()
super.onDestroyView()
binding = null
}
@@ -129,7 +147,81 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
}
override fun showSettingsList(settingsList: ArrayList<SettingsItem>) {
- adapter!!.setSettings(settingsList)
+ val query = activityView?.settingsSearchQuery.orEmpty()
+ val isShowingSearch =
+ menuTag == MenuTag.SETTINGS && activityView?.isSettingsSearchActive == true
+ if (!isShowingSearch) {
+ adapter!!.setSettings(settingsList)
+ }
+ if (menuTag == MenuTag.SETTINGS) {
+ warmUpSearchIndex()
+ if (isShowingSearch) {
+ applySettingsFilter(query)
+ }
+ }
+
+ val position = arguments?.getInt(
+ ARGUMENT_SCROLL_TO_SETTING_POSITION,
+ RecyclerView.NO_POSITION
+ ) ?: RecyclerView.NO_POSITION
+ if (!hasScrolledToSearchResult && position in settingsList.indices) {
+ hasScrolledToSearchResult = true
+ binding?.listSettings?.post {
+ val recyclerView = binding?.listSettings ?: return@post
+ (recyclerView.layoutManager as? LinearLayoutManager)
+ ?.scrollToPositionWithOffset(position, 0)
+ highlightSearchResult(position, settingsList[position])
+ }
+ }
+ }
+
+ fun filterSettings(query: String) {
+ if (!this::presenter.isInitialized || presenter.settings == null) {
+ return
+ }
+
+ applySettingsFilter(query)
+ }
+
+ private fun applySettingsFilter(query: String) {
+ searchJob?.cancel()
+ if (query.isBlank()) {
+ val results = if (activityView?.isSettingsSearchActive == true) {
+ arrayListOf()
+ } else {
+ presenter.getSettingsList()
+ }
+ showSearchResults(query, results)
+ return
+ }
+
+ searchJob = viewLifecycleOwner.lifecycleScope.launch {
+ delay(SEARCH_QUERY_DEBOUNCE_MS)
+ val results = presenter.searchSettings(query)
+ if (activityView?.settingsSearchQuery == query) {
+ showSearchResults(query, results)
+ }
+ }
+ }
+
+ private fun warmUpSearchIndex() {
+ if (searchIndexWarmupJob?.isActive == true) {
+ return
+ }
+
+ searchIndexWarmupJob = viewLifecycleOwner.lifecycleScope.launch {
+ presenter.prepareSearchIndex()
+ }
+ }
+
+ private fun showSearchResults(query: String, results: ArrayList<SettingsItem>) {
+ adapter!!.setSettings(results)
+ binding?.textNoSearchResults?.text =
+ getString(R.string.search_settings_no_results, query.trim())
+ binding?.textNoSearchResults?.visibility =
+ if (query.isNotBlank() && results.isEmpty()) View.VISIBLE else View.GONE
+ binding?.listSettings?.visibility =
+ if (query.isNotBlank() && results.isEmpty()) View.GONE else View.VISIBLE
}
override fun loadSubMenu(menuKey: MenuTag) {
@@ -146,6 +238,34 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
)
}
+ override fun loadSearchResult(menuKey: MenuTag, settingPosition: Int, extras: Bundle?) {
+ activityView!!.showSearchResult(
+ menuKey,
+ settingPosition,
+ requireArguments().getString(ARGUMENT_GAME_ID)!!,
+ extras
+ )
+ }
+
+ private fun highlightSearchResult(position: Int, setting: SettingsItem) {
+ val recyclerView = binding?.listSettings ?: return
+ highlightedSearchResult = setting
+ highlightedSearchResultPosition = position
+ recyclerView.post {
+ (recyclerView.findViewHolderForAdapterPosition(position) as? SettingViewHolder)
+ ?.highlightSearchResult()
+ }
+ }
+
+ private fun clearSearchResultHighlight() {
+ val recyclerView = binding?.listSettings
+ (recyclerView?.findViewHolderForAdapterPosition(
+ highlightedSearchResultPosition
+ ) as? SettingViewHolder)?.clearSearchResultHighlight()
+ highlightedSearchResult = null
+ highlightedSearchResultPosition = RecyclerView.NO_POSITION
+ }
+
override fun showDialogFragment(fragment: DialogFragment) {
activityView!!.showDialogFragment(fragment)
}
@@ -157,7 +277,11 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
override val settings: Settings?
get() = presenter.settings
- override fun onSettingChanged() {
+ override fun onSettingChanged(setting: SettingsItem?) {
+ if (setting == null || setting === highlightedSearchResult) {
+ clearSearchResultHighlight()
+ }
+ presenter.invalidateSearchIndex()
activityView!!.onSettingChanged()
}
@@ -174,6 +298,10 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
return activityView!!.hasMenuTagActionForValue(menuTag, value)
}
+ override fun getMenuTagActionExtras(menuTag: MenuTag, value: Int): Bundle? {
+ return activityView!!.getMenuTagActionExtras(menuTag, value)
+ }
+
override var isMappingAllDevices: Boolean
get() = activityView!!.isMappingAllDevices
set(allDevices) {
@@ -256,6 +384,8 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
companion object {
private const val ARGUMENT_MENU_TAG = "menu_tag"
private const val ARGUMENT_GAME_ID = "game_id"
+ const val ARGUMENT_SCROLL_TO_SETTING_POSITION = "scroll_to_setting_position"
+ private const val SEARCH_QUERY_DEBOUNCE_MS = 120L
private val titles: MutableMap<MenuTag, Int> = EnumMap(MenuTag::class.java)
init {
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt
index a9ca9c3c3d..210a34b23f 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt
@@ -12,7 +12,11 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.collection.ArraySet
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.currentCoroutineContext
+import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.dolphinemu.dolphinemu.NativeLibrary
import org.dolphinemu.dolphinemu.R
@@ -29,28 +33,63 @@ import org.dolphinemu.dolphinemu.features.input.model.view.InputDeviceSetting
import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting
import org.dolphinemu.dolphinemu.features.input.ui.ProfileDialog
import org.dolphinemu.dolphinemu.features.input.ui.ProfileDialogPresenter
-import org.dolphinemu.dolphinemu.features.settings.model.*
-import org.dolphinemu.dolphinemu.features.settings.model.view.*
+import org.dolphinemu.dolphinemu.features.settings.model.AbstractBooleanSetting
+import org.dolphinemu.dolphinemu.features.settings.model.AbstractIntSetting
+import org.dolphinemu.dolphinemu.features.settings.model.AchievementModel
import org.dolphinemu.dolphinemu.features.settings.model.AchievementModel.logout
+import org.dolphinemu.dolphinemu.features.settings.model.AdHocBooleanSetting
+import org.dolphinemu.dolphinemu.features.settings.model.AdHocStringSetting
+import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
+import org.dolphinemu.dolphinemu.features.settings.model.FloatSetting
+import org.dolphinemu.dolphinemu.features.settings.model.IntSetting
+import org.dolphinemu.dolphinemu.features.settings.model.PostProcessing
+import org.dolphinemu.dolphinemu.features.settings.model.ScaledIntSetting
+import org.dolphinemu.dolphinemu.features.settings.model.Settings
+import org.dolphinemu.dolphinemu.features.settings.model.StringSetting
+import org.dolphinemu.dolphinemu.features.settings.model.view.DateTimeChoiceSetting
+import org.dolphinemu.dolphinemu.features.settings.model.view.DirectoryPicker
+import org.dolphinemu.dolphinemu.features.settings.model.view.FilePicker
+import org.dolphinemu.dolphinemu.features.settings.model.view.FloatSliderSetting
+import org.dolphinemu.dolphinemu.features.settings.model.view.HeaderSetting
+import org.dolphinemu.dolphinemu.features.settings.model.view.HyperLinkHeaderSetting
+import org.dolphinemu.dolphinemu.features.settings.model.view.InputStringSetting
+import org.dolphinemu.dolphinemu.features.settings.model.view.IntSliderSetting
+import org.dolphinemu.dolphinemu.features.settings.model.view.InvertedSwitchSetting
+import org.dolphinemu.dolphinemu.features.settings.model.view.LogSwitchSetting
+import org.dolphinemu.dolphinemu.features.settings.model.view.PercentSliderSetting
+import org.dolphinemu.dolphinemu.features.settings.model.view.RunRunnable
+import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem
+import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsSearchResult
+import org.dolphinemu.dolphinemu.features.settings.model.view.SingleChoiceSetting
+import org.dolphinemu.dolphinemu.features.settings.model.view.SingleChoiceSettingDynamicDescriptions
+import org.dolphinemu.dolphinemu.features.settings.model.view.StringSingleChoiceSetting
+import org.dolphinemu.dolphinemu.features.settings.model.view.SubmenuSetting
+import org.dolphinemu.dolphinemu.features.settings.model.view.SwitchSetting
import org.dolphinemu.dolphinemu.model.GpuDriverMetadata
-import org.dolphinemu.dolphinemu.utils.*
-import kotlin.collections.ArrayList
+import org.dolphinemu.dolphinemu.utils.BooleanSupplier
+import org.dolphinemu.dolphinemu.utils.EGLHelper
+import org.dolphinemu.dolphinemu.utils.GpuDriverHelper
+import org.dolphinemu.dolphinemu.utils.GpuDriverInstallResult
+import org.dolphinemu.dolphinemu.utils.ThemeHelper
+import org.dolphinemu.dolphinemu.utils.ThreadUtil
+import org.dolphinemu.dolphinemu.utils.WiiUtils
+import java.util.Locale
import kotlin.math.ceil
import kotlin.math.floor
class SettingsFragmentPresenter(
- private val fragmentView: SettingsFragmentView,
- private val context: Context
+ private val fragmentView: SettingsFragmentView, private val context: Context
) {
private lateinit var menuTag: MenuTag
private var gameId: String? = null
private var settingsList: ArrayList<SettingsItem>? = null
+ private var searchableSettings: List<SearchableSetting>? = null
+ private val searchIndexMutex = Mutex()
+ private var searchIndexGeneration = 0
private var hasOldControllerSettings = false
-
- private var serialPort1Type = 0
- private var controllerNumber = 0
- private var controllerType = 0
+ private var menuExtras = Bundle()
+ private var shouldUpdateWarnings = true
var gpuDriver: GpuDriverMetadata? = null
private val libNameSetting: StringSetting = StringSetting.GFX_DRIVER_LIB_NAME
@@ -58,23 +97,11 @@ class SettingsFragmentPresenter(
fun onCreate(menuTag: MenuTag, gameId: String?, extras: Bundle) {
this.gameId = gameId
this.menuTag = menuTag
+ menuExtras = Bundle(extras)
- if (menuTag.isGCPadMenu || menuTag.isWiimoteExtensionMenu) {
- controllerNumber = menuTag.subType
- controllerType = extras.getInt(ARG_CONTROLLER_TYPE)
- } else if (menuTag.isWiimoteMenu || menuTag.isWiimoteSubmenu) {
- controllerNumber = menuTag.subType
- } else if (menuTag.isSerialPort1Menu) {
- serialPort1Type = extras.getInt(ARG_SERIALPORT1_TYPE)
- } else if (
- menuTag == MenuTag.GRAPHICS
- && this.gameId.isNullOrEmpty()
- && NativeLibrary.IsUninitialized()
- && GpuDriverHelper.supportsCustomDriverLoading()
- ) {
- this.gpuDriver =
- GpuDriverHelper.getInstalledDriverMetadata()
- ?: GpuDriverHelper.getSystemDriverMetadata(context.applicationContext)
+ if (menuTag == MenuTag.GRAPHICS && this.gameId.isNullOrEmpty() && NativeLibrary.IsUninitialized() && GpuDriverHelper.supportsCustomDriverLoading()) {
+ this.gpuDriver = GpuDriverHelper.getInstalledDriverMetadata()
+ ?: GpuDriverHelper.getSystemDriverMetadata(context.applicationContext)
}
}
@@ -100,85 +127,222 @@ class SettingsFragmentPresenter(
}
fun loadSettingsList() {
- val sl = ArrayList<SettingsItem>()
- when (menuTag) {
- MenuTag.SETTINGS -> addTopLevelSettings(sl)
- MenuTag.CONFIG -> addConfigSettings(sl)
- MenuTag.CONFIG_GENERAL -> addGeneralSettings(sl)
- MenuTag.CONFIG_INTERFACE -> addInterfaceSettings(sl)
- MenuTag.CONFIG_AUDIO -> addAudioSettings(sl)
- MenuTag.CONFIG_PATHS -> addPathsSettings(sl)
- MenuTag.CONFIG_GAME_CUBE -> addGameCubeSettings(sl)
- MenuTag.CONFIG_WII -> addWiiSettings(sl)
- MenuTag.CONFIG_ACHIEVEMENTS -> addAchievementSettings(sl);
- MenuTag.CONFIG_ADVANCED -> addAdvancedSettings(sl)
- MenuTag.GRAPHICS -> addGraphicsSettings(sl)
- MenuTag.CONFIG_SERIALPORT1 -> addSerialPortSubSettings(sl, serialPort1Type)
- MenuTag.GCPAD_TYPE -> addGcPadSettings(sl)
- MenuTag.WIIMOTE -> addWiimoteSettings(sl)
- MenuTag.ENHANCEMENTS -> addEnhanceSettings(sl)
- MenuTag.COLOR_CORRECTION -> addColorCorrectionSettings(sl)
- MenuTag.STEREOSCOPY -> addStereoSettings(sl)
- MenuTag.HACKS -> addHackSettings(sl)
- MenuTag.STATISTICS -> addStatisticsSettings(sl)
- MenuTag.ADVANCED_GRAPHICS -> addAdvancedGraphicsSettings(sl)
- MenuTag.CONFIG_LOG -> addLogConfigurationSettings(sl)
- MenuTag.DEBUG -> addDebugSettings(sl)
- MenuTag.GCPAD_1,
- MenuTag.GCPAD_2,
- MenuTag.GCPAD_3,
- MenuTag.GCPAD_4 -> addGcPadSubSettings(
- sl,
- controllerNumber,
- controllerType
- )
+ invalidateSearchIndex()
+ settingsList = tryBuildSettingsList(menuTag, menuExtras, true)
+ ?: throw UnsupportedOperationException("Unimplemented menu")
+ fragmentView.showSettingsList(settingsList!!)
+ }
- MenuTag.WIIMOTE_1,
- MenuTag.WIIMOTE_2,
- MenuTag.WIIMOTE_3,
- MenuTag.WIIMOTE_4 -> addWiimoteSubSettings(
- sl,
- controllerNumber
- )
+ fun getSettingsList(): ArrayList<SettingsItem> = settingsList ?: arrayListOf()
- MenuTag.WIIMOTE_EXTENSION_1,
- MenuTag.WIIMOTE_EXTENSION_2,
- MenuTag.WIIMOTE_EXTENSION_3,
- MenuTag.WIIMOTE_EXTENSION_4 -> addExtensionTypeSettings(
- sl,
- controllerNumber,
- controllerType
- )
+ suspend fun prepareSearchIndex() {
+ getSearchIndex()
+ }
- MenuTag.WIIMOTE_GENERAL_1,
- MenuTag.WIIMOTE_GENERAL_2,
- MenuTag.WIIMOTE_GENERAL_3,
- MenuTag.WIIMOTE_GENERAL_4 -> addWiimoteGeneralSubSettings(
- sl,
- controllerNumber
- )
+ suspend fun searchSettings(query: String): ArrayList<SettingsItem> {
+ val normalizedQuery = query.trim().lowercase(Locale.ROOT)
+ if (normalizedQuery.isEmpty()) {
+ return getSettingsList()
+ }
- MenuTag.WIIMOTE_MOTION_SIMULATION_1,
- MenuTag.WIIMOTE_MOTION_SIMULATION_2,
- MenuTag.WIIMOTE_MOTION_SIMULATION_3,
- MenuTag.WIIMOTE_MOTION_SIMULATION_4 -> addWiimoteMotionSimulationSubSettings(
- sl,
- controllerNumber
- )
+ val terms = normalizedQuery.split(Regex("\\s+"))
+ val index = getSearchIndex()
+ return withContext(Dispatchers.Default) {
+ index.asSequence()
+ .filter { setting -> terms.all(setting.normalizedSearchText::contains) }
+ .map { setting ->
+ val score = when {
+ setting.normalizedName == normalizedQuery -> 0
+ setting.normalizedName.startsWith(normalizedQuery) -> 1
+ terms.all(setting.normalizedName::contains) -> 2
+ setting.normalizedCategory.contains(normalizedQuery) -> 3
+ else -> 4
+ }
+ score to SettingsSearchResult(
+ setting.name,
+ setting.description,
+ setting.menuTag,
+ setting.position,
+ setting.navigationExtras
+ )
+ }
+ .sortedWith(compareBy<Pair<Int, SettingsSearchResult>> { it.first }.thenBy(String.CASE_INSENSITIVE_ORDER) { it.second.name.toString() })
+ .mapTo(ArrayList()) { it.second }
+ }
+ }
- MenuTag.WIIMOTE_MOTION_INPUT_1,
- MenuTag.WIIMOTE_MOTION_INPUT_2,
- MenuTag.WIIMOTE_MOTION_INPUT_3,
- MenuTag.WIIMOTE_MOTION_INPUT_4 -> addWiimoteMotionInputSubSettings(
- sl,
- controllerNumber
- )
+ private suspend fun getSearchIndex(): List<SearchableSetting> {
+ return searchIndexMutex.withLock {
+ searchableSettings?.let { return@withLock it }
- else -> throw UnsupportedOperationException("Unimplemented menu")
+ val generation = searchIndexGeneration
+ val index = withContext(Dispatchers.IO) {
+ buildSearchIndex()
+ }
+ if (generation == searchIndexGeneration) {
+ searchableSettings = index
+ }
+ index
}
+ }
- settingsList = sl
- fragmentView.showSettingsList(settingsList!!)
+ private suspend fun buildSearchIndex(): List<SearchableSetting> {
+ return getSearchableMenus().flatMap { searchableMenu ->
+ currentCoroutineContext().ensureActive()
+ searchableMenu.settings.withIndex().asSequence()
+ .filterNot { it.value is HeaderSetting || it.value is SubmenuSetting }
+ .map { indexedItem ->
+ val item = indexedItem.value
+ val name = item.name.toString()
+ val description = item.description.toString()
+ SearchableSetting(
+ name = name,
+ description = searchableMenu.category,
+ menuTag = searchableMenu.menuTag,
+ navigationExtras = searchableMenu.navigationExtras,
+ position = indexedItem.index,
+ normalizedName = name.lowercase(Locale.ROOT),
+ normalizedCategory = searchableMenu.category.lowercase(Locale.ROOT),
+ normalizedSearchText = "$name $description ${searchableMenu.category}".lowercase(
+ Locale.ROOT
+ )
+ )
+ }.toList()
+ }
+ }
+
+ fun invalidateSearchIndex() {
+ searchableSettings = null
+ searchIndexGeneration++
+ }
+
+ private suspend fun getSearchableMenus(): List<SearchableMenu> {
+ val menus = mutableListOf<SearchableMenu>()
+ val visitedMenus = mutableSetOf<String>()
+
+ suspend fun visitMenu(
+ menuTag: MenuTag,
+ category: String,
+ navigationExtras: Bundle? = null,
+ actionValue: Int? = null
+ ) {
+ currentCoroutineContext().ensureActive()
+ if (!visitedMenus.add("$menuTag:$actionValue")) {
+ return
+ }
+
+ val items = tryBuildSettingsList(menuTag, navigationExtras ?: Bundle(), false) ?: return
+ if (menuTag != MenuTag.SETTINGS) {
+ menus += SearchableMenu(menuTag, category, navigationExtras, items)
+ }
+
+ suspend fun visitMenuAction(
+ childMenuTag: MenuTag?, selectedValue: Int, childCategory: String
+ ) {
+ if (childMenuTag != null && fragmentView.hasMenuTagActionForValue(
+ childMenuTag, selectedValue
+ )
+ ) {
+ visitMenu(
+ childMenuTag,
+ childCategory,
+ fragmentView.getMenuTagActionExtras(childMenuTag, selectedValue),
+ selectedValue
+ )
+ }
+ }
+
+ for (item in items) {
+ currentCoroutineContext().ensureActive()
+ val childCategory = if (category.isEmpty()) item.name.toString()
+ else context.getString(
+ R.string.search_settings_category_path, category, item.name
+ )
+
+ when (item) {
+ is SubmenuSetting -> visitMenu(item.menuKey, childCategory)
+ is SingleChoiceSetting -> {
+ visitMenuAction(item.menuTag, item.selectedValue, childCategory)
+ }
+
+ is StringSingleChoiceSetting -> {
+ visitMenuAction(item.menuTag, item.selectedValueIndex, childCategory)
+ }
+ }
+ }
+ }
+
+ visitMenu(MenuTag.SETTINGS, "")
+ return menus
+ }
+
+ private fun tryBuildSettingsList(
+ targetMenuTag: MenuTag, extras: Bundle, updateWarnings: Boolean
+ ): ArrayList<SettingsItem>? {
+ val sl = ArrayList<SettingsItem>()
+ var isSupportedMenu = true
+ val previousMenuTag = menuTag
+ val previousShouldUpdateWarnings = shouldUpdateWarnings
+ menuTag = targetMenuTag
+ shouldUpdateWarnings = updateWarnings
+ try {
+ when (targetMenuTag) {
+ MenuTag.SETTINGS -> addTopLevelSettings(sl)
+ MenuTag.CONFIG -> addConfigSettings(sl)
+ MenuTag.CONFIG_GENERAL -> addGeneralSettings(sl)
+ MenuTag.CONFIG_INTERFACE -> addInterfaceSettings(sl)
+ MenuTag.CONFIG_AUDIO -> addAudioSettings(sl)
+ MenuTag.CONFIG_PATHS -> addPathsSettings(sl)
+ MenuTag.CONFIG_GAME_CUBE -> addGameCubeSettings(sl)
+ MenuTag.CONFIG_WII -> addWiiSettings(sl)
+ MenuTag.CONFIG_ACHIEVEMENTS -> addAchievementSettings(sl)
+ MenuTag.CONFIG_ADVANCED -> addAdvancedSettings(sl)
+ MenuTag.GRAPHICS -> addGraphicsSettings(sl)
+ MenuTag.CONFIG_SERIALPORT1 -> addSerialPortSubSettings(
+ sl, extras.getInt(ARG_SERIALPORT1_TYPE)
+ )
+
+ MenuTag.GCPAD_TYPE -> addGcPadSettings(sl)
+ MenuTag.WIIMOTE -> addWiimoteSettings(sl)
+ MenuTag.ENHANCEMENTS -> addEnhanceSettings(sl)
+ MenuTag.COLOR_CORRECTION -> addColorCorrectionSettings(sl)
+ MenuTag.STEREOSCOPY -> addStereoSettings(sl)
+ MenuTag.HACKS -> addHackSettings(sl)
+ MenuTag.STATISTICS -> addStatisticsSettings(sl)
+ MenuTag.ADVANCED_GRAPHICS -> addAdvancedGraphicsSettings(sl)
+ MenuTag.CONFIG_LOG -> addLogConfigurationSettings(sl)
+ MenuTag.DEBUG -> addDebugSettings(sl)
+ MenuTag.GCPAD_1, MenuTag.GCPAD_2, MenuTag.GCPAD_3, MenuTag.GCPAD_4 -> addGcPadSubSettings(
+ sl, targetMenuTag.subType, extras.getInt(ARG_CONTROLLER_TYPE)
+ )
+
+ MenuTag.WIIMOTE_1, MenuTag.WIIMOTE_2, MenuTag.WIIMOTE_3, MenuTag.WIIMOTE_4 -> addWiimoteSubSettings(
+ sl, targetMenuTag.subType
+ )
+
+ MenuTag.WIIMOTE_EXTENSION_1, MenuTag.WIIMOTE_EXTENSION_2, MenuTag.WIIMOTE_EXTENSION_3, MenuTag.WIIMOTE_EXTENSION_4 -> addExtensionTypeSettings(
+ sl, targetMenuTag.subType, extras.getInt(ARG_CONTROLLER_TYPE)
+ )
+
+ MenuTag.WIIMOTE_GENERAL_1, MenuTag.WIIMOTE_GENERAL_2, MenuTag.WIIMOTE_GENERAL_3, MenuTag.WIIMOTE_GENERAL_4 -> addWiimoteGeneralSubSettings(
+ sl, targetMenuTag.subType
+ )
+
+ MenuTag.WIIMOTE_MOTION_SIMULATION_1, MenuTag.WIIMOTE_MOTION_SIMULATION_2, MenuTag.WIIMOTE_MOTION_SIMULATION_3, MenuTag.WIIMOTE_MOTION_SIMULATION_4 -> addWiimoteMotionSimulationSubSettings(
+ sl, targetMenuTag.subType
+ )
+
+ MenuTag.WIIMOTE_MOTION_INPUT_1, MenuTag.WIIMOTE_MOTION_INPUT_2, MenuTag.WIIMOTE_MOTION_INPUT_3, MenuTag.WIIMOTE_MOTION_INPUT_4 -> addWiimoteMotionInputSubSettings(
+ sl, targetMenuTag.subType
+ )
+
+ else -> isSupportedMenu = false
+ }
+ } finally {
+ menuTag = previousMenuTag
+ shouldUpdateWarnings = previousShouldUpdateWarnings
+ }
+ return if (isSupportedMenu) sl else null
}
private fun addTopLevelSettings(sl: ArrayList<SettingsItem>) {
@@ -205,9 +369,9 @@ class SettingsFragmentPresenter(
sl.add(SubmenuSetting(context, R.string.log_submenu, MenuTag.CONFIG_LOG))
sl.add(SubmenuSetting(context, R.string.debug_submenu, MenuTag.DEBUG))
sl.add(
- RunRunnable(context, R.string.user_data_submenu, 0, 0, 0, false)
- { UserDataActivity.launch(context) }
- )
+ RunRunnable(
+ context, R.string.user_data_submenu, 0, 0, 0, false
+ ) { UserDataActivity.launch(context) })
}
private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
@@ -221,10 +385,7 @@ class SettingsFragmentPresenter(
)
sl.add(
SwitchSetting(
- context,
- BooleanSetting.MAIN_ENABLE_CHEATS,
- R.string.enable_cheats,
- 0
+ context, BooleanSetting.MAIN_ENABLE_CHEATS, R.string.enable_cheats, 0
)
)
sl.add(
@@ -237,10 +398,7 @@ class SettingsFragmentPresenter(
)
sl.add(
SwitchSetting(
- context,
- BooleanSetting.MAIN_AUTO_DISC_CHANGE,
- R.string.auto_disc_change,
- 0
+ context, BooleanSetting.MAIN_AUTO_DISC_CHANGE, R.string.auto_disc_change, 0
)
)
sl.add(
@@ -268,10 +426,7 @@ class SettingsFragmentPresenter(
)
sl.add(
SwitchSetting(
- context,
- BooleanSetting.MAIN_ANALYTICS_ENABLED,
- R.string.analytics,
- 0
+ context, BooleanSetting.MAIN_ANALYTICS_ENABLED, R.string.analytics, 0
)
)
sl.add(
@@ -282,8 +437,7 @@ class SettingsFragmentPresenter(
R.string.analytics_new_id_confirmation,
0,
true
- ) { NativeLibrary.GenerateNewStatisticsId() }
- )
+ ) { NativeLibrary.GenerateNewStatisticsId() })
sl.add(
SwitchSetting(
context,
@@ -298,8 +452,9 @@ class SettingsFragmentPresenter(
// Hide the orientation setting if the device only supports one orientation. Old devices which
// support both portrait and landscape may report support for neither, so we use ==, not &&.
val packageManager = context.packageManager
- if (packageManager.hasSystemFeature(PackageManager.FEATURE_SCREEN_PORTRAIT) ==
- packageManager.hasSystemFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE)
+ if (packageManager.hasSystemFeature(PackageManager.FEATURE_SCREEN_PORTRAIT) == packageManager.hasSystemFeature(
+ PackageManager.FEATURE_SCREEN_LANDSCAPE
+ )
) {
sl.add(
SingleChoiceSetting(
@@ -368,8 +523,7 @@ class SettingsFragmentPresenter(
override fun setInt(settings: Settings, newValue: Int) {
IntSetting.MAIN_INTERFACE_THEME.setInt(settings, newValue)
ThemeHelper.saveTheme(
- (fragmentView.fragmentActivity as AppCompatActivity),
- newValue
+ (fragmentView.fragmentActivity as AppCompatActivity), newValue
)
}
}
@@ -417,8 +571,7 @@ class SettingsFragmentPresenter(
override fun setInt(settings: Settings, newValue: Int) {
IntSetting.MAIN_INTERFACE_THEME_MODE.setInt(settings, newValue)
ThemeHelper.saveThemeMode(
- (fragmentView.fragmentActivity as AppCompatActivity),
- newValue
+ (fragmentView.fragmentActivity as AppCompatActivity), newValue
)
}
}
@@ -451,8 +604,7 @@ class SettingsFragmentPresenter(
override fun setBoolean(settings: Settings, newValue: Boolean) {
BooleanSetting.MAIN_USE_BLACK_BACKGROUNDS.setBoolean(settings, newValue)
ThemeHelper.saveBackgroundSetting(
- (fragmentView.fragmentActivity as AppCompatActivity),
- newValue
+ (fragmentView.fragmentActivity as AppCompatActivity), newValue
)
}
}
@@ -501,17 +653,16 @@ class SettingsFragmentPresenter(
}
override val isOverridden: Boolean
- get() = BooleanSetting.MAIN_DSP_HLE.isOverridden ||
- BooleanSetting.MAIN_DSP_JIT.isOverridden
+ get() = BooleanSetting.MAIN_DSP_HLE.isOverridden || BooleanSetting.MAIN_DSP_JIT.isOverridden
override val isRuntimeEditable: Boolean
- get() = BooleanSetting.MAIN_DSP_HLE.isRuntimeEditable &&
- BooleanSetting.MAIN_DSP_JIT.isRuntimeEditable
+ get() = BooleanSetting.MAIN_DSP_HLE.isRuntimeEditable && BooleanSetting.MAIN_DSP_JIT.isRuntimeEditable
override fun delete(settings: Settings): Boolean {
// Not short circuiting
- return BooleanSetting.MAIN_DSP_HLE.delete(settings) and
- BooleanSetting.MAIN_DSP_JIT.delete(settings)
+ return BooleanSetting.MAIN_DSP_HLE.delete(settings) and BooleanSetting.MAIN_DSP_JIT.delete(
+ settings
+ )
}
}
@@ -566,14 +717,7 @@ class SettingsFragmentPresenter(
)
sl.add(
IntSliderSetting(
- context,
- IntSetting.MAIN_AUDIO_VOLUME,
- R.string.audio_volume,
- 0,
- 0,
- 100,
- "%",
- 1
+ context, IntSetting.MAIN_AUDIO_VOLUME, R.string.audio_volume, 0, 0, 100, "%", 1
)
)
}
@@ -581,10 +725,7 @@ class SettingsFragmentPresenter(
private fun addPathsSettings(sl: ArrayList<SettingsItem>) {
sl.add(
SwitchSetting(
- context,
- BooleanSetting.MAIN_RECURSIVE_ISO_PATHS,
- R.string.search_subfolders,
- 0
+ context, BooleanSetting.MAIN_RECURSIVE_ISO_PATHS, R.string.search_subfolders, 0
)
)
sl.add(
@@ -802,10 +943,7 @@ class SettingsFragmentPresenter(
)
sl.add(
SwitchSetting(
- context,
- BooleanSetting.MAIN_ALLOW_SD_WRITES,
- R.string.wii_sd_card_allow_writes,
- 0
+ context, BooleanSetting.MAIN_ALLOW_SD_WRITES, R.string.wii_sd_card_allow_writes, 0
)
)
sl.add(
@@ -847,8 +985,7 @@ class SettingsFragmentPresenter(
R.string.wii_sd_card_folder_to_file_confirmation,
0,
false
- ) { convertOnThread { WiiUtils.syncSdFolderToSdImage() } }
- )
+ ) { convertOnThread { WiiUtils.syncSdFolderToSdImage() } })
sl.add(
RunRunnable(
context,
@@ -857,16 +994,12 @@ class SettingsFragmentPresenter(
R.string.wii_sd_card_file_to_folder_confirmation,
0,
false
- ) { convertOnThread { WiiUtils.syncSdImageToSdFolder() } }
- )
+ ) { convertOnThread { WiiUtils.syncSdImageToSdFolder() } })
sl.add(HeaderSetting(context, R.string.wii_wiimote_settings, 0))
sl.add(
SwitchSetting(
- context,
- BooleanSetting.SYSCONF_WIIMOTE_MOTOR,
- R.string.wiimote_rumble,
- 0
+ context, BooleanSetting.SYSCONF_WIIMOTE_MOTOR, R.string.wiimote_rumble, 0
)
)
sl.add(
@@ -939,18 +1072,12 @@ class SettingsFragmentPresenter(
)
sl.add(
SwitchSetting(
- context,
- BooleanSetting.MAIN_EMULATE_WII_SPEAK,
- R.string.emulate_wii_speak,
- 0
+ context, BooleanSetting.MAIN_EMULATE_WII_SPEAK, R.string.emulate_wii_speak, 0
)
)
sl.add(
SwitchSetting(
- context,
- BooleanSetting.MAIN_WII_SPEAK_MUTED,
- R.string.mute_wii_speak,
- 0
+ context, BooleanSetting.MAIN_WII_SPEAK_MUTED, R.string.mute_wii_speak, 0
)
)
}
@@ -962,10 +1089,8 @@ class SettingsFragmentPresenter(
override fun setBoolean(settings: Settings, newValue: Boolean) {
BooleanSetting.ACHIEVEMENTS_ENABLED.setBoolean(settings, newValue)
- if (newValue)
- AchievementModel.init()
- else
- AchievementModel.shutdown()
+ if (newValue) AchievementModel.init()
+ else AchievementModel.shutdown()
loadSettingsList()
}
@@ -985,38 +1110,25 @@ class SettingsFragmentPresenter(
sl.add(
SwitchSetting(
- context,
- achievementsEnabledSetting,
- R.string.achievements_enabled,
- 0
+ context, achievementsEnabledSetting, R.string.achievements_enabled, 0
)
)
if (BooleanSetting.ACHIEVEMENTS_ENABLED.boolean) {
if (StringSetting.ACHIEVEMENTS_API_TOKEN.string == "") {
sl.add(
RunRunnable(
- context,
- R.string.achievements_login,
- 0,
- 0,
- 0,
- false
+ context, R.string.achievements_login, 0, 0, 0, false
) {
- fragmentView.showDialogFragment(LoginDialog(this))
- loadSettingsList()
+ fragmentView.showDialogFragment(LoginDialog(this))
+ loadSettingsList()
})
} else {
sl.add(
RunRunnable(
- context,
- R.string.achievements_logout,
- 0,
- 0,
- 0,
- false
+ context, R.string.achievements_logout, 0, 0, 0, false
) {
- logout()
- loadSettingsList()
+ logout()
+ loadSettingsList()
})
}
sl.add(
@@ -1112,17 +1224,16 @@ class SettingsFragmentPresenter(
}
override val isOverridden: Boolean
- get() = BooleanSetting.MAIN_SYNC_ON_SKIP_IDLE.isOverridden ||
- BooleanSetting.MAIN_SYNC_GPU.isOverridden
+ get() = BooleanSetting.MAIN_SYNC_ON_SKIP_IDLE.isOverridden || BooleanSetting.MAIN_SYNC_GPU.isOverridden
override val isRuntimeEditable: Boolean
- get() = BooleanSetting.MAIN_SYNC_ON_SKIP_IDLE.isRuntimeEditable &&
- BooleanSetting.MAIN_SYNC_GPU.isRuntimeEditable
+ get() = BooleanSetting.MAIN_SYNC_ON_SKIP_IDLE.isRuntimeEditable && BooleanSetting.MAIN_SYNC_GPU.isRuntimeEditable
override fun delete(settings: Settings): Boolean {
// Not short circuiting
- return BooleanSetting.MAIN_SYNC_ON_SKIP_IDLE.delete(settings) and
- BooleanSetting.MAIN_SYNC_GPU.delete(settings)
+ return BooleanSetting.MAIN_SYNC_ON_SKIP_IDLE.delete(settings) and BooleanSetting.MAIN_SYNC_GPU.delete(
+ settings
+ )
}
}
@@ -1243,26 +1354,12 @@ class SettingsFragmentPresenter(
)
sl.add(
IntSliderSetting(
- context,
- mem1Size,
- R.string.main_mem1_size,
- 0,
- 24,
- 64,
- "MB",
- 1
+ context, mem1Size, R.string.main_mem1_size, 0, 24, 64, "MB", 1
)
)
sl.add(
IntSliderSetting(
- context,
- mem2Size,
- R.string.main_mem2_size,
- 0,
- 64,
- 128,
- "MB",
- 1
+ context, mem2Size, R.string.main_mem2_size, 0, 64, 128, "MB", 1
)
)
@@ -1289,10 +1386,7 @@ class SettingsFragmentPresenter(
)
sl.add(
DateTimeChoiceSetting(
- context,
- StringSetting.MAIN_CUSTOM_RTC_VALUE,
- R.string.set_custom_rtc,
- 0
+ context, StringSetting.MAIN_CUSTOM_RTC_VALUE, R.string.set_custom_rtc, 0
)
)
@@ -1520,42 +1614,29 @@ class SettingsFragmentPresenter(
sl.add(HeaderSetting(context, R.string.graphics_more_settings, 0))
sl.add(
SubmenuSetting(
- context,
- R.string.enhancements_submenu,
- MenuTag.ENHANCEMENTS
+ context, R.string.enhancements_submenu, MenuTag.ENHANCEMENTS
)
)
sl.add(
SubmenuSetting(
- context,
- R.string.hacks_submenu,
- MenuTag.HACKS
+ context, R.string.hacks_submenu, MenuTag.HACKS
)
)
sl.add(
SubmenuSetting(
- context,
- R.string.statistics_submenu,
- MenuTag.STATISTICS
+ context, R.string.statistics_submenu, MenuTag.STATISTICS
)
)
sl.add(
SubmenuSetting(
- context,
- R.string.advanced_graphics_submenu,
- MenuTag.ADVANCED_GRAPHICS
+ context, R.string.advanced_graphics_submenu, MenuTag.ADVANCED_GRAPHICS
)
)
- if (
- this.gpuDriver != null && this.gameId.isNullOrEmpty()
- && NativeLibrary.IsUninitialized()
- && GpuDriverHelper.supportsCustomDriverLoading()
- ) {
+ if (this.gpuDriver != null && this.gameId.isNullOrEmpty() && NativeLibrary.IsUninitialized() && GpuDriverHelper.supportsCustomDriverLoading()) {
sl.add(
SubmenuSetting(
- context,
- R.string.gpu_driver_submenu, MenuTag.GPU_DRIVERS
+ context, R.string.gpu_driver_submenu, MenuTag.GPU_DRIVERS
)
)
}
@@ -1604,9 +1685,7 @@ class SettingsFragmentPresenter(
)
sl.add(
SubmenuSetting(
- context,
- R.string.color_correction_submenu,
- MenuTag.COLOR_CORRECTION
+ context, R.string.color_correction_submenu, MenuTag.COLOR_CORRECTION
)
)
@@ -1697,9 +1776,7 @@ class SettingsFragmentPresenter(
) {
sl.add(
SubmenuSetting(
- context,
- R.string.stereoscopy_submenu,
- MenuTag.STEREOSCOPY
+ context, R.string.stereoscopy_submenu, MenuTag.STEREOSCOPY
)
)
}
@@ -1743,10 +1820,7 @@ class SettingsFragmentPresenter(
)
add(
SwitchSetting(
- context,
- BooleanSetting.GFX_CC_CORRECT_GAMMA,
- R.string.correct_sdr_gamma,
- 0
+ context, BooleanSetting.GFX_CC_CORRECT_GAMMA, R.string.correct_sdr_gamma, 0
)
)
}
@@ -2053,10 +2127,10 @@ class SettingsFragmentPresenter(
IntSetting.GFX_CROP_CUSTOM_LEFT,
R.string.crop_custom_left,
R.string.crop_custom_left_description,
- min=0,
- max=640,
- units="px",
- stepSize=1,
+ min = 0,
+ max = 640,
+ units = "px",
+ stepSize = 1,
)
)
sl.add(
@@ -2075,18 +2149,12 @@ class SettingsFragmentPresenter(
sl.add(HeaderSetting(context, R.string.misc, 0))
sl.add(
SwitchSetting(
- context,
- BooleanSetting.SYSCONF_PROGRESSIVE_SCAN,
- R.string.progressive_scan,
- 0
+ context, BooleanSetting.SYSCONF_PROGRESSIVE_SCAN, R.string.progressive_scan, 0
)
)
sl.add(
SwitchSetting(
- context,
- BooleanSetting.GFX_VSYNC,
- R.string.vsync,
- R.string.vsync_description
+ context, BooleanSetting.GFX_VSYNC, R.string.vsync, R.string.vsync_description
)
)
sl.add(
@@ -2242,35 +2310,21 @@ class SettingsFragmentPresenter(
IntSetting.LOGGER_VERBOSITY,
R.string.log_verbosity,
0,
- getLogVerbosityEntries(), getLogVerbosityValues()
+ getLogVerbosityEntries(),
+ getLogVerbosityValues()
)
)
sl.add(
RunRunnable(
- context,
- R.string.log_enable_all,
- 0,
- R.string.log_enable_all_confirmation,
- 0,
- true
+ context, R.string.log_enable_all, 0, R.string.log_enable_all_confirmation, 0, true
) { setAllLogTypes(true) })
sl.add(
RunRunnable(
- context,
- R.string.log_disable_all,
- 0,
- R.string.log_disable_all_confirmation,
- 0,
- true
+ context, R.string.log_disable_all, 0, R.string.log_disable_all_confirmation, 0, true
) { setAllLogTypes(false) })
sl.add(
RunRunnable(
- context,
- R.string.log_clear,
- 0,
- R.string.log_clear_confirmation,
- 0,
- true
+ context, R.string.log_clear, 0, R.string.log_clear_confirmation, 0, true
) { SettingsAdapter.clearLog() })
sl.add(HeaderSetting(context, R.string.log_types, 0))
@@ -2283,10 +2337,7 @@ class SettingsFragmentPresenter(
sl.add(HeaderSetting(context, R.string.debug_warning, 0))
sl.add(
InvertedSwitchSetting(
- context,
- BooleanSetting.MAIN_FASTMEM,
- R.string.debug_fastmem,
- 0
+ context, BooleanSetting.MAIN_FASTMEM, R.string.debug_fastmem, 0
)
)
sl.add(
@@ -2299,10 +2350,7 @@ class SettingsFragmentPresenter(
)
sl.add(
InvertedSwitchSetting(
- context,
- BooleanSetting.MAIN_FASTMEM_ARENA,
- R.string.debug_fastmem_arena,
- 0
+ context, BooleanSetting.MAIN_FASTMEM_ARENA, R.string.debug_fastmem_arena, 0
)
)
sl.add(
@@ -2321,7 +2369,7 @@ class SettingsFragmentPresenter(
BooleanSetting.MAIN_DEBUG_JIT_ENABLE_PROFILING,
R.string.debug_jit_enable_block_profiling,
0
- )
+ )
)
sl.add(
RunRunnable(
@@ -2331,26 +2379,16 @@ class SettingsFragmentPresenter(
R.string.debug_jit_wipe_block_profiling_data_alert,
0,
true
- ) { NativeLibrary.WipeJitBlockProfilingData() }
- )
+ ) { NativeLibrary.WipeJitBlockProfilingData() })
sl.add(
RunRunnable(
- context,
- R.string.debug_jit_write_block_log_dump,
- 0,
- 0,
- 0,
- true
- ) { NativeLibrary.WriteJitBlockLogDump() }
- )
+ context, R.string.debug_jit_write_block_log_dump, 0, 0, 0, true
+ ) { NativeLibrary.WriteJitBlockLogDump() })
sl.add(HeaderSetting(context, R.string.debug_jit_header, 0))
sl.add(
SwitchSetting(
- context,
- BooleanSetting.MAIN_DEBUG_JIT_OFF,
- R.string.debug_jitoff,
- 0
+ context, BooleanSetting.MAIN_DEBUG_JIT_OFF, R.string.debug_jitoff, 0
)
)
sl.add(
@@ -2387,18 +2425,12 @@ class SettingsFragmentPresenter(
)
sl.add(
SwitchSetting(
- context,
- BooleanSetting.MAIN_DEBUG_JIT_INTEGER_OFF,
- R.string.debug_jitintegeroff,
- 0
+ context, BooleanSetting.MAIN_DEBUG_JIT_INTEGER_OFF, R.string.debug_jitintegeroff, 0
)
)
sl.add(
SwitchSetting(
- context,
- BooleanSetting.MAIN_DEBUG_JIT_PAIRED_OFF,
- R.string.debug_jitpairedoff,
- 0
+ context, BooleanSetting.MAIN_DEBUG_JIT_PAIRED_OFF, R.string.debug_jitpairedoff, 0
)
)
sl.add(
@@ -2411,10 +2443,7 @@ class SettingsFragmentPresenter(
)
sl.add(
SwitchSetting(
- context,
- BooleanSetting.MAIN_DEBUG_JIT_BRANCH_OFF,
- R.string.debug_jitbranchoff,
- 0
+ context, BooleanSetting.MAIN_DEBUG_JIT_BRANCH_OFF, R.string.debug_jitbranchoff, 0
)
)
sl.add(
@@ -2474,7 +2503,9 @@ class SettingsFragmentPresenter(
)
}
- private fun addGcPadSubSettings(sl: ArrayList<SettingsItem>, gcPadNumber: Int, gcPadType: Int) {
+ private fun addGcPadSubSettings(
+ sl: ArrayList<SettingsItem>, gcPadNumber: Int, gcPadType: Int
+ ) {
when (gcPadType) {
6, 8, 9, 10, 11 -> {
// Emulated
@@ -2484,9 +2515,10 @@ class SettingsFragmentPresenter(
addControllerPerGameSettings(sl, gcPad, gcPadNumber)
} else {
addControllerMetaSettings(sl, gcPad)
- addControllerMappingSettings(sl, gcPad, null)
+ addControllerMappingSettings(sl, gcPad, gcPadNumber, null)
}
}
+
7 -> {
// Emulated keyboard controller
val gcKeyboard = EmulatedController.getGcKeyboard(gcPadNumber)
@@ -2496,9 +2528,12 @@ class SettingsFragmentPresenter(
} else {
sl.add(HeaderSetting(context, R.string.keyboard_controller_warning, 0))
addControllerMetaSettings(sl, gcKeyboard)
- addControllerMappingSettings(sl, gcKeyboard, null)
+ addControllerMappingSettings(
+ sl, gcKeyboard, gcPadNumber, null
+ )
}
}
+
12 -> {
// Adapter
sl.add(
@@ -2521,7 +2556,9 @@ class SettingsFragmentPresenter(
}
}
- private fun addWiimoteSubSettings(sl: ArrayList<SettingsItem>, wiimoteNumber: Int) {
+ private fun addWiimoteSubSettings(
+ sl: ArrayList<SettingsItem>, wiimoteNumber: Int
+ ) {
val wiimote = EmulatedController.getWiimote(wiimoteNumber)
if (!TextUtils.isEmpty(gameId)) {
@@ -2557,39 +2594,40 @@ class SettingsFragmentPresenter(
addControllerMappingSettings(
sl,
wiimote,
+ wiimoteNumber,
ArraySet(listOf(ControlGroup.TYPE_ATTACHMENTS, ControlGroup.TYPE_OTHER))
)
}
}
private fun addExtensionTypeSettings(
- sl: ArrayList<SettingsItem>,
- wiimoteNumber: Int,
- extensionType: Int
+ sl: ArrayList<SettingsItem>, wiimoteNumber: Int, extensionType: Int
) {
addContainerMappingSettings(
sl,
EmulatedController.getWiimote(wiimoteNumber),
EmulatedController.getWiimoteAttachment(wiimoteNumber, extensionType),
+ wiimoteNumber,
null
)
}
- private fun addWiimoteGeneralSubSettings(sl: ArrayList<SettingsItem>, wiimoteNumber: Int) {
+ private fun addWiimoteGeneralSubSettings(
+ sl: ArrayList<SettingsItem>, wiimoteNumber: Int
+ ) {
addControllerMappingSettings(
sl,
EmulatedController.getWiimote(wiimoteNumber),
+ wiimoteNumber,
setOf(ControlGroup.TYPE_BUTTONS)
)
}
private fun addWiimoteMotionSimulationSubSettings(
- sl: ArrayList<SettingsItem>,
- wiimoteNumber: Int
+ sl: ArrayList<SettingsItem>, wiimoteNumber: Int
) {
addControllerMappingSettings(
- sl, EmulatedController.getWiimote(wiimoteNumber),
- ArraySet(
+ sl, EmulatedController.getWiimote(wiimoteNumber), wiimoteNumber, ArraySet(
listOf(
ControlGroup.TYPE_FORCE,
ControlGroup.TYPE_TILT,
@@ -2600,10 +2638,11 @@ class SettingsFragmentPresenter(
)
}
- private fun addWiimoteMotionInputSubSettings(sl: ArrayList<SettingsItem>, wiimoteNumber: Int) {
+ private fun addWiimoteMotionInputSubSettings(
+ sl: ArrayList<SettingsItem>, wiimoteNumber: Int
+ ) {
addControllerMappingSettings(
- sl, EmulatedController.getWiimote(wiimoteNumber),
- ArraySet(
+ sl, EmulatedController.getWiimote(wiimoteNumber), wiimoteNumber, ArraySet(
listOf(
ControlGroup.TYPE_IMU_ACCELEROMETER,
ControlGroup.TYPE_IMU_GYROSCOPE,
@@ -2621,9 +2660,7 @@ class SettingsFragmentPresenter(
* @param controllerNumber The index of the controller, 0-3.
*/
private fun addControllerPerGameSettings(
- sl: ArrayList<SettingsItem>,
- controller: EmulatedController,
- controllerNumber: Int
+ sl: ArrayList<SettingsItem>, controller: EmulatedController, controllerNumber: Int
) {
val profiles = ProfileDialogPresenter(menuTag).getProfileNames(false)
val profileKey = controller.getProfileKey() + "Profile" + (controllerNumber + 1)
@@ -2648,15 +2685,11 @@ class SettingsFragmentPresenter(
* @param controller The controller to add settings for.
*/
private fun addControllerMetaSettings(
- sl: ArrayList<SettingsItem>,
- controller: EmulatedController
+ sl: ArrayList<SettingsItem>, controller: EmulatedController
) {
sl.add(
InputDeviceSetting(
- context,
- R.string.input_device,
- 0,
- controller
+ context, R.string.input_device, 0, controller
)
)
@@ -2698,12 +2731,7 @@ class SettingsFragmentPresenter(
) { clearControllerSettings(controller) })
sl.add(
RunRunnable(
- context,
- R.string.input_profiles,
- 0,
- 0,
- 0,
- true
+ context, R.string.input_profiles, 0, 0, 0, true
) { fragmentView.showDialogFragment(ProfileDialog.create(menuTag)) })
updateOldControllerSettingsWarningVisibility(controller)
@@ -2714,14 +2742,18 @@ class SettingsFragmentPresenter(
*
* @param sl The list to place controller settings into.
* @param controller The controller to add settings for.
+ * @param controllerNumber The zero-based controller number.
* @param groupTypeFilter If this is non-null, only groups whose types match this are considered.
*/
private fun addControllerMappingSettings(
- sl: ArrayList<SettingsItem>,
- controller: EmulatedController,
- groupTypeFilter: Set<Int>?
+ sl: ArrayList<SettingsItem>,
+ controller: EmulatedController,
+ controllerNumber: Int,
+ groupTypeFilter: Set<Int>?
) {
- addContainerMappingSettings(sl, controller, controller, groupTypeFilter)
+ addContainerMappingSettings(
+ sl, controller, controller, controllerNumber, groupTypeFilter
+ )
}
/**
@@ -2730,12 +2762,14 @@ class SettingsFragmentPresenter(
* @param sl The list to place controller settings into.
* @param controller The encompassing controller.
* @param container The container of control groups to add settings for.
+ * @param controllerNumber The zero-based controller number.
* @param groupTypeFilter If this is non-null, only groups whose types match this are considered.
*/
private fun addContainerMappingSettings(
sl: ArrayList<SettingsItem>,
controller: EmulatedController,
container: ControlGroupContainer,
+ controllerNumber: Int,
groupTypeFilter: Set<Int>?
) {
updateOldControllerSettingsWarningVisibility(controller)
@@ -2751,10 +2785,7 @@ class SettingsFragmentPresenter(
if (group.getDefaultEnabledValue() != ControlGroup.DEFAULT_ENABLED_ALWAYS) {
sl.add(
SwitchSetting(
- context,
- ControlGroupEnabledSetting(group),
- R.string.enabled,
- 0
+ context, ControlGroupEnabledSetting(group), R.string.enabled, 0
)
)
}
@@ -2768,8 +2799,11 @@ class SettingsFragmentPresenter(
val attachmentSetting = group.getAttachmentSetting()
sl.add(
SingleChoiceSetting(
- context, InputMappingIntSetting(attachmentSetting),
- R.string.wiimote_extensions, 0, R.array.wiimoteExtensionsEntries,
+ context,
+ InputMappingIntSetting(attachmentSetting),
+ R.string.wiimote_extensions,
+ 0,
+ R.array.wiimoteExtensionsEntries,
R.array.wiimoteExtensionsValues,
MenuTag.getWiimoteExtensionMenuTag(controllerNumber)
)
@@ -2813,10 +2847,14 @@ class SettingsFragmentPresenter(
}
private fun updateOldControllerSettingsWarningVisibility(controller: EmulatedController) {
+ if (!shouldUpdateWarnings) {
+ return
+ }
+
val defaultDevice = controller.getDefaultDevice()
- hasOldControllerSettings = defaultDevice.startsWith("Android/") &&
- defaultDevice.endsWith("/Touchscreen")
+ hasOldControllerSettings =
+ defaultDevice.startsWith("Android/") && defaultDevice.endsWith("/Touchscreen")
fragmentView.setOldControllerSettingsWarningVisibility(hasOldControllerSettings)
}
@@ -2836,10 +2874,7 @@ class SettingsFragmentPresenter(
for (logType in NativeLibrary.GetLogTypeNames()) {
AdHocBooleanSetting(
- Settings.FILE_LOGGER,
- Settings.SECTION_LOGGER_LOGS,
- logType.first,
- false
+ Settings.FILE_LOGGER, Settings.SECTION_LOGGER_LOGS, logType.first, false
).setBoolean(settings!!, value)
}
@@ -2851,8 +2886,7 @@ class SettingsFragmentPresenter(
fragmentView.fragmentActivity,
R.string.wii_converting,
0,
- { context.resources.getString(if (f.get()) R.string.wii_convert_success else R.string.wii_convert_failure) }
- )
+ { context.resources.getString(if (f.get()) R.string.wii_convert_success else R.string.wii_convert_failure) })
}
fun installDriver(uri: Uri) {
@@ -2884,9 +2918,8 @@ class SettingsFragmentPresenter(
GpuDriverHelper.uninstallDriver()
withContext(Dispatchers.Main) {
with(this@SettingsFragmentPresenter) {
- this.gpuDriver =
- GpuDriverHelper.getInstalledDriverMetadata()
- ?: GpuDriverHelper.getSystemDriverMetadata(context.applicationContext)
+ this.gpuDriver = GpuDriverHelper.getInstalledDriverMetadata()
+ ?: GpuDriverHelper.getSystemDriverMetadata(context.applicationContext)
this.libNameSetting.setString(this.settings!!, "")
}
fragmentView.onDriverUninstallDone()
@@ -2898,6 +2931,24 @@ class SettingsFragmentPresenter(
const val ARG_CONTROLLER_TYPE = "controller_type"
const val ARG_SERIALPORT1_TYPE = "serialport1_type"
+ private data class SearchableSetting(
+ val name: String,
+ val description: String,
+ val menuTag: MenuTag,
+ val navigationExtras: Bundle?,
+ val position: Int,
+ val normalizedName: String,
+ val normalizedCategory: String,
+ val normalizedSearchText: String
+ )
+
+ private data class SearchableMenu(
+ val menuTag: MenuTag,
+ val category: String,
+ val navigationExtras: Bundle?,
+ val settings: List<SettingsItem>
+ )
+
// Value obtained from LogLevel in Common/Logging/Log.h
private fun getLogVerbosityEntries(): Int {
// GetMaxLogLevel is effectively a constant, but we can't call it before loading
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentView.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentView.kt
index c0b39400a7..4530792bee 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentView.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentView.kt
@@ -2,6 +2,7 @@
package org.dolphinemu.dolphinemu.features.settings.ui
+import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle
@@ -50,6 +51,12 @@ interface SettingsFragmentView {
* @param menuKey Identifier for the settings group that should be shown.
*/
fun loadSubMenu(menuKey: MenuTag)
+
+ /**
+ * Opens the settings screen containing a search result and scrolls to the result.
+ */
+ fun loadSearchResult(menuKey: MenuTag, settingPosition: Int, extras: Bundle?)
+
fun showDialogFragment(fragment: DialogFragment)
/**
@@ -67,7 +74,7 @@ interface SettingsFragmentView {
/**
* Have the fragment tell the containing Activity that a Setting was modified.
*/
- fun onSettingChanged()
+ fun onSettingChanged(setting: SettingsItem? = null)
/**
* Refetches the values of all controller settings.
@@ -96,6 +103,11 @@ interface SettingsFragmentView {
fun hasMenuTagActionForValue(menuTag: MenuTag, value: Int): Boolean
/**
+ * Returns the arguments used when opening a navigable setting's associated screen.
+ */
+ fun getMenuTagActionExtras(menuTag: MenuTag, value: Int): Bundle?
+
+ /**
* Controls whether the input mapping dialog should detect inputs from all devices,
* not just the device configured for the controller.
*/
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingViewHolder.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingViewHolder.kt
index dd5d4c97f0..f1ba059864 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingViewHolder.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingViewHolder.kt
@@ -2,14 +2,20 @@
package org.dolphinemu.dolphinemu.features.settings.ui.viewholder
+import android.animation.ValueAnimator
import android.content.DialogInterface
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.LayerDrawable
import android.graphics.Paint
import android.graphics.Typeface
import android.view.View
import android.view.View.OnLongClickListener
+import android.view.animation.DecelerateInterpolator
import android.widget.TextView
import android.widget.Toast
import androidx.lifecycle.LifecycleOwner
+import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.dolphinemu.dolphinemu.DolphinApplication
import org.dolphinemu.dolphinemu.R
@@ -21,6 +27,9 @@ abstract class SettingViewHolder(itemView: View, protected val adapter: Settings
LifecycleViewHolder(itemView, adapter.getFragmentLifecycle()),
LifecycleOwner, View.OnClickListener, OnLongClickListener {
+ private val defaultBackground: Drawable? = itemView.background
+ private var searchResultHighlightAnimator: ValueAnimator? = null
+
init {
itemView.setOnClickListener(this)
itemView.setOnLongClickListener(this)
@@ -39,6 +48,35 @@ abstract class SettingViewHolder(itemView: View, protected val adapter: Settings
}
}
+ fun highlightSearchResult() {
+ clearSearchResultHighlight()
+
+ val highlight = ColorDrawable(
+ MaterialColors.getColor(
+ itemView, com.google.android.material.R.attr.colorSecondaryContainer
+ )
+ ).apply { alpha = 0 }
+ itemView.background = if (defaultBackground == null) {
+ highlight
+ } else {
+ LayerDrawable(arrayOf(highlight, defaultBackground))
+ }
+ searchResultHighlightAnimator = ValueAnimator.ofInt(
+ 0, SEARCH_RESULT_HIGHLIGHT_MAX_ALPHA
+ ).apply {
+ duration = SEARCH_RESULT_HIGHLIGHT_FADE_IN_DURATION_MS
+ interpolator = DecelerateInterpolator()
+ addUpdateListener { highlight.alpha = it.animatedValue as Int }
+ start()
+ }
+ }
+
+ fun clearSearchResultHighlight() {
+ searchResultHighlightAnimator?.cancel()
+ searchResultHighlightAnimator = null
+ itemView.background = defaultBackground
+ }
+
/**
* Called by the adapter to set this ViewHolder's child views to display the list item
* it must now represent.
@@ -102,4 +140,9 @@ abstract class SettingViewHolder(itemView: View, protected val adapter: Settings
Toast.LENGTH_SHORT
).show()
}
+
+ companion object {
+ private const val SEARCH_RESULT_HIGHLIGHT_FADE_IN_DURATION_MS = 180L
+ private const val SEARCH_RESULT_HIGHLIGHT_MAX_ALPHA = 255
+ }
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingsSearchResultViewHolder.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingsSearchResultViewHolder.kt
new file mode 100644
index 0000000000..f9bad5042b
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingsSearchResultViewHolder.kt
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.settings.ui.viewholder
+
+import android.view.View
+import org.dolphinemu.dolphinemu.databinding.ListItemSearchResultBinding
+import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem
+import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsSearchResult
+import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter
+
+class SettingsSearchResultViewHolder(
+ private val binding: ListItemSearchResultBinding,
+ adapter: SettingsAdapter
+) : SettingViewHolder(binding.root, adapter) {
+ private lateinit var result: SettingsSearchResult
+
+ override val item: SettingsItem
+ get() = result
+
+ override fun bind(item: SettingsItem) {
+ result = item as SettingsSearchResult
+ binding.textSettingName.text = item.name
+ binding.textSettingDescription.text = item.description
+ }
+
+ override fun onClick(clicked: View) {
+ adapter.onSearchResultClick(result)
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ThemeHelper.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ThemeHelper.kt
index 2aa2122ad5..89ff3e0cb0 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ThemeHelper.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ThemeHelper.kt
@@ -170,7 +170,10 @@ object ThemeHelper {
activity: AppCompatActivity, toolbar: MaterialToolbar, appBarLayout: AppBarLayout
) {
appBarLayout.addOnOffsetChangedListener { layout: AppBarLayout, verticalOffset: Int ->
- if (-verticalOffset >= layout.totalScrollRange / 2) {
+ if (
+ layout.totalScrollRange > 0 &&
+ -verticalOffset >= layout.totalScrollRange / 2
+ ) {
@ColorInt val color =
ElevationOverlayProvider(appBarLayout.context).compositeOverlay(
MaterialColors.getColor(appBarLayout, R.attr.colorSurface),