summaryrefslogtreecommitdiff
path: root/Source
diff options
context:
space:
mode:
authorJosJuice <josjuice@gmail.com>2026-08-23 12:19:09 +0200
committerGitHub <noreply@github.com>2026-08-23 12:19:09 +0200
commit474fa6e6f21d05e27d8e33bbf069ecbeed58b60a (patch)
tree9cd21bf47890af648988f1c86131985b3fa3daae /Source
parentb3d74ab6022457bef29c08c26156a9ee145c0d76 (diff)
parent93706022d5d6974c96b75a129bf278b693b99a6e (diff)
Merge pull request #14742 from Simonx22/android/settings-search-next
Android: Add global settings search
Diffstat (limited to 'Source')
-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.kt225
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityPresenter.kt124
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityView.kt34
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt232
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt165
-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.kt28
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ThemeHelper.kt76
-rw-r--r--Source/Android/app/src/main/res/anim/anim_settings_search_pop_in.xml7
-rw-r--r--Source/Android/app/src/main/res/anim/anim_settings_search_pop_out.xml6
-rw-r--r--Source/Android/app/src/main/res/color/settings_search_outline.xml4
-rw-r--r--Source/Android/app/src/main/res/drawable/ic_search.xml10
-rw-r--r--Source/Android/app/src/main/res/layout/activity_settings.xml145
-rw-r--r--Source/Android/app/src/main/res/layout/fragment_settings.xml15
-rw-r--r--Source/Android/app/src/main/res/layout/list_item_search_result.xml36
-rw-r--r--Source/Android/app/src/main/res/values/strings.xml4
20 files changed, 1295 insertions, 656 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..19c08b9a05 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,78 @@ 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 +207,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 +226,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 +246,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 +278,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 +289,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 +320,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 +430,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 +454,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..5baee17f02 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
@@ -10,14 +10,17 @@ import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner
import org.dolphinemu.dolphinemu.utils.Log
class SettingsActivityPresenter(
- private val activityView: SettingsActivityView,
- var settings: Settings?
+ private val activityView: SettingsActivityView, var settings: Settings?
) {
private var menuTag: MenuTag? = null
private var gameId: String? = null
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 +35,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 +125,49 @@ 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..95951f1ae9 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,19 +11,36 @@ 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.
* @param addToStack Whether or not this fragment should replace a previous one.
*/
fun showSettingsFragment(
- menuTag: MenuTag,
- extras: Bundle?,
- addToStack: Boolean,
- gameId: String
+ menuTag: MenuTag, extras: Bundle?, addToStack: Boolean, gameId: String
)
/**
+ * 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 +104,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 +124,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..6252350d32 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
@@ -27,14 +27,46 @@ import com.google.android.material.slider.Slider
import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat
import org.dolphinemu.dolphinemu.R
-import org.dolphinemu.dolphinemu.databinding.*
+import org.dolphinemu.dolphinemu.databinding.DialogAdvancedMappingBinding
+import org.dolphinemu.dolphinemu.databinding.DialogInputStringBinding
+import org.dolphinemu.dolphinemu.databinding.DialogSliderBinding
+import org.dolphinemu.dolphinemu.databinding.ListItemHeaderBinding
+import org.dolphinemu.dolphinemu.databinding.ListItemMappingBinding
+import org.dolphinemu.dolphinemu.databinding.ListItemSearchResultBinding
+import org.dolphinemu.dolphinemu.databinding.ListItemSettingBinding
+import org.dolphinemu.dolphinemu.databinding.ListItemSettingSwitchBinding
+import org.dolphinemu.dolphinemu.databinding.ListItemSubmenuBinding
import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting
import org.dolphinemu.dolphinemu.features.input.ui.AdvancedMappingDialog
import org.dolphinemu.dolphinemu.features.input.ui.MotionAlertDialog
import org.dolphinemu.dolphinemu.features.input.ui.viewholder.InputMappingControlSettingViewHolder
import org.dolphinemu.dolphinemu.features.settings.model.Settings
-import org.dolphinemu.dolphinemu.features.settings.model.view.*
-import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.*
+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.InputStringSetting
+import org.dolphinemu.dolphinemu.features.settings.model.view.IntSliderSetting
+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.SliderSetting
+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.features.settings.ui.viewholder.DateTimeSettingViewHolder
+import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.FilePickerViewHolder
+import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.HeaderHyperLinkViewHolder
+import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.HeaderViewHolder
+import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.InputStringSettingViewHolder
+import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.RunRunnableViewHolder
+import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SettingViewHolder
+import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SettingsSearchResultViewHolder
+import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SingleChoiceViewHolder
+import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SliderViewHolder
+import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SubmenuViewHolder
+import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SwitchSettingViewHolder
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization
import org.dolphinemu.dolphinemu.utils.FileBrowserHelper
import org.dolphinemu.dolphinemu.utils.Log
@@ -42,14 +74,13 @@ import org.dolphinemu.dolphinemu.utils.PermissionsHandler
import java.io.File
import java.io.IOException
import java.io.RandomAccessFile
-import java.util.*
+import java.util.Calendar
+import java.util.TimeZone
import kotlin.math.roundToInt
class SettingsAdapter(
- private val fragmentView: SettingsFragmentView,
- private val context: Context
-) :
- RecyclerView.Adapter<SettingViewHolder>(), DialogInterface.OnClickListener,
+ private val fragmentView: SettingsFragmentView, private val context: Context
+) : RecyclerView.Adapter<SettingViewHolder>(), DialogInterface.OnClickListener,
Slider.OnChangeListener {
private var settingsList: ArrayList<SettingsItem>? = null
private var clickedItem: SettingsItem? = null
@@ -68,55 +99,59 @@ class SettingsAdapter(
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
SettingsItem.TYPE_HEADER -> HeaderViewHolder(
- ListItemHeaderBinding.inflate(inflater, parent, false),
- this
+ ListItemHeaderBinding.inflate(inflater, parent, false), this
)
+
SettingsItem.TYPE_SWITCH -> SwitchSettingViewHolder(
- ListItemSettingSwitchBinding.inflate(inflater, parent, false),
- this
+ ListItemSettingSwitchBinding.inflate(inflater, parent, false), this
)
- SettingsItem.TYPE_STRING_SINGLE_CHOICE,
- SettingsItem.TYPE_SINGLE_CHOICE_DYNAMIC_DESCRIPTIONS,
- SettingsItem.TYPE_SINGLE_CHOICE -> SingleChoiceViewHolder(
- ListItemSettingBinding.inflate(inflater, parent, false),
- this
+
+ SettingsItem.TYPE_STRING_SINGLE_CHOICE, SettingsItem.TYPE_SINGLE_CHOICE_DYNAMIC_DESCRIPTIONS, SettingsItem.TYPE_SINGLE_CHOICE -> SingleChoiceViewHolder(
+ ListItemSettingBinding.inflate(inflater, parent, false), this
)
+
SettingsItem.TYPE_SLIDER -> SliderViewHolder(
- ListItemSettingBinding.inflate(inflater, parent, false),
- this,
- context
+ ListItemSettingBinding.inflate(inflater, parent, false), this, context
)
+
SettingsItem.TYPE_SUBMENU -> SubmenuViewHolder(
- ListItemSubmenuBinding.inflate(inflater, parent, false),
- this
+ ListItemSubmenuBinding.inflate(inflater, parent, false), this
)
+
SettingsItem.TYPE_INPUT_MAPPING_CONTROL -> InputMappingControlSettingViewHolder(
- ListItemMappingBinding.inflate(inflater, parent, false),
- this
+ ListItemMappingBinding.inflate(inflater, parent, false), this
)
- SettingsItem.TYPE_FILE_PICKER,
- SettingsItem.TYPE_DIRECTORY_PICKER -> FilePickerViewHolder(
- ListItemSettingBinding.inflate(inflater, parent, false),
- this
+
+ SettingsItem.TYPE_FILE_PICKER, SettingsItem.TYPE_DIRECTORY_PICKER -> FilePickerViewHolder(
+ ListItemSettingBinding.inflate(inflater, parent, false), this
)
+
SettingsItem.TYPE_RUN_RUNNABLE -> RunRunnableViewHolder(
- ListItemSettingBinding.inflate(inflater, parent, false),
- this, context
+ ListItemSettingBinding.inflate(inflater, parent, false), this, context
)
+
SettingsItem.TYPE_STRING -> InputStringSettingViewHolder(
ListItemSettingBinding.inflate(inflater, parent, false), this
)
+
SettingsItem.TYPE_HYPERLINK_HEADER -> HeaderHyperLinkViewHolder(
ListItemHeaderBinding.inflate(inflater, parent, false), this
)
+
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 +178,7 @@ class SettingsAdapter(
fun clearSetting(item: SettingsItem) {
item.clear(settings!!)
- fragmentView.onSettingChanged()
+ fragmentView.onSettingChanged(item)
}
fun notifyAllSettingsChanged() {
@@ -153,7 +188,7 @@ class SettingsAdapter(
fun onBooleanClick(item: SwitchSetting, checked: Boolean) {
item.setChecked(settings!!, checked)
- fragmentView.onSettingChanged()
+ fragmentView.onSettingChanged(item)
}
fun onInputStringClick(item: InputStringSetting, position: Int) {
@@ -161,29 +196,24 @@ class SettingsAdapter(
val binding = DialogInputStringBinding.inflate(inflater)
val input = binding.input
input.setText(item.selectedValue)
- dialog = MaterialAlertDialogBuilder(fragmentView.fragmentActivity)
- .setView(binding.root)
+ dialog = MaterialAlertDialogBuilder(fragmentView.fragmentActivity).setView(binding.root)
.setMessage(item.description)
.setPositiveButton(R.string.ok) { _: DialogInterface?, _: Int ->
val editTextInput = input.text.toString()
if (item.selectedValue != editTextInput) {
notifyItemChanged(position)
- fragmentView.onSettingChanged()
+ fragmentView.onSettingChanged(item)
}
item.setSelectedValue(fragmentView.settings!!, editTextInput)
- }
- .setNegativeButton(R.string.cancel, null)
- .show()
+ }.setNegativeButton(R.string.cancel, null).show()
}
fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) {
clickedItem = item
clickedPosition = position
val value = getSelectionForSingleChoiceValue(item)
- dialog = MaterialAlertDialogBuilder(fragmentView.fragmentActivity)
- .setTitle(item.name)
- .setSingleChoiceItems(item.choicesId, value, this)
- .show()
+ dialog = MaterialAlertDialogBuilder(fragmentView.fragmentActivity).setTitle(item.name)
+ .setSingleChoiceItems(item.choicesId, value, this).show()
}
fun onStringSingleChoiceClick(item: StringSingleChoiceSetting, position: Int) {
@@ -193,35 +223,26 @@ class SettingsAdapter(
val choices = item.choices
val noChoicesAvailableString = item.noChoicesAvailableString
dialog = if (noChoicesAvailableString != 0 && choices.isEmpty()) {
- MaterialAlertDialogBuilder(fragmentView.fragmentActivity)
- .setTitle(item.name)
- .setMessage(noChoicesAvailableString)
- .setPositiveButton(R.string.ok, null)
- .show()
+ MaterialAlertDialogBuilder(fragmentView.fragmentActivity).setTitle(item.name)
+ .setMessage(noChoicesAvailableString).setPositiveButton(R.string.ok, null).show()
} else {
- MaterialAlertDialogBuilder(fragmentView.fragmentActivity)
- .setTitle(item.name)
+ MaterialAlertDialogBuilder(fragmentView.fragmentActivity).setTitle(item.name)
.setSingleChoiceItems(
- item.choices, item.selectedValueIndex,
- this
- )
- .show()
+ item.choices, item.selectedValueIndex, this
+ ).show()
}
}
fun onSingleChoiceDynamicDescriptionsClick(
- item: SingleChoiceSettingDynamicDescriptions,
- position: Int
+ item: SingleChoiceSettingDynamicDescriptions, position: Int
) {
clickedItem = item
clickedPosition = position
val value = getSelectionForSingleChoiceDynamicDescriptionsValue(item)
- dialog = MaterialAlertDialogBuilder(fragmentView.fragmentActivity)
- .setTitle(item.name)
- .setSingleChoiceItems(item.choicesId, value, this)
- .show()
+ dialog = MaterialAlertDialogBuilder(fragmentView.fragmentActivity).setTitle(item.name)
+ .setSingleChoiceItems(item.choicesId, value, this).show()
}
fun onSliderClick(item: SliderSetting, position: Int) {
@@ -251,6 +272,7 @@ class SettingsAdapter(
slider.valueTo = item.max
slider.stepSize = item.stepSize
}
+
is IntSliderSetting -> {
slider.valueFrom = item.min.toFloat()
slider.valueTo = item.max.toFloat()
@@ -260,29 +282,27 @@ class SettingsAdapter(
slider.value = (seekbarProgress / slider.stepSize).roundToInt() * slider.stepSize
slider.addOnChangeListener(this)
- dialog = MaterialAlertDialogBuilder(fragmentView.fragmentActivity)
- .setTitle(item.name)
- .setView(binding.root)
- .setPositiveButton(R.string.ok, this)
- .show()
+ dialog = MaterialAlertDialogBuilder(fragmentView.fragmentActivity).setTitle(item.name)
+ .setView(binding.root).setPositiveButton(R.string.ok, this).show()
}
fun onSubmenuClick(item: SubmenuSetting) {
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)
- .setMessage(R.string.input_binding_no_device)
- .setPositiveButton(R.string.ok, this)
- .show()
+ MaterialAlertDialogBuilder(fragmentView.fragmentActivity).setMessage(R.string.input_binding_no_device)
+ .setPositiveButton(R.string.ok, this).show()
return
}
val dialog = MotionAlertDialog(
- fragmentView.fragmentActivity, item,
- fragmentView.isMappingAllDevices
+ fragmentView.fragmentActivity, item, fragmentView.isMappingAllDevices
)
val background = ContextCompat.getDrawable(context, R.drawable.dialog_round)
@@ -296,18 +316,16 @@ class SettingsAdapter(
dialog.setTitle(R.string.input_binding)
dialog.setMessage(
String.format(
- context.getString(R.string.input_binding_description),
- item.name
+ context.getString(R.string.input_binding_description), item.name
)
)
dialog.setButton(AlertDialog.BUTTON_NEGATIVE, context.getString(R.string.cancel), this)
dialog.setButton(
- AlertDialog.BUTTON_NEUTRAL,
- context.getString(R.string.clear)
+ AlertDialog.BUTTON_NEUTRAL, context.getString(R.string.clear)
) { _: DialogInterface?, _: Int -> item.clearValue() }
dialog.setOnDismissListener {
notifyItemChanged(position)
- fragmentView.onSettingChanged()
+ fragmentView.onSettingChanged(item)
}
dialog.setCanceledOnTouchOutside(false)
dialog.show()
@@ -317,10 +335,7 @@ class SettingsAdapter(
val inflater = LayoutInflater.from(context)
val binding = DialogAdvancedMappingBinding.inflate(inflater)
val dialog = AdvancedMappingDialog(
- context,
- binding,
- item.controlReference,
- item.controller
+ context, binding, item.controlReference, item.controller
)
val background = ContextCompat.getDrawable(context, R.drawable.dialog_round)
@@ -338,12 +353,11 @@ 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(
- AlertDialog.BUTTON_NEUTRAL,
- context.getString(R.string.clear)
+ AlertDialog.BUTTON_NEUTRAL, context.getString(R.string.clear)
) { _: DialogInterface?, _: Int -> }
dialog.setCanceledOnTouchOutside(false)
dialog.show()
@@ -361,14 +375,12 @@ class SettingsAdapter(
val directoryPicker = item as DirectoryPicker
if (!PermissionsHandler.isExternalStorageLegacy()) {
- MaterialAlertDialogBuilder(context)
- .setMessage(R.string.path_not_changeable_scoped_storage)
+ MaterialAlertDialogBuilder(context).setMessage(R.string.path_not_changeable_scoped_storage)
.setPositiveButton(R.string.ok) { dialog: DialogInterface, _: Int -> dialog.dismiss() }
.show()
} else {
val intent = FileBrowserHelper.createDirectoryPickerIntent(
- fragmentView.fragmentActivity,
- FileBrowserHelper.GAME_EXTENSIONS
+ fragmentView.fragmentActivity, FileBrowserHelper.GAME_EXTENSIONS
)
directoryPicker.launcher.launch(intent)
}
@@ -400,32 +412,24 @@ class SettingsAdapter(
calendar.timeZone = TimeZone.getTimeZone("UTC")
// Start and end epoch times available for the Wii's date picker
- val calendarConstraints = CalendarConstraints.Builder()
- .setStart(946684800000L)
- .setEnd(2082672000000L)
- .build()
+ val calendarConstraints =
+ CalendarConstraints.Builder().setStart(946684800000L).setEnd(2082672000000L).build()
var timeFormat = TimeFormat.CLOCK_12H
if (DateFormat.is24HourFormat(fragmentView.fragmentActivity)) {
timeFormat = TimeFormat.CLOCK_24H
}
- val datePicker = MaterialDatePicker.Builder.datePicker()
- .setSelection(storedTime)
- .setTitleText(R.string.select_rtc_date)
- .setCalendarConstraints(calendarConstraints)
- .build()
- val timePicker = MaterialTimePicker.Builder()
- .setTimeFormat(timeFormat)
- .setHour(calendar[Calendar.HOUR_OF_DAY])
- .setMinute(calendar[Calendar.MINUTE])
- .setTitleText(R.string.select_rtc_time)
+ val datePicker = MaterialDatePicker.Builder.datePicker().setSelection(storedTime)
+ .setTitleText(R.string.select_rtc_date).setCalendarConstraints(calendarConstraints)
.build()
+ val timePicker = MaterialTimePicker.Builder().setTimeFormat(timeFormat)
+ .setHour(calendar[Calendar.HOUR_OF_DAY]).setMinute(calendar[Calendar.MINUTE])
+ .setTitleText(R.string.select_rtc_time).build()
datePicker.addOnPositiveButtonClickListener {
timePicker.show(
- fragmentView.fragmentActivity.supportFragmentManager,
- "TimePicker"
+ fragmentView.fragmentActivity.supportFragmentManager, "TimePicker"
)
}
timePicker.addOnPositiveButtonClickListener {
@@ -435,7 +439,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 +452,7 @@ class SettingsAdapter(
if (filePicker.getSelectedValue() != selectedFile) {
notifyItemChanged(clickedPosition)
- fragmentView.onSettingChanged()
+ fragmentView.onSettingChanged(filePicker)
}
filePicker.setSelectedValue(fragmentView.settings!!, selectedFile)
@@ -470,44 +474,50 @@ 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)
closeDialog()
}
+
is SingleChoiceSettingDynamicDescriptions -> {
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)
closeDialog()
}
+
is StringSingleChoiceSetting -> {
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)
closeDialog()
}
+
is IntSliderSetting -> {
val sliderSetting = clickedItem as IntSliderSetting
if (sliderSetting.selectedValue != seekbarProgress.toInt()) {
- fragmentView.onSettingChanged()
+ fragmentView.onSettingChanged(sliderSetting)
}
sliderSetting.setSelectedValue(settings!!, seekbarProgress.toInt())
closeDialog()
}
+
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()
}
@@ -587,8 +598,7 @@ class SettingsAdapter(
}
private fun getValueForSingleChoiceDynamicDescriptionsSelection(
- item: SingleChoiceSettingDynamicDescriptions,
- which: Int
+ item: SingleChoiceSettingDynamicDescriptions, which: Int
): Int {
val valuesId = item.valuesId
return if (valuesId > 0) {
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..e0f96e77f1 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,17 +24,22 @@ 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.*
-import kotlin.collections.ArrayList
+import java.util.EnumMap
class SettingsFragment : Fragment(), SettingsFragmentView {
private lateinit var presenter: SettingsFragmentPresenter
@@ -51,6 +56,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
@@ -82,9 +92,7 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
}
override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
+ inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
binding = FragmentSettingsBinding.inflate(inflater, container, false)
return binding!!.root
@@ -92,7 +100,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 +119,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 +144,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) {
@@ -139,13 +228,35 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
}
activityView!!.showSettingsFragment(
- menuKey,
- null,
- true,
- requireArguments().getString(ARGUMENT_GAME_ID)!!
+ menuKey, null, true, requireArguments().getString(ARGUMENT_GAME_ID)!!
+ )
+ }
+
+ 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 +268,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 +289,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) {
@@ -203,17 +322,13 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
}
val msg = "${presenter.gpuDriver!!.name} ${presenter.gpuDriver!!.driverVersion}"
- MaterialAlertDialogBuilder(requireContext())
- .setTitle(getString(R.string.gpu_driver_dialog_title))
- .setMessage(msg)
- .setNegativeButton(android.R.string.cancel, null)
+ MaterialAlertDialogBuilder(requireContext()).setTitle(getString(R.string.gpu_driver_dialog_title))
+ .setMessage(msg).setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(R.string.gpu_driver_dialog_system) { _: DialogInterface?, _: Int ->
presenter.useSystemDriver()
- }
- .setPositiveButton(R.string.gpu_driver_dialog_install) { _: DialogInterface?, _: Int ->
+ }.setPositiveButton(R.string.gpu_driver_dialog_install) { _: DialogInterface?, _: Int ->
askForDriverFile()
- }
- .show()
+ }.show()
}
override fun getFragmentLifecycle(): Lifecycle {
@@ -230,16 +345,12 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
override fun onDriverInstallDone(result: GpuDriverInstallResult) {
val view = binding?.root ?: return
- Snackbar
- .make(view, resolveInstallResultString(result), Snackbar.LENGTH_LONG)
- .show()
+ Snackbar.make(view, resolveInstallResultString(result), Snackbar.LENGTH_LONG).show()
}
override fun onDriverUninstallDone() {
Toast.makeText(
- requireContext(),
- R.string.gpu_driver_dialog_uninstall_done,
- Toast.LENGTH_SHORT
+ requireContext(), R.string.gpu_driver_dialog_uninstall_done, Toast.LENGTH_SHORT
).show()
}
@@ -256,6 +367,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..77414fedce 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.Paint
import android.graphics.Typeface
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.LayerDrawable
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..9471d6bf43
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingsSearchResultViewHolder.kt
@@ -0,0 +1,28 @@
+// 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..8b41fc6c55 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
@@ -1,20 +1,20 @@
package org.dolphinemu.dolphinemu.utils
-import androidx.appcompat.app.AppCompatActivity
-import org.dolphinemu.dolphinemu.R
+import android.content.res.Configuration
import android.os.Build
-import androidx.core.content.ContextCompat
+import androidx.annotation.ColorInt
+import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
-import androidx.core.view.WindowInsetsControllerCompat
+import androidx.core.content.ContextCompat
import androidx.core.view.WindowCompat
-import org.dolphinemu.dolphinemu.ui.main.ThemeProvider
-import android.content.res.Configuration
-import com.google.android.material.appbar.MaterialToolbar
+import androidx.core.view.WindowInsetsControllerCompat
+import androidx.preference.PreferenceManager
import com.google.android.material.appbar.AppBarLayout
-import com.google.android.material.elevation.ElevationOverlayProvider
+import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.color.MaterialColors
-import androidx.annotation.ColorInt
-import androidx.preference.PreferenceManager
+import com.google.android.material.elevation.ElevationOverlayProvider
+import org.dolphinemu.dolphinemu.R
+import org.dolphinemu.dolphinemu.ui.main.ThemeProvider
object ThemeHelper {
@@ -52,8 +52,7 @@ object ThemeHelper {
.getInt(CURRENT_THEME_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
activity.delegate.localNightMode = themeMode
val windowController = WindowCompat.getInsetsController(
- activity.window,
- activity.window.decorView
+ activity.window, activity.window.decorView
)
val systemReportedThemeMode =
activity.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
@@ -62,6 +61,7 @@ object ThemeHelper {
Configuration.UI_MODE_NIGHT_NO -> setLightModeSystemBars(windowController)
Configuration.UI_MODE_NIGHT_YES -> setDarkModeSystemBars(windowController)
}
+
AppCompatDelegate.MODE_NIGHT_NO -> setLightModeSystemBars(windowController)
AppCompatDelegate.MODE_NIGHT_YES -> setDarkModeSystemBars(windowController)
}
@@ -83,66 +83,50 @@ object ThemeHelper {
@JvmStatic
fun saveTheme(activity: AppCompatActivity, themeValue: Int) {
- PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
- .edit()
- .putInt(CURRENT_THEME, themeValue)
- .apply()
+ PreferenceManager.getDefaultSharedPreferences(activity.applicationContext).edit()
+ .putInt(CURRENT_THEME, themeValue).apply()
activity.recreate()
}
@JvmStatic
fun deleteThemeKey(activity: AppCompatActivity) {
- PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
- .edit()
- .remove(CURRENT_THEME)
- .apply()
+ PreferenceManager.getDefaultSharedPreferences(activity.applicationContext).edit()
+ .remove(CURRENT_THEME).apply()
activity.recreate()
}
@JvmStatic
fun saveThemeMode(activity: AppCompatActivity, themeModeValue: Int) {
- PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
- .edit()
- .putInt(CURRENT_THEME_MODE, themeModeValue)
- .apply()
+ PreferenceManager.getDefaultSharedPreferences(activity.applicationContext).edit()
+ .putInt(CURRENT_THEME_MODE, themeModeValue).apply()
setThemeMode(activity)
}
@JvmStatic
fun deleteThemeModeKey(activity: AppCompatActivity) {
- PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
- .edit()
- .remove(CURRENT_THEME_MODE)
- .apply()
+ PreferenceManager.getDefaultSharedPreferences(activity.applicationContext).edit()
+ .remove(CURRENT_THEME_MODE).apply()
setThemeMode(activity)
}
@JvmStatic
fun saveBackgroundSetting(activity: AppCompatActivity, backgroundValue: Boolean) {
- PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
- .edit()
- .putBoolean(USE_BLACK_BACKGROUNDS, backgroundValue)
- .apply()
+ PreferenceManager.getDefaultSharedPreferences(activity.applicationContext).edit()
+ .putBoolean(USE_BLACK_BACKGROUNDS, backgroundValue).apply()
activity.recreate()
}
@JvmStatic
fun deleteBackgroundSetting(activity: AppCompatActivity) {
- PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
- .edit()
- .remove(USE_BLACK_BACKGROUNDS)
- .apply()
+ PreferenceManager.getDefaultSharedPreferences(activity.applicationContext).edit()
+ .remove(USE_BLACK_BACKGROUNDS).apply()
activity.recreate()
}
@JvmStatic
fun resetThemePreferences(activity: AppCompatActivity, applyImmediately: Boolean = false) {
- PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
- .edit()
- .remove(CURRENT_THEME)
- .remove(CURRENT_THEME_MODE)
- .remove(USE_BLACK_BACKGROUNDS)
- .apply()
+ PreferenceManager.getDefaultSharedPreferences(activity.applicationContext).edit()
+ .remove(CURRENT_THEME).remove(CURRENT_THEME_MODE).remove(USE_BLACK_BACKGROUNDS).apply()
activity.delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
activity.delegate.applyDayNight()
if (applyImmediately) {
@@ -170,7 +154,7 @@ 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),
@@ -180,8 +164,7 @@ object ThemeHelper {
setStatusBarColor(activity, color)
} else {
@ColorInt val statusBarColor = ContextCompat.getColor(
- activity.applicationContext,
- android.R.color.transparent
+ activity.applicationContext, android.R.color.transparent
)
@ColorInt val appBarColor = MaterialColors.getColor(toolbar, R.attr.colorSurface)
toolbar.setBackgroundColor(appBarColor)
@@ -198,8 +181,7 @@ object ThemeHelper {
setStatusBarColor(activity, color)
} else {
@ColorInt val statusBarColor = ContextCompat.getColor(
- activity.applicationContext,
- android.R.color.transparent
+ activity.applicationContext, android.R.color.transparent
)
setStatusBarColor(activity, statusBarColor)
}
diff --git a/Source/Android/app/src/main/res/anim/anim_settings_search_pop_in.xml b/Source/Android/app/src/main/res/anim/anim_settings_search_pop_in.xml
new file mode 100644
index 0000000000..338676f504
--- /dev/null
+++ b/Source/Android/app/src/main/res/anim/anim_settings_search_pop_in.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+ android:duration="180"
+ android:fromAlpha="0"
+ android:interpolator="@android:anim/decelerate_interpolator"
+ android:startOffset="60"
+ android:toAlpha="1" />
diff --git a/Source/Android/app/src/main/res/anim/anim_settings_search_pop_out.xml b/Source/Android/app/src/main/res/anim/anim_settings_search_pop_out.xml
new file mode 100644
index 0000000000..f131732c71
--- /dev/null
+++ b/Source/Android/app/src/main/res/anim/anim_settings_search_pop_out.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+ android:duration="90"
+ android:fromAlpha="1"
+ android:interpolator="@android:anim/accelerate_interpolator"
+ android:toAlpha="0" />
diff --git a/Source/Android/app/src/main/res/color/settings_search_outline.xml b/Source/Android/app/src/main/res/color/settings_search_outline.xml
new file mode 100644
index 0000000000..5a4a93e941
--- /dev/null
+++ b/Source/Android/app/src/main/res/color/settings_search_outline.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:alpha="0.45" android:color="?attr/colorOutline" />
+</selector>
diff --git a/Source/Android/app/src/main/res/drawable/ic_search.xml b/Source/Android/app/src/main/res/drawable/ic_search.xml
new file mode 100644
index 0000000000..c85c3c5e61
--- /dev/null
+++ b/Source/Android/app/src/main/res/drawable/ic_search.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M9.5,3a6.5,6.5 0,1 0,0 13a6.5,6.5 0,0 0,0 -13zM9.5,5a4.5,4.5 0,1 1,0 9a4.5,4.5 0,0 1,0 -9zM14.65,13.24l5.56,5.56l-1.41,1.41l-5.56,-5.56z" />
+</vector>
diff --git a/Source/Android/app/src/main/res/layout/activity_settings.xml b/Source/Android/app/src/main/res/layout/activity_settings.xml
index 533893df12..fbecfd3886 100644
--- a/Source/Android/app/src/main/res/layout/activity_settings.xml
+++ b/Source/Android/app/src/main/res/layout/activity_settings.xml
@@ -1,22 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
-<androidx.coordinatorlayout.widget.CoordinatorLayout
+<androidx.coordinatorlayout.widget.CoordinatorLayout android:id="@+id/coordinator_main"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/coordinator_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface">
+ <FrameLayout
+ android:id="@+id/frame_content_settings"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior" />
+
+ <TextView
+ android:id="@+id/old_controller_settings_warning"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:background="?attr/colorErrorContainer"
+ android:clickable="true"
+ android:focusable="false"
+ android:text="@string/old_controller_settings"
+ android:textColor="?attr/colorOnErrorContainer"
+ android:visibility="invisible" />
+
+ <View
+ android:id="@+id/workaround_view"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_gravity="bottom"
+ android:background="@android:color/transparent"
+ android:clickable="true"
+ android:focusable="false" />
+
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
+ android:background="@android:color/transparent"
+ app:backgroundTint="@android:color/transparent"
app:elevation="0dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
- style="?attr/collapsingToolbarLayoutMediumStyle"
android:id="@+id/toolbar_settings_layout"
+ style="?attr/collapsingToolbarLayoutMediumStyle"
android:layout_width="match_parent"
android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
app:contentScrim="@android:color/transparent"
@@ -31,33 +59,94 @@
</com.google.android.material.appbar.CollapsingToolbarLayout>
- </com.google.android.material.appbar.AppBarLayout>
+ <FrameLayout
+ android:id="@+id/settings_search_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/colorSurface"
+ android:paddingBottom="@dimen/spacing_medlarge">
- <FrameLayout
- android:id="@+id/frame_content_settings"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
+ <com.google.android.material.card.MaterialCardView
+ android:id="@+id/settings_search_preview"
+ android:layout_width="match_parent"
+ android:layout_height="56dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:foreground="?android:attr/selectableItemBackground"
+ android:layout_marginEnd="@dimen/spacing_large"
+ android:layout_marginStart="@dimen/spacing_large"
+ app:cardBackgroundColor="?attr/colorSurfaceVariant"
+ app:cardCornerRadius="28dp"
+ app:cardElevation="0dp"
+ app:strokeColor="@color/settings_search_outline"
+ app:strokeWidth="1dp">
- <TextView
- android:id="@+id/old_controller_settings_warning"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- android:background="?attr/colorErrorContainer"
- android:text="@string/old_controller_settings"
- android:textColor="?attr/colorOnErrorContainer"
- android:visibility="invisible"
- android:clickable="true"
- android:focusable="false" />
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ android:paddingEnd="@dimen/spacing_large"
+ android:paddingStart="20dp">
- <View
- android:id="@+id/workaround_view"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_gravity="bottom"
- android:clickable="true"
- android:focusable="false"
- android:background="@android:color/transparent" />
+ <ImageView
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:contentDescription="@null"
+ app:srcCompat="@drawable/ic_search"
+ app:tint="?android:attr/textColorSecondary" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/spacing_large"
+ android:text="@string/search_settings"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textSize="18sp" />
+
+ </LinearLayout>
+
+ </com.google.android.material.card.MaterialCardView>
+
+ </FrameLayout>
+
+ <LinearLayout
+ android:id="@+id/settings_search_mode_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/colorSurface"
+ android:orientation="vertical"
+ android:visibility="gone">
+
+ <com.google.android.material.appbar.MaterialToolbar
+ android:id="@+id/settings_search_toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ android:background="?attr/colorSurface"
+ app:navigationContentDescription="@string/search_settings_back"
+ app:navigationIcon="?attr/homeAsUpIndicator">
+
+ <androidx.appcompat.widget.SearchView
+ android:id="@+id/settings_search"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/transparent"
+ android:imeOptions="actionSearch"
+ android:inputType="text"
+ app:iconifiedByDefault="false"
+ app:queryBackground="@android:color/transparent"
+ app:queryHint="@string/search_settings"
+ app:searchIcon="@null" />
+
+ </com.google.android.material.appbar.MaterialToolbar>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="@color/settings_search_outline" />
+
+ </LinearLayout>
+
+ </com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/Source/Android/app/src/main/res/layout/fragment_settings.xml b/Source/Android/app/src/main/res/layout/fragment_settings.xml
index 77b673b53c..5b8896d9f7 100644
--- a/Source/Android/app/src/main/res/layout/fragment_settings.xml
+++ b/Source/Android/app/src/main/res/layout/fragment_settings.xml
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -11,4 +10,16 @@
android:layout_height="match_parent"
android:clipToPadding="false" />
+ <TextView
+ android:id="@+id/text_no_search_results"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:padding="@dimen/spacing_xtralarge"
+ android:text="@string/search_settings_no_results"
+ android:textAlignment="center"
+ android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
+ android:textColor="?android:attr/textColorSecondary"
+ android:visibility="gone" />
+
</FrameLayout>
diff --git a/Source/Android/app/src/main/res/layout/list_item_search_result.xml b/Source/Android/app/src/main/res/layout/list_item_search_result.xml
new file mode 100644
index 0000000000..237fc46c04
--- /dev/null
+++ b/Source/Android/app/src/main/res/layout/list_item_search_result.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:clickable="true"
+ android:focusable="true"
+ android:minHeight="64dp"
+ android:orientation="vertical"
+ android:paddingBottom="@dimen/spacing_large"
+ android:paddingEnd="@dimen/spacing_large"
+ android:paddingStart="@dimen/spacing_large"
+ android:paddingTop="@dimen/spacing_large">
+
+ <TextView
+ android:id="@+id/text_setting_name"
+ style="@style/TextAppearance.MaterialComponents.Headline5"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAlignment="viewStart"
+ android:textSize="16sp"
+ tools:text="Internal Resolution" />
+
+ <TextView
+ android:id="@+id/text_setting_description"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/spacing_small"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:textAlignment="viewStart"
+ android:textColor="?android:attr/textColorSecondary"
+ tools:text="Graphics Settings › Enhancements" />
+
+</LinearLayout>
diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml
index a6419ec83b..2f41de8fc7 100644
--- a/Source/Android/app/src/main/res/values/strings.xml
+++ b/Source/Android/app/src/main/res/values/strings.xml
@@ -62,6 +62,10 @@
<!-- Main Preference Fragment -->
<string name="settings">Settings</string>
+ <string name="search_settings">Search settings</string>
+ <string name="search_settings_back">Back to settings</string>
+ <string name="search_settings_no_results">No settings found for “%1$s”</string>
+ <string name="search_settings_category_path">%1$s › %2$s</string>
<string name="game_settings">Game Settings: %1$s</string>
<string name="config">Config</string>
<string name="graphics_settings">Graphics Settings</string>