summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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
12import android.view.ViewGroup 12import android.view.ViewGroup
13import android.widget.TextView 13import android.widget.TextView
14import androidx.appcompat.app.AlertDialog 14import androidx.appcompat.app.AlertDialog
15import androidx.fragment.app.Fragment
15import androidx.lifecycle.ViewModelProvider 16import androidx.lifecycle.ViewModelProvider
16import androidx.navigation.findNavController 17import androidx.navigation.findNavController
17import androidx.recyclerview.widget.AsyncDifferConfig 18import androidx.recyclerview.widget.AsyncDifferConfig
@@ -37,7 +38,7 @@ import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
37import org.yuzu.yuzu_emu.model.SettingsViewModel 38import org.yuzu.yuzu_emu.model.SettingsViewModel
38 39
39class SettingsAdapter( 40class SettingsAdapter(
40 private val fragment: SettingsFragment, 41 private val fragment: Fragment,
41 private val context: Context 42 private val context: Context
42) : ListAdapter<SettingsItem, SettingViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()), 43) : ListAdapter<SettingsItem, SettingViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
43 DialogInterface.OnClickListener { 44 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
13import androidx.core.view.updatePadding 13import androidx.core.view.updatePadding
14import androidx.fragment.app.Fragment 14import androidx.fragment.app.Fragment
15import androidx.fragment.app.activityViewModels 15import androidx.fragment.app.activityViewModels
16import androidx.navigation.findNavController
16import androidx.navigation.fragment.navArgs 17import androidx.navigation.fragment.navArgs
17import androidx.recyclerview.widget.LinearLayoutManager 18import androidx.recyclerview.widget.LinearLayoutManager
18import com.google.android.material.divider.MaterialDividerItemDecoration 19import com.google.android.material.divider.MaterialDividerItemDecoration
19import com.google.android.material.transition.MaterialSharedAxis 20import com.google.android.material.transition.MaterialSharedAxis
20import org.yuzu.yuzu_emu.R 21import org.yuzu.yuzu_emu.R
21import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding 22import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
23import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
22import org.yuzu.yuzu_emu.model.SettingsViewModel 24import org.yuzu.yuzu_emu.model.SettingsViewModel
23 25
24class SettingsFragment : Fragment() { 26class SettingsFragment : Fragment() {
@@ -84,11 +86,43 @@ class SettingsFragment : Fragment() {
84 } 86 }
85 } 87 }
86 88
89 settingsViewModel.isUsingSearch.observe(viewLifecycleOwner) {
90 if (it) {
91 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
92 exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
93 } else {
94 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
95 exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
96 }
97 }
98
99 if (args.menuTag == SettingsFile.FILE_NAME_CONFIG) {
100 binding.toolbarSettings.inflateMenu(R.menu.menu_settings)
101 binding.toolbarSettings.setOnMenuItemClickListener {
102 when (it.itemId) {
103 R.id.action_search -> {
104 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
105 exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
106 view.findNavController()
107 .navigate(R.id.action_settingsFragment_to_settingsSearchFragment)
108 true
109 }
110
111 else -> false
112 }
113 }
114 }
115
87 presenter.onViewCreated() 116 presenter.onViewCreated()
88 117
89 setInsets() 118 setInsets()
90 } 119 }
91 120
121 override fun onResume() {
122 super.onResume()
123 settingsViewModel.setIsUsingSearch(false)
124 }
125
92 override fun onDetach() { 126 override fun onDetach() {
93 super.onDetach() 127 super.onDetach()
94 settingsAdapter?.closeDialog() 128 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 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.content.Context
7import android.os.Bundle
8import android.view.LayoutInflater
9import android.view.View
10import android.view.ViewGroup
11import android.view.inputmethod.InputMethodManager
12import androidx.core.view.ViewCompat
13import androidx.core.view.WindowInsetsCompat
14import androidx.core.view.updatePadding
15import androidx.core.widget.doOnTextChanged
16import androidx.fragment.app.Fragment
17import androidx.fragment.app.activityViewModels
18import androidx.recyclerview.widget.LinearLayoutManager
19import com.google.android.material.divider.MaterialDividerItemDecoration
20import com.google.android.material.transition.MaterialSharedAxis
21import info.debatty.java.stringsimilarity.Cosine
22import org.yuzu.yuzu_emu.R
23import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
24import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
25import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
26import org.yuzu.yuzu_emu.model.SettingsViewModel
27import org.yuzu.yuzu_emu.utils.NativeConfig
28
29class SettingsSearchFragment : Fragment() {
30 private var _binding: FragmentSettingsSearchBinding? = null
31 private val binding get() = _binding!!
32
33 private var settingsAdapter: SettingsAdapter? = null
34
35 private val settingsViewModel: SettingsViewModel by activityViewModels()
36
37 override fun onCreate(savedInstanceState: Bundle?) {
38 super.onCreate(savedInstanceState)
39 enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
40 returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
41 }
42
43 override fun onCreateView(
44 inflater: LayoutInflater,
45 container: ViewGroup?,
46 savedInstanceState: Bundle?
47 ): View {
48 _binding = FragmentSettingsSearchBinding.inflate(layoutInflater)
49 return binding.root
50 }
51
52 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
53 super.onViewCreated(view, savedInstanceState)
54 settingsViewModel.setIsUsingSearch(true)
55
56 if (savedInstanceState != null) {
57 binding.searchText.setText(savedInstanceState.getString(SEARCH_TEXT))
58 }
59
60 settingsAdapter = SettingsAdapter(this, requireContext())
61
62 val dividerDecoration = MaterialDividerItemDecoration(
63 requireContext(),
64 LinearLayoutManager.VERTICAL
65 )
66 dividerDecoration.isLastItemDecorated = false
67 binding.settingsList.apply {
68 adapter = settingsAdapter
69 layoutManager = LinearLayoutManager(requireContext())
70 addItemDecoration(dividerDecoration)
71 }
72
73 focusSearch()
74
75 binding.backButton.setOnClickListener { settingsViewModel.setShouldNavigateBack(true) }
76 binding.searchBackground.setOnClickListener { focusSearch() }
77 binding.clearButton.setOnClickListener { binding.searchText.setText("") }
78 binding.searchText.doOnTextChanged { _, _, _, _ ->
79 search()
80 binding.settingsList.smoothScrollToPosition(0)
81 }
82 settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) {
83 if (it) {
84 settingsViewModel.setShouldReloadSettingsList(false)
85 search()
86 }
87 }
88
89 search()
90
91 setInsets()
92 }
93
94 override fun onDetach() {
95 super.onDetach()
96 settingsAdapter?.closeDialog()
97 }
98
99 override fun onSaveInstanceState(outState: Bundle) {
100 super.onSaveInstanceState(outState)
101 outState.putString(SEARCH_TEXT, binding.searchText.text.toString())
102 }
103
104 private fun search() {
105 val searchTerm = binding.searchText.text.toString().lowercase()
106 binding.clearButton.visibility =
107 if (searchTerm.isEmpty()) View.INVISIBLE else View.VISIBLE
108 if (searchTerm.isEmpty()) {
109 binding.noResultsView.visibility = View.VISIBLE
110 settingsAdapter?.submitList(emptyList())
111 return
112 }
113
114 val baseList = SettingsItem.settingsItems
115 val similarityAlgorithm = if (searchTerm.length > 2) Cosine() else Cosine(1)
116 val sortedList: List<SettingsItem> = baseList.mapNotNull { item ->
117 val title = getString(item.value.nameId).lowercase()
118 val similarity = similarityAlgorithm.similarity(searchTerm, title)
119 if (similarity > 0.08) {
120 Pair(similarity, item)
121 } else {
122 null
123 }
124 }.sortedByDescending { it.first }.mapNotNull {
125 val item = it.second.value
126 val pairedSettingKey = item.setting.pairedSettingKey
127 val optionalSetting: SettingsItem? = if (pairedSettingKey.isNotEmpty()) {
128 val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false)
129 if (pairedSettingValue) it.second.value else null
130 } else {
131 it.second.value
132 }
133 optionalSetting
134 }
135 settingsAdapter?.submitList(sortedList)
136 binding.noResultsView.visibility =
137 if (sortedList.isEmpty()) View.VISIBLE else View.INVISIBLE
138 }
139
140 private fun focusSearch() {
141 binding.searchText.requestFocus()
142 val imm = requireActivity()
143 .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
144 imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT)
145 }
146
147 private fun setInsets() =
148 ViewCompat.setOnApplyWindowInsetsListener(
149 binding.root
150 ) { _: View, windowInsets: WindowInsetsCompat ->
151 val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med)
152 val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge)
153 val topMargin = resources.getDimensionPixelSize(R.dimen.spacing_chip)
154
155 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
156 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
157
158 val leftInsets = barInsets.left + cutoutInsets.left
159 val rightInsets = barInsets.right + cutoutInsets.right
160
161 binding.settingsList.updatePadding(bottom = barInsets.bottom + extraListSpacing)
162 binding.frameSearch.updatePadding(
163 left = leftInsets + sideMargin,
164 top = barInsets.top + topMargin,
165 right = rightInsets + sideMargin
166 )
167 binding.noResultsView.updatePadding(
168 left = leftInsets,
169 right = rightInsets,
170 bottom = barInsets.bottom
171 )
172
173 val mlpSettingsList = binding.settingsList.layoutParams as ViewGroup.MarginLayoutParams
174 mlpSettingsList.leftMargin = leftInsets + sideMargin
175 mlpSettingsList.rightMargin = rightInsets + sideMargin
176 binding.settingsList.layoutParams = mlpSettingsList
177
178 val mlpDivider = binding.divider.layoutParams as ViewGroup.MarginLayoutParams
179 mlpDivider.leftMargin = leftInsets + sideMargin
180 mlpDivider.rightMargin = rightInsets + sideMargin
181 binding.divider.layoutParams = mlpDivider
182
183 windowInsets
184 }
185
186 companion object {
187 const val SEARCH_TEXT = "SearchText"
188 }
189}
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() {
27 private val _shouldReloadSettingsList = MutableLiveData(false) 27 private val _shouldReloadSettingsList = MutableLiveData(false)
28 val shouldReloadSettingsList: LiveData<Boolean> get() = _shouldReloadSettingsList 28 val shouldReloadSettingsList: LiveData<Boolean> get() = _shouldReloadSettingsList
29 29
30 private val _isUsingSearch = MutableLiveData(false)
31 val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch
32
30 fun setToolbarTitle(value: String) { 33 fun setToolbarTitle(value: String) {
31 _toolbarTitle.value = value 34 _toolbarTitle.value = value
32 } 35 }
@@ -47,6 +50,10 @@ class SettingsViewModel : ViewModel() {
47 _shouldReloadSettingsList.value = value 50 _shouldReloadSettingsList.value = value
48 } 51 }
49 52
53 fun setIsUsingSearch(value: Boolean) {
54 _isUsingSearch.value = value
55 }
56
50 fun clear() { 57 fun clear() {
51 game = null 58 game = null
52 shouldSave = false 59 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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 xmlns:tools="http://schemas.android.com/tools"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent">
7
8 <RelativeLayout
9 android:id="@+id/relativeLayout"
10 android:layout_width="0dp"
11 android:layout_height="0dp"
12 app:layout_constraintBottom_toBottomOf="parent"
13 app:layout_constraintEnd_toEndOf="parent"
14 app:layout_constraintStart_toStartOf="parent"
15 app:layout_constraintTop_toBottomOf="@+id/divider">
16
17 <LinearLayout
18 android:id="@+id/no_results_view"
19 android:layout_width="match_parent"
20 android:layout_height="match_parent"
21 android:gravity="center"
22 android:orientation="vertical">
23
24 <ImageView
25 android:id="@+id/icon_no_results"
26 android:layout_width="match_parent"
27 android:layout_height="80dp"
28 android:src="@drawable/ic_search" />
29
30 <com.google.android.material.textview.MaterialTextView
31 android:id="@+id/notice_text"
32 style="@style/TextAppearance.Material3.TitleLarge"
33 android:layout_width="match_parent"
34 android:layout_height="wrap_content"
35 android:gravity="center"
36 android:paddingTop="8dp"
37 android:text="@string/search_settings"
38 tools:visibility="visible" />
39
40 </LinearLayout>
41
42 <androidx.recyclerview.widget.RecyclerView
43 android:id="@+id/settings_list"
44 android:layout_width="match_parent"
45 android:layout_height="match_parent"
46 android:clipToPadding="false" />
47
48 </RelativeLayout>
49
50 <FrameLayout
51 android:id="@+id/frame_search"
52 android:layout_width="match_parent"
53 android:layout_height="wrap_content"
54 android:clipToPadding="false"
55 app:layout_constraintEnd_toEndOf="parent"
56 app:layout_constraintStart_toStartOf="parent"
57 app:layout_constraintTop_toTopOf="parent">
58
59 <com.google.android.material.card.MaterialCardView
60 android:id="@+id/search_background"
61 style="?attr/materialCardViewFilledStyle"
62 android:layout_width="match_parent"
63 android:layout_height="56dp"
64 app:cardCornerRadius="28dp">
65
66 <LinearLayout
67 android:id="@+id/search_container"
68 android:layout_width="match_parent"
69 android:layout_height="match_parent"
70 android:layout_marginEnd="56dp"
71 android:orientation="horizontal">
72
73 <Button
74 android:id="@+id/back_button"
75 style="?attr/materialIconButtonFilledTonalStyle"
76 android:layout_width="wrap_content"
77 android:layout_height="wrap_content"
78 android:layout_gravity="center_vertical"
79 android:layout_marginStart="8dp"
80 app:backgroundTint="@android:color/transparent"
81 app:icon="@drawable/ic_back" />
82
83 <EditText
84 android:id="@+id/search_text"
85 android:layout_width="match_parent"
86 android:layout_height="match_parent"
87 android:background="@android:color/transparent"
88 android:hint="@string/search_settings"
89 android:imeOptions="flagNoFullscreen"
90 android:inputType="text"
91 android:maxLines="1" />
92
93 </LinearLayout>
94
95 <Button
96 android:id="@+id/clear_button"
97 style="?attr/materialIconButtonFilledTonalStyle"
98 android:layout_width="wrap_content"
99 android:layout_height="wrap_content"
100 android:layout_gravity="center_vertical|end"
101 android:layout_marginEnd="8dp"
102 android:visibility="invisible"
103 app:backgroundTint="@android:color/transparent"
104 app:icon="@drawable/ic_clear"
105 tools:visibility="visible" />
106
107 </com.google.android.material.card.MaterialCardView>
108
109 </FrameLayout>
110
111 <com.google.android.material.divider.MaterialDivider
112 android:id="@+id/divider"
113 android:layout_width="match_parent"
114 android:layout_height="wrap_content"
115 android:layout_marginTop="20dp"
116 app:layout_constraintEnd_toEndOf="parent"
117 app:layout_constraintStart_toStartOf="parent"
118 app:layout_constraintTop_toBottomOf="@+id/frame_search" />
119
120</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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<menu xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto">
4
5 <item
6 android:id="@+id/action_search"
7 android:icon="@drawable/ic_search"
8 android:title="@string/home_search"
9 app:showAsAction="always" />
10
11</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 @@
15 android:name="game" 15 android:name="game"
16 app:argType="org.yuzu.yuzu_emu.model.Game" 16 app:argType="org.yuzu.yuzu_emu.model.Game"
17 app:nullable="true" /> 17 app:nullable="true" />
18 <action
19 android:id="@+id/action_settingsFragment_to_settingsSearchFragment"
20 app:destination="@id/settingsSearchFragment" />
18 </fragment> 21 </fragment>
19 22
20 <action 23 <action
21 android:id="@+id/action_global_settingsFragment" 24 android:id="@+id/action_global_settingsFragment"
22 app:destination="@id/settingsFragment" /> 25 app:destination="@id/settingsFragment" />
23 26
27 <fragment
28 android:id="@+id/settingsSearchFragment"
29 android:name="org.yuzu.yuzu_emu.fragments.SettingsSearchFragment"
30 android:label="SettingsSearchFragment" />
31
24</navigation> 32</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 @@
43 <string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string> 43 <string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string>
44 <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string> 44 <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
45 <string name="home_search_games">Search games</string> 45 <string name="home_search_games">Search games</string>
46 <string name="search_settings">Search settings</string>
46 <string name="games_dir_selected">Games directory selected</string> 47 <string name="games_dir_selected">Games directory selected</string>
47 <string name="install_prod_keys">Install prod.keys</string> 48 <string name="install_prod_keys">Install prod.keys</string>
48 <string name="install_prod_keys_description">Required to decrypt retail games</string> 49 <string name="install_prod_keys_description">Required to decrypt retail games</string>