Compare commits

..

1 Commits

Author SHA1 Message Date
bda8a00421 Android #70 2023-09-14 00:57:22 +00:00
34 changed files with 382 additions and 596 deletions

View File

@ -10,12 +10,8 @@ import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
@ -90,11 +86,7 @@ class HomeSettingAdapter(
binding.optionIcon.alpha = 0.5f binding.optionIcon.alpha = 0.5f
} }
viewLifecycle.lifecycleScope.launch { option.details.observe(viewLifecycle) { updateOptionDetails(it) }
viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
option.details.collect { updateOptionDetails(it) }
}
}
binding.optionDetail.postDelayed( binding.optionDetail.postDelayed(
{ {
binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE

View File

@ -80,17 +80,6 @@ object Settings {
const val SECTION_THEME = "Theme" const val SECTION_THEME = "Theme"
const val SECTION_DEBUG = "Debug" const val SECTION_DEBUG = "Debug"
enum class MenuTag(val titleId: Int) {
SECTION_ROOT(R.string.advanced_settings),
SECTION_GENERAL(R.string.preferences_general),
SECTION_SYSTEM(R.string.preferences_system),
SECTION_RENDERER(R.string.preferences_graphics),
SECTION_AUDIO(R.string.preferences_audio),
SECTION_CPU(R.string.cpu),
SECTION_THEME(R.string.preferences_theme),
SECTION_DEBUG(R.string.preferences_debug);
}
const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
const val PREF_OVERLAY_VERSION = "OverlayVersion" const val PREF_OVERLAY_VERSION = "OverlayVersion"

View File

@ -3,12 +3,10 @@
package org.yuzu.yuzu_emu.features.settings.model.view package org.yuzu.yuzu_emu.features.settings.model.view
import org.yuzu.yuzu_emu.features.settings.model.Settings
class SubmenuSetting( class SubmenuSetting(
titleId: Int, titleId: Int,
descriptionId: Int, descriptionId: Int,
val menuKey: Settings.MenuTag val menuKey: String
) : SettingsItem(emptySetting, titleId, descriptionId) { ) : SettingsItem(emptySetting, titleId, descriptionId) {
override val type = TYPE_SUBMENU override val type = TYPE_SUBMENU
} }

View File

@ -13,14 +13,9 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.navArgs import androidx.navigation.navArgs
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import java.io.IOException import java.io.IOException
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
@ -71,39 +66,25 @@ class SettingsActivity : AppCompatActivity() {
) )
} }
lifecycleScope.apply { settingsViewModel.shouldRecreate.observe(this) {
launch { if (it) {
repeatOnLifecycle(Lifecycle.State.CREATED) { settingsViewModel.setShouldRecreate(false)
settingsViewModel.shouldRecreate.collectLatest { recreate()
if (it) {
settingsViewModel.setShouldRecreate(false)
recreate()
}
}
}
} }
launch { }
repeatOnLifecycle(Lifecycle.State.CREATED) { settingsViewModel.shouldNavigateBack.observe(this) {
settingsViewModel.shouldNavigateBack.collectLatest { if (it) {
if (it) { settingsViewModel.setShouldNavigateBack(false)
settingsViewModel.setShouldNavigateBack(false) navigateBack()
navigateBack()
}
}
}
} }
launch { }
repeatOnLifecycle(Lifecycle.State.CREATED) { settingsViewModel.shouldShowResetSettingsDialog.observe(this) {
settingsViewModel.shouldShowResetSettingsDialog.collectLatest { if (it) {
if (it) { settingsViewModel.setShouldShowResetSettingsDialog(false)
settingsViewModel.setShouldShowResetSettingsDialog(false) ResetSettingsDialogFragment().show(
ResetSettingsDialogFragment().show( supportFragmentManager,
supportFragmentManager, ResetSettingsDialogFragment.TAG
ResetSettingsDialogFragment.TAG )
)
}
}
}
} }
} }

View File

@ -3,7 +3,6 @@
package org.yuzu.yuzu_emu.features.settings.ui package org.yuzu.yuzu_emu.features.settings.ui
import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -14,19 +13,14 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.divider.MaterialDividerItemDecoration import com.google.android.material.divider.MaterialDividerItemDecoration
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.SettingsViewModel import org.yuzu.yuzu_emu.model.SettingsViewModel
class SettingsFragment : Fragment() { class SettingsFragment : Fragment() {
@ -57,17 +51,15 @@ class SettingsFragment : Fragment() {
return binding.root return binding.root
} }
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
settingsAdapter = SettingsAdapter(this, requireContext()) settingsAdapter = SettingsAdapter(this, requireContext())
presenter = SettingsFragmentPresenter( presenter = SettingsFragmentPresenter(
settingsViewModel, settingsViewModel,
settingsAdapter!!, settingsAdapter!!,
args.menuTag args.menuTag,
args.game?.gameId ?: ""
) )
binding.toolbarSettingsLayout.title = getString(args.menuTag.titleId)
val dividerDecoration = MaterialDividerItemDecoration( val dividerDecoration = MaterialDividerItemDecoration(
requireContext(), requireContext(),
LinearLayoutManager.VERTICAL LinearLayoutManager.VERTICAL
@ -83,31 +75,28 @@ class SettingsFragment : Fragment() {
settingsViewModel.setShouldNavigateBack(true) settingsViewModel.setShouldNavigateBack(true)
} }
viewLifecycleOwner.lifecycleScope.apply { settingsViewModel.toolbarTitle.observe(viewLifecycleOwner) {
launch { if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it
repeatOnLifecycle(Lifecycle.State.CREATED) { }
settingsViewModel.shouldReloadSettingsList.collectLatest {
if (it) { settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) {
settingsViewModel.setShouldReloadSettingsList(false) if (it) {
presenter.loadSettingsList() settingsViewModel.setShouldReloadSettingsList(false)
} presenter.loadSettingsList()
}
}
}
launch {
settingsViewModel.isUsingSearch.collectLatest {
if (it) {
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
} else {
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
}
}
} }
} }
if (args.menuTag == Settings.MenuTag.SECTION_ROOT) { settingsViewModel.isUsingSearch.observe(viewLifecycleOwner) {
if (it) {
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
} else {
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
}
}
if (args.menuTag == SettingsFile.FILE_NAME_CONFIG) {
binding.toolbarSettings.inflateMenu(R.menu.menu_settings) binding.toolbarSettings.inflateMenu(R.menu.menu_settings)
binding.toolbarSettings.setOnMenuItemClickListener { binding.toolbarSettings.setOnMenuItemClickListener {
when (it.itemId) { when (it.itemId) {

View File

@ -6,6 +6,7 @@ package org.yuzu.yuzu_emu.features.settings.ui
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Build import android.os.Build
import android.text.TextUtils
import android.widget.Toast import android.widget.Toast
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
@ -19,13 +20,15 @@ import org.yuzu.yuzu_emu.features.settings.model.LongSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.features.settings.model.view.* import org.yuzu.yuzu_emu.features.settings.model.view.*
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.SettingsViewModel import org.yuzu.yuzu_emu.model.SettingsViewModel
import org.yuzu.yuzu_emu.utils.NativeConfig import org.yuzu.yuzu_emu.utils.NativeConfig
class SettingsFragmentPresenter( class SettingsFragmentPresenter(
private val settingsViewModel: SettingsViewModel, private val settingsViewModel: SettingsViewModel,
private val adapter: SettingsAdapter, private val adapter: SettingsAdapter,
private var menuTag: Settings.MenuTag private var menuTag: String,
private var gameId: String
) { ) {
private var settingsList = ArrayList<SettingsItem>() private var settingsList = ArrayList<SettingsItem>()
@ -50,15 +53,24 @@ class SettingsFragmentPresenter(
} }
fun loadSettingsList() { fun loadSettingsList() {
if (!TextUtils.isEmpty(gameId)) {
settingsViewModel.setToolbarTitle(
context.getString(
R.string.advanced_settings_game,
gameId
)
)
}
val sl = ArrayList<SettingsItem>() val sl = ArrayList<SettingsItem>()
when (menuTag) { when (menuTag) {
Settings.MenuTag.SECTION_ROOT -> addConfigSettings(sl) SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl)
Settings.MenuTag.SECTION_GENERAL -> addGeneralSettings(sl) Settings.SECTION_GENERAL -> addGeneralSettings(sl)
Settings.MenuTag.SECTION_SYSTEM -> addSystemSettings(sl) Settings.SECTION_SYSTEM -> addSystemSettings(sl)
Settings.MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl) Settings.SECTION_RENDERER -> addGraphicsSettings(sl)
Settings.MenuTag.SECTION_AUDIO -> addAudioSettings(sl) Settings.SECTION_AUDIO -> addAudioSettings(sl)
Settings.MenuTag.SECTION_THEME -> addThemeSettings(sl) Settings.SECTION_THEME -> addThemeSettings(sl)
Settings.MenuTag.SECTION_DEBUG -> addDebugSettings(sl) Settings.SECTION_DEBUG -> addDebugSettings(sl)
else -> { else -> {
val context = YuzuApplication.appContext val context = YuzuApplication.appContext
Toast.makeText( Toast.makeText(
@ -74,12 +86,13 @@ class SettingsFragmentPresenter(
} }
private fun addConfigSettings(sl: ArrayList<SettingsItem>) { private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
settingsViewModel.setToolbarTitle(context.getString(R.string.advanced_settings))
sl.apply { sl.apply {
add(SubmenuSetting(R.string.preferences_general, 0, Settings.MenuTag.SECTION_GENERAL)) add(SubmenuSetting(R.string.preferences_general, 0, Settings.SECTION_GENERAL))
add(SubmenuSetting(R.string.preferences_system, 0, Settings.MenuTag.SECTION_SYSTEM)) add(SubmenuSetting(R.string.preferences_system, 0, Settings.SECTION_SYSTEM))
add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.MenuTag.SECTION_RENDERER)) add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.SECTION_RENDERER))
add(SubmenuSetting(R.string.preferences_audio, 0, Settings.MenuTag.SECTION_AUDIO)) add(SubmenuSetting(R.string.preferences_audio, 0, Settings.SECTION_AUDIO))
add(SubmenuSetting(R.string.preferences_debug, 0, Settings.MenuTag.SECTION_DEBUG)) add(SubmenuSetting(R.string.preferences_debug, 0, Settings.SECTION_DEBUG))
add( add(
RunnableSetting(R.string.reset_to_default, 0, false) { RunnableSetting(R.string.reset_to_default, 0, false) {
settingsViewModel.setShouldShowResetSettingsDialog(true) settingsViewModel.setShouldShowResetSettingsDialog(true)
@ -89,6 +102,7 @@ class SettingsFragmentPresenter(
} }
private fun addGeneralSettings(sl: ArrayList<SettingsItem>) { private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_general))
sl.apply { sl.apply {
add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key) add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key)
add(ShortSetting.RENDERER_SPEED_LIMIT.key) add(ShortSetting.RENDERER_SPEED_LIMIT.key)
@ -98,6 +112,7 @@ class SettingsFragmentPresenter(
} }
private fun addSystemSettings(sl: ArrayList<SettingsItem>) { private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_system))
sl.apply { sl.apply {
add(BooleanSetting.USE_DOCKED_MODE.key) add(BooleanSetting.USE_DOCKED_MODE.key)
add(IntSetting.REGION_INDEX.key) add(IntSetting.REGION_INDEX.key)
@ -108,6 +123,7 @@ class SettingsFragmentPresenter(
} }
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_graphics))
sl.apply { sl.apply {
add(IntSetting.RENDERER_ACCURACY.key) add(IntSetting.RENDERER_ACCURACY.key)
add(IntSetting.RENDERER_RESOLUTION.key) add(IntSetting.RENDERER_RESOLUTION.key)
@ -124,6 +140,7 @@ class SettingsFragmentPresenter(
} }
private fun addAudioSettings(sl: ArrayList<SettingsItem>) { private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_audio))
sl.apply { sl.apply {
add(IntSetting.AUDIO_OUTPUT_ENGINE.key) add(IntSetting.AUDIO_OUTPUT_ENGINE.key)
add(ByteSetting.AUDIO_VOLUME.key) add(ByteSetting.AUDIO_VOLUME.key)
@ -131,6 +148,7 @@ class SettingsFragmentPresenter(
} }
private fun addThemeSettings(sl: ArrayList<SettingsItem>) { private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_theme))
sl.apply { sl.apply {
val theme: AbstractIntSetting = object : AbstractIntSetting { val theme: AbstractIntSetting = object : AbstractIntSetting {
override val int: Int override val int: Int
@ -243,6 +261,7 @@ class SettingsFragmentPresenter(
} }
private fun addDebugSettings(sl: ArrayList<SettingsItem>) { private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_debug))
sl.apply { sl.apply {
add(HeaderSetting(R.string.gpu)) add(HeaderSetting(R.string.gpu))
add(IntSetting.RENDERER_BACKEND.key) add(IntSetting.RENDERER_BACKEND.key)

View File

@ -39,7 +39,6 @@ import androidx.window.layout.WindowLayoutInfo
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.Slider import com.google.android.material.slider.Slider
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.HomeNavigationDirections import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
@ -50,6 +49,7 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
import org.yuzu.yuzu_emu.features.settings.model.IntSetting import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.EmulationViewModel import org.yuzu.yuzu_emu.model.EmulationViewModel
import org.yuzu.yuzu_emu.overlay.InputOverlay import org.yuzu.yuzu_emu.overlay.InputOverlay
@ -129,8 +129,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
return binding.root return binding.root
} }
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.surfaceEmulation.holder.addCallback(this) binding.surfaceEmulation.holder.addCallback(this)
binding.showFpsText.setTextColor(Color.YELLOW) binding.showFpsText.setTextColor(Color.YELLOW)
@ -165,7 +163,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
R.id.menu_settings -> { R.id.menu_settings -> {
val action = HomeNavigationDirections.actionGlobalSettingsActivity( val action = HomeNavigationDirections.actionGlobalSettingsActivity(
null, null,
Settings.MenuTag.SECTION_ROOT SettingsFile.FILE_NAME_CONFIG
) )
binding.root.findNavController().navigate(action) binding.root.findNavController().navigate(action)
true true
@ -207,80 +205,59 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
) )
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
WindowInfoTracker.getOrCreate(requireContext())
.windowLayoutInfo(requireActivity())
.collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) }
}
}
GameIconUtils.loadGameIcon(game, binding.loadingImage) GameIconUtils.loadGameIcon(game, binding.loadingImage)
binding.loadingTitle.text = game.title binding.loadingTitle.text = game.title
binding.loadingTitle.isSelected = true binding.loadingTitle.isSelected = true
binding.loadingText.isSelected = true binding.loadingText.isSelected = true
viewLifecycleOwner.lifecycleScope.apply { emulationViewModel.shaderProgress.observe(viewLifecycleOwner) {
launch { if (it > 0 && it != emulationViewModel.totalShaders.value!!) {
repeatOnLifecycle(Lifecycle.State.STARTED) { binding.loadingProgressIndicator.isIndeterminate = false
WindowInfoTracker.getOrCreate(requireContext())
.windowLayoutInfo(requireActivity())
.collect {
updateFoldableLayout(requireActivity() as EmulationActivity, it)
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
emulationViewModel.shaderProgress.collectLatest {
if (it > 0 && it != emulationViewModel.totalShaders.value) {
binding.loadingProgressIndicator.isIndeterminate = false
if (it < binding.loadingProgressIndicator.max) { if (it < binding.loadingProgressIndicator.max) {
binding.loadingProgressIndicator.progress = it binding.loadingProgressIndicator.progress = it
} }
} }
if (it == emulationViewModel.totalShaders.value) { if (it == emulationViewModel.totalShaders.value!!) {
binding.loadingText.setText(R.string.loading) binding.loadingText.setText(R.string.loading)
binding.loadingProgressIndicator.isIndeterminate = true binding.loadingProgressIndicator.isIndeterminate = true
}
}
}
} }
launch { }
repeatOnLifecycle(Lifecycle.State.CREATED) { emulationViewModel.totalShaders.observe(viewLifecycleOwner) {
emulationViewModel.totalShaders.collectLatest { binding.loadingProgressIndicator.max = it
binding.loadingProgressIndicator.max = it }
} emulationViewModel.shaderMessage.observe(viewLifecycleOwner) {
} if (it.isNotEmpty()) {
binding.loadingText.text = it
} }
launch { }
repeatOnLifecycle(Lifecycle.State.CREATED) {
emulationViewModel.shaderMessage.collectLatest {
if (it.isNotEmpty()) {
binding.loadingText.text = it
}
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
emulationViewModel.emulationStarted.collectLatest {
if (it) {
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
ViewUtils.showView(binding.surfaceInputOverlay)
ViewUtils.hideView(binding.loadingIndicator)
// Setup overlay emulationViewModel.emulationStarted.observe(viewLifecycleOwner) { started ->
updateShowFpsOverlay() if (started) {
} binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
} ViewUtils.showView(binding.surfaceInputOverlay)
} ViewUtils.hideView(binding.loadingIndicator)
// Setup overlay
updateShowFpsOverlay()
} }
launch { }
repeatOnLifecycle(Lifecycle.State.CREATED) {
emulationViewModel.isEmulationStopping.collectLatest { emulationViewModel.isEmulationStopping.observe(viewLifecycleOwner) {
if (it) { if (it) {
binding.loadingText.setText(R.string.shutting_down) binding.loadingText.setText(R.string.shutting_down)
ViewUtils.showView(binding.loadingIndicator) ViewUtils.showView(binding.loadingIndicator)
ViewUtils.hideView(binding.inputContainer) ViewUtils.hideView(binding.inputContainer)
ViewUtils.hideView(binding.showFpsText) ViewUtils.hideView(binding.showFpsText)
}
}
}
} }
} }
} }
@ -297,7 +274,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
} }
} else { } else {
if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) { if (EmulationMenuSettings.showOverlay &&
emulationViewModel.emulationStarted.value == true
) {
binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.post {
binding.surfaceInputOverlay.visibility = View.VISIBLE binding.surfaceInputOverlay.visibility = View.VISIBLE
} }

View File

@ -37,6 +37,7 @@ import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
import org.yuzu.yuzu_emu.features.DocumentProvider import org.yuzu.yuzu_emu.features.DocumentProvider
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.HomeSetting import org.yuzu.yuzu_emu.model.HomeSetting
import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity import org.yuzu.yuzu_emu.ui.main.MainActivity
@ -77,7 +78,7 @@ class HomeSettingsFragment : Fragment() {
{ {
val action = HomeNavigationDirections.actionGlobalSettingsActivity( val action = HomeNavigationDirections.actionGlobalSettingsActivity(
null, null,
Settings.MenuTag.SECTION_ROOT SettingsFile.FILE_NAME_CONFIG
) )
binding.root.findNavController().navigate(action) binding.root.findNavController().navigate(action)
} }
@ -99,7 +100,7 @@ class HomeSettingsFragment : Fragment() {
{ {
val action = HomeNavigationDirections.actionGlobalSettingsActivity( val action = HomeNavigationDirections.actionGlobalSettingsActivity(
null, null,
Settings.MenuTag.SECTION_THEME Settings.SECTION_THEME
) )
binding.root.findNavController().navigate(action) binding.root.findNavController().navigate(action)
} }

View File

@ -9,12 +9,8 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.model.TaskViewModel import org.yuzu.yuzu_emu.model.TaskViewModel
@ -32,27 +28,21 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
.create() .create()
dialog.setCanceledOnTouchOutside(false) dialog.setCanceledOnTouchOutside(false)
viewLifecycleOwner.lifecycleScope.launch { taskViewModel.isComplete.observe(this) { complete ->
repeatOnLifecycle(Lifecycle.State.CREATED) { if (complete) {
taskViewModel.isComplete.collect { dialog.dismiss()
if (it) { when (val result = taskViewModel.result.value) {
dialog.dismiss() is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG).show()
when (val result = taskViewModel.result.value) { is MessageDialogFragment -> result.show(
is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG) requireActivity().supportFragmentManager,
.show() MessageDialogFragment.TAG
)
is MessageDialogFragment -> result.show(
requireActivity().supportFragmentManager,
MessageDialogFragment.TAG
)
}
taskViewModel.clear()
}
} }
taskViewModel.clear()
} }
} }
if (!taskViewModel.isRunning.value) { if (taskViewModel.isRunning.value == false) {
taskViewModel.runTask() taskViewModel.runTask()
} }
return dialog return dialog

View File

@ -3,7 +3,6 @@
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
@ -18,13 +17,9 @@ import androidx.core.view.updatePadding
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import info.debatty.java.stringsimilarity.Jaccard import info.debatty.java.stringsimilarity.Jaccard
import info.debatty.java.stringsimilarity.JaroWinkler import info.debatty.java.stringsimilarity.JaroWinkler
import kotlinx.coroutines.launch
import java.util.Locale import java.util.Locale
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
@ -57,8 +52,6 @@ class SearchFragment : Fragment() {
return binding.root return binding.root
} }
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
homeViewModel.setNavigationVisibility(visible = true, animated = false) homeViewModel.setNavigationVisibility(visible = true, animated = false)
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
@ -86,32 +79,21 @@ class SearchFragment : Fragment() {
filterAndSearch() filterAndSearch()
} }
viewLifecycleOwner.lifecycleScope.apply { gamesViewModel.apply {
launch { searchFocused.observe(viewLifecycleOwner) { searchFocused ->
repeatOnLifecycle(Lifecycle.State.CREATED) { if (searchFocused) {
gamesViewModel.searchFocused.collect { focusSearch()
if (it) { gamesViewModel.setSearchFocused(false)
focusSearch()
gamesViewModel.setSearchFocused(false)
}
}
} }
} }
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) { games.observe(viewLifecycleOwner) { filterAndSearch() }
gamesViewModel.games.collect { filterAndSearch() } searchedGames.observe(viewLifecycleOwner) {
} (binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
} if (it.isEmpty()) {
launch { binding.noResultsView.visibility = View.VISIBLE
repeatOnLifecycle(Lifecycle.State.CREATED) { } else {
gamesViewModel.searchedGames.collect { binding.noResultsView.visibility = View.GONE
(binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
if (it.isEmpty()) {
binding.noResultsView.visibility = View.VISIBLE
} else {
binding.noResultsView.visibility = View.GONE
}
}
} }
} }
} }
@ -127,7 +109,7 @@ class SearchFragment : Fragment() {
private inner class ScoredGame(val score: Double, val item: Game) private inner class ScoredGame(val score: Double, val item: Game)
private fun filterAndSearch() { private fun filterAndSearch() {
val baseList = gamesViewModel.games.value val baseList = gamesViewModel.games.value!!
val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) { val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) {
R.id.chip_recently_played -> { R.id.chip_recently_played -> {
baseList.filter { baseList.filter {

View File

@ -15,14 +15,10 @@ import androidx.core.view.updatePadding
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.divider.MaterialDividerItemDecoration import com.google.android.material.divider.MaterialDividerItemDecoration
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
import info.debatty.java.stringsimilarity.Cosine import info.debatty.java.stringsimilarity.Cosine
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
@ -83,14 +79,10 @@ class SettingsSearchFragment : Fragment() {
search() search()
binding.settingsList.smoothScrollToPosition(0) binding.settingsList.smoothScrollToPosition(0)
} }
viewLifecycleOwner.lifecycleScope.launch { settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) {
repeatOnLifecycle(Lifecycle.State.CREATED) { if (it) {
settingsViewModel.shouldReloadSettingsList.collect { settingsViewModel.setShouldReloadSettingsList(false)
if (it) { search()
settingsViewModel.setShouldReloadSettingsList(false)
search()
}
}
} }
} }

View File

@ -22,14 +22,10 @@ import androidx.core.view.isVisible
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import com.google.android.material.transition.MaterialFadeThrough import com.google.android.material.transition.MaterialFadeThrough
import kotlinx.coroutines.launch
import java.io.File import java.io.File
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
@ -210,14 +206,10 @@ class SetupFragment : Fragment() {
) )
} }
viewLifecycleOwner.lifecycleScope.launch { homeViewModel.shouldPageForward.observe(viewLifecycleOwner) {
repeatOnLifecycle(Lifecycle.State.CREATED) { if (it) {
homeViewModel.shouldPageForward.collect { pageForward()
if (it) { homeViewModel.setShouldPageForward(false)
pageForward()
homeViewModel.setShouldPageForward(false)
}
}
} }
} }

View File

@ -3,28 +3,28 @@
package org.yuzu.yuzu_emu.model package org.yuzu.yuzu_emu.model
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class EmulationViewModel : ViewModel() { class EmulationViewModel : ViewModel() {
val emulationStarted: StateFlow<Boolean> get() = _emulationStarted private val _emulationStarted = MutableLiveData(false)
private val _emulationStarted = MutableStateFlow(false) val emulationStarted: LiveData<Boolean> get() = _emulationStarted
val isEmulationStopping: StateFlow<Boolean> get() = _isEmulationStopping private val _isEmulationStopping = MutableLiveData(false)
private val _isEmulationStopping = MutableStateFlow(false) val isEmulationStopping: LiveData<Boolean> get() = _isEmulationStopping
val shaderProgress: StateFlow<Int> get() = _shaderProgress private val _shaderProgress = MutableLiveData(0)
private val _shaderProgress = MutableStateFlow(0) val shaderProgress: LiveData<Int> get() = _shaderProgress
val totalShaders: StateFlow<Int> get() = _totalShaders private val _totalShaders = MutableLiveData(0)
private val _totalShaders = MutableStateFlow(0) val totalShaders: LiveData<Int> get() = _totalShaders
val shaderMessage: StateFlow<String> get() = _shaderMessage private val _shaderMessage = MutableLiveData("")
private val _shaderMessage = MutableStateFlow("") val shaderMessage: LiveData<String> get() = _shaderMessage
fun setEmulationStarted(started: Boolean) { fun setEmulationStarted(started: Boolean) {
_emulationStarted.value = started _emulationStarted.postValue(started)
} }
fun setIsEmulationStopping(value: Boolean) { fun setIsEmulationStopping(value: Boolean) {
@ -50,18 +50,10 @@ class EmulationViewModel : ViewModel() {
} }
fun clear() { fun clear() {
setEmulationStarted(false) _emulationStarted.value = false
setIsEmulationStopping(false) _isEmulationStopping.value = false
setShaderProgress(0) _shaderProgress.value = 0
setTotalShaders(0) _totalShaders.value = 0
setShaderMessage("") _shaderMessage.value = ""
}
companion object {
const val KEY_EMULATION_STARTED = "EmulationStarted"
const val KEY_IS_EMULATION_STOPPING = "IsEmulationStarting"
const val KEY_SHADER_PROGRESS = "ShaderProgress"
const val KEY_TOTAL_SHADERS = "TotalShaders"
const val KEY_SHADER_MESSAGE = "ShaderMessage"
} }
} }

View File

@ -5,13 +5,13 @@ package org.yuzu.yuzu_emu.model
import android.net.Uri import android.net.Uri
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import java.util.Locale import java.util.Locale
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
@ -24,23 +24,23 @@ import org.yuzu.yuzu_emu.utils.GameHelper
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
class GamesViewModel : ViewModel() { class GamesViewModel : ViewModel() {
val games: StateFlow<List<Game>> get() = _games private val _games = MutableLiveData<List<Game>>(emptyList())
private val _games = MutableStateFlow(emptyList<Game>()) val games: LiveData<List<Game>> get() = _games
val searchedGames: StateFlow<List<Game>> get() = _searchedGames private val _searchedGames = MutableLiveData<List<Game>>(emptyList())
private val _searchedGames = MutableStateFlow(emptyList<Game>()) val searchedGames: LiveData<List<Game>> get() = _searchedGames
val isReloading: StateFlow<Boolean> get() = _isReloading private val _isReloading = MutableLiveData(false)
private val _isReloading = MutableStateFlow(false) val isReloading: LiveData<Boolean> get() = _isReloading
val shouldSwapData: StateFlow<Boolean> get() = _shouldSwapData private val _shouldSwapData = MutableLiveData(false)
private val _shouldSwapData = MutableStateFlow(false) val shouldSwapData: LiveData<Boolean> get() = _shouldSwapData
val shouldScrollToTop: StateFlow<Boolean> get() = _shouldScrollToTop private val _shouldScrollToTop = MutableLiveData(false)
private val _shouldScrollToTop = MutableStateFlow(false) val shouldScrollToTop: LiveData<Boolean> get() = _shouldScrollToTop
val searchFocused: StateFlow<Boolean> get() = _searchFocused private val _searchFocused = MutableLiveData(false)
private val _searchFocused = MutableStateFlow(false) val searchFocused: LiveData<Boolean> get() = _searchFocused
init { init {
// Ensure keys are loaded so that ROM metadata can be decrypted. // Ensure keys are loaded so that ROM metadata can be decrypted.
@ -79,36 +79,36 @@ class GamesViewModel : ViewModel() {
) )
) )
_games.value = sortedList _games.postValue(sortedList)
} }
fun setSearchedGames(games: List<Game>) { fun setSearchedGames(games: List<Game>) {
_searchedGames.value = games _searchedGames.postValue(games)
} }
fun setShouldSwapData(shouldSwap: Boolean) { fun setShouldSwapData(shouldSwap: Boolean) {
_shouldSwapData.value = shouldSwap _shouldSwapData.postValue(shouldSwap)
} }
fun setShouldScrollToTop(shouldScroll: Boolean) { fun setShouldScrollToTop(shouldScroll: Boolean) {
_shouldScrollToTop.value = shouldScroll _shouldScrollToTop.postValue(shouldScroll)
} }
fun setSearchFocused(searchFocused: Boolean) { fun setSearchFocused(searchFocused: Boolean) {
_searchFocused.value = searchFocused _searchFocused.postValue(searchFocused)
} }
fun reloadGames(directoryChanged: Boolean) { fun reloadGames(directoryChanged: Boolean) {
if (isReloading.value) { if (isReloading.value == true) {
return return
} }
_isReloading.value = true _isReloading.postValue(true)
viewModelScope.launch { viewModelScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
NativeLibrary.resetRomMetadata() NativeLibrary.resetRomMetadata()
setGames(GameHelper.getGames()) setGames(GameHelper.getGames())
_isReloading.value = false _isReloading.postValue(false)
if (directoryChanged) { if (directoryChanged) {
setShouldSwapData(true) setShouldSwapData(true)

View File

@ -3,8 +3,8 @@
package org.yuzu.yuzu_emu.model package org.yuzu.yuzu_emu.model
import kotlinx.coroutines.flow.MutableStateFlow import androidx.lifecycle.LiveData
import kotlinx.coroutines.flow.StateFlow import androidx.lifecycle.MutableLiveData
data class HomeSetting( data class HomeSetting(
val titleId: Int, val titleId: Int,
@ -14,5 +14,5 @@ data class HomeSetting(
val isEnabled: () -> Boolean = { true }, val isEnabled: () -> Boolean = { true },
val disabledTitleId: Int = 0, val disabledTitleId: Int = 0,
val disabledMessageId: Int = 0, val disabledMessageId: Int = 0,
val details: StateFlow<String> = MutableStateFlow("") val details: LiveData<String> = MutableLiveData("")
) )

View File

@ -5,43 +5,47 @@ package org.yuzu.yuzu_emu.model
import android.net.Uri import android.net.Uri
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.utils.GameHelper import org.yuzu.yuzu_emu.utils.GameHelper
class HomeViewModel : ViewModel() { class HomeViewModel : ViewModel() {
val navigationVisible: StateFlow<Pair<Boolean, Boolean>> get() = _navigationVisible private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>()
private val _navigationVisible = MutableStateFlow(Pair(false, false)) val navigationVisible: LiveData<Pair<Boolean, Boolean>> get() = _navigationVisible
val statusBarShadeVisible: StateFlow<Boolean> get() = _statusBarShadeVisible private val _statusBarShadeVisible = MutableLiveData(true)
private val _statusBarShadeVisible = MutableStateFlow(true) val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible
val shouldPageForward: StateFlow<Boolean> get() = _shouldPageForward private val _shouldPageForward = MutableLiveData(false)
private val _shouldPageForward = MutableStateFlow(false) val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward
val gamesDir: StateFlow<String> get() = _gamesDir private val _gamesDir = MutableLiveData(
private val _gamesDir = MutableStateFlow(
Uri.parse( Uri.parse(
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
.getString(GameHelper.KEY_GAME_PATH, "") .getString(GameHelper.KEY_GAME_PATH, "")
).path ?: "" ).path ?: ""
) )
val gamesDir: LiveData<String> get() = _gamesDir
var navigatedToSetup = false var navigatedToSetup = false
init {
_navigationVisible.value = Pair(false, false)
}
fun setNavigationVisibility(visible: Boolean, animated: Boolean) { fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
if (navigationVisible.value.first == visible) { if (_navigationVisible.value?.first == visible) {
return return
} }
_navigationVisible.value = Pair(visible, animated) _navigationVisible.value = Pair(visible, animated)
} }
fun setStatusBarShadeVisibility(visible: Boolean) { fun setStatusBarShadeVisibility(visible: Boolean) {
if (statusBarShadeVisible.value == visible) { if (_statusBarShadeVisible.value == visible) {
return return
} }
_statusBarShadeVisible.value = visible _statusBarShadeVisible.value = visible

View File

@ -3,43 +3,48 @@
package org.yuzu.yuzu_emu.model package org.yuzu.yuzu_emu.model
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
class SettingsViewModel : ViewModel() { class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
var game: Game? = null var game: Game? = null
var shouldSave = false var shouldSave = false
var clickedItem: SettingsItem? = null var clickedItem: SettingsItem? = null
val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate private val _toolbarTitle = MutableLiveData("")
private val _shouldRecreate = MutableStateFlow(false) val toolbarTitle: LiveData<String> get() = _toolbarTitle
val shouldNavigateBack: StateFlow<Boolean> get() = _shouldNavigateBack private val _shouldRecreate = MutableLiveData(false)
private val _shouldNavigateBack = MutableStateFlow(false) val shouldRecreate: LiveData<Boolean> get() = _shouldRecreate
val shouldShowResetSettingsDialog: StateFlow<Boolean> get() = _shouldShowResetSettingsDialog private val _shouldNavigateBack = MutableLiveData(false)
private val _shouldShowResetSettingsDialog = MutableStateFlow(false) val shouldNavigateBack: LiveData<Boolean> get() = _shouldNavigateBack
val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList private val _shouldShowResetSettingsDialog = MutableLiveData(false)
private val _shouldReloadSettingsList = MutableStateFlow(false) val shouldShowResetSettingsDialog: LiveData<Boolean> get() = _shouldShowResetSettingsDialog
val isUsingSearch: StateFlow<Boolean> get() = _isUsingSearch private val _shouldReloadSettingsList = MutableLiveData(false)
private val _isUsingSearch = MutableStateFlow(false) val shouldReloadSettingsList: LiveData<Boolean> get() = _shouldReloadSettingsList
val sliderProgress: StateFlow<Int> get() = _sliderProgress private val _isUsingSearch = MutableLiveData(false)
private val _sliderProgress = MutableStateFlow(-1) val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch
val sliderTextValue: StateFlow<String> get() = _sliderTextValue val sliderProgress = savedStateHandle.getStateFlow(KEY_SLIDER_PROGRESS, -1)
private val _sliderTextValue = MutableStateFlow("")
val adapterItemChanged: StateFlow<Int> get() = _adapterItemChanged val sliderTextValue = savedStateHandle.getStateFlow(KEY_SLIDER_TEXT_VALUE, "")
private val _adapterItemChanged = MutableStateFlow(-1)
val adapterItemChanged = savedStateHandle.getStateFlow(KEY_ADAPTER_ITEM_CHANGED, -1)
fun setToolbarTitle(value: String) {
_toolbarTitle.value = value
}
fun setShouldRecreate(value: Boolean) { fun setShouldRecreate(value: Boolean) {
_shouldRecreate.value = value _shouldRecreate.value = value
@ -62,8 +67,8 @@ class SettingsViewModel : ViewModel() {
} }
fun setSliderTextValue(value: Float, units: String) { fun setSliderTextValue(value: Float, units: String) {
_sliderProgress.value = value.toInt() savedStateHandle[KEY_SLIDER_PROGRESS] = value
_sliderTextValue.value = String.format( savedStateHandle[KEY_SLIDER_TEXT_VALUE] = String.format(
YuzuApplication.appContext.getString(R.string.value_with_units), YuzuApplication.appContext.getString(R.string.value_with_units),
value.toInt().toString(), value.toInt().toString(),
units units
@ -71,15 +76,21 @@ class SettingsViewModel : ViewModel() {
} }
fun setSliderProgress(value: Float) { fun setSliderProgress(value: Float) {
_sliderProgress.value = value.toInt() savedStateHandle[KEY_SLIDER_PROGRESS] = value
} }
fun setAdapterItemChanged(value: Int) { fun setAdapterItemChanged(value: Int) {
_adapterItemChanged.value = value savedStateHandle[KEY_ADAPTER_ITEM_CHANGED] = value
} }
fun clear() { fun clear() {
game = null game = null
shouldSave = false shouldSave = false
} }
companion object {
const val KEY_SLIDER_TEXT_VALUE = "SliderTextValue"
const val KEY_SLIDER_PROGRESS = "SliderProgress"
const val KEY_ADAPTER_ITEM_CHANGED = "AdapterItemChanged"
}
} }

View File

@ -3,25 +3,29 @@
package org.yuzu.yuzu_emu.model package org.yuzu.yuzu_emu.model
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class TaskViewModel : ViewModel() { class TaskViewModel : ViewModel() {
val result: StateFlow<Any> get() = _result private val _result = MutableLiveData<Any>()
private val _result = MutableStateFlow(Any()) val result: LiveData<Any> = _result
val isComplete: StateFlow<Boolean> get() = _isComplete private val _isComplete = MutableLiveData<Boolean>()
private val _isComplete = MutableStateFlow(false) val isComplete: LiveData<Boolean> = _isComplete
val isRunning: StateFlow<Boolean> get() = _isRunning private val _isRunning = MutableLiveData<Boolean>()
private val _isRunning = MutableStateFlow(false) val isRunning: LiveData<Boolean> = _isRunning
lateinit var task: () -> Any lateinit var task: () -> Any
init {
clear()
}
fun clear() { fun clear() {
_result.value = Any() _result.value = Any()
_isComplete.value = false _isComplete.value = false
@ -29,16 +33,15 @@ class TaskViewModel : ViewModel() {
} }
fun runTask() { fun runTask() {
if (isRunning.value) { if (_isRunning.value == true) {
return return
} }
_isRunning.value = true _isRunning.value = true
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
val res = task() val res = task()
_result.value = res _result.postValue(res)
_isComplete.value = true _isComplete.postValue(true)
_isRunning.value = false
} }
} }
} }

View File

@ -3,7 +3,6 @@
package org.yuzu.yuzu_emu.ui package org.yuzu.yuzu_emu.ui
import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -15,12 +14,8 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import com.google.android.material.transition.MaterialFadeThrough import com.google.android.material.transition.MaterialFadeThrough
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.GameAdapter import org.yuzu.yuzu_emu.adapters.GameAdapter
import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding
@ -49,8 +44,6 @@ class GamesFragment : Fragment() {
return binding.root return binding.root
} }
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
homeViewModel.setNavigationVisibility(visible = true, animated = false) homeViewModel.setNavigationVisibility(visible = true, animated = false)
@ -87,48 +80,37 @@ class GamesFragment : Fragment() {
if (_binding == null) { if (_binding == null) {
return@post return@post
} }
binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value!!
} }
} }
viewLifecycleOwner.lifecycleScope.apply { gamesViewModel.apply {
launch { // Watch for when we get updates to any of our games lists
repeatOnLifecycle(Lifecycle.State.RESUMED) { isReloading.observe(viewLifecycleOwner) { isReloading ->
gamesViewModel.isReloading.collect { binding.swipeRefresh.isRefreshing = it } binding.swipeRefresh.isRefreshing = isReloading
}
games.observe(viewLifecycleOwner) {
(binding.gridGames.adapter as GameAdapter).submitList(it)
if (it.isEmpty()) {
binding.noticeText.visibility = View.VISIBLE
} else {
binding.noticeText.visibility = View.GONE
} }
} }
launch { shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData ->
repeatOnLifecycle(Lifecycle.State.RESUMED) { if (shouldSwapData) {
gamesViewModel.games.collect { (binding.gridGames.adapter as GameAdapter).submitList(
(binding.gridGames.adapter as GameAdapter).submitList(it) gamesViewModel.games.value!!
if (it.isEmpty()) { )
binding.noticeText.visibility = View.VISIBLE gamesViewModel.setShouldSwapData(false)
} else {
binding.noticeText.visibility = View.GONE
}
}
} }
} }
launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) { // Check if the user reselected the games menu item and then scroll to top of the list
gamesViewModel.shouldSwapData.collect { shouldScrollToTop.observe(viewLifecycleOwner) { shouldScroll ->
if (it) { if (shouldScroll) {
(binding.gridGames.adapter as GameAdapter).submitList( scrollToTop()
gamesViewModel.games.value gamesViewModel.setShouldScrollToTop(false)
)
gamesViewModel.setShouldSwapData(false)
}
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
gamesViewModel.shouldScrollToTop.collect {
if (it) {
scrollToTop()
gamesViewModel.setShouldScrollToTop(false)
}
}
} }
} }
} }

View File

@ -19,9 +19,7 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
@ -42,6 +40,7 @@ import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.GamesViewModel import org.yuzu.yuzu_emu.model.GamesViewModel
@ -108,7 +107,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
R.id.homeSettingsFragment -> { R.id.homeSettingsFragment -> {
val action = HomeNavigationDirections.actionGlobalSettingsActivity( val action = HomeNavigationDirections.actionGlobalSettingsActivity(
null, null,
Settings.MenuTag.SECTION_ROOT SettingsFile.FILE_NAME_CONFIG
) )
navHostFragment.navController.navigate(action) navHostFragment.navController.navigate(action)
} }
@ -116,22 +115,16 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
} }
// Prevents navigation from being drawn for a short time on recreation if set to hidden // Prevents navigation from being drawn for a short time on recreation if set to hidden
if (!homeViewModel.navigationVisible.value.first) { if (!homeViewModel.navigationVisible.value?.first!!) {
binding.navigationView.visibility = View.INVISIBLE binding.navigationView.visibility = View.INVISIBLE
binding.statusBarShade.visibility = View.INVISIBLE binding.statusBarShade.visibility = View.INVISIBLE
} }
lifecycleScope.apply { homeViewModel.navigationVisible.observe(this) {
launch { showNavigation(it.first, it.second)
repeatOnLifecycle(Lifecycle.State.CREATED) { }
homeViewModel.navigationVisible.collect { showNavigation(it.first, it.second) } homeViewModel.statusBarShadeVisible.observe(this) { visible ->
} showStatusBarShade(visible)
}
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
homeViewModel.statusBarShadeVisible.collect { showStatusBarShade(it) }
}
}
} }
// Dismiss previous notifications (should not happen unless a crash occurred) // Dismiss previous notifications (should not happen unless a crash occurred)

View File

@ -82,7 +82,7 @@
app:nullable="true" /> app:nullable="true" />
<argument <argument
android:name="menuTag" android:name="menuTag"
app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" /> app:argType="string" />
</activity> </activity>
<action <action

View File

@ -10,7 +10,7 @@
android:label="SettingsFragment"> android:label="SettingsFragment">
<argument <argument
android:name="menuTag" android:name="menuTag"
app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" /> app:argType="string" />
<argument <argument
android:name="game" android:name="game"
app:argType="org.yuzu.yuzu_emu.model.Game" app:argType="org.yuzu.yuzu_emu.model.Game"

View File

@ -112,7 +112,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
SUB(Service, NCM) \ SUB(Service, NCM) \
SUB(Service, NFC) \ SUB(Service, NFC) \
SUB(Service, NFP) \ SUB(Service, NFP) \
SUB(Service, NGC) \ SUB(Service, NGCT) \
SUB(Service, NIFM) \ SUB(Service, NIFM) \
SUB(Service, NIM) \ SUB(Service, NIM) \
SUB(Service, NOTIF) \ SUB(Service, NOTIF) \

View File

@ -80,7 +80,7 @@ enum class Class : u8 {
Service_NCM, ///< The NCM service Service_NCM, ///< The NCM service
Service_NFC, ///< The NFC (Near-field communication) service Service_NFC, ///< The NFC (Near-field communication) service
Service_NFP, ///< The NFP service Service_NFP, ///< The NFP service
Service_NGC, ///< The NGC (No Good Content) service Service_NGCT, ///< The NGCT (No Good Content for Terra) service
Service_NIFM, ///< The NIFM (Network interface) service Service_NIFM, ///< The NIFM (Network interface) service
Service_NIM, ///< The NIM service Service_NIM, ///< The NIM service
Service_NOTIF, ///< The NOTIF (Notification) service Service_NOTIF, ///< The NOTIF (Notification) service

View File

@ -19,8 +19,8 @@
namespace Common { namespace Common {
template <typename Condvar, typename Lock, typename Pred> template <typename Condvar, typename Lock, typename Pred>
void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) { void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred&& pred) {
cv.wait(lk, token, std::move(pred)); cv.wait(lock, token, std::move(pred));
} }
template <typename Rep, typename Period> template <typename Rep, typename Period>
@ -332,17 +332,13 @@ private:
namespace Common { namespace Common {
template <typename Condvar, typename Lock, typename Pred> template <typename Condvar, typename Lock, typename Pred>
void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred pred) { void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred pred) {
if (token.stop_requested()) { if (token.stop_requested()) {
return; return;
} }
std::stop_callback callback(token, [&] { std::stop_callback callback(token, [&] { cv.notify_all(); });
{ std::scoped_lock lk2{*lk.mutex()}; } cv.wait(lock, [&] { return pred() || token.stop_requested(); });
cv.notify_all();
});
cv.wait(lk, [&] { return pred() || token.stop_requested(); });
} }
template <typename Rep, typename Period> template <typename Rep, typename Period>
@ -357,10 +353,8 @@ bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep,
std::stop_callback cb(token, [&] { std::stop_callback cb(token, [&] {
// Wake up the waiting thread. // Wake up the waiting thread.
{ std::unique_lock lk{m};
std::scoped_lock lk{m}; stop_requested = true;
stop_requested = true;
}
cv.notify_one(); cv.notify_one();
}); });

View File

@ -627,8 +627,8 @@ add_library(core STATIC
hle/service/nfp/nfp_interface.h hle/service/nfp/nfp_interface.h
hle/service/nfp/nfp_result.h hle/service/nfp/nfp_result.h
hle/service/nfp/nfp_types.h hle/service/nfp/nfp_types.h
hle/service/ngc/ngc.cpp hle/service/ngct/ngct.cpp
hle/service/ngc/ngc.h hle/service/ngct/ngct.h
hle/service/nifm/nifm.cpp hle/service/nifm/nifm.cpp
hle/service/nifm/nifm.h hle/service/nifm/nifm.h
hle/service/nim/nim.cpp hle/service/nim/nim.cpp

View File

@ -1,150 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/string_util.h"
#include "core/core.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/ngc/ngc.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"
namespace Service::NGC {
class NgctServiceImpl final : public ServiceFramework<NgctServiceImpl> {
public:
explicit NgctServiceImpl(Core::System& system_) : ServiceFramework{system_, "ngct:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &NgctServiceImpl::Match, "Match"},
{1, &NgctServiceImpl::Filter, "Filter"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void Match(HLERequestContext& ctx) {
const auto buffer = ctx.ReadBuffer();
const auto text = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(buffer.data()), buffer.size());
LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
// Return false since we don't censor anything
rb.Push(false);
}
void Filter(HLERequestContext& ctx) {
const auto buffer = ctx.ReadBuffer();
const auto text = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(buffer.data()), buffer.size());
LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text);
// Return the same string since we don't censor anything
ctx.WriteBuffer(buffer);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
};
class NgcServiceImpl final : public ServiceFramework<NgcServiceImpl> {
public:
explicit NgcServiceImpl(Core::System& system_) : ServiceFramework(system_, "ngc:u") {
// clang-format off
static const FunctionInfo functions[] = {
{0, &NgcServiceImpl::GetContentVersion, "GetContentVersion"},
{1, &NgcServiceImpl::Check, "Check"},
{2, &NgcServiceImpl::Mask, "Mask"},
{3, &NgcServiceImpl::Reload, "Reload"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
static constexpr u32 NgcContentVersion = 1;
// This is nn::ngc::detail::ProfanityFilterOption
struct ProfanityFilterOption {
INSERT_PADDING_BYTES_NOINIT(0x20);
};
static_assert(sizeof(ProfanityFilterOption) == 0x20,
"ProfanityFilterOption has incorrect size");
void GetContentVersion(HLERequestContext& ctx) {
LOG_INFO(Service_NGC, "(STUBBED) called");
// This calls nn::ngc::ProfanityFilter::GetContentVersion
const u32 version = NgcContentVersion;
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(version);
}
void Check(HLERequestContext& ctx) {
LOG_INFO(Service_NGC, "(STUBBED) called");
struct InputParameters {
u32 flags;
ProfanityFilterOption option;
};
IPC::RequestParser rp{ctx};
[[maybe_unused]] const auto params = rp.PopRaw<InputParameters>();
[[maybe_unused]] const auto input = ctx.ReadBuffer(0);
// This calls nn::ngc::ProfanityFilter::CheckProfanityWords
const u32 out_flags = 0;
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(out_flags);
}
void Mask(HLERequestContext& ctx) {
LOG_INFO(Service_NGC, "(STUBBED) called");
struct InputParameters {
u32 flags;
ProfanityFilterOption option;
};
IPC::RequestParser rp{ctx};
[[maybe_unused]] const auto params = rp.PopRaw<InputParameters>();
const auto input = ctx.ReadBuffer(0);
// This calls nn::ngc::ProfanityFilter::MaskProfanityWordsInText
const u32 out_flags = 0;
ctx.WriteBuffer(input);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(out_flags);
}
void Reload(HLERequestContext& ctx) {
LOG_INFO(Service_NGC, "(STUBBED) called");
// This reloads the database.
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
};
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
server_manager->RegisterNamedService("ngct:u", std::make_shared<NgctServiceImpl>(system));
server_manager->RegisterNamedService("ngc:u", std::make_shared<NgcServiceImpl>(system));
ServerManager::RunServer(std::move(server_manager));
}
} // namespace Service::NGC

View File

@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/string_util.h"
#include "core/core.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/ngct/ngct.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"
namespace Service::NGCT {
class IService final : public ServiceFramework<IService> {
public:
explicit IService(Core::System& system_) : ServiceFramework{system_, "ngct:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IService::Match, "Match"},
{1, &IService::Filter, "Filter"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void Match(HLERequestContext& ctx) {
const auto buffer = ctx.ReadBuffer();
const auto text = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(buffer.data()), buffer.size());
LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
// Return false since we don't censor anything
rb.Push(false);
}
void Filter(HLERequestContext& ctx) {
const auto buffer = ctx.ReadBuffer();
const auto text = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(buffer.data()), buffer.size());
LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text);
// Return the same string since we don't censor anything
ctx.WriteBuffer(buffer);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
};
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
server_manager->RegisterNamedService("ngct:u", std::make_shared<IService>(system));
ServerManager::RunServer(std::move(server_manager));
}
} // namespace Service::NGCT

View File

@ -7,8 +7,8 @@ namespace Core {
class System; class System;
} }
namespace Service::NGC { namespace Service::NGCT {
void LoopProcess(Core::System& system); void LoopProcess(Core::System& system);
} // namespace Service::NGC } // namespace Service::NGCT

View File

@ -43,7 +43,7 @@
#include "core/hle/service/ncm/ncm.h" #include "core/hle/service/ncm/ncm.h"
#include "core/hle/service/nfc/nfc.h" #include "core/hle/service/nfc/nfc.h"
#include "core/hle/service/nfp/nfp.h" #include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/ngc/ngc.h" #include "core/hle/service/ngct/ngct.h"
#include "core/hle/service/nifm/nifm.h" #include "core/hle/service/nifm/nifm.h"
#include "core/hle/service/nim/nim.h" #include "core/hle/service/nim/nim.h"
#include "core/hle/service/npns/npns.h" #include "core/hle/service/npns/npns.h"
@ -257,7 +257,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
kernel.RunOnGuestCoreProcess("NCM", [&] { NCM::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("NCM", [&] { NCM::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("nfc", [&] { NFC::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("nfc", [&] { NFC::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("nfp", [&] { NFP::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("nfp", [&] { NFP::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("ngc", [&] { NGC::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("ngct", [&] { NGCT::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("nim", [&] { NIM::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("nim", [&] { NIM::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("npns", [&] { NPNS::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("npns", [&] { NPNS::LoopProcess(system); });

View File

@ -204,7 +204,9 @@ Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& ind
if (def.count > 1) { if (def.count > 1) {
throw NotImplementedException("Indirect texture sample"); throw NotImplementedException("Indirect texture sample");
} }
return ctx.OpLoad(ctx.image_buffer_type, def.id); const Id sampler_id{def.id};
const Id id{ctx.OpLoad(ctx.sampled_texture_buffer_type, sampler_id)};
return ctx.OpImage(ctx.image_buffer_type, id);
} else { } else {
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)}; const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
if (def.count > 1) { if (def.count > 1) {

View File

@ -1247,8 +1247,9 @@ void EmitContext::DefineTextureBuffers(const Info& info, u32& binding) {
} }
const spv::ImageFormat format{spv::ImageFormat::Unknown}; const spv::ImageFormat format{spv::ImageFormat::Unknown};
image_buffer_type = TypeImage(F32[1], spv::Dim::Buffer, 0U, false, false, 1, format); image_buffer_type = TypeImage(F32[1], spv::Dim::Buffer, 0U, false, false, 1, format);
sampled_texture_buffer_type = TypeSampledImage(image_buffer_type);
const Id type{TypePointer(spv::StorageClass::UniformConstant, image_buffer_type)}; const Id type{TypePointer(spv::StorageClass::UniformConstant, sampled_texture_buffer_type)};
texture_buffers.reserve(info.texture_buffer_descriptors.size()); texture_buffers.reserve(info.texture_buffer_descriptors.size());
for (const TextureBufferDescriptor& desc : info.texture_buffer_descriptors) { for (const TextureBufferDescriptor& desc : info.texture_buffer_descriptors) {
if (desc.count != 1) { if (desc.count != 1) {

View File

@ -206,6 +206,7 @@ public:
Id output_u32{}; Id output_u32{};
Id image_buffer_type{}; Id image_buffer_type{};
Id sampled_texture_buffer_type{};
Id image_u32{}; Id image_u32{};
std::array<UniformDefinitions, Info::MAX_CBUFS> cbufs{}; std::array<UniformDefinitions, Info::MAX_CBUFS> cbufs{};

View File

@ -719,7 +719,6 @@ typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_ad
return nullptr; return nullptr;
} }
const auto& image_map_ids = it->second; const auto& image_map_ids = it->second;
boost::container::small_vector<const ImageBase*, 4> valid_images;
for (const ImageMapId map_id : image_map_ids) { for (const ImageMapId map_id : image_map_ids) {
const ImageMapView& map = slot_map_views[map_id]; const ImageMapView& map = slot_map_views[map_id];
const ImageBase& image = slot_images[map.image_id]; const ImageBase& image = slot_images[map.image_id];
@ -729,20 +728,8 @@ typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_ad
if (image.image_view_ids.empty()) { if (image.image_view_ids.empty()) {
continue; continue;
} }
valid_images.push_back(&image); return &slot_image_views[image.image_view_ids.at(0)];
} }
if (valid_images.size() == 1) [[likely]] {
return &slot_image_views[valid_images[0]->image_view_ids.at(0)];
}
if (valid_images.size() > 0) [[unlikely]] {
std::ranges::sort(valid_images, [](const auto* a, const auto* b) {
return a->modification_tick > b->modification_tick;
});
return &slot_image_views[valid_images[0]->image_view_ids.at(0)];
}
return nullptr; return nullptr;
} }