summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCharles Lombardo <clombardo169@gmail.com>2023-08-24 22:11:08 +0200
committerCharles Lombardo <clombardo169@gmail.com>2023-08-30 01:42:42 +0200
commitfd5c7b21ddc0e40df049773d9106e349072352f3 (patch)
tree65ddb2544937e6efc9141050870b511d514919f7
parentandroid: Implement paired settings (diff)
downloadyuzu-fd5c7b21ddc0e40df049773d9106e349072352f3.tar
yuzu-fd5c7b21ddc0e40df049773d9106e349072352f3.tar.gz
yuzu-fd5c7b21ddc0e40df049773d9106e349072352f3.tar.bz2
yuzu-fd5c7b21ddc0e40df049773d9106e349072352f3.tar.lz
yuzu-fd5c7b21ddc0e40df049773d9106e349072352f3.tar.xz
yuzu-fd5c7b21ddc0e40df049773d9106e349072352f3.tar.zst
yuzu-fd5c7b21ddc0e40df049773d9106e349072352f3.zip
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt34
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt189
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt7
-rw-r--r--src/android/app/src/main/res/layout/fragment_settings_search.xml120
-rw-r--r--src/android/app/src/main/res/menu/menu_settings.xml11
-rw-r--r--src/android/app/src/main/res/navigation/settings_navigation.xml8
-rw-r--r--src/android/app/src/main/res/values/strings.xml1
8 files changed, 372 insertions, 1 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
index ff1e64e0a..f5eba1222 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
@@ -12,6 +12,7 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.findNavController
import androidx.recyclerview.widget.AsyncDifferConfig
@@ -37,7 +38,7 @@ import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
import org.yuzu.yuzu_emu.model.SettingsViewModel
class SettingsAdapter(
- private val fragment: SettingsFragment,
+ private val fragment: Fragment,
private val context: Context
) : ListAdapter<SettingsItem, SettingViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
DialogInterface.OnClickListener {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
index 5890b0642..0ea587a88 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
@@ -13,12 +13,14 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
+import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.divider.MaterialDividerItemDecoration
import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
+import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.SettingsViewModel
class SettingsFragment : Fragment() {
@@ -84,11 +86,43 @@ class SettingsFragment : Fragment() {
}
}
+ settingsViewModel.isUsingSearch.observe(viewLifecycleOwner) {
+ if (it) {
+ reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
+ exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
+ } else {
+ reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+ exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
+ }
+ }
+
+ if (args.menuTag == SettingsFile.FILE_NAME_CONFIG) {
+ binding.toolbarSettings.inflateMenu(R.menu.menu_settings)
+ binding.toolbarSettings.setOnMenuItemClickListener {
+ when (it.itemId) {
+ R.id.action_search -> {
+ reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
+ exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
+ view.findNavController()
+ .navigate(R.id.action_settingsFragment_to_settingsSearchFragment)
+ true
+ }
+
+ else -> false
+ }
+ }
+ }
+
presenter.onViewCreated()
setInsets()
}
+ override fun onResume() {
+ super.onResume()
+ settingsViewModel.setIsUsingSearch(false)
+ }
+
override fun onDetach() {
super.onDetach()
settingsAdapter?.closeDialog()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
new file mode 100644
index 000000000..4f93db4ad
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
@@ -0,0 +1,189 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.fragments
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.inputmethod.InputMethodManager
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.updatePadding
+import androidx.core.widget.doOnTextChanged
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.google.android.material.divider.MaterialDividerItemDecoration
+import com.google.android.material.transition.MaterialSharedAxis
+import info.debatty.java.stringsimilarity.Cosine
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
+import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
+import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
+import org.yuzu.yuzu_emu.model.SettingsViewModel
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
+class SettingsSearchFragment : Fragment() {
+ private var _binding: FragmentSettingsSearchBinding? = null
+ private val binding get() = _binding!!
+
+ private var settingsAdapter: SettingsAdapter? = null
+
+ private val settingsViewModel: SettingsViewModel by activityViewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
+ returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentSettingsSearchBinding.inflate(layoutInflater)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ settingsViewModel.setIsUsingSearch(true)
+
+ if (savedInstanceState != null) {
+ binding.searchText.setText(savedInstanceState.getString(SEARCH_TEXT))
+ }
+
+ settingsAdapter = SettingsAdapter(this, requireContext())
+
+ val dividerDecoration = MaterialDividerItemDecoration(
+ requireContext(),
+ LinearLayoutManager.VERTICAL
+ )
+ dividerDecoration.isLastItemDecorated = false
+ binding.settingsList.apply {
+ adapter = settingsAdapter
+ layoutManager = LinearLayoutManager(requireContext())
+ addItemDecoration(dividerDecoration)
+ }
+
+ focusSearch()
+
+ binding.backButton.setOnClickListener { settingsViewModel.setShouldNavigateBack(true) }
+ binding.searchBackground.setOnClickListener { focusSearch() }
+ binding.clearButton.setOnClickListener { binding.searchText.setText("") }
+ binding.searchText.doOnTextChanged { _, _, _, _ ->
+ search()
+ binding.settingsList.smoothScrollToPosition(0)
+ }
+ settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) {
+ if (it) {
+ settingsViewModel.setShouldReloadSettingsList(false)
+ search()
+ }
+ }
+
+ search()
+
+ setInsets()
+ }
+
+ override fun onDetach() {
+ super.onDetach()
+ settingsAdapter?.closeDialog()
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ outState.putString(SEARCH_TEXT, binding.searchText.text.toString())
+ }
+
+ private fun search() {
+ val searchTerm = binding.searchText.text.toString().lowercase()
+ binding.clearButton.visibility =
+ if (searchTerm.isEmpty()) View.INVISIBLE else View.VISIBLE
+ if (searchTerm.isEmpty()) {
+ binding.noResultsView.visibility = View.VISIBLE
+ settingsAdapter?.submitList(emptyList())
+ return
+ }
+
+ val baseList = SettingsItem.settingsItems
+ val similarityAlgorithm = if (searchTerm.length > 2) Cosine() else Cosine(1)
+ val sortedList: List<SettingsItem> = baseList.mapNotNull { item ->
+ val title = getString(item.value.nameId).lowercase()
+ val similarity = similarityAlgorithm.similarity(searchTerm, title)
+ if (similarity > 0.08) {
+ Pair(similarity, item)
+ } else {
+ null
+ }
+ }.sortedByDescending { it.first }.mapNotNull {
+ val item = it.second.value
+ val pairedSettingKey = item.setting.pairedSettingKey
+ val optionalSetting: SettingsItem? = if (pairedSettingKey.isNotEmpty()) {
+ val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false)
+ if (pairedSettingValue) it.second.value else null
+ } else {
+ it.second.value
+ }
+ optionalSetting
+ }
+ settingsAdapter?.submitList(sortedList)
+ binding.noResultsView.visibility =
+ if (sortedList.isEmpty()) View.VISIBLE else View.INVISIBLE
+ }
+
+ private fun focusSearch() {
+ binding.searchText.requestFocus()
+ val imm = requireActivity()
+ .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
+ imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT)
+ }
+
+ private fun setInsets() =
+ ViewCompat.setOnApplyWindowInsetsListener(
+ binding.root
+ ) { _: View, windowInsets: WindowInsetsCompat ->
+ val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med)
+ val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge)
+ val topMargin = resources.getDimensionPixelSize(R.dimen.spacing_chip)
+
+ val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
+ val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
+
+ val leftInsets = barInsets.left + cutoutInsets.left
+ val rightInsets = barInsets.right + cutoutInsets.right
+
+ binding.settingsList.updatePadding(bottom = barInsets.bottom + extraListSpacing)
+ binding.frameSearch.updatePadding(
+ left = leftInsets + sideMargin,
+ top = barInsets.top + topMargin,
+ right = rightInsets + sideMargin
+ )
+ binding.noResultsView.updatePadding(
+ left = leftInsets,
+ right = rightInsets,
+ bottom = barInsets.bottom
+ )
+
+ val mlpSettingsList = binding.settingsList.layoutParams as ViewGroup.MarginLayoutParams
+ mlpSettingsList.leftMargin = leftInsets + sideMargin
+ mlpSettingsList.rightMargin = rightInsets + sideMargin
+ binding.settingsList.layoutParams = mlpSettingsList
+
+ val mlpDivider = binding.divider.layoutParams as ViewGroup.MarginLayoutParams
+ mlpDivider.leftMargin = leftInsets + sideMargin
+ mlpDivider.rightMargin = rightInsets + sideMargin
+ binding.divider.layoutParams = mlpDivider
+
+ windowInsets
+ }
+
+ companion object {
+ const val SEARCH_TEXT = "SearchText"
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
index 6f2276293..a0cb7225f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
@@ -27,6 +27,9 @@ class SettingsViewModel : ViewModel() {
private val _shouldReloadSettingsList = MutableLiveData(false)
val shouldReloadSettingsList: LiveData<Boolean> get() = _shouldReloadSettingsList
+ private val _isUsingSearch = MutableLiveData(false)
+ val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch
+
fun setToolbarTitle(value: String) {
_toolbarTitle.value = value
}
@@ -47,6 +50,10 @@ class SettingsViewModel : ViewModel() {
_shouldReloadSettingsList.value = value
}
+ fun setIsUsingSearch(value: Boolean) {
+ _isUsingSearch.value = value
+ }
+
fun clear() {
game = null
shouldSave = false
diff --git a/src/android/app/src/main/res/layout/fragment_settings_search.xml b/src/android/app/src/main/res/layout/fragment_settings_search.xml
new file mode 100644
index 000000000..c779ed2fc
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_settings_search.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <RelativeLayout
+ android:id="@+id/relativeLayout"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/divider">
+
+ <LinearLayout
+ android:id="@+id/no_results_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="vertical">
+
+ <ImageView
+ android:id="@+id/icon_no_results"
+ android:layout_width="match_parent"
+ android:layout_height="80dp"
+ android:src="@drawable/ic_search" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/notice_text"
+ style="@style/TextAppearance.Material3.TitleLarge"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:paddingTop="8dp"
+ android:text="@string/search_settings"
+ tools:visibility="visible" />
+
+ </LinearLayout>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/settings_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipToPadding="false" />
+
+ </RelativeLayout>
+
+ <FrameLayout
+ android:id="@+id/frame_search"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
+
+ <com.google.android.material.card.MaterialCardView
+ android:id="@+id/search_background"
+ style="?attr/materialCardViewFilledStyle"
+ android:layout_width="match_parent"
+ android:layout_height="56dp"
+ app:cardCornerRadius="28dp">
+
+ <LinearLayout
+ android:id="@+id/search_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="56dp"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/back_button"
+ style="?attr/materialIconButtonFilledTonalStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="8dp"
+ app:backgroundTint="@android:color/transparent"
+ app:icon="@drawable/ic_back" />
+
+ <EditText
+ android:id="@+id/search_text"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/transparent"
+ android:hint="@string/search_settings"
+ android:imeOptions="flagNoFullscreen"
+ android:inputType="text"
+ android:maxLines="1" />
+
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/clear_button"
+ style="?attr/materialIconButtonFilledTonalStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginEnd="8dp"
+ android:visibility="invisible"
+ app:backgroundTint="@android:color/transparent"
+ app:icon="@drawable/ic_clear"
+ tools:visibility="visible" />
+
+ </com.google.android.material.card.MaterialCardView>
+
+ </FrameLayout>
+
+ <com.google.android.material.divider.MaterialDivider
+ android:id="@+id/divider"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/frame_search" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/menu/menu_settings.xml b/src/android/app/src/main/res/menu/menu_settings.xml
new file mode 100644
index 000000000..21501a471
--- /dev/null
+++ b/src/android/app/src/main/res/menu/menu_settings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <item
+ android:id="@+id/action_search"
+ android:icon="@drawable/ic_search"
+ android:title="@string/home_search"
+ app:showAsAction="always" />
+
+</menu>
diff --git a/src/android/app/src/main/res/navigation/settings_navigation.xml b/src/android/app/src/main/res/navigation/settings_navigation.xml
index b36200c65..88e1b4587 100644
--- a/src/android/app/src/main/res/navigation/settings_navigation.xml
+++ b/src/android/app/src/main/res/navigation/settings_navigation.xml
@@ -15,10 +15,18 @@
android:name="game"
app:argType="org.yuzu.yuzu_emu.model.Game"
app:nullable="true" />
+ <action
+ android:id="@+id/action_settingsFragment_to_settingsSearchFragment"
+ app:destination="@id/settingsSearchFragment" />
</fragment>
<action
android:id="@+id/action_global_settingsFragment"
app:destination="@id/settingsFragment" />
+ <fragment
+ android:id="@+id/settingsSearchFragment"
+ android:name="org.yuzu.yuzu_emu.fragments.SettingsSearchFragment"
+ android:label="SettingsSearchFragment" />
+
</navigation>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 6b782780a..d43891cec 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -43,6 +43,7 @@
<string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string>
<string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
<string name="home_search_games">Search games</string>
+ <string name="search_settings">Search settings</string>
<string name="games_dir_selected">Games directory selected</string>
<string name="install_prod_keys">Install prod.keys</string>
<string name="install_prod_keys_description">Required to decrypt retail games</string>