android: MainActivity overhaul
This moves several parts of the main activity into fragments that manage themselves to react to changes. UI changes like the appearance of a new search view or when the games list changes now gets updated via multiple view models. This also starts a conversion to the androidx navigation component which furthers the goals mentioned previously with more fragment responsibility. This will eventually allow us to use one activity with interchanging fragments and multiple view models that are stored within that central activity. fdasmaster
parent
859c40f00e
commit
233ae9ab69
@ -0,0 +1,55 @@
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
|
||||
import org.yuzu.yuzu_emu.model.HomeOption
|
||||
|
||||
class HomeOptionAdapter(private val activity: AppCompatActivity, var options: List<HomeOption>) :
|
||||
RecyclerView.Adapter<HomeOptionAdapter.HomeOptionViewHolder>(),
|
||||
View.OnClickListener {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder {
|
||||
val binding = CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
binding.root.setOnClickListener(this)
|
||||
return HomeOptionViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return options.size
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: HomeOptionViewHolder, position: Int) {
|
||||
holder.bind(options[position])
|
||||
}
|
||||
|
||||
override fun onClick(view: View) {
|
||||
val holder = view.tag as HomeOptionViewHolder
|
||||
holder.option.onClick.invoke()
|
||||
}
|
||||
|
||||
inner class HomeOptionViewHolder(val binding: CardHomeOptionBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
lateinit var option: HomeOption
|
||||
|
||||
init {
|
||||
itemView.tag = this
|
||||
}
|
||||
|
||||
fun bind(option: HomeOption) {
|
||||
this.option = option
|
||||
binding.optionTitle.text = activity.resources.getString(option.titleId)
|
||||
binding.optionDescription.text = activity.resources.getString(option.descriptionId)
|
||||
binding.optionIcon.setImageDrawable(
|
||||
ResourcesCompat.getDrawable(
|
||||
activity.resources,
|
||||
option.iconId,
|
||||
activity.theme
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,281 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.adapters.HomeOptionAdapter
|
||||
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentOptionsBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeOption
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||
import org.yuzu.yuzu_emu.utils.GameHelper
|
||||
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||
import java.io.IOException
|
||||
|
||||
class OptionsFragment : Fragment() {
|
||||
private var _binding: FragmentOptionsBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val gamesViewModel: GamesViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentOptionsBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val optionsList: List<HomeOption> = listOf(
|
||||
HomeOption(
|
||||
R.string.add_games,
|
||||
R.string.add_games_description,
|
||||
R.drawable.ic_add
|
||||
) { getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) },
|
||||
HomeOption(
|
||||
R.string.install_prod_keys,
|
||||
R.string.install_prod_keys_description,
|
||||
R.drawable.ic_unlock
|
||||
) { getProdKey.launch(arrayOf("*/*")) },
|
||||
HomeOption(
|
||||
R.string.install_amiibo_keys,
|
||||
R.string.install_amiibo_keys_description,
|
||||
R.drawable.ic_nfc
|
||||
) { getAmiiboKey.launch(arrayOf("*/*")) },
|
||||
HomeOption(
|
||||
R.string.install_gpu_driver,
|
||||
R.string.install_gpu_driver_description,
|
||||
R.drawable.ic_input
|
||||
) { driverInstaller() },
|
||||
HomeOption(
|
||||
R.string.settings,
|
||||
R.string.settings_description,
|
||||
R.drawable.ic_settings
|
||||
) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") }
|
||||
)
|
||||
|
||||
binding.optionsList.apply {
|
||||
layoutManager = LinearLayoutManager(requireContext())
|
||||
adapter = HomeOptionAdapter(requireActivity() as AppCompatActivity, optionsList)
|
||||
}
|
||||
|
||||
requireActivity().window.statusBarColor = ThemeHelper.getColorWithOpacity(
|
||||
MaterialColors.getColor(
|
||||
binding.root,
|
||||
R.attr.colorSurface
|
||||
), ThemeHelper.SYSTEM_BAR_ALPHA
|
||||
)
|
||||
|
||||
setInsets()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
private fun driverInstaller() {
|
||||
// Get the driver name for the dialog message.
|
||||
var driverName = GpuDriverHelper.customDriverName
|
||||
if (driverName == null) {
|
||||
driverName = getString(R.string.system_gpu_driver)
|
||||
}
|
||||
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(getString(R.string.select_gpu_driver_title))
|
||||
.setMessage(driverName)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.select_gpu_driver_default) { _: DialogInterface?, _: Int ->
|
||||
GpuDriverHelper.installDefaultDriver(requireContext())
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.select_gpu_driver_use_default,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
.setNeutralButton(R.string.select_gpu_driver_install) { _: DialogInterface?, _: Int ->
|
||||
getDriver.launch(arrayOf("application/zip"))
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.scrollViewOptions) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
view.setPadding(
|
||||
insets.left,
|
||||
insets.top,
|
||||
insets.right,
|
||||
insets.bottom + resources.getDimensionPixelSize(R.dimen.spacing_navigation)
|
||||
)
|
||||
windowInsets
|
||||
}
|
||||
|
||||
private val getGamesDirectory =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
|
||||
if (result == null)
|
||||
return@registerForActivityResult
|
||||
|
||||
val takeFlags =
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
requireActivity().contentResolver.takePersistableUriPermission(
|
||||
result,
|
||||
takeFlags
|
||||
)
|
||||
|
||||
// When a new directory is picked, we currently will reset the existing games
|
||||
// database. This effectively means that only one game directory is supported.
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit()
|
||||
.putString(GameHelper.KEY_GAME_PATH, result.toString())
|
||||
.apply()
|
||||
|
||||
gamesViewModel.reloadGames(true)
|
||||
}
|
||||
|
||||
private val getProdKey =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
if (result == null)
|
||||
return@registerForActivityResult
|
||||
|
||||
val takeFlags =
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
requireActivity().contentResolver.takePersistableUriPermission(
|
||||
result,
|
||||
takeFlags
|
||||
)
|
||||
|
||||
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
|
||||
if (FileUtil.copyUriToInternalStorage(requireContext(), result, dstPath, "prod.keys")) {
|
||||
if (NativeLibrary.reloadKeys()) {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.install_keys_success,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
gamesViewModel.reloadGames(true)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.install_keys_failure,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val getAmiiboKey =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
if (result == null)
|
||||
return@registerForActivityResult
|
||||
|
||||
val takeFlags =
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
requireActivity().contentResolver.takePersistableUriPermission(
|
||||
result,
|
||||
takeFlags
|
||||
)
|
||||
|
||||
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
|
||||
if (FileUtil.copyUriToInternalStorage(
|
||||
requireContext(),
|
||||
result,
|
||||
dstPath,
|
||||
"key_retail.bin"
|
||||
)
|
||||
) {
|
||||
if (NativeLibrary.reloadKeys()) {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.install_keys_success,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.install_amiibo_keys_failure,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val getDriver =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
if (result == null)
|
||||
return@registerForActivityResult
|
||||
|
||||
val takeFlags =
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
requireActivity().contentResolver.takePersistableUriPermission(
|
||||
result,
|
||||
takeFlags
|
||||
)
|
||||
|
||||
val progressBinding = DialogProgressBarBinding.inflate(layoutInflater)
|
||||
progressBinding.progressBar.isIndeterminate = true
|
||||
val installationDialog = MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.installing_driver)
|
||||
.setView(progressBinding.root)
|
||||
.show()
|
||||
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
// Ignore file exceptions when a user selects an invalid zip
|
||||
try {
|
||||
GpuDriverHelper.installCustomDriver(requireContext(), result)
|
||||
} catch (_: IOException) {
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
installationDialog.dismiss()
|
||||
|
||||
val driverName = GpuDriverHelper.customDriverName
|
||||
if (driverName != null) {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
getString(
|
||||
R.string.select_gpu_driver_install_success,
|
||||
driverName
|
||||
),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.select_gpu_driver_error,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +1,58 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.model
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.utils.GameHelper
|
||||
|
||||
class GamesViewModel : ViewModel() {
|
||||
private val _games = MutableLiveData<ArrayList<Game>>()
|
||||
val games: LiveData<ArrayList<Game>> get() = _games
|
||||
private val _games = MutableLiveData<List<Game>>(emptyList())
|
||||
val games: LiveData<List<Game>> get() = _games
|
||||
|
||||
private val _searchedGames = MutableLiveData<List<Game>>(emptyList())
|
||||
val searchedGames: LiveData<List<Game>> get() = _searchedGames
|
||||
|
||||
private val _isReloading = MutableLiveData(false)
|
||||
val isReloading: LiveData<Boolean> get() = _isReloading
|
||||
|
||||
private val _shouldSwapData = MutableLiveData(false)
|
||||
val shouldSwapData: LiveData<Boolean> get() = _shouldSwapData
|
||||
|
||||
init {
|
||||
_games.value = ArrayList()
|
||||
reloadGames(false)
|
||||
}
|
||||
|
||||
fun setGames(games: ArrayList<Game>) {
|
||||
_games.value = games
|
||||
fun setSearchedGames(games: List<Game>) {
|
||||
_searchedGames.postValue(games)
|
||||
}
|
||||
|
||||
fun setShouldSwapData(shouldSwap: Boolean) {
|
||||
_shouldSwapData.postValue(shouldSwap)
|
||||
}
|
||||
|
||||
fun reloadGames(directoryChanged: Boolean) {
|
||||
if (isReloading.value == true)
|
||||
return
|
||||
_isReloading.postValue(true)
|
||||
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
NativeLibrary.resetRomMetadata()
|
||||
_games.postValue(GameHelper.getGames())
|
||||
_isReloading.postValue(false)
|
||||
|
||||
if (directoryChanged) {
|
||||
setShouldSwapData(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.model
|
||||
|
||||
data class HomeOption(
|
||||
val titleId: Int,
|
||||
val descriptionId: Int,
|
||||
val iconId: Int,
|
||||
val onClick: () -> Unit
|
||||
)
|
@ -0,0 +1,17 @@
|
||||
package org.yuzu.yuzu_emu.model
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
class HomeViewModel : ViewModel() {
|
||||
private val _navigationVisible = MutableLiveData(true)
|
||||
val navigationVisible: LiveData<Boolean> get() = _navigationVisible
|
||||
|
||||
fun setNavigationVisible(visible: Boolean) {
|
||||
if (_navigationVisible.value == visible) {
|
||||
return
|
||||
}
|
||||
_navigationVisible.value = visible
|
||||
}
|
||||
}
|
@ -0,0 +1,220 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
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 com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.search.SearchView
|
||||
import com.google.android.material.search.SearchView.TransitionState
|
||||
import info.debatty.java.stringsimilarity.Jaccard
|
||||
import org.yuzu.yuzu_emu.R
|
||||
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.utils.ThemeHelper
|
||||
import java.util.Locale
|
||||
|
||||
class GamesFragment : Fragment() {
|
||||
private var _binding: FragmentGamesBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val gamesViewModel: GamesViewModel by activityViewModels()
|
||||
private val homeViewModel: HomeViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentGamesBinding.inflate(inflater)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.gridGames.apply {
|
||||
layoutManager = AutofitGridLayoutManager(
|
||||
requireContext(),
|
||||
requireContext().resources.getDimensionPixelSize(R.dimen.card_width)
|
||||
)
|
||||
adapter = GameAdapter(requireActivity() as AppCompatActivity)
|
||||
}
|
||||
setUpSearch()
|
||||
|
||||
// Add swipe down to refresh gesture
|
||||
binding.swipeRefresh.setOnRefreshListener {
|
||||
gamesViewModel.reloadGames(false)
|
||||
}
|
||||
|
||||
// Set theme color to the refresh animation's background
|
||||
binding.swipeRefresh.setProgressBackgroundColorSchemeColor(
|
||||
MaterialColors.getColor(binding.swipeRefresh, R.attr.colorPrimary)
|
||||
)
|
||||
binding.swipeRefresh.setColorSchemeColors(
|
||||
MaterialColors.getColor(binding.swipeRefresh, R.attr.colorOnPrimary)
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
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)
|
||||
gamesViewModel.setShouldSwapData(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
setInsets()
|
||||
|
||||
// Make sure the loading indicator appears even if the layout is told to refresh before being fully drawn
|
||||
binding.swipeRefresh.post {
|
||||
binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value!!
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
private fun searchShown() {
|
||||
homeViewModel.setNavigationVisible(false)
|
||||
requireActivity().window.statusBarColor =
|
||||
ContextCompat.getColor(requireContext(), android.R.color.transparent)
|
||||
}
|
||||
|
||||
private fun searchHidden() {
|
||||
homeViewModel.setNavigationVisible(true)
|
||||
requireActivity().window.statusBarColor = ThemeHelper.getColorWithOpacity(
|
||||
MaterialColors.getColor(
|
||||
binding.root,
|
||||
R.attr.colorSurface
|
||||
), ThemeHelper.SYSTEM_BAR_ALPHA
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.gridGames) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med)
|
||||
|
||||
view.setPadding(
|
||||
insets.left,
|
||||
insets.top + resources.getDimensionPixelSize(R.dimen.spacing_search),
|
||||
insets.right,
|
||||
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(
|
||||
false,
|
||||
insets.top + resources.getDimensionPixelSize(R.dimen.spacing_refresh_start),
|
||||
insets.top + resources.getDimensionPixelSize(R.dimen.spacing_refresh_end)
|
||||
)
|
||||
|
||||
windowInsets
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.ui.main
|
||||
|
||||
import org.yuzu.yuzu_emu.BuildConfig
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
|
||||
class MainPresenter(private val view: MainView) {
|
||||
fun onCreate() {
|
||||
val versionName = BuildConfig.VERSION_NAME
|
||||
view.setVersionString(versionName)
|
||||
}
|
||||
|
||||
private fun launchFileListActivity(request: Int) {
|
||||
view.launchFileListActivity(request)
|
||||
}
|
||||
|
||||
fun handleOptionSelection(itemId: Int): Boolean {
|
||||
when (itemId) {
|
||||
R.id.menu_settings_core -> {
|
||||
view.launchSettingsActivity(SettingsFile.FILE_NAME_CONFIG)
|
||||
return true
|
||||
}
|
||||
R.id.button_add_directory -> {
|
||||
launchFileListActivity(REQUEST_ADD_DIRECTORY)
|
||||
return true
|
||||
}
|
||||
R.id.button_install_keys -> {
|
||||
launchFileListActivity(REQUEST_INSTALL_KEYS)
|
||||
return true
|
||||
}
|
||||
R.id.button_install_amiibo_keys -> {
|
||||
launchFileListActivity(REQUEST_INSTALL_AMIIBO_KEYS)
|
||||
return true
|
||||
}
|
||||
R.id.button_select_gpu_driver -> {
|
||||
launchFileListActivity(REQUEST_SELECT_GPU_DRIVER)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val REQUEST_ADD_DIRECTORY = 1
|
||||
const val REQUEST_INSTALL_KEYS = 2
|
||||
const val REQUEST_INSTALL_AMIIBO_KEYS = 3
|
||||
const val REQUEST_SELECT_GPU_DRIVER = 4
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.ui.main
|
||||
|
||||
/**
|
||||
* Abstraction for the screen that shows on application launch.
|
||||
* Implementations will differ primarily to target touch-screen
|
||||
* or non-touch screen devices.
|
||||
*/
|
||||
interface MainView {
|
||||
/**
|
||||
* Pass the view the native library's version string. Displaying
|
||||
* it is optional.
|
||||
*
|
||||
* @param version A string pulled from native code.
|
||||
*/
|
||||
fun setVersionString(version: String)
|
||||
|
||||
fun launchSettingsActivity(menuTag: String)
|
||||
|
||||
fun launchFileListActivity(request: Int)
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.ui.platform
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.adapters.GameAdapter
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentGridBinding
|
||||
import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.utils.GameHelper
|
||||
|
||||
class PlatformGamesFragment : Fragment() {
|
||||
private var _binding: FragmentGridBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private lateinit var gamesViewModel: GamesViewModel
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentGridBinding.inflate(inflater)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
gamesViewModel = ViewModelProvider(requireActivity())[GamesViewModel::class.java]
|
||||
|
||||
binding.gridGames.apply {
|
||||
layoutManager = AutofitGridLayoutManager(
|
||||
requireContext(),
|
||||
requireContext().resources.getDimensionPixelSize(R.dimen.card_width)
|
||||
)
|
||||
adapter =
|
||||
GameAdapter(requireActivity() as AppCompatActivity, gamesViewModel.games.value!!)
|
||||
}
|
||||
|
||||
// Add swipe down to refresh gesture
|
||||
binding.swipeRefresh.setOnRefreshListener {
|
||||
refresh()
|
||||
binding.swipeRefresh.isRefreshing = false
|
||||
}
|
||||
|
||||
// Set theme color to the refresh animation's background
|
||||
binding.swipeRefresh.setProgressBackgroundColorSchemeColor(
|
||||
MaterialColors.getColor(binding.swipeRefresh, R.attr.colorPrimary)
|
||||
)
|
||||
binding.swipeRefresh.setColorSchemeColors(
|
||||
MaterialColors.getColor(binding.swipeRefresh, R.attr.colorOnPrimary)
|
||||
)
|
||||
|
||||
gamesViewModel.games.observe(viewLifecycleOwner) {
|
||||
(binding.gridGames.adapter as GameAdapter).swapData(it)
|
||||
updateTextView()
|
||||
}
|
||||
|
||||
setInsets()
|
||||
|
||||
refresh()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
refresh()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
gamesViewModel.setGames(GameHelper.getGames())
|
||||
updateTextView()
|
||||
}
|
||||
|
||||
private fun updateTextView() {
|
||||
if (_binding == null)
|
||||
return
|
||||
|
||||
binding.gamelistEmptyText.visibility =
|
||||
if ((binding.gridGames.adapter as GameAdapter).itemCount == 0) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
private fun setInsets() {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.gridGames) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
view.updatePadding(bottom = insets.bottom)
|
||||
windowInsets
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "PlatformGamesFragment"
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import androidx.preference.PreferenceManager
|
||||
import android.text.Html
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||
import org.yuzu.yuzu_emu.ui.main.MainPresenter
|
||||
|
||||
object StartupHandler {
|
||||
private val preferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
|
||||
private fun handleStartupPromptDismiss(parent: MainActivity) {
|
||||
parent.launchFileListActivity(MainPresenter.REQUEST_INSTALL_KEYS)
|
||||
}
|
||||
|
||||
private fun markFirstBoot() {
|
||||
preferences.edit()
|
||||
.putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false)
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun handleInit(parent: MainActivity) {
|
||||
if (preferences.getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true)) {
|
||||
markFirstBoot()
|
||||
val alert = MaterialAlertDialogBuilder(parent)
|
||||
.setMessage(Html.fromHtml(parent.resources.getString(R.string.app_disclaimer)))
|
||||
.setTitle(R.string.app_name)
|
||||
.setIcon(R.drawable.ic_launcher)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.setOnDismissListener {
|
||||
handleStartupPromptDismiss(parent)
|
||||
}
|
||||
.show()
|
||||
(alert.findViewById<View>(android.R.id.message) as TextView?)!!.movementMethod =
|
||||
LinkMovementMethod.getInstance()
|
||||
}
|
||||
}
|
||||
}
|
@ -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,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
||||
</vector>
|
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M21,3.01H3c-1.1,0 -2,0.9 -2,2V9h2V4.99h18v14.03H3V15H1v4.01c0,1.1 0.9,1.98 2,1.98h18c1.1,0 2,-0.88 2,-1.98v-14c0,-1.11 -0.9,-2 -2,-2zM11,16l4,-4 -4,-4v3H1v2h10v3z" />
|
||||
</vector>
|
@ -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="M20,2L4,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM20,20L4,20L4,4h16v16zM18,6h-5c-1.1,0 -2,0.9 -2,2v2.28c-0.6,0.35 -1,0.98 -1,1.72 0,1.1 0.9,2 2,2s2,-0.9 2,-2c0,-0.74 -0.4,-1.38 -1,-1.72L13,8h3v8L8,16L8,8h2L10,6L6,6v12h12L18,6z" />
|
||||
</vector>
|
@ -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="M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z" />
|
||||
</vector>
|
@ -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="M12,17c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6h1.9c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM18,20L6,20L6,10h12v10z" />
|
||||
</vector>
|
@ -0,0 +1,18 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="614.697dp"
|
||||
android:height="683dp"
|
||||
android:viewportWidth="614.4"
|
||||
android:viewportHeight="682.67">
|
||||
<group>
|
||||
<clip-path android:pathData="M-43,-46.67h699.6v777.33h-699.6z" />
|
||||
<path
|
||||
android:fillColor="?attr/colorPrimary"
|
||||
android:pathData="M340.81,138V682.08c150.26,0 272.06,-121.81 272.06,-272.06S491.07,138 340.81,138M394,197.55a219.06,219.06 0,0 1,0 424.94V197.55" />
|
||||
</group>
|
||||
<group>
|
||||
<clip-path android:pathData="M-43,-46.67h699.6v777.33h-699.6z" />
|
||||
<path
|
||||
android:fillColor="?attr/colorPrimary"
|
||||
android:pathData="M272.79,1.92C122.53,1.92 0.73,123.73 0.73,274s121.8,272.07 272.06,272.07ZM219.65,61.51v425A219,219 0,0 1,118 119.18,217.51 217.51,0 0,1 219.65,61.51" />
|
||||
</group>
|
||||
</vector>
|
@ -1,28 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<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/coordinator_main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar_main"
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragment_container"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:defaultNavHost="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:navGraph="@navigation/home_navigation"
|
||||
tools:layout="@layout/fragment_games" />
|
||||
|
||||
<com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
android:id="@+id/navigation_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
app:liftOnScrollTargetViewId="@id/grid_games">
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:menu="@menu/menu_navigation" />
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar_main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/games_platform_frame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
@ -0,0 +1,53 @@
|
||||
<?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"
|
||||
style="?attr/materialCardViewFilledStyle"
|
||||
android:id="@+id/option_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="8dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/option_icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="28dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
app:tint="?attr/colorPrimary" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
style="@style/TextAppearance.Material3.BodyMedium"
|
||||
android:id="@+id/option_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="@string/install_prod_keys" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
style="@style/TextAppearance.Material3.BodySmall"
|
||||
android:id="@+id/option_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="@string/install_prod_keys_description" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
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:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/colorSurface">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipe_refresh"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
app:layout_behavior="@string/searchbar_scrolling_view_behavior">
|
||||
|
||||
<RelativeLayout
|
||||
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"
|
||||
app:liftOnScrollTargetViewId="@id/grid_games">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<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" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</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">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/grid_search"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
tools:listitem="@layout/card_game" />
|
||||
|
||||
</com.google.android.material.search.SearchView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -1,37 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipe_refresh"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gamelist_empty_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:text="@string/empty_gamelist"
|
||||
android:textSize="18sp"
|
||||
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"
|
||||
tools:listitem="@layout/card_game" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
</FrameLayout>
|
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/scroll_view_options"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/colorSurface"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="?attr/colorSurface">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="128dp"
|
||||
android:layout_margin="64dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:src="@drawable/ic_yuzu_themed" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/options_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
@ -1,47 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/button_file_menu"
|
||||
android:icon="@drawable/ic_folder"
|
||||
android:title="@string/select_game_folder"
|
||||
app:showAsAction="ifRoom">
|
||||
|
||||
<menu>
|
||||
|
||||
<item
|
||||
android:id="@+id/button_add_directory"
|
||||
android:icon="@drawable/ic_folder"
|
||||
android:title="@string/select_game_folder"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/button_install_keys"
|
||||
android:icon="@drawable/ic_install"
|
||||
android:title="@string/install_keys"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/button_install_amiibo_keys"
|
||||
android:icon="@drawable/ic_install"
|
||||
android:title="@string/install_amiibo_keys"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/button_select_gpu_driver"
|
||||
android:icon="@drawable/ic_settings"
|
||||
android:title="@string/select_gpu_driver"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
</menu>
|
||||
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_settings_core"
|
||||
android:icon="@drawable/ic_settings"
|
||||
android:title="@string/grid_menu_core_settings"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
</menu>
|
@ -0,0 +1,14 @@
|
||||
<?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/ic_controller"
|
||||
android:title="@string/home_games" />
|
||||
|
||||
<item
|
||||
android:id="@+id/optionsFragment"
|
||||
android:icon="@drawable/ic_options"
|
||||
android:title="@string/home_options" />
|
||||
|
||||
</menu>
|
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/home_navigation"
|
||||
app:startDestination="@id/gamesFragment">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/gamesFragment"
|
||||
android:name="org.yuzu.yuzu_emu.ui.GamesFragment"
|
||||
android:label="PlatformGamesFragment" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/optionsFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.OptionsFragment"
|
||||
android:label="OptionsFragment" />
|
||||
|
||||
</navigation>
|
Loading…
Reference in New Issue