summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCharles Lombardo <clombardo169@gmail.com>2023-04-30 00:35:28 +0200
committerbunnei <bunneidev@gmail.com>2023-06-03 09:05:57 +0200
commit6df030998a254bdf2a713d7b326bc3dd7f69acae (patch)
tree36c3573678c6fb3cc8d3ec6f512e5a4bd19013ee
parentandroid: Fix potential zip traversal exploit (diff)
downloadyuzu-6df030998a254bdf2a713d7b326bc3dd7f69acae.tar
yuzu-6df030998a254bdf2a713d7b326bc3dd7f69acae.tar.gz
yuzu-6df030998a254bdf2a713d7b326bc3dd7f69acae.tar.bz2
yuzu-6df030998a254bdf2a713d7b326bc3dd7f69acae.tar.lz
yuzu-6df030998a254bdf2a713d7b326bc3dd7f69acae.tar.xz
yuzu-6df030998a254bdf2a713d7b326bc3dd7f69acae.tar.zst
yuzu-6df030998a254bdf2a713d7b326bc3dd7f69acae.zip
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt222
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt7
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt116
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt43
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt15
-rw-r--r--src/android/app/src/main/res/drawable/ic_clear.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_search.xml9
-rw-r--r--src/android/app/src/main/res/layout/activity_main.xml1
-rw-r--r--src/android/app/src/main/res/layout/fragment_games.xml74
-rw-r--r--src/android/app/src/main/res/layout/fragment_search.xml180
-rw-r--r--src/android/app/src/main/res/menu/menu_navigation.xml5
-rw-r--r--src/android/app/src/main/res/navigation/home_navigation.xml5
-rw-r--r--src/android/app/src/main/res/values/dimens.xml7
-rw-r--r--src/android/app/src/main/res/values/strings.xml9
20 files changed, 551 insertions, 189 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
index eca84a694..b9f975e2b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
@@ -13,6 +13,7 @@ import android.view.ViewGroup
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
+import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
@@ -21,6 +22,7 @@ import coil.load
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.databinding.CardGameBinding
import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.model.Game
@@ -51,6 +53,14 @@ class GameAdapter(private val activity: AppCompatActivity) :
*/
override fun onClick(view: View) {
val holder = view.tag as GameViewHolder
+ val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
+ preferences.edit()
+ .putLong(
+ holder.game.keyLastPlayedTime,
+ System.currentTimeMillis()
+ )
+ .apply()
+
EmulationActivity.launch(activity, holder.game)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
index 0e7c181ea..eb29d6c96 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
@@ -21,6 +21,7 @@ import androidx.core.app.NotificationManagerCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.R
@@ -30,6 +31,7 @@ import org.yuzu.yuzu_emu.features.DocumentProvider
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.HomeSetting
+import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
@@ -39,6 +41,8 @@ class HomeSettingsFragment : Fragment() {
private lateinit var mainActivity: MainActivity
+ private val homeViewModel: HomeViewModel by activityViewModels()
+
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -49,6 +53,7 @@ class HomeSettingsFragment : Fragment() {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ homeViewModel.setNavigationVisibility(visible = true, animated = false)
mainActivity = requireActivity() as MainActivity
val optionsList: List<HomeSetting> = listOf(
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
new file mode 100644
index 000000000..5babd9bbf
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
@@ -0,0 +1,222 @@
+// 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.content.SharedPreferences
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.inputmethod.InputMethodManager
+import androidx.appcompat.app.AppCompatActivity
+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.preference.PreferenceManager
+import info.debatty.java.stringsimilarity.Jaccard
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.YuzuApplication
+import org.yuzu.yuzu_emu.adapters.GameAdapter
+import org.yuzu.yuzu_emu.databinding.FragmentSearchBinding
+import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager
+import org.yuzu.yuzu_emu.model.Game
+import org.yuzu.yuzu_emu.model.GamesViewModel
+import org.yuzu.yuzu_emu.model.HomeViewModel
+import org.yuzu.yuzu_emu.utils.FileUtil
+import org.yuzu.yuzu_emu.utils.Log
+import java.util.Locale
+
+class SearchFragment : Fragment() {
+ private var _binding: FragmentSearchBinding? = null
+ private val binding get() = _binding!!
+
+ private val gamesViewModel: GamesViewModel by activityViewModels()
+ private val homeViewModel: HomeViewModel by activityViewModels()
+
+ private lateinit var preferences: SharedPreferences
+
+ companion object {
+ private const val SEARCH_TEXT = "SearchText"
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentSearchBinding.inflate(layoutInflater)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ homeViewModel.setNavigationVisibility(visible = true, animated = false)
+ preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
+
+ if (savedInstanceState != null) {
+ binding.searchText.setText(savedInstanceState.getString(SEARCH_TEXT))
+ }
+
+ gamesViewModel.searchFocused.observe(viewLifecycleOwner) { searchFocused ->
+ if (searchFocused) {
+ focusSearch()
+ gamesViewModel.setSearchFocused(false)
+ }
+ }
+
+ binding.gridGamesSearch.apply {
+ layoutManager = AutofitGridLayoutManager(
+ requireContext(),
+ requireContext().resources.getDimensionPixelSize(R.dimen.card_width)
+ )
+ adapter = GameAdapter(requireActivity() as AppCompatActivity)
+ }
+
+ binding.chipGroup.setOnCheckedStateChangeListener { _, _ -> filterAndSearch() }
+
+ binding.searchText.doOnTextChanged { text: CharSequence?, _: Int, _: Int, _: Int ->
+ if (text.toString().isNotEmpty()) {
+ binding.clearButton.visibility = View.VISIBLE
+ } else {
+ binding.clearButton.visibility = View.INVISIBLE
+ }
+ filterAndSearch()
+ }
+
+ gamesViewModel.games.observe(viewLifecycleOwner) { filterAndSearch() }
+ gamesViewModel.searchedGames.observe(viewLifecycleOwner) {
+ (binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
+ if (it.isEmpty()) {
+ binding.noResultsView.visibility = View.VISIBLE
+ } else {
+ binding.noResultsView.visibility = View.GONE
+ }
+ }
+
+ binding.clearButton.setOnClickListener { binding.searchText.setText("") }
+
+ binding.searchBackground.setOnClickListener { focusSearch() }
+
+ setInsets()
+ filterAndSearch()
+ }
+
+ private inner class ScoredGame(val score: Double, val item: Game)
+
+ private fun filterAndSearch() {
+ val baseList = gamesViewModel.games.value!!
+ val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) {
+ R.id.chip_recently_played -> {
+ baseList.filter {
+ val lastPlayedTime = preferences.getLong(it.keyLastPlayedTime, 0L)
+ lastPlayedTime > (System.currentTimeMillis() - 24 * 60 * 60 * 1000)
+ }
+ }
+
+ R.id.chip_recently_added -> {
+ baseList.filter {
+ val addedTime = preferences.getLong(it.keyAddedToLibraryTime, 0L)
+ addedTime > (System.currentTimeMillis() - 24 * 60 * 60 * 1000)
+ }
+ }
+
+ R.id.chip_homebrew -> {
+ baseList.filter {
+ Log.error("Guh - ${it.path}")
+ FileUtil.hasExtension(it.path, "nro")
+ || FileUtil.hasExtension(it.path, "nso")
+ }
+ }
+
+ R.id.chip_retail -> baseList.filter {
+ FileUtil.hasExtension(it.path, "xci")
+ || FileUtil.hasExtension(it.path, "nsp")
+ }
+
+ else -> baseList
+ }
+
+ if (binding.searchText.text.toString().isEmpty()
+ && binding.chipGroup.checkedChipId != View.NO_ID) {
+ gamesViewModel.setSearchedGames(filteredList)
+ return
+ }
+
+ val searchTerm = binding.searchText.text.toString().lowercase(Locale.getDefault())
+ val searchAlgorithm = Jaccard(2)
+ val sortedList: List<Game> = filteredList.mapNotNull { game ->
+ val title = game.title.lowercase(Locale.getDefault())
+ val score = searchAlgorithm.similarity(searchTerm, title)
+ if (score > 0.03) {
+ ScoredGame(score, game)
+ } else {
+ null
+ }
+ }.sortedByDescending { it.score }.map { it.item }
+ gamesViewModel.setSearchedGames(sortedList)
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ if (_binding != null) {
+ outState.putString(SEARCH_TEXT, binding.searchText.text.toString())
+ }
+ }
+
+ private fun focusSearch() {
+ if (_binding != null) {
+ 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 insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
+ val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med)
+ val navigationSpacing = resources.getDimensionPixelSize(R.dimen.spacing_navigation)
+ val chipSpacing = resources.getDimensionPixelSize(R.dimen.spacing_chip)
+
+ binding.frameSearch.updatePadding(
+ left = insets.left,
+ top = insets.top,
+ right = insets.right
+ )
+
+ binding.gridGamesSearch.setPadding(
+ insets.left,
+ extraListSpacing,
+ insets.right,
+ insets.bottom + resources.getDimensionPixelSize(R.dimen.spacing_navigation) + extraListSpacing
+ )
+
+ binding.noResultsView.updatePadding(
+ left = insets.left,
+ right = insets.right,
+ bottom = insets.bottom + navigationSpacing
+ )
+
+ val mlpDivider = binding.divider.layoutParams as ViewGroup.MarginLayoutParams
+ mlpDivider.leftMargin = insets.left + chipSpacing
+ mlpDivider.rightMargin = insets.right + chipSpacing
+ binding.divider.layoutParams = mlpDivider
+
+ binding.chipGroup.updatePadding(
+ left = insets.left + chipSpacing,
+ right = insets.right + chipSpacing
+ )
+
+ windowInsets
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
index 3d2f8719c..13b8315db 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
@@ -71,7 +71,7 @@ class SetupFragment : Fragment() {
mainActivity = requireActivity() as MainActivity
- homeViewModel.setNavigationVisibility(false)
+ homeViewModel.setNavigationVisibility(visible = false, animated = false)
requireActivity().onBackPressedDispatcher.addCallback(
viewLifecycleOwner,
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
index db494e40f..c5cde9d05 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
@@ -16,6 +16,9 @@ class Game(
val gameId: String,
val company: String
) : Parcelable {
+ val keyAddedToLibraryTime get() = "${gameId}_AddedToLibraryTime"
+ val keyLastPlayedTime get() = "${gameId}_LastPlayed"
+
companion object {
val extensions: Set<String> = HashSet(
listOf(".xci", ".nsp", ".nca", ".nro")
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
index 95bad38c6..1d0846b08 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
@@ -29,6 +29,9 @@ class GamesViewModel : ViewModel() {
private val _shouldScrollToTop = MutableLiveData(false)
val shouldScrollToTop: LiveData<Boolean> get() = _shouldScrollToTop
+ private val _searchFocused = MutableLiveData(false)
+ val searchFocused: LiveData<Boolean> get() = _searchFocused
+
init {
reloadGames(false)
}
@@ -45,6 +48,10 @@ class GamesViewModel : ViewModel() {
_shouldScrollToTop.postValue(shouldScroll)
}
+ fun setSearchFocused(searchFocused: Boolean) {
+ _searchFocused.postValue(searchFocused)
+ }
+
fun reloadGames(directoryChanged: Boolean) {
if (isReloading.value == true)
return
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
index acda8663a..b959ae4ba 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
@@ -5,19 +5,23 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class HomeViewModel : ViewModel() {
- private val _navigationVisible = MutableLiveData(true)
- val navigationVisible: LiveData<Boolean> get() = _navigationVisible
+ private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>()
+ val navigationVisible: LiveData<Pair<Boolean, Boolean>> get() = _navigationVisible
private val _statusBarShadeVisible = MutableLiveData(true)
val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible
var navigatedToSetup = false
- fun setNavigationVisibility(visible: Boolean) {
- if (_navigationVisible.value == visible) {
+ init {
+ _navigationVisible.value = Pair(false, false)
+ }
+
+ fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
+ if (_navigationVisible.value?.first == visible) {
return
}
- _navigationVisible.value = visible
+ _navigationVisible.value = Pair(visible, animated)
}
fun setStatusBarShadeVisibility(visible: Boolean) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt
index 227ca1afc..6f9e04f7e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt
@@ -52,19 +52,7 @@ class GamesFragment : Fragment() {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- // Use custom back navigation so the user doesn't back out of the app when trying to back
- // out of the search view
- requireActivity().onBackPressedDispatcher.addCallback(
- viewLifecycleOwner,
- object : OnBackPressedCallback(true) {
- override fun handleOnBackPressed() {
- if (binding.searchView.currentTransitionState == TransitionState.SHOWN) {
- binding.searchView.hide()
- } else {
- requireActivity().finish()
- }
- }
- })
+ homeViewModel.setNavigationVisibility(visible = true, animated = false)
binding.gridGames.apply {
layoutManager = AutofitGridLayoutManager(
@@ -73,7 +61,6 @@ class GamesFragment : Fragment() {
)
adapter = GameAdapter(requireActivity() as AppCompatActivity)
}
- setUpSearch()
// Add swipe down to refresh gesture
binding.swipeRefresh.setOnRefreshListener {
@@ -91,21 +78,16 @@ class GamesFragment : Fragment() {
// Watch for when we get updates to any of our games lists
gamesViewModel.isReloading.observe(viewLifecycleOwner) { isReloading ->
binding.swipeRefresh.isRefreshing = isReloading
-
- if (!isReloading) {
- if (gamesViewModel.games.value!!.isEmpty()) {
- binding.noticeText.visibility = View.VISIBLE
- } else {
- binding.noticeText.visibility = View.GONE
- }
- }
}
gamesViewModel.games.observe(viewLifecycleOwner) {
(binding.gridGames.adapter as GameAdapter).submitList(it)
+ if (it.isEmpty()) {
+ binding.noticeText.visibility = View.VISIBLE
+ } else {
+ binding.noticeText.visibility = View.GONE
+ }
}
- gamesViewModel.searchedGames.observe(viewLifecycleOwner) {
- (binding.gridSearch.adapter as GameAdapter).submitList(it)
- }
+
gamesViewModel.shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData ->
if (shouldSwapData) {
(binding.gridGames.adapter as GameAdapter).submitList(gamesViewModel.games.value)
@@ -113,31 +95,6 @@ class GamesFragment : Fragment() {
}
}
- // Hide bottom navigation and FAB when using the search view
- binding.searchView.addTransitionListener { _: SearchView, _: TransitionState, newState: TransitionState ->
- when (newState) {
- TransitionState.SHOWING,
- TransitionState.SHOWN -> {
- (binding.gridSearch.adapter as GameAdapter).submitList(emptyList())
- searchShown()
- }
- TransitionState.HIDDEN,
- TransitionState.HIDING -> {
- gamesViewModel.setSearchedGames(emptyList())
- searchHidden()
- binding.appBarSearch.setExpanded(true)
- }
- }
- }
-
- // Ensure that bottom navigation or FAB don't appear upon recreation
- val searchState = binding.searchView.currentTransitionState
- if (searchState == TransitionState.SHOWN) {
- searchShown()
- } else if (searchState == TransitionState.HIDDEN) {
- searchHidden()
- }
-
// Check if the user reselected the games menu item and then scroll to top of the list
gamesViewModel.shouldScrollToTop.observe(viewLifecycleOwner) { shouldScroll ->
if (shouldScroll) {
@@ -162,71 +119,24 @@ class GamesFragment : Fragment() {
_binding = null
}
- private fun searchShown() {
- homeViewModel.setNavigationVisibility(false)
- homeViewModel.setStatusBarShadeVisibility(false)
- }
-
- private fun searchHidden() {
- homeViewModel.setNavigationVisibility(true)
- homeViewModel.setStatusBarShadeVisibility(true)
- }
-
- private inner class ScoredGame(val score: Double, val item: Game)
-
- private fun setUpSearch() {
- binding.gridSearch.apply {
- layoutManager = AutofitGridLayoutManager(
- requireContext(),
- requireContext().resources.getDimensionPixelSize(R.dimen.card_width)
- )
- adapter = GameAdapter(requireActivity() as AppCompatActivity)
- }
-
- binding.searchView.editText.doOnTextChanged { text: CharSequence?, _: Int, _: Int, _: Int ->
- val searchTerm = text.toString().lowercase(Locale.getDefault())
- val searchAlgorithm = Jaccard(2)
- val sortedList: List<Game> = gamesViewModel.games.value!!.mapNotNull { game ->
- val title = game.title.lowercase(Locale.getDefault())
- val score = searchAlgorithm.similarity(searchTerm, title)
- if (score > 0.03) {
- ScoredGame(score, game)
- } else {
- null
- }
- }.sortedByDescending { it.score }.map { it.item }
- gamesViewModel.setSearchedGames(sortedList)
- }
- }
-
- fun scrollToTop() {
+ private fun scrollToTop() {
if (_binding != null) {
binding.gridGames.smoothScrollToPosition(0)
}
}
private fun setInsets() =
- ViewCompat.setOnApplyWindowInsetsListener(binding.gridGames) { view: View, windowInsets: WindowInsetsCompat ->
+ ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
- val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med)
+ val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_large)
- view.updatePadding(
- top = insets.top + resources.getDimensionPixelSize(R.dimen.spacing_search),
+ binding.gridGames.updatePadding(
+ top = insets.top + extraListSpacing,
bottom = insets.bottom + resources.getDimensionPixelSize(R.dimen.spacing_navigation) + extraListSpacing
)
- binding.gridSearch.updatePadding(
- left = insets.left,
- top = extraListSpacing,
- right = insets.right,
- bottom = insets.bottom + extraListSpacing
- )
- binding.swipeRefresh.setSlingshotDistance(
- resources.getDimensionPixelSize(R.dimen.spacing_refresh_slingshot)
- )
- binding.swipeRefresh.setProgressViewOffset(
+ binding.swipeRefresh.setProgressViewEndTarget(
false,
- insets.top + resources.getDimensionPixelSize(R.dimen.spacing_refresh_start),
insets.top + resources.getDimensionPixelSize(R.dimen.spacing_refresh_end)
)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index 473d38a29..35b66d1f2 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -7,6 +7,7 @@ import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.ViewGroup.MarginLayoutParams
+import android.view.WindowManager
import android.view.animation.PathInterpolator
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
@@ -60,6 +61,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
setContentView(binding.root)
WindowCompat.setDecorFitsSystemWindows(window, false)
+ window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
window.statusBarColor =
ContextCompat.getColor(applicationContext, android.R.color.transparent)
@@ -75,26 +77,30 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
setUpNavigation(navHostFragment.navController)
(binding.navigationBar as NavigationBarView).setOnItemReselectedListener {
- if (it.itemId == R.id.gamesFragment) {
- gamesViewModel.setShouldScrollToTop(true)
+ when (it.itemId) {
+ R.id.gamesFragment -> gamesViewModel.setShouldScrollToTop(true)
+ R.id.searchFragment -> gamesViewModel.setSearchFocused(true)
}
}
binding.statusBarShade.setBackgroundColor(
- MaterialColors.getColor(
- binding.root,
- R.attr.colorSurface
+ ThemeHelper.getColorWithOpacity(
+ MaterialColors.getColor(
+ binding.root,
+ R.attr.colorSurface
+ ),
+ ThemeHelper.SYSTEM_BAR_ALPHA
)
)
// Prevents navigation from being drawn for a short time on recreation if set to hidden
- if (homeViewModel.navigationVisible.value == false) {
+ if (!homeViewModel.navigationVisible.value?.first!!) {
binding.navigationBar.visibility = View.INVISIBLE
binding.statusBarShade.visibility = View.INVISIBLE
}
- homeViewModel.navigationVisible.observe(this) { visible ->
- showNavigation(visible)
+ homeViewModel.navigationVisible.observe(this) {
+ showNavigation(it.first, it.second)
}
homeViewModel.statusBarShadeVisible.observe(this) { visible ->
showStatusBarShade(visible)
@@ -109,7 +115,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
fun finishSetup(navController: NavController) {
navController.navigate(R.id.action_firstTimeSetupFragment_to_gamesFragment)
binding.navigationBar.setupWithNavController(navController)
- showNavigation(true)
+ showNavigation(visible = true, animated = true)
ThemeHelper.setNavigationBarColor(
this,
@@ -132,7 +138,16 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
}
- private fun showNavigation(visible: Boolean) {
+ private fun showNavigation(visible: Boolean, animated: Boolean) {
+ if (!animated) {
+ if (visible) {
+ binding.navigationBar.visibility = View.VISIBLE
+ } else {
+ binding.navigationBar.visibility = View.INVISIBLE
+ }
+ return
+ }
+
binding.navigationBar.animate().apply {
if (visible) {
binding.navigationBar.visibility = View.VISIBLE
@@ -196,10 +211,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
themeId = resId
}
- private fun hasExtension(path: String, extension: String): Boolean {
- return path.substring(path.lastIndexOf(".") + 1).contains(extension)
- }
-
val getGamesDirectory =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
if (result == null)
@@ -232,7 +243,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
if (result == null)
return@registerForActivityResult
- if (!hasExtension(result.toString(), "keys")) {
+ if (!FileUtil.hasExtension(result.toString(), "keys")) {
Toast.makeText(
applicationContext,
R.string.invalid_keys_file,
@@ -278,7 +289,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
if (result == null)
return@registerForActivityResult
- if (!hasExtension(result.toString(), "bin")) {
+ if (!FileUtil.hasExtension(result.toString(), "bin")) {
Toast.makeText(
applicationContext,
R.string.invalid_keys_file,
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
index d16ed96ac..0e3305026 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
@@ -292,4 +292,8 @@ object FileUtil {
}
}
}
+
+ fun hasExtension(path: String, extension: String): Boolean {
+ return path.substring(path.lastIndexOf(".") + 1).contains(extension)
+ }
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
index c463a66d8..9dd43343f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
@@ -3,6 +3,7 @@
package org.yuzu.yuzu_emu.utils
+import android.content.SharedPreferences
import android.net.Uri
import androidx.preference.PreferenceManager
import org.yuzu.yuzu_emu.NativeLibrary
@@ -14,12 +15,15 @@ import kotlin.collections.ArrayList
object GameHelper {
const val KEY_GAME_PATH = "game_path"
+ private lateinit var preferences: SharedPreferences
+
fun getGames(): ArrayList<Game> {
val games = ArrayList<Game>()
val context = YuzuApplication.appContext
val gamesDir =
PreferenceManager.getDefaultSharedPreferences(context).getString(KEY_GAME_PATH, "")
val gamesUri = Uri.parse(gamesDir)
+ preferences = PreferenceManager.getDefaultSharedPreferences(context)
// Ensure keys are loaded so that ROM metadata can be decrypted.
NativeLibrary.reloadKeys()
@@ -60,7 +64,7 @@ object GameHelper {
)
}
- return Game(
+ val newGame = Game(
name,
NativeLibrary.getDescription(filePath).replace("\n", " "),
NativeLibrary.getRegions(filePath),
@@ -68,5 +72,14 @@ object GameHelper {
gameId,
NativeLibrary.getCompany(filePath)
)
+
+ val addedTime = preferences.getLong(newGame.keyAddedToLibraryTime, 0L)
+ if (addedTime == 0L) {
+ preferences.edit()
+ .putLong(newGame.keyAddedToLibraryTime, System.currentTimeMillis())
+ .apply()
+ }
+
+ return newGame
}
}
diff --git a/src/android/app/src/main/res/drawable/ic_clear.xml b/src/android/app/src/main/res/drawable/ic_clear.xml
new file mode 100644
index 000000000..b6edb1d32
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_clear.xml
@@ -0,0 +1,9 @@
+<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="?attr/colorControlNormal"
+ android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
+</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_search.xml b/src/android/app/src/main/res/drawable/ic_search.xml
new file mode 100644
index 000000000..bb0726851
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_search.xml
@@ -0,0 +1,9 @@
+<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="?attr/colorControlNormal"
+ android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
+</vector>
diff --git a/src/android/app/src/main/res/layout/activity_main.xml b/src/android/app/src/main/res/layout/activity_main.xml
index 59812ab8e..6ca426b54 100644
--- a/src/android/app/src/main/res/layout/activity_main.xml
+++ b/src/android/app/src/main/res/layout/activity_main.xml
@@ -29,6 +29,7 @@
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/menu_navigation"
+ app:labelVisibilityMode="selected"
tools:visibility="visible" />
<View
diff --git a/src/android/app/src/main/res/layout/fragment_games.xml b/src/android/app/src/main/res/layout/fragment_games.xml
index c4c3eacf4..8b6d0b3b6 100644
--- a/src/android/app/src/main/res/layout/fragment_games.xml
+++ b/src/android/app/src/main/res/layout/fragment_games.xml
@@ -1,74 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
-<androidx.coordinatorlayout.widget.CoordinatorLayout
+<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
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:id="@+id/coordinator_main"
+ android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="?attr/colorSurface">
+ android:background="?attr/colorSurface"
+ android:clipToPadding="false">
- <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
- android:id="@+id/swipe_refresh"
+ <RelativeLayout
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipToPadding="false"
- app:layout_behavior="@string/searchbar_scrolling_view_behavior">
+ android:layout_height="match_parent">
- <RelativeLayout
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/notice_text"
+ style="@style/TextAppearance.Material3.BodyLarge"
android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <com.google.android.material.textview.MaterialTextView
- android:id="@+id/notice_text"
- style="@style/TextAppearance.Material3.BodyLarge"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center"
- android:padding="@dimen/spacing_large"
- android:text="@string/empty_gamelist"
- tools:visibility="gone" />
-
- <androidx.recyclerview.widget.RecyclerView
- android:id="@+id/grid_games"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipToPadding="false"
- tools:listitem="@layout/card_game" />
-
- </RelativeLayout>
-
- </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
-
- <com.google.android.material.appbar.AppBarLayout
- android:id="@+id/app_bar_search"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:fitsSystemWindows="true"
- app:liftOnScrollTargetViewId="@id/grid_games">
-
- <com.google.android.material.search.SearchBar
- android:id="@+id/search_bar"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:hint="@string/home_search_games" />
-
- </com.google.android.material.appbar.AppBarLayout>
-
- <com.google.android.material.search.SearchView
- android:id="@+id/search_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:hint="@string/home_search_games"
- app:layout_anchor="@id/search_bar">
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:padding="@dimen/spacing_large"
+ android:text="@string/empty_gamelist"
+ tools:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView
- android:id="@+id/grid_search"
+ android:id="@+id/grid_games"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
tools:listitem="@layout/card_game" />
- </com.google.android.material.search.SearchView>
+ </RelativeLayout>
-</androidx.coordinatorlayout.widget.CoordinatorLayout>
+</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_search.xml b/src/android/app/src/main/res/layout/fragment_search.xml
new file mode 100644
index 000000000..3b1aefdfb
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_search.xml
@@ -0,0 +1,180 @@
+<?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"
+ android:background="?attr/colorSurface">
+
+ <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:orientation="vertical"
+ android:gravity="center">
+
+ <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_and_filter_games"
+ tools:visibility="visible" />
+
+ </LinearLayout>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/grid_games_search"
+ 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:layout_margin="20dp"
+ 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_marginStart="24dp"
+ android:layout_marginEnd="56dp"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:layout_width="28dp"
+ android:layout_height="28dp"
+ android:layout_gravity="center_vertical"
+ android:layout_marginEnd="24dp"
+ android:src="@drawable/ic_search"
+ app:tint="?attr/colorOnSurfaceVariant" />
+
+ <EditText
+ android:id="@+id/search_text"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/transparent"
+ android:hint="@string/home_search_games"
+ android:inputType="text"
+ android:maxLines="1"
+ android:imeOptions="flagNoFullscreen" />
+
+ </LinearLayout>
+
+ <ImageView
+ android:id="@+id/clear_button"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginEnd="24dp"
+ android:background="?attr/selectableItemBackground"
+ android:src="@drawable/ic_clear"
+ android:visibility="invisible"
+ app:tint="?attr/colorOnSurfaceVariant"
+ tools:visibility="visible" />
+
+ </com.google.android.material.card.MaterialCardView>
+
+ </FrameLayout>
+
+ <HorizontalScrollView
+ android:id="@+id/horizontalScrollView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fadingEdge="horizontal"
+ android:scrollbars="none"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/frame_search">
+
+ <com.google.android.material.chip.ChipGroup
+ android:id="@+id/chip_group"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false"
+ android:paddingVertical="4dp"
+ app:chipSpacingHorizontal="12dp"
+ app:singleLine="true"
+ app:singleSelection="true">
+
+ <com.google.android.material.chip.Chip
+ android:id="@+id/chip_recently_played"
+ style="@style/Widget.Material3.Chip.Suggestion.Elevated"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:checked="false"
+ android:text="@string/search_recently_played"
+ app:chipCornerRadius="28dp" />
+
+ <com.google.android.material.chip.Chip
+ android:id="@+id/chip_recently_added"
+ style="@style/Widget.Material3.Chip.Suggestion.Elevated"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:checked="false"
+ android:text="@string/search_recently_added"
+ app:chipCornerRadius="28dp" />
+
+ <com.google.android.material.chip.Chip
+ android:id="@+id/chip_retail"
+ style="@style/Widget.Material3.Chip.Suggestion.Elevated"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:checked="false"
+ android:text="@string/search_retail"
+ app:chipCornerRadius="28dp" />
+
+ <com.google.android.material.chip.Chip
+ android:id="@+id/chip_homebrew"
+ style="@style/Widget.Material3.Chip.Suggestion.Elevated"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:checked="false"
+ android:text="@string/search_homebrew"
+ app:chipCornerRadius="28dp" />
+
+ </com.google.android.material.chip.ChipGroup>
+
+ </HorizontalScrollView>
+
+ <com.google.android.material.divider.MaterialDivider
+ android:id="@+id/divider"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="20dp"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/horizontalScrollView" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/menu/menu_navigation.xml b/src/android/app/src/main/res/menu/menu_navigation.xml
index e46133604..ed10e6e51 100644
--- a/src/android/app/src/main/res/menu/menu_navigation.xml
+++ b/src/android/app/src/main/res/menu/menu_navigation.xml
@@ -7,6 +7,11 @@
android:title="@string/home_games" />
<item
+ android:id="@+id/searchFragment"
+ android:icon="@drawable/ic_search"
+ android:title="@string/home_search" />
+
+ <item
android:id="@+id/homeSettingsFragment"
android:icon="@drawable/ic_settings"
android:title="@string/home_settings" />
diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml
index d500d165b..0f43ba556 100644
--- a/src/android/app/src/main/res/navigation/home_navigation.xml
+++ b/src/android/app/src/main/res/navigation/home_navigation.xml
@@ -25,4 +25,9 @@
app:popUpToInclusive="true" />
</fragment>
+ <fragment
+ android:id="@+id/searchFragment"
+ android:name="org.yuzu.yuzu_emu.fragments.SearchFragment"
+ android:label="SearchFragment" />
+
</navigation>
diff --git a/src/android/app/src/main/res/values/dimens.xml b/src/android/app/src/main/res/values/dimens.xml
index ab2583938..28a6d25cf 100644
--- a/src/android/app/src/main/res/values/dimens.xml
+++ b/src/android/app/src/main/res/values/dimens.xml
@@ -5,11 +5,10 @@
<dimen name="spacing_large">16dp</dimen>
<dimen name="spacing_xtralarge">32dp</dimen>
<dimen name="spacing_list">64dp</dimen>
+ <dimen name="spacing_chip">20dp</dimen>
<dimen name="spacing_navigation">80dp</dimen>
- <dimen name="spacing_search">88dp</dimen>
- <dimen name="spacing_refresh_slingshot">80dp</dimen>
- <dimen name="spacing_refresh_start">32dp</dimen>
- <dimen name="spacing_refresh_end">96dp</dimen>
+ <dimen name="spacing_search">128dp</dimen>
+ <dimen name="spacing_refresh_end">72dp</dimen>
<dimen name="menu_width">256dp</dimen>
<dimen name="card_width">165dp</dimen>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index c55b9e06b..9c7ab3c26 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -32,7 +32,10 @@
<!-- Home strings -->
<string name="home_games">Games</string>
+ <string name="home_search">Search</string>
<string name="home_settings">Settings</string>
+ <string name="empty_gamelist">No files were found or no game directory has been selected yet.</string>
+ <string name="search_and_filter_games">Search and filter games</string>
<string name="select_games_folder">Select games folder</string>
<string name="select_games_folder_description">Allows yuzu to populate the games list</string>
<string name="add_games_warning">Skip selecting games folder?</string>
@@ -58,6 +61,10 @@
<string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
<string name="advanced_settings">Advanced settings</string>
<string name="settings_description">Configure emulator settings</string>
+ <string name="search_recently_played">Recently Played</string>
+ <string name="search_recently_added">Recently Added</string>
+ <string name="search_retail">Retail</string>
+ <string name="search_homebrew">Homebrew</string>
<string name="open_user_folder">Open yuzu folder</string>
<string name="open_user_folder_description">Manage yuzu\'s internal files</string>
<string name="no_file_manager">No file manager found</string>
@@ -151,8 +158,6 @@
<string name="load_settings">Loading Settings…</string>
- <string name="empty_gamelist">No files were found or no game directory has been selected yet.</string>
-
<!-- Software keyboard -->
<string name="software_keyboard">Software Keyboard</string>