From 6df030998a254bdf2a713d7b326bc3dd7f69acae Mon Sep 17 00:00:00 2001
From: Charles Lombardo <clombardo169@gmail.com>
Date: Sat, 29 Apr 2023 18:35:28 -0400
Subject: [PATCH] android: Search Fragment

---
 .../org/yuzu/yuzu_emu/adapters/GameAdapter.kt |  10 +
 .../fragments/HomeSettingsFragment.kt         |   5 +
 .../yuzu/yuzu_emu/fragments/SearchFragment.kt | 222 ++++++++++++++++++
 .../yuzu/yuzu_emu/fragments/SetupFragment.kt  |   2 +-
 .../main/java/org/yuzu/yuzu_emu/model/Game.kt |   3 +
 .../org/yuzu/yuzu_emu/model/GamesViewModel.kt |   7 +
 .../org/yuzu/yuzu_emu/model/HomeViewModel.kt  |  14 +-
 .../org/yuzu/yuzu_emu/ui/GamesFragment.kt     | 116 +--------
 .../org/yuzu/yuzu_emu/ui/main/MainActivity.kt |  43 ++--
 .../java/org/yuzu/yuzu_emu/utils/FileUtil.kt  |   4 +
 .../org/yuzu/yuzu_emu/utils/GameHelper.kt     |  15 +-
 .../app/src/main/res/drawable/ic_clear.xml    |   9 +
 .../app/src/main/res/drawable/ic_search.xml   |   9 +
 .../app/src/main/res/layout/activity_main.xml |   1 +
 .../src/main/res/layout/fragment_games.xml    |  74 ++----
 .../src/main/res/layout/fragment_search.xml   | 180 ++++++++++++++
 .../app/src/main/res/menu/menu_navigation.xml |   5 +
 .../main/res/navigation/home_navigation.xml   |   5 +
 .../app/src/main/res/values/dimens.xml        |   7 +-
 .../app/src/main/res/values/strings.xml       |   9 +-
 20 files changed, 551 insertions(+), 189 deletions(-)
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
 create mode 100644 src/android/app/src/main/res/drawable/ic_clear.xml
 create mode 100644 src/android/app/src/main/res/drawable/ic_search.xml
 create mode 100644 src/android/app/src/main/res/layout/fragment_search.xml

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 eca84a694e..b9f975e2b5 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 0e7c181ea3..eb29d6c964 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 0000000000..5babd9bbff
--- /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 3d2f8719c0..13b8315db6 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 db494e40fe..c5cde9d059 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 95bad38c65..1d0846b080 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 acda8663aa..b959ae4bae 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 227ca1afc3..6f9e04f7eb 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 473d38a291..35b66d1f20 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 d16ed96ace..0e33050262 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 c463a66d89..9dd43343f6 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 0000000000..b6edb1d328
--- /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 0000000000..bb0726851c
--- /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 59812ab8e7..6ca426b54a 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 c4c3eacf4b..8b6d0b3b63 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 0000000000..3b1aefdfb8
--- /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 e46133604b..ed10e6e510 100644
--- a/src/android/app/src/main/res/menu/menu_navigation.xml
+++ b/src/android/app/src/main/res/menu/menu_navigation.xml
@@ -6,6 +6,11 @@
         android:icon="@drawable/ic_controller"
         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"
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 d500d165b1..0f43ba556a 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 ab25839384..28a6d25cf5 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 c55b9e06ba..9c7ab3c26f 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>