aboutsummaryrefslogtreecommitdiff
path: root/src/android
diff options
context:
space:
mode:
authorBriar <205427297+icy-briar@users.noreply.github.com>2025-04-09 00:01:32 +0200
committerGitHub <noreply@github.com>2025-04-09 00:01:32 +0200
commit8ef4da686cad5476a497a30e6325b908c54fb0fb (patch)
treedd36107838d022b476fb069b6017528d34fcc8db /src/android
parent52eab40fc8c3896ea34a044f3ceb6a130f017d25 (diff)
Refactor Android Game Screen UI
* Port SearchFragment functionality to GameFragment @kleidis Co-authored-by: Briar <205427297+icybriarr@users.noreply.github.com> * Remove the bottom navigation bar and SearchFragment remaining code @ishan09811 * Add 2 new game view types `Grid & `List` to GameAdapter @kleidis * Fix padding on header * Change app name to uppercase --------- Co-authored-by: Kleidis <167202775+kleidis@users.noreply.github.com> Co-authored-by: Briar <205427297+icybriarr@users.noreply.github.com> Co-authored-by: Ishan09811 <156402647+Ishan09811@users.noreply.github.com>
Diffstat (limited to 'src/android')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt76
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddGameFolderDialogFragment.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AppletLauncherFragment.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt31
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt218
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt31
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt12
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt281
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt83
-rw-r--r--src/android/app/src/main/res/drawable/ic_eye.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_filter.xml9
-rw-r--r--src/android/app/src/main/res/layout-w600dp/activity_main.xml12
-rw-r--r--src/android/app/src/main/res/layout/activity_main.xml13
-rw-r--r--src/android/app/src/main/res/layout/card_game_grid.xml (renamed from src/android/app/src/main/res/layout/card_game.xml)2
-rw-r--r--src/android/app/src/main/res/layout/card_game_list.xml67
-rw-r--r--src/android/app/src/main/res/layout/fragment_games.xml231
-rw-r--r--src/android/app/src/main/res/layout/fragment_search.xml184
-rw-r--r--src/android/app/src/main/res/menu-w600dp/menu_navigation.xml19
-rw-r--r--src/android/app/src/main/res/menu/menu_game_filters.xml14
-rw-r--r--src/android/app/src/main/res/menu/menu_game_views.xml12
-rw-r--r--src/android/app/src/main/res/menu/menu_navigation.xml19
-rw-r--r--src/android/app/src/main/res/navigation/home_navigation.xml12
-rw-r--r--src/android/app/src/main/res/values-w600dp/integers.xml3
-rw-r--r--src/android/app/src/main/res/values/integers.xml2
-rw-r--r--src/android/app/src/main/res/values/strings.xml7
34 files changed, 717 insertions, 692 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 b1f247ac36..671e897448 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
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.adapters
@@ -16,13 +16,15 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.preference.PreferenceManager
+import androidx.viewbinding.ViewBinding
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
-import org.yuzu.yuzu_emu.databinding.CardGameBinding
+import org.yuzu.yuzu_emu.databinding.CardGameListBinding
+import org.yuzu.yuzu_emu.databinding.CardGameGridBinding
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.utils.GameIconUtils
@@ -31,22 +33,70 @@ import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class GameAdapter(private val activity: AppCompatActivity) :
AbstractDiffAdapter<Game, GameAdapter.GameViewHolder>(exact = false) {
+
+ companion object {
+ const val VIEW_TYPE_GRID = 0
+ const val VIEW_TYPE_LIST = 1
+ }
+
+ private var viewType = 0
+
+ fun setViewType(type: Int) {
+ viewType = type
+ notifyDataSetChanged()
+ }
+
+ override fun getItemViewType(position: Int): Int = viewType
+
+
+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder {
- CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
- .also { return GameViewHolder(it) }
+ val binding = when (viewType) {
+ VIEW_TYPE_LIST -> CardGameListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ VIEW_TYPE_GRID -> CardGameGridBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ else -> throw IllegalArgumentException("Invalid view type")
+ }
+ return GameViewHolder(binding, viewType)
}
- inner class GameViewHolder(val binding: CardGameBinding) :
- AbstractViewHolder<Game>(binding) {
+ inner class GameViewHolder(
+ private val binding: ViewBinding,
+ private val viewType: Int
+ ) : AbstractViewHolder<Game>(binding) {
+
+
override fun bind(model: Game) {
- binding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP
- GameIconUtils.loadGameIcon(model, binding.imageGameScreen)
+ when (viewType) {
+ VIEW_TYPE_LIST -> bindListView(model)
+ VIEW_TYPE_GRID -> bindGridView(model)
+ }
+ }
+
+ private fun bindListView(model: Game) {
+ val listBinding = binding as CardGameListBinding
+
+ listBinding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP
+ GameIconUtils.loadGameIcon(model, listBinding.imageGameScreen)
+
+ listBinding.textGameTitle.text = model.title.replace("[\\t\\n\\r]+".toRegex(), " ")
+ listBinding.textGameDeveloper.text = model.developer
+
+ listBinding.textGameTitle.marquee()
+ listBinding.cardGameList.setOnClickListener { onClick(model) }
+ listBinding.cardGameList.setOnLongClickListener { onLongClick(model) }
+ }
+
+ private fun bindGridView(model: Game) {
+ val gridBinding = binding as CardGameGridBinding
+
+ gridBinding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP
+ GameIconUtils.loadGameIcon(model, gridBinding.imageGameScreen)
- binding.textGameTitle.text = model.title.replace("[\\t\\n\\r]+".toRegex(), " ")
+ gridBinding.textGameTitle.text = model.title.replace("[\\t\\n\\r]+".toRegex(), " ")
- binding.textGameTitle.marquee()
- binding.cardGame.setOnClickListener { onClick(model) }
- binding.cardGame.setOnLongClickListener { onLongClick(model) }
+ gridBinding.textGameTitle.marquee()
+ gridBinding.cardGameGrid.setOnClickListener { onClick(model) }
+ gridBinding.cardGameGrid.setOnLongClickListener { onLongClick(model) }
}
fun onClick(game: Game) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
index ff4f0e5dfb..fc627473a2 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
@@ -1,5 +1,6 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
package org.yuzu.yuzu_emu.fragments
@@ -50,7 +51,6 @@ class AboutFragment : Fragment() {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- homeViewModel.setNavigationVisibility(visible = false, animated = true)
homeViewModel.setStatusBarShadeVisibility(visible = false)
binding.toolbarAbout.setNavigationOnClickListener {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddGameFolderDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddGameFolderDialogFragment.kt
index 9fab88248d..b027547cef 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddGameFolderDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddGameFolderDialogFragment.kt
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
@@ -33,7 +33,8 @@ class AddGameFolderDialogFragment : DialogFragment() {
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
val newGameDir = GameDir(folderUriString!!, binding.deepScanSwitch.isChecked)
homeViewModel.setGamesDirSelected(true)
- gamesViewModel.addFolder(newGameDir)
+ val calledFromGameFragment = requireArguments().getBoolean("calledFromGameFragment", false)
+ gamesViewModel.addFolder(newGameDir, calledFromGameFragment)
}
.setNegativeButton(android.R.string.cancel, null)
.setView(binding.root)
@@ -45,9 +46,10 @@ class AddGameFolderDialogFragment : DialogFragment() {
private const val FOLDER_URI_STRING = "FolderUriString"
- fun newInstance(folderUriString: String): AddGameFolderDialogFragment {
+ fun newInstance(folderUriString: String, calledFromGameFragment: Boolean): AddGameFolderDialogFragment {
val args = Bundle()
args.putString(FOLDER_URI_STRING, folderUriString)
+ args.putBoolean("calledFromGameFragment", calledFromGameFragment)
val fragment = AddGameFolderDialogFragment()
fragment.arguments = args
return fragment
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt
index 110aa29600..5229e25475 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
@@ -59,7 +59,6 @@ class AddonsFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- homeViewModel.setNavigationVisibility(visible = false, animated = false)
homeViewModel.setStatusBarShadeVisibility(false)
binding.toolbarAddons.setNavigationOnClickListener {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AppletLauncherFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AppletLauncherFragment.kt
index 1514e38289..f39bb1affd 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AppletLauncherFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AppletLauncherFragment.kt
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
@@ -47,7 +47,6 @@ class AppletLauncherFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- homeViewModel.setNavigationVisibility(visible = false, animated = true)
homeViewModel.setStatusBarShadeVisibility(visible = false)
binding.toolbarApplets.setNavigationOnClickListener {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
index 8b23a10217..c58c02506c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
@@ -62,7 +62,6 @@ class DriverManagerFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- homeViewModel.setNavigationVisibility(visible = false, animated = true)
homeViewModel.setStatusBarShadeVisibility(visible = false)
driverViewModel.onOpenDriverManager(args.game)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt
index 3a6f7a38c4..83ae5622a2 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
@@ -53,7 +53,6 @@ class GameFoldersFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- homeViewModel.setNavigationVisibility(visible = false, animated = true)
homeViewModel.setStatusBarShadeVisibility(visible = false)
binding.toolbarFolders.setNavigationOnClickListener {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt
index 97a8954bb2..3cdee36d9c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
@@ -59,7 +59,6 @@ class GameInfoFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- homeViewModel.setNavigationVisibility(visible = false, animated = false)
homeViewModel.setStatusBarShadeVisibility(false)
binding.apply {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt
index c06842c596..8dbd69121b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
@@ -76,7 +76,6 @@ class GamePropertiesFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- homeViewModel.setNavigationVisibility(visible = false, animated = true)
homeViewModel.setStatusBarShadeVisibility(true)
binding.buttonBack.setOnClickListener {
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 006a7a4e6d..463caea421 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
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
@@ -70,7 +70,6 @@ class HomeSettingsFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- homeViewModel.setNavigationVisibility(visible = true, animated = true)
homeViewModel.setStatusBarShadeVisibility(visible = true)
mainActivity = requireActivity() as MainActivity
@@ -389,32 +388,20 @@ class HomeSettingsFragment : Fragment() {
}
private fun setInsets() =
- ViewCompat.setOnApplyWindowInsetsListener(
- binding.root
- ) { view: View, windowInsets: WindowInsetsCompat ->
+ ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
- val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation)
- val spacingNavigationRail =
- resources.getDimensionPixelSize(R.dimen.spacing_navigation_rail)
-
- val leftInsets = barInsets.left + cutoutInsets.left
- val rightInsets = barInsets.right + cutoutInsets.right
binding.scrollViewSettings.updatePadding(
top = barInsets.top,
- bottom = barInsets.bottom
)
- binding.scrollViewSettings.updateMargins(left = leftInsets, right = rightInsets)
-
- binding.linearLayoutSettings.updatePadding(bottom = spacingNavigation)
-
- if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
- binding.linearLayoutSettings.updatePadding(left = spacingNavigationRail)
- } else {
- binding.linearLayoutSettings.updatePadding(right = spacingNavigationRail)
- }
+ binding.homeSettingsList.updatePadding(
+ left = barInsets.left + cutoutInsets.left,
+ top = cutoutInsets.top,
+ right = barInsets.right + cutoutInsets.right,
+ bottom = barInsets.bottom
+ )
windowInsets
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt
index d218da1c88..22976900f2 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
@@ -66,7 +66,6 @@ class InstallableFragment : Fragment() {
val mainActivity = requireActivity() as MainActivity
- homeViewModel.setNavigationVisibility(visible = false, animated = true)
homeViewModel.setStatusBarShadeVisibility(visible = false)
binding.toolbarInstallables.setNavigationOnClickListener {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt
index f17f621f85..5fdcea29fa 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt
@@ -1,5 +1,6 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
package org.yuzu.yuzu_emu.fragments
@@ -45,7 +46,6 @@ class LicensesFragment : Fragment() {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- homeViewModel.setNavigationVisibility(visible = false, animated = true)
homeViewModel.setStatusBarShadeVisibility(visible = false)
binding.toolbarLicenses.setNavigationOnClickListener {
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
deleted file mode 100644
index 662ae9760a..0000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
+++ /dev/null
@@ -1,218 +0,0 @@
-// 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 info.debatty.java.stringsimilarity.JaroWinkler
-import java.util.Locale
-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.ViewUtils.setVisible
-import org.yuzu.yuzu_emu.utils.collect
-
-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?) {
- super.onViewCreated(view, savedInstanceState)
- homeViewModel.setNavigationVisibility(visible = true, animated = true)
- homeViewModel.setStatusBarShadeVisibility(true)
- preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
-
- if (savedInstanceState != null) {
- binding.searchText.setText(savedInstanceState.getString(SEARCH_TEXT))
- }
-
- 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 ->
- binding.clearButton.setVisible(text.toString().isNotEmpty())
- filterAndSearch()
- }
-
- gamesViewModel.searchFocused.collect(
- viewLifecycleOwner,
- resetState = { gamesViewModel.setSearchFocused(false) }
- ) { if (it) focusSearch() }
- gamesViewModel.games.collect(viewLifecycleOwner) { filterAndSearch() }
- gamesViewModel.searchedGames.collect(viewLifecycleOwner) {
- (binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
- binding.noResultsView.setVisible(it.isNotEmpty())
- }
-
- 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)
- }.sortedByDescending { preferences.getLong(it.keyLastPlayedTime, 0L) }
- }
-
- R.id.chip_recently_added -> {
- baseList.filter {
- val addedTime = preferences.getLong(it.keyAddedToLibraryTime, 0L)
- addedTime > (System.currentTimeMillis() - 24 * 60 * 60 * 1000)
- }.sortedByDescending { preferences.getLong(it.keyAddedToLibraryTime, 0L) }
- }
-
- R.id.chip_homebrew -> baseList.filter { it.isHomebrew }
-
- R.id.chip_retail -> baseList.filter { !it.isHomebrew }
-
- 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 = if (searchTerm.length > 1) Jaccard(2) else JaroWinkler()
- 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: View, windowInsets: WindowInsetsCompat ->
- val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
- val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
- val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med)
- val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation)
- val spacingNavigationRail =
- resources.getDimensionPixelSize(R.dimen.spacing_navigation_rail)
- val chipSpacing = resources.getDimensionPixelSize(R.dimen.spacing_chip)
-
- binding.constraintSearch.updatePadding(
- left = barInsets.left + cutoutInsets.left,
- top = barInsets.top,
- right = barInsets.right + cutoutInsets.right
- )
-
- binding.gridGamesSearch.updatePadding(
- top = extraListSpacing,
- bottom = barInsets.bottom + spacingNavigation + extraListSpacing
- )
- binding.noResultsView.updatePadding(bottom = spacingNavigation + barInsets.bottom)
-
- val mlpDivider = binding.divider.layoutParams as ViewGroup.MarginLayoutParams
- if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
- binding.frameSearch.updatePadding(left = spacingNavigationRail)
- binding.gridGamesSearch.updatePadding(left = spacingNavigationRail)
- binding.noResultsView.updatePadding(left = spacingNavigationRail)
- binding.chipGroup.updatePadding(
- left = chipSpacing + spacingNavigationRail,
- right = chipSpacing
- )
- mlpDivider.leftMargin = chipSpacing + spacingNavigationRail
- mlpDivider.rightMargin = chipSpacing
- } else {
- binding.frameSearch.updatePadding(right = spacingNavigationRail)
- binding.gridGamesSearch.updatePadding(right = spacingNavigationRail)
- binding.noResultsView.updatePadding(right = spacingNavigationRail)
- binding.chipGroup.updatePadding(
- left = chipSpacing,
- right = chipSpacing + spacingNavigationRail
- )
- mlpDivider.leftMargin = chipSpacing
- mlpDivider.rightMargin = chipSpacing + spacingNavigationRail
- }
- binding.divider.layoutParams = mlpDivider
-
- 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 4f7548e98e..5110145001 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
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
@@ -78,7 +78,6 @@ class SetupFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
mainActivity = requireActivity() as MainActivity
- homeViewModel.setNavigationVisibility(visible = false, animated = false)
requireActivity().onBackPressedDispatcher.addCallback(
viewLifecycleOwner,
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 5ae05b5cc2..72a15ccef3 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
@@ -1,9 +1,10 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.model
import android.net.Uri
+import android.widget.Toast
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
@@ -15,9 +16,9 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import org.yuzu.yuzu_emu.NativeLibrary
+import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.utils.GameHelper
import org.yuzu.yuzu_emu.utils.NativeConfig
@@ -27,9 +28,6 @@ class GamesViewModel : ViewModel() {
val games: StateFlow<List<Game>> get() = _games
private val _games = MutableStateFlow(emptyList<Game>())
- val searchedGames: StateFlow<List<Game>> get() = _searchedGames
- private val _searchedGames = MutableStateFlow(emptyList<Game>())
-
val isReloading: StateFlow<Boolean> get() = _isReloading
private val _isReloading = MutableStateFlow(false)
@@ -47,6 +45,8 @@ class GamesViewModel : ViewModel() {
private val _folders = MutableStateFlow(mutableListOf<GameDir>())
val folders = _folders.asStateFlow()
+ private val _filteredGames = MutableStateFlow<List<Game>>(emptyList())
+
init {
// Ensure keys are loaded so that ROM metadata can be decrypted.
NativeLibrary.reloadKeys()
@@ -66,10 +66,6 @@ class GamesViewModel : ViewModel() {
_games.value = sortedList
}
- fun setSearchedGames(games: List<Game>) {
- _searchedGames.value = games
- }
-
fun setShouldSwapData(shouldSwap: Boolean) {
_shouldSwapData.value = shouldSwap
}
@@ -82,6 +78,10 @@ class GamesViewModel : ViewModel() {
_searchFocused.value = searchFocused
}
+ fun setFilteredGames(games: List<Game>) {
+ _filteredGames.value = games
+ }
+
fun reloadGames(directoriesChanged: Boolean, firstStartup: Boolean = false) {
if (reloading.get()) {
return
@@ -131,12 +131,21 @@ class GamesViewModel : ViewModel() {
}
}
- fun addFolder(gameDir: GameDir) =
+ fun addFolder(gameDir: GameDir, savedFromGameFragment: Boolean) =
viewModelScope.launch {
withContext(Dispatchers.IO) {
NativeConfig.addGameDir(gameDir)
getGameDirs(true)
}
+
+ if (savedFromGameFragment) {
+ NativeConfig.saveGlobalConfig()
+ Toast.makeText(
+ YuzuApplication.appContext,
+ R.string.add_directory_success,
+ Toast.LENGTH_SHORT
+ ).show()
+ }
}
fun removeFolder(gameDir: GameDir) =
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 cfc777b81c..eb1a329e8d 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
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.model
@@ -10,9 +10,6 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class HomeViewModel : ViewModel() {
- val navigationVisible: StateFlow<Pair<Boolean, Boolean>> get() = _navigationVisible
- private val _navigationVisible = MutableStateFlow(Pair(false, false))
-
val statusBarShadeVisible: StateFlow<Boolean> get() = _statusBarShadeVisible
private val _statusBarShadeVisible = MutableStateFlow(true)
@@ -36,13 +33,6 @@ class HomeViewModel : ViewModel() {
var navigatedToSetup = false
- fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
- if (navigationVisible.value.first == visible) {
- return
- }
- _navigationVisible.value = Pair(visible, animated)
- }
-
fun setStatusBarShadeVisibility(visible: Boolean) {
if (statusBarShadeVisible.value == visible) {
return
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 fadb20e394..78abd974f6 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
@@ -1,35 +1,69 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.ui
+import android.content.Context
+import android.content.Intent
+import android.content.res.Configuration
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.view.inputmethod.InputMethodManager
+import android.widget.PopupMenu
+import androidx.activity.result.contract.ActivityResultContracts
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.navigation.fragment.findNavController
+import androidx.preference.PreferenceManager
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.color.MaterialColors
+import info.debatty.java.stringsimilarity.Jaccard
+import info.debatty.java.stringsimilarity.JaroWinkler
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.FragmentGamesBinding
-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.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
-import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
import org.yuzu.yuzu_emu.utils.collect
+import java.util.Locale
+import androidx.core.content.edit
class GamesFragment : Fragment() {
private var _binding: FragmentGamesBinding? = null
private val binding get() = _binding!!
+ companion object {
+ private const val SEARCH_TEXT = "SearchText"
+ private const val PREF_VIEW_TYPE = "GamesViewType"
+ private const val PREF_SORT_TYPE = "GamesSortType"
+ }
+
private val gamesViewModel: GamesViewModel by activityViewModels()
private val homeViewModel: HomeViewModel by activityViewModels()
+ private lateinit var gameAdapter: GameAdapter
+
+ private val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
+
+ private lateinit var mainActivity: MainActivity
+ private val getGamesDirectory =
+ registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
+ if (result != null) {
+ mainActivity.processGamesDir(result, true)
+ }
+ }
+
override fun onCreateView(
inflater: LayoutInflater,
@@ -42,17 +76,19 @@ class GamesFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- homeViewModel.setNavigationVisibility(visible = true, animated = true)
homeViewModel.setStatusBarShadeVisibility(true)
+ mainActivity = requireActivity() as MainActivity
- binding.gridGames.apply {
- layoutManager = AutofitGridLayoutManager(
- requireContext(),
- requireContext().resources.getDimensionPixelSize(R.dimen.card_width)
- )
- adapter = GameAdapter(requireActivity() as AppCompatActivity)
+ if (savedInstanceState != null) {
+ binding.searchText.setText(savedInstanceState.getString(SEARCH_TEXT))
}
+ gameAdapter = GameAdapter(
+ requireActivity() as AppCompatActivity,
+ )
+
+ applyGridGamesBinding()
+
binding.swipeRefresh.apply {
// Add swipe down to refresh gesture
setOnRefreshListener {
@@ -90,14 +126,14 @@ class GamesFragment : Fragment() {
)
}
gamesViewModel.games.collect(viewLifecycleOwner) {
- (binding.gridGames.adapter as GameAdapter).submitList(it)
+ setAdapter(it)
}
gamesViewModel.shouldSwapData.collect(
viewLifecycleOwner,
resetState = { gamesViewModel.setShouldSwapData(false) }
) {
if (it) {
- (binding.gridGames.adapter as GameAdapter).submitList(gamesViewModel.games.value)
+ setAdapter(gamesViewModel.games.value)
}
}
gamesViewModel.shouldScrollToTop.collect(
@@ -105,9 +141,193 @@ class GamesFragment : Fragment() {
resetState = { gamesViewModel.setShouldScrollToTop(false) }
) { if (it) scrollToTop() }
- setInsets()
+ setupTopView()
+
+ binding.addDirectory.setOnClickListener {
+ getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
+ }
+
+ setInsets()
+ }
+
+ val applyGridGamesBinding = {
+ binding.gridGames.apply {
+ val savedViewType = preferences.getInt(PREF_VIEW_TYPE, GameAdapter.VIEW_TYPE_GRID)
+ gameAdapter.setViewType(savedViewType)
+ currentFilter = preferences.getInt(PREF_SORT_TYPE, View.NO_ID)
+ adapter = gameAdapter
+
+ val gameGrid = when (savedViewType) {
+ GameAdapter.VIEW_TYPE_LIST -> R.integer.game_columns_list
+ GameAdapter.VIEW_TYPE_GRID -> R.integer.game_columns_grid
+ else -> 0
+ }
+
+ layoutManager = GridLayoutManager(requireContext(), resources.getInteger(gameGrid))
+
+ }
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ if (_binding != null) {
+ outState.putString(SEARCH_TEXT, binding.searchText.text.toString())
+ }
+ }
+
+ private fun setAdapter(games: List<Game>) {
+ val currentSearchText = binding.searchText.text.toString()
+ val currentFilter = binding.filterButton.id
+
+
+ if (currentSearchText.isNotEmpty() || currentFilter != View.NO_ID) {
+ filterAndSearch(games)
+ } else {
+ (binding.gridGames.adapter as GameAdapter).submitList(games)
+ gamesViewModel.setFilteredGames(games)
+ }
+ }
+
+ private fun setupTopView() {
+ binding.searchText.doOnTextChanged() { text: CharSequence?, _: Int, _: Int, _: Int ->
+ if (text.toString().isNotEmpty()) {
+ binding.clearButton.visibility = View.VISIBLE
+ } else {
+ binding.clearButton.visibility = View.INVISIBLE
+ }
+ filterAndSearch()
+ }
+
+ binding.clearButton.setOnClickListener { binding.searchText.setText("") }
+ binding.searchBackground.setOnClickListener { focusSearch() }
+
+ // Setup view button
+ binding.viewButton.setOnClickListener { showViewMenu(it) }
+
+ // Setup filter button
+ binding.filterButton.setOnClickListener { view ->
+ showFilterMenu(view)
+ }
+
+ // Setup settings button
+ binding.settingsButton.setOnClickListener { navigateToSettings() }
+ }
+
+ private fun navigateToSettings() {
+ val navController = findNavController()
+ navController.navigate(R.id.action_gamesFragment_to_homeSettingsFragment)
}
+ private fun showViewMenu(anchor: View) {
+ val popup = PopupMenu(requireContext(), anchor)
+ popup.menuInflater.inflate(R.menu.menu_game_views, popup.menu)
+
+ val currentViewType = (preferences.getInt(PREF_VIEW_TYPE, GameAdapter.VIEW_TYPE_GRID))
+ when (currentViewType) {
+ GameAdapter.VIEW_TYPE_LIST -> popup.menu.findItem(R.id.view_list).isChecked = true
+ GameAdapter.VIEW_TYPE_GRID -> popup.menu.findItem(R.id.view_grid).isChecked = true
+ }
+
+ popup.setOnMenuItemClickListener { item ->
+ when (item.itemId) {
+ R.id.view_grid -> {
+ preferences.edit() { putInt(PREF_VIEW_TYPE, GameAdapter.VIEW_TYPE_GRID) }
+ applyGridGamesBinding()
+ item.isChecked = true
+ true
+ }
+ R.id.view_list -> {
+ preferences.edit() { putInt(PREF_VIEW_TYPE, GameAdapter.VIEW_TYPE_LIST) }
+ applyGridGamesBinding()
+ item.isChecked = true
+ true
+ }
+ else -> false
+ }
+ }
+
+ popup.show()
+ }
+
+ private fun showFilterMenu(anchor: View) {
+ val popup = PopupMenu(requireContext(), anchor)
+ popup.menuInflater.inflate(R.menu.menu_game_filters, popup.menu)
+
+ // Set checked state based on current filter
+ when (currentFilter) {
+ R.id.alphabetical -> popup.menu.findItem(R.id.alphabetical).isChecked = true
+ R.id.filter_recently_played -> popup.menu.findItem(R.id.filter_recently_played).isChecked =
+ true
+
+ R.id.filter_recently_added -> popup.menu.findItem(R.id.filter_recently_added).isChecked =
+ true
+ }
+
+ popup.setOnMenuItemClickListener { item ->
+ currentFilter = item.itemId
+ preferences.edit().putInt(PREF_SORT_TYPE, currentFilter).apply()
+ filterAndSearch()
+ true
+ }
+
+ popup.show()
+ }
+
+ // Track current filter
+ private var currentFilter = View.NO_ID
+
+ private fun filterAndSearch(baseList: List<Game> = gamesViewModel.games.value) {
+ val filteredList: List<Game> = when (currentFilter) {
+ R.id.alphabetical -> baseList.sortedBy { it.title }
+ R.id.filter_recently_played -> {
+ baseList.filter {
+ val lastPlayedTime = preferences.getLong(it.keyLastPlayedTime, 0L)
+ lastPlayedTime > (System.currentTimeMillis() - 24 * 60 * 60 * 1000)
+ }.sortedByDescending { preferences.getLong(it.keyLastPlayedTime, 0L) }
+ }
+
+ R.id.filter_recently_added -> {
+ baseList.filter {
+ val addedTime = preferences.getLong(it.keyAddedToLibraryTime, 0L)
+ addedTime > (System.currentTimeMillis() - 24 * 60 * 60 * 1000)
+ }.sortedByDescending { preferences.getLong(it.keyAddedToLibraryTime, 0L) }
+ }
+
+ else -> baseList
+ }
+
+ val searchTerm = binding.searchText.text.toString().lowercase(Locale.getDefault())
+ if (searchTerm.isEmpty()) {
+ (binding.gridGames.adapter as GameAdapter).submitList(filteredList)
+ gamesViewModel.setFilteredGames(filteredList)
+ return
+ }
+
+ val searchAlgorithm = if (searchTerm.length > 1) Jaccard(2) else JaroWinkler()
+ val sortedList = 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 }
+
+ (binding.gridGames.adapter as GameAdapter).submitList(sortedList)
+ gamesViewModel.setFilteredGames(sortedList)
+ }
+
+ private inner class ScoredGame(val score: Double, val item: Game)
+
+ private fun focusSearch() {
+ binding.searchText.requestFocus()
+ val imm = requireActivity()
+ .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
+ imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT)
+ }
+
+
override fun onDestroyView() {
super.onDestroyView()
_binding = null
@@ -125,15 +345,10 @@ class GamesFragment : Fragment() {
) { view: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
- val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_large)
val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation)
- val spacingNavigationRail =
resources.getDimensionPixelSize(R.dimen.spacing_navigation_rail)
-
- binding.gridGames.updatePadding(
- top = barInsets.top + extraListSpacing,
- bottom = barInsets.bottom + spacingNavigation + extraListSpacing
- )
+ val isLandscape =
+ resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
binding.swipeRefresh.setProgressViewEndTarget(
false,
@@ -142,18 +357,28 @@ class GamesFragment : Fragment() {
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
- val left: Int
- val right: Int
+ val mlpSwipe = binding.swipeRefresh.layoutParams as ViewGroup.MarginLayoutParams
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
- left = leftInsets + spacingNavigationRail
- right = rightInsets
+ mlpSwipe.leftMargin = leftInsets
+ mlpSwipe.rightMargin = rightInsets
} else {
- left = leftInsets
- right = rightInsets + spacingNavigationRail
+ mlpSwipe.leftMargin = leftInsets
+ mlpSwipe.rightMargin = rightInsets
}
- binding.swipeRefresh.updateMargins(left = left, right = right)
+ binding.swipeRefresh.layoutParams = mlpSwipe
binding.noticeText.updatePadding(bottom = spacingNavigation)
+ binding.header.updatePadding(top = cutoutInsets.top + resources.getDimensionPixelSize(R.dimen.spacing_large) + if (isLandscape) barInsets.top else 0)
+ binding.gridGames.updatePadding(
+ top = resources.getDimensionPixelSize(R.dimen.spacing_med)
+ )
+
+ val mlpFab = binding.addDirectory.layoutParams as ViewGroup.MarginLayoutParams
+ val fabPadding = resources.getDimensionPixelSize(R.dimen.spacing_large)
+ mlpFab.leftMargin = leftInsets + fabPadding
+ mlpFab.bottomMargin = barInsets.bottom + fabPadding
+ mlpFab.rightMargin = rightInsets + fabPadding
+ binding.addDirectory.layoutParams = mlpFab
windowInsets
}
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 a0de1bbd8b..58ff109deb 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
@@ -121,27 +121,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
setUpNavigation(navHostFragment.navController)
- (binding.navigationView as NavigationBarView).setOnItemReselectedListener {
- when (it.itemId) {
- R.id.gamesFragment -> gamesViewModel.setShouldScrollToTop(true)
- R.id.searchFragment -> gamesViewModel.setSearchFocused(true)
- R.id.homeSettingsFragment -> {
- val action = HomeNavigationDirections.actionGlobalSettingsActivity(
- null,
- Settings.MenuTag.SECTION_ROOT
- )
- navHostFragment.navController.navigate(action)
- }
- }
- }
- // Prevents navigation from being drawn for a short time on recreation if set to hidden
- if (!homeViewModel.navigationVisible.value.first) {
- binding.navigationView.setVisible(visible = false, gone = false)
- binding.statusBarShade.setVisible(visible = false, gone = false)
- }
-
- homeViewModel.navigationVisible.collect(this) { showNavigation(it.first, it.second) }
homeViewModel.statusBarShadeVisible.collect(this) { showStatusBarShade(it) }
homeViewModel.contentToInstall.collect(
this,
@@ -175,8 +155,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
fun finishSetup(navController: NavController) {
navController.navigate(R.id.action_firstTimeSetupFragment_to_gamesFragment)
- (binding.navigationView as NavigationBarView).setupWithNavController(navController)
- showNavigation(visible = true, animated = true)
}
private fun setUpNavigation(navController: NavController) {
@@ -186,62 +164,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
if (firstTimeSetup && !homeViewModel.navigatedToSetup) {
navController.navigate(R.id.firstTimeSetupFragment)
homeViewModel.navigatedToSetup = true
- } else {
- (binding.navigationView as NavigationBarView).setupWithNavController(navController)
- }
- }
-
- private fun showNavigation(visible: Boolean, animated: Boolean) {
- if (!animated) {
- binding.navigationView.setVisible(visible)
- return
}
-
- val smallLayout = resources.getBoolean(R.bool.small_layout)
- binding.navigationView.animate().apply {
- if (visible) {
- binding.navigationView.setVisible(true)
- duration = 300
- interpolator = PathInterpolator(0.05f, 0.7f, 0.1f, 1f)
-
- if (smallLayout) {
- binding.navigationView.translationY =
- binding.navigationView.height.toFloat() * 2
- translationY(0f)
- } else {
- if (ViewCompat.getLayoutDirection(binding.navigationView) ==
- ViewCompat.LAYOUT_DIRECTION_LTR
- ) {
- binding.navigationView.translationX =
- binding.navigationView.width.toFloat() * -2
- translationX(0f)
- } else {
- binding.navigationView.translationX =
- binding.navigationView.width.toFloat() * 2
- translationX(0f)
- }
- }
- } else {
- duration = 300
- interpolator = PathInterpolator(0.3f, 0f, 0.8f, 0.15f)
-
- if (smallLayout) {
- translationY(binding.navigationView.height.toFloat() * 2)
- } else {
- if (ViewCompat.getLayoutDirection(binding.navigationView) ==
- ViewCompat.LAYOUT_DIRECTION_LTR
- ) {
- translationX(binding.navigationView.width.toFloat() * -2)
- } else {
- translationX(binding.navigationView.width.toFloat() * 2)
- }
- }
- }
- }.withEndAction {
- if (!visible) {
- binding.navigationView.setVisible(visible = false, gone = false)
- }
- }.start()
}
private fun showStatusBarShade(visible: Boolean) {
@@ -254,7 +177,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
interpolator = PathInterpolator(0.05f, 0.7f, 0.1f, 1f)
} else {
duration = 300
- translationY(binding.navigationView.height.toFloat() * -2)
+ translationY(binding.statusBarShade.height.toFloat() * -2)
interpolator = PathInterpolator(0.3f, 0f, 0.8f, 0.15f)
}
}.withEndAction {
@@ -299,7 +222,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
}
- fun processGamesDir(result: Uri) {
+ fun processGamesDir(result: Uri, calledFromGameFragment: Boolean = false) {
contentResolver.takePersistableUriPermission(
result,
Intent.FLAG_GRANT_READ_URI_PERMISSION
@@ -316,7 +239,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return
}
- AddGameFolderDialogFragment.newInstance(uriString)
+ AddGameFolderDialogFragment.newInstance(uriString, calledFromGameFragment)
.show(supportFragmentManager, AddGameFolderDialogFragment.TAG)
}
diff --git a/src/android/app/src/main/res/drawable/ic_eye.xml b/src/android/app/src/main/res/drawable/ic_eye.xml
new file mode 100644
index 0000000000..63afd1ba4f
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_eye.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="#000000"
+ android:pathData="M12,4.5C7.05,4.5 2.73,7.61 1,12c1.73,4.39 6.05,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6.05,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4z"/>
+</vector> \ No newline at end of file
diff --git a/src/android/app/src/main/res/drawable/ic_filter.xml b/src/android/app/src/main/res/drawable/ic_filter.xml
new file mode 100644
index 0000000000..ad0a048f51
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_filter.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="M10,18h4v-2h-4v2zM3,6v2h18L21,6L3,6zM6,13h12v-2L6,11v2z"/>
+</vector> \ No newline at end of file
diff --git a/src/android/app/src/main/res/layout-w600dp/activity_main.xml b/src/android/app/src/main/res/layout-w600dp/activity_main.xml
index 74bee872e9..d4188e7c9d 100644
--- a/src/android/app/src/main/res/layout-w600dp/activity_main.xml
+++ b/src/android/app/src/main/res/layout-w600dp/activity_main.xml
@@ -21,18 +21,6 @@
app:navGraph="@navigation/home_navigation"
tools:layout="@layout/fragment_games" />
- <com.google.android.material.navigationrail.NavigationRailView
- android:id="@+id/navigation_view"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:visibility="invisible"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- app:labelVisibilityMode="selected"
- app:menu="@menu/menu_navigation"
- tools:visibility="visible" />
-
<View
android:id="@+id/status_bar_shade"
android:layout_width="0dp"
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 5f59138607..8adb35ee46 100644
--- a/src/android/app/src/main/res/layout/activity_main.xml
+++ b/src/android/app/src/main/res/layout/activity_main.xml
@@ -21,19 +21,6 @@
app:navGraph="@navigation/home_navigation"
tools:layout="@layout/fragment_games" />
- <com.google.android.material.bottomnavigation.BottomNavigationView
- android:id="@+id/navigation_view"
- android:background="?attr/colorSurface"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:visibility="invisible"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:menu="@menu/menu_navigation"
- app:labelVisibilityMode="selected"
- tools:visibility="visible" />
-
<View
android:id="@+id/status_bar_shade"
android:layout_width="0dp"
diff --git a/src/android/app/src/main/res/layout/card_game.xml b/src/android/app/src/main/res/layout/card_game_grid.xml
index 5ae5afd32e..31fed597f4 100644
--- a/src/android/app/src/main/res/layout/card_game.xml
+++ b/src/android/app/src/main/res/layout/card_game_grid.xml
@@ -6,7 +6,7 @@
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
- android:id="@+id/card_game"
+ android:id="@+id/card_game_grid"
style="?attr/materialCardViewElevatedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/src/android/app/src/main/res/layout/card_game_list.xml b/src/android/app/src/main/res/layout/card_game_list.xml
new file mode 100644
index 0000000000..bbb1000628
--- /dev/null
+++ b/src/android/app/src/main/res/layout/card_game_list.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.google.android.material.card.MaterialCardView 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/card_game_list"
+ style="?attr/materialCardViewStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="8dp"
+ android:background="?attr/selectableItemBackground"
+ android:clickable="true"
+ android:focusable="true"
+ android:transitionName="card_game"
+ app:cardCornerRadius="14dp"
+ app:cardElevation="0dp">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="8dp">
+
+ <com.google.android.material.imageview.ShapeableImageView
+ android:id="@+id/image_game_screen"
+ android:layout_width="66dp"
+ android:layout_height="66dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:shapeAppearance="@style/ShapeAppearance.Material3.Corner.Medium"
+ tools:src="@drawable/default_icon" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/text_game_title"
+ style="@style/TextAppearance.Material3.TitleMedium"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ android:requiresFadingEdge="horizontal"
+ android:textAlignment="viewStart"
+ android:singleLine="true"
+ android:textSize="14sp"
+ app:layout_constraintBottom_toTopOf="@+id/text_game_developer"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/image_game_screen"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_chainStyle="packed"
+ tools:text="The Legend of Zelda: Skyward Sword" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/text_game_developer"
+ style="@style/TextAppearance.Material3.BodySmall"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ android:requiresFadingEdge="horizontal"
+ android:textAlignment="viewStart"
+ android:singleLine="true"
+ android:textSize="12sp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/image_game_screen"
+ app:layout_constraintTop_toBottomOf="@+id/text_game_title"
+ tools:text="Generic" />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+</com.google.android.material.card.MaterialCardView>
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 cc280b1ffd..a3747b21da 100644
--- a/src/android/app/src/main/res/layout/fragment_games.xml
+++ b/src/android/app/src/main/res/layout/fragment_games.xml
@@ -1,35 +1,224 @@
-<?xml version="1.0" encoding="utf-8"?>
-<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+<androidx.constraintlayout.widget.ConstraintLayout
+
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/swipe_refresh"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface"
- android:clipToPadding="false">
+ >
- <RelativeLayout
+ <LinearLayout
+ android:id="@+id/header"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginHorizontal="20dp"
+ app:layout_constraintTop_toTopOf="parent"
+ >
<com.google.android.material.textview.MaterialTextView
- android:id="@+id/notice_text"
- style="@style/TextAppearance.Material3.BodyLarge"
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/app_name"
+ android:textAppearance="@style/TextAppearance.Material3.HeadlineLarge"
+ android:textSize="27sp"
+ android:textStyle="bold"
+ />
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ />
+
+ <com.google.android.material.card.MaterialCardView
+ android:id="@+id/view_button"
+ style="?attr/materialCardViewFilledStyle"
+ android:layout_width="42dp"
+ android:layout_height="42dp"
+ app:cardCornerRadius="21dp"
+ >
+
+ <ImageView
+ android:layout_width="18dp"
+ android:layout_height="18dp"
+ android:layout_gravity="center"
+ android:src="@drawable/ic_eye"
+ app:tint="?attr/colorOnSurfaceVariant"
+ />
+
+ </com.google.android.material.card.MaterialCardView>
+
+ <Space
+ android:layout_width="15dp"
+ android:layout_height="wrap_content"
+ />
+
+ <com.google.android.material.card.MaterialCardView
+ android:id="@+id/filter_button"
+ style="?attr/materialCardViewFilledStyle"
+ android:layout_width="42dp"
+ android:layout_height="42dp"
+ app:cardCornerRadius="21dp"
+ >
+
+ <ImageView
+ android:layout_width="18dp"
+ android:layout_height="18dp"
+ android:layout_gravity="center"
+ android:src="@drawable/ic_filter"
+ app:tint="?attr/colorOnSurfaceVariant"
+ />
+
+ </com.google.android.material.card.MaterialCardView>
+
+ <Space
+ android:layout_width="15dp"
+ android:layout_height="wrap_content"
+ />
+
+ <com.google.android.material.card.MaterialCardView
+ android:id="@+id/settings_button"
+ style="?attr/materialCardViewFilledStyle"
+ android:layout_width="42dp"
+ android:layout_height="42dp"
+ app:cardCornerRadius="21dp"
+ >
+
+ <ImageView
+ android:layout_width="18dp"
+ android:layout_height="18dp"
+ android:layout_gravity="center"
+ android:src="@drawable/ic_settings"
+ app:tint="?attr/colorOnSurfaceVariant"
+ />
+
+ </com.google.android.material.card.MaterialCardView>
+
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/frame_search"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:layout_marginHorizontal="15dp"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/header"
+ >
+
+ <com.google.android.material.card.MaterialCardView
+ android:id="@+id/search_background"
+ style="?attr/materialCardViewFilledStyle"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center"
- android:padding="@dimen/spacing_large"
- android:text="@string/empty_gamelist"
- android:visibility="gone" />
+ android:layout_height="48dp"
+ app:cardCornerRadius="21dp"
+ >
+
+ <LinearLayout
+ android:id="@+id/search_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="18dp"
+ android:layout_marginEnd="42dp"
+ android:orientation="horizontal"
+ >
+
+ <ImageView
+ android:layout_width="21dp"
+ android:layout_height="21dp"
+ android:layout_gravity="center_vertical"
+ android:layout_marginEnd="18dp"
+ 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="18dp"
+ android:layout_height="18dp"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginEnd="18dp"
+ android:background="?attr/selectableItemBackground"
+ android:src="@drawable/ic_clear"
+ android:visibility="invisible"
+ app:tint="?attr/colorOnSurfaceVariant"
+ />
+
+ </com.google.android.material.card.MaterialCardView>
+
+ </FrameLayout>
- <androidx.recyclerview.widget.RecyclerView
- android:id="@+id/grid_games"
+ <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+ android:id="@+id/swipe_refresh"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_marginTop="8dp"
+ app:layout_constraintTop_toBottomOf="@id/frame_search"
+ app:layout_constraintBottom_toBottomOf="parent"
+ >
+
+ <RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:clipToPadding="false"
- android:defaultFocusHighlightEnabled="false"
- tools:listitem="@layout/card_game" />
+ >
+
+ <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"
+ android:visibility="gone"
+ />
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/grid_games"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipToPadding="false"
+ android:scrollbarStyle="outsideOverlay"
+ android:scrollbars="vertical"
+ android:fadeScrollbars="true"
+ android:paddingHorizontal="4dp"
+ android:paddingVertical="4dp"
+ />
+
+
+ </RelativeLayout>
+
+ </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
- </RelativeLayout>
+ <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
+ android:id="@+id/add_directory"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="16dp"
+ android:contentDescription="Select_game_folder"
+ android:text="@string/folder"
+ app:icon="@drawable/ic_cartridge_outline"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ android:textColor="?attr/colorOnPrimaryContainer"
+ app:backgroundTint="?attr/colorPrimaryContainer"
+ app:iconTint="?attr/colorOnPrimaryContainer"
+ />
-</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
+</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file
diff --git a/src/android/app/src/main/res/layout/fragment_search.xml b/src/android/app/src/main/res/layout/fragment_search.xml
deleted file mode 100644
index efdfd7047d..0000000000
--- a/src/android/app/src/main/res/layout/fragment_search.xml
+++ /dev/null
@@ -1,184 +0,0 @@
-<?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:id="@+id/constraint_search"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="?attr/colorSurface"
- android:clipToPadding="false">
-
- <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_marginTop="12dp"
- android:layout_marginHorizontal="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:checkedChip="@id/chip_recently_played"
- 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-w600dp/menu_navigation.xml b/src/android/app/src/main/res/menu-w600dp/menu_navigation.xml
deleted file mode 100644
index dd7698e785..0000000000
--- a/src/android/app/src/main/res/menu-w600dp/menu_navigation.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item
- android:id="@+id/homeSettingsFragment"
- android:icon="@drawable/selector_settings"
- android:title="@string/home_settings" />
-
- <item
- android:id="@+id/searchFragment"
- android:icon="@drawable/ic_search"
- android:title="@string/home_search" />
-
- <item
- android:id="@+id/gamesFragment"
- android:icon="@drawable/selector_cartridge"
- android:title="@string/home_games" />
-
-</menu>
diff --git a/src/android/app/src/main/res/menu/menu_game_filters.xml b/src/android/app/src/main/res/menu/menu_game_filters.xml
new file mode 100644
index 0000000000..e0032da827
--- /dev/null
+++ b/src/android/app/src/main/res/menu/menu_game_filters.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <group android:checkableBehavior="single">
+ <item android:id="@+id/alphabetical"
+ android:title="@string/alphabetical"
+ android:checked="true"/>
+ <item
+ android:id="@+id/filter_recently_played"
+ android:title="@string/search_recently_played" />
+ <item
+ android:id="@+id/filter_recently_added"
+ android:title="@string/search_recently_added" />
+ </group>
+</menu> \ No newline at end of file
diff --git a/src/android/app/src/main/res/menu/menu_game_views.xml b/src/android/app/src/main/res/menu/menu_game_views.xml
new file mode 100644
index 0000000000..18e95bc5ad
--- /dev/null
+++ b/src/android/app/src/main/res/menu/menu_game_views.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <group android:checkableBehavior="single">
+ <item
+ android:id="@+id/view_grid"
+ android:title="@string/view_grid"/>
+ <item
+ android:id="@+id/view_list"
+ android:title="@string/view_list"
+ android:checked="true"/>
+ </group>
+</menu> \ No newline at end of file
diff --git a/src/android/app/src/main/res/menu/menu_navigation.xml b/src/android/app/src/main/res/menu/menu_navigation.xml
deleted file mode 100644
index da128c5a12..0000000000
--- a/src/android/app/src/main/res/menu/menu_navigation.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item
- android:id="@+id/gamesFragment"
- android:icon="@drawable/selector_cartridge"
- 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/selector_settings"
- android:title="@string/home_settings" />
-
-</menu>
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 8e57e866af..c9409875fd 100644
--- a/src/android/app/src/main/res/navigation/home_navigation.xml
+++ b/src/android/app/src/main/res/navigation/home_navigation.xml
@@ -8,6 +8,13 @@
android:id="@+id/gamesFragment"
android:name="org.yuzu.yuzu_emu.ui.GamesFragment"
android:label="PlatformGamesFragment" />
+ <action
+ android:id="@+id/action_gamesFragment_to_homeSettingsFragment"
+ app:destination="@id/homeSettingsFragment"
+ app:enterAnim="@anim/nav_default_enter_anim"
+ app:exitAnim="@anim/nav_default_exit_anim"
+ app:popEnterAnim="@anim/nav_default_pop_enter_anim"
+ app:popExitAnim="@anim/nav_default_pop_exit_anim" />
<fragment
android:id="@+id/homeSettingsFragment"
@@ -42,11 +49,6 @@
</fragment>
<fragment
- android:id="@+id/searchFragment"
- android:name="org.yuzu.yuzu_emu.fragments.SearchFragment"
- android:label="SearchFragment" />
-
- <fragment
android:id="@+id/aboutFragment"
android:name="org.yuzu.yuzu_emu.fragments.AboutFragment"
android:label="AboutFragment" >
diff --git a/src/android/app/src/main/res/values-w600dp/integers.xml b/src/android/app/src/main/res/values-w600dp/integers.xml
index 9975db801a..81f5bf68f7 100644
--- a/src/android/app/src/main/res/values-w600dp/integers.xml
+++ b/src/android/app/src/main/res/values-w600dp/integers.xml
@@ -2,5 +2,8 @@
<resources>
<integer name="grid_columns">2</integer>
+ <integer name="game_columns_list">2</integer>
+ <integer name="game_columns_grid">4</integer>
+
</resources>
diff --git a/src/android/app/src/main/res/values/integers.xml b/src/android/app/src/main/res/values/integers.xml
index 1c6f5db93a..5ddc913c41 100644
--- a/src/android/app/src/main/res/values/integers.xml
+++ b/src/android/app/src/main/res/values/integers.xml
@@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="grid_columns">1</integer>
+ <integer name="game_columns_list">1</integer>
+ <integer name="game_columns_grid">2</integer>
<!-- Default SWITCH landscape layout -->
<integer name="BUTTON_A_X">760</integer>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 6f0b06d596..c4ae05860e 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -2,7 +2,7 @@
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<!-- General application strings -->
- <string name="app_name" translatable="false">eden</string>
+ <string name="app_name" translatable="false">Eden</string>
<string name="app_disclaimer">This software will run games for the Nintendo Switch game console. No game titles or keys are included.&lt;br /&gt;&lt;br /&gt;Before you begin, please locate your <![CDATA[<b> prod.keys </b>]]> file on your device storage.&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href="https://yuzu-emu.org/help/quickstart">Learn more</a>]]></string>
<string name="notice_notification_channel_name">Notices and errors</string>
<string name="notice_notification_channel_id" translatable="false">noticesAndErrors</string>
@@ -28,6 +28,11 @@
<string name="step_complete">Complete!</string>
<!-- Home strings -->
+ <string name="alphabetical">Alphabetical</string>
+ <string name="view_list">List</string>
+ <string name="view_grid">Grid</string>
+ <string name="folder">Folder</string>
+ <string name="add_directory_success">"New game directory added successfully "</string>
<string name="home_games">Games</string>
<string name="home_search">Search</string>
<string name="home_settings">Settings</string>