Compare commits
12 Commits
android-25
...
android-25
Author | SHA1 | Date | |
---|---|---|---|
1c5e43502a | |||
2d4ae8dce6 | |||
6a1bb4430c | |||
a1ff7c92bf | |||
b6121c8c97 | |||
ae9140a715 | |||
7a10fdd2f2 | |||
3007032b12 | |||
fb34d40a1c | |||
abcb021211 | |||
807fbad23c | |||
7a1e249314 |
@ -7,9 +7,10 @@
|
||||
| [13006](https://github.com/yuzu-emu/yuzu//pull/13006) | [`3067bfd12`](https://github.com/yuzu-emu/yuzu//pull/13006/files) | buffer_cache: use mapped range with large vertex buffer size | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||
| [13026](https://github.com/yuzu-emu/yuzu//pull/13026) | [`462ea921e`](https://github.com/yuzu-emu/yuzu//pull/13026/files) | shader_recompiler: fix non-const offset for arrayed image types | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||
| [13030](https://github.com/yuzu-emu/yuzu//pull/13030) | [`4cbafc1ef`](https://github.com/yuzu-emu/yuzu//pull/13030/files) | service: audio: Rewrite IAudioController to new IPC | [german77](https://github.com/german77/) | Yes |
|
||||
| [13035](https://github.com/yuzu-emu/yuzu//pull/13035) | [`940a71422`](https://github.com/yuzu-emu/yuzu//pull/13035/files) | vi: manage resources independently of nvnflinger and refactor | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||
| [13048](https://github.com/yuzu-emu/yuzu//pull/13048) | [`4cdf18095`](https://github.com/yuzu-emu/yuzu//pull/13048/files) | ns: rewrite for new IPC | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||
| [13070](https://github.com/yuzu-emu/yuzu//pull/13070) | [`911ee8fd1`](https://github.com/yuzu-emu/yuzu//pull/13070/files) | am: account for offset in transfer memory storage | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||
| [13032](https://github.com/yuzu-emu/yuzu//pull/13032) | [`ec02a1cfe`](https://github.com/yuzu-emu/yuzu//pull/13032/files) | service: Implement functions needed by Qlaunch | [german77](https://github.com/german77/) | Yes |
|
||||
| [13034](https://github.com/yuzu-emu/yuzu//pull/13034) | [`50ecad547`](https://github.com/yuzu-emu/yuzu//pull/13034/files) | android: Input mapping | [t895](https://github.com/t895/) | Yes |
|
||||
| [13035](https://github.com/yuzu-emu/yuzu//pull/13035) | [`a07f0883b`](https://github.com/yuzu-emu/yuzu//pull/13035/files) | vi: manage resources independently of nvnflinger and refactor | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||
| [13048](https://github.com/yuzu-emu/yuzu//pull/13048) | [`72d806db0`](https://github.com/yuzu-emu/yuzu//pull/13048/files) | ns: rewrite for new IPC | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||
|
||||
|
||||
End of merge log. You can find the original README.md below the break.
|
||||
|
@ -3,21 +3,24 @@
|
||||
|
||||
package org.yuzu.yuzu_emu
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.Html
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.Surface
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.Keep
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import java.lang.ref.WeakReference
|
||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||
import org.yuzu.yuzu_emu.fragments.CoreErrorDialogFragment
|
||||
import org.yuzu.yuzu_emu.utils.DocumentsTree
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||
import org.yuzu.yuzu_emu.utils.Log
|
||||
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
|
||||
import org.yuzu.yuzu_emu.model.InstallResult
|
||||
import org.yuzu.yuzu_emu.model.Patch
|
||||
import org.yuzu.yuzu_emu.model.GameVerificationResult
|
||||
@ -181,13 +184,46 @@ object NativeLibrary {
|
||||
ErrorUnknown
|
||||
}
|
||||
|
||||
var coreErrorAlertResult = false
|
||||
val coreErrorAlertLock = Object()
|
||||
private var coreErrorAlertResult = false
|
||||
private val coreErrorAlertLock = Object()
|
||||
|
||||
class CoreErrorDialogFragment : DialogFragment() {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val title = requireArguments().serializable<String>("title")
|
||||
val message = requireArguments().serializable<String>("message")
|
||||
|
||||
return MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(R.string.continue_button, null)
|
||||
.setNegativeButton(R.string.abort_button) { _: DialogInterface?, _: Int ->
|
||||
coreErrorAlertResult = false
|
||||
synchronized(coreErrorAlertLock) { coreErrorAlertLock.notify() }
|
||||
}
|
||||
.create()
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
coreErrorAlertResult = true
|
||||
synchronized(coreErrorAlertLock) { coreErrorAlertLock.notify() }
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(title: String?, message: String?): CoreErrorDialogFragment {
|
||||
val frag = CoreErrorDialogFragment()
|
||||
val args = Bundle()
|
||||
args.putString("title", title)
|
||||
args.putString("message", message)
|
||||
frag.arguments = args
|
||||
return frag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCoreErrorImpl(title: String, message: String) {
|
||||
val emulationActivity = sEmulationActivity.get()
|
||||
if (emulationActivity == null) {
|
||||
Log.error("[NativeLibrary] EmulationActivity not present")
|
||||
error("[NativeLibrary] EmulationActivity not present")
|
||||
return
|
||||
}
|
||||
|
||||
@ -203,7 +239,7 @@ object NativeLibrary {
|
||||
fun onCoreError(error: CoreError?, details: String): Boolean {
|
||||
val emulationActivity = sEmulationActivity.get()
|
||||
if (emulationActivity == null) {
|
||||
Log.error("[NativeLibrary] EmulationActivity not present")
|
||||
error("[NativeLibrary] EmulationActivity not present")
|
||||
return false
|
||||
}
|
||||
|
||||
@ -234,7 +270,7 @@ object NativeLibrary {
|
||||
}
|
||||
|
||||
// Show the AlertDialog on the main thread.
|
||||
emulationActivity.runOnUiThread { onCoreErrorImpl(title, message) }
|
||||
emulationActivity.runOnUiThread(Runnable { onCoreErrorImpl(title, message) })
|
||||
|
||||
// Wait for the lock to notify that it is complete.
|
||||
synchronized(coreErrorAlertLock) { coreErrorAlertLock.wait() }
|
||||
|
@ -80,14 +80,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
InputHandler.updateControllerData()
|
||||
val players = NativeConfig.getInputSettings(true)
|
||||
var hasConfiguredControllers = false
|
||||
players.forEach {
|
||||
if (it.hasMapping()) {
|
||||
hasConfiguredControllers = true
|
||||
}
|
||||
}
|
||||
if (!hasConfiguredControllers && InputHandler.androidControllers.isNotEmpty()) {
|
||||
val playerOne = NativeConfig.getInputSettings(true)[0]
|
||||
if (!playerOne.hasMapping() && InputHandler.androidControllers.isNotEmpty()) {
|
||||
var params: ParamPackage? = null
|
||||
for (controller in InputHandler.registeredControllers) {
|
||||
if (controller.get("port", -1) == 0) {
|
||||
|
@ -3,15 +3,15 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
||||
import org.yuzu.yuzu_emu.model.Driver
|
||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||
|
||||
class DriverAdapter(private val driverViewModel: DriverViewModel) :
|
||||
@ -44,15 +44,25 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) :
|
||||
}
|
||||
|
||||
// Delay marquee by 3s
|
||||
title.marquee()
|
||||
version.marquee()
|
||||
description.marquee()
|
||||
title.postDelayed(
|
||||
{
|
||||
title.isSelected = true
|
||||
title.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
version.isSelected = true
|
||||
version.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
description.isSelected = true
|
||||
description.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
},
|
||||
3000
|
||||
)
|
||||
title.text = model.title
|
||||
version.text = model.version
|
||||
description.text = model.description
|
||||
buttonDelete.setVisible(
|
||||
model.title != binding.root.context.getString(R.string.system_gpu_driver)
|
||||
)
|
||||
if (model.title != binding.root.context.getString(R.string.system_gpu_driver)) {
|
||||
buttonDelete.visibility = View.VISIBLE
|
||||
} else {
|
||||
buttonDelete.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.net.Uri
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
@ -11,7 +12,6 @@ import org.yuzu.yuzu_emu.databinding.CardFolderBinding
|
||||
import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment
|
||||
import org.yuzu.yuzu_emu.model.GameDir
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||
|
||||
class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) :
|
||||
@ -29,7 +29,13 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie
|
||||
override fun bind(model: GameDir) {
|
||||
binding.apply {
|
||||
path.text = Uri.parse(model.uriString).path
|
||||
path.marquee()
|
||||
path.postDelayed(
|
||||
{
|
||||
path.isSelected = true
|
||||
path.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
},
|
||||
3000
|
||||
)
|
||||
|
||||
buttonEdit.setOnClickListener {
|
||||
GameFolderPropertiesDialogFragment.newInstance(model)
|
||||
|
@ -4,6 +4,7 @@
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.net.Uri
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
@ -26,7 +27,6 @@ import org.yuzu.yuzu_emu.databinding.CardGameBinding
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.utils.GameIconUtils
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||
|
||||
class GameAdapter(private val activity: AppCompatActivity) :
|
||||
@ -44,7 +44,14 @@ class GameAdapter(private val activity: AppCompatActivity) :
|
||||
|
||||
binding.textGameTitle.text = model.title.replace("[\\t\\n\\r]+".toRegex(), " ")
|
||||
|
||||
binding.textGameTitle.marquee()
|
||||
binding.textGameTitle.postDelayed(
|
||||
{
|
||||
binding.textGameTitle.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
binding.textGameTitle.isSelected = true
|
||||
},
|
||||
3000
|
||||
)
|
||||
|
||||
binding.cardGame.setOnClickListener { onClick(model) }
|
||||
binding.cardGame.setOnLongClickListener { onLongClick(model) }
|
||||
}
|
||||
|
@ -3,18 +3,21 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding
|
||||
import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
|
||||
import org.yuzu.yuzu_emu.model.GameProperty
|
||||
import org.yuzu.yuzu_emu.model.InstallableProperty
|
||||
import org.yuzu.yuzu_emu.model.SubmenuProperty
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||
|
||||
class GamePropertiesAdapter(
|
||||
@ -73,15 +76,23 @@ class GamePropertiesAdapter(
|
||||
)
|
||||
)
|
||||
|
||||
binding.details.marquee()
|
||||
binding.details.postDelayed({
|
||||
binding.details.isSelected = true
|
||||
binding.details.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
}, 3000)
|
||||
|
||||
if (submenuProperty.details != null) {
|
||||
binding.details.setVisible(true)
|
||||
binding.details.visibility = View.VISIBLE
|
||||
binding.details.text = submenuProperty.details.invoke()
|
||||
} else if (submenuProperty.detailsFlow != null) {
|
||||
binding.details.setVisible(true)
|
||||
submenuProperty.detailsFlow.collect(viewLifecycle) { binding.details.text = it }
|
||||
binding.details.visibility = View.VISIBLE
|
||||
viewLifecycle.lifecycleScope.launch {
|
||||
viewLifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
submenuProperty.detailsFlow.collect { binding.details.text = it }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.details.setVisible(false)
|
||||
binding.details.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -101,10 +112,14 @@ class GamePropertiesAdapter(
|
||||
)
|
||||
)
|
||||
|
||||
binding.buttonInstall.setVisible(installableProperty.install != null)
|
||||
binding.buttonInstall.setOnClickListener { installableProperty.install?.invoke() }
|
||||
binding.buttonExport.setVisible(installableProperty.export != null)
|
||||
binding.buttonExport.setOnClickListener { installableProperty.export?.invoke() }
|
||||
if (installableProperty.install != null) {
|
||||
binding.buttonInstall.visibility = View.VISIBLE
|
||||
binding.buttonInstall.setOnClickListener { installableProperty.install.invoke() }
|
||||
}
|
||||
if (installableProperty.export != null) {
|
||||
binding.buttonExport.visibility = View.VISIBLE
|
||||
binding.buttonExport.setOnClickListener { installableProperty.export.invoke() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,19 +3,22 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
|
||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||
import org.yuzu.yuzu_emu.model.HomeSetting
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||
|
||||
class HomeSettingAdapter(
|
||||
@ -56,8 +59,18 @@ class HomeSettingAdapter(
|
||||
binding.optionIcon.alpha = 0.5f
|
||||
}
|
||||
|
||||
model.details.collect(viewLifecycle) { updateOptionDetails(it) }
|
||||
binding.optionDetail.marquee()
|
||||
viewLifecycle.lifecycleScope.launch {
|
||||
viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
model.details.collect { updateOptionDetails(it) }
|
||||
}
|
||||
}
|
||||
binding.optionDetail.postDelayed(
|
||||
{
|
||||
binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
binding.optionDetail.isSelected = true
|
||||
},
|
||||
3000
|
||||
)
|
||||
|
||||
binding.root.setOnClickListener { onClick(model) }
|
||||
}
|
||||
@ -77,7 +90,7 @@ class HomeSettingAdapter(
|
||||
private fun updateOptionDetails(detailString: String) {
|
||||
if (detailString.isNotEmpty()) {
|
||||
binding.optionDetail.text = detailString
|
||||
binding.optionDetail.setVisible(true)
|
||||
binding.optionDetail.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,10 +4,10 @@
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import org.yuzu.yuzu_emu.databinding.CardInstallableBinding
|
||||
import org.yuzu.yuzu_emu.model.Installable
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||
|
||||
class InstallableAdapter(installables: List<Installable>) :
|
||||
@ -26,10 +26,14 @@ class InstallableAdapter(installables: List<Installable>) :
|
||||
binding.title.setText(model.titleId)
|
||||
binding.description.setText(model.descriptionId)
|
||||
|
||||
binding.buttonInstall.setVisible(model.install != null)
|
||||
binding.buttonInstall.setOnClickListener { model.install?.invoke() }
|
||||
binding.buttonExport.setVisible(model.export != null)
|
||||
binding.buttonExport.setOnClickListener { model.export?.invoke() }
|
||||
if (model.install != null) {
|
||||
binding.buttonInstall.visibility = View.VISIBLE
|
||||
binding.buttonInstall.setOnClickListener { model.install.invoke() }
|
||||
}
|
||||
if (model.export != null) {
|
||||
binding.buttonExport.visibility = View.VISIBLE
|
||||
binding.buttonExport.setOnClickListener { model.export.invoke() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,12 +4,12 @@
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||
import org.yuzu.yuzu_emu.fragments.LicenseBottomSheetDialogFragment
|
||||
import org.yuzu.yuzu_emu.model.License
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||
|
||||
class LicenseAdapter(private val activity: AppCompatActivity, licenses: List<License>) :
|
||||
@ -25,7 +25,7 @@ class LicenseAdapter(private val activity: AppCompatActivity, licenses: List<Lic
|
||||
binding.apply {
|
||||
textSettingName.text = root.context.getString(model.titleId)
|
||||
textSettingDescription.text = root.context.getString(model.descriptionId)
|
||||
textSettingValue.setVisible(false)
|
||||
textSettingValue.visibility = View.GONE
|
||||
|
||||
root.setOnClickListener { onClick(model) }
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.text.Html
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
@ -16,7 +17,6 @@ import org.yuzu.yuzu_emu.model.SetupCallback
|
||||
import org.yuzu.yuzu_emu.model.SetupPage
|
||||
import org.yuzu.yuzu_emu.model.StepState
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||
|
||||
class SetupAdapter(val activity: AppCompatActivity, pages: List<SetupPage>) :
|
||||
@ -30,8 +30,8 @@ class SetupAdapter(val activity: AppCompatActivity, pages: List<SetupPage>) :
|
||||
AbstractViewHolder<SetupPage>(binding), SetupCallback {
|
||||
override fun bind(model: SetupPage) {
|
||||
if (model.stepCompleted.invoke() == StepState.COMPLETE) {
|
||||
binding.buttonAction.setVisible(visible = false, gone = false)
|
||||
binding.textConfirmation.setVisible(true)
|
||||
binding.buttonAction.visibility = View.INVISIBLE
|
||||
binding.textConfirmation.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
binding.icon.setImageDrawable(
|
||||
|
@ -11,13 +11,16 @@ import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.DialogInputProfilesBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.InputProfileSetting
|
||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class InputProfileDialogFragment : DialogFragment() {
|
||||
private var position = 0
|
||||
@ -107,21 +110,25 @@ class InputProfileDialogFragment : DialogFragment() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
settingsViewModel.shouldShowDeleteProfileDialog.collect(viewLifecycleOwner) {
|
||||
if (it.isNotEmpty()) {
|
||||
MessageDialogFragment.newInstance(
|
||||
activity = requireActivity(),
|
||||
titleId = R.string.delete_input_profile,
|
||||
descriptionId = R.string.delete_input_profile_description,
|
||||
positiveAction = {
|
||||
setting.deleteProfile(it)
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
},
|
||||
negativeAction = {},
|
||||
negativeButtonTitleId = android.R.string.cancel
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
settingsViewModel.setShouldShowDeleteProfileDialog("")
|
||||
dismiss()
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldShowDeleteProfileDialog.collect {
|
||||
if (it.isNotEmpty()) {
|
||||
MessageDialogFragment.newInstance(
|
||||
activity = requireActivity(),
|
||||
titleId = R.string.delete_input_profile,
|
||||
descriptionId = R.string.delete_input_profile_description,
|
||||
positiveAction = {
|
||||
setting.deleteProfile(it)
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
},
|
||||
negativeAction = {},
|
||||
negativeButtonTitleId = android.R.string.cancel
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
settingsViewModel.setShouldShowDeleteProfileDialog("")
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,9 +13,14 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
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.navArgs
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import java.io.IOException
|
||||
import org.yuzu.yuzu_emu.R
|
||||
@ -65,23 +70,39 @@ class SettingsActivity : AppCompatActivity() {
|
||||
)
|
||||
}
|
||||
|
||||
settingsViewModel.shouldRecreate.collect(
|
||||
this,
|
||||
resetState = { settingsViewModel.setShouldRecreate(false) }
|
||||
) { if (it) recreate() }
|
||||
settingsViewModel.shouldNavigateBack.collect(
|
||||
this,
|
||||
resetState = { settingsViewModel.setShouldNavigateBack(false) }
|
||||
) { if (it) navigateBack() }
|
||||
settingsViewModel.shouldShowResetSettingsDialog.collect(
|
||||
this,
|
||||
resetState = { settingsViewModel.setShouldShowResetSettingsDialog(false) }
|
||||
) {
|
||||
if (it) {
|
||||
ResetSettingsDialogFragment().show(
|
||||
supportFragmentManager,
|
||||
ResetSettingsDialogFragment.TAG
|
||||
)
|
||||
lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldRecreate.collectLatest {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldRecreate(false)
|
||||
recreate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldNavigateBack.collectLatest {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldNavigateBack(false)
|
||||
navigateBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldShowResetSettingsDialog.collectLatest {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldShowResetSettingsDialog(false)
|
||||
ResetSettingsDialogFragment().show(
|
||||
supportFragmentManager,
|
||||
ResetSettingsDialogFragment.TAG
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,8 +11,12 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.slider.Slider
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
|
||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
||||
@ -25,7 +29,6 @@ import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
|
||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener {
|
||||
private var type = 0
|
||||
@ -166,11 +169,17 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
when (type) {
|
||||
SettingsItem.TYPE_SLIDER -> {
|
||||
settingsViewModel.sliderTextValue.collect(viewLifecycleOwner) {
|
||||
sliderBinding.textValue.text = it
|
||||
}
|
||||
settingsViewModel.sliderProgress.collect(viewLifecycleOwner) {
|
||||
sliderBinding.slider.value = it.toFloat()
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.sliderTextValue.collect {
|
||||
sliderBinding.textValue.text = it
|
||||
}
|
||||
}
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.sliderProgress.collect {
|
||||
sliderBinding.slider.value = it.toFloat()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,17 +13,21 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
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.databinding.FragmentSettingsBinding
|
||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class SettingsFragment : Fragment() {
|
||||
private lateinit var presenter: SettingsFragmentPresenter
|
||||
@ -59,7 +63,8 @@ class SettingsFragment : Fragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector", "NotifyDataSetChanged")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
settingsAdapter = SettingsAdapter(this, requireContext())
|
||||
@ -95,37 +100,65 @@ class SettingsFragment : Fragment() {
|
||||
settingsViewModel.setShouldNavigateBack(true)
|
||||
}
|
||||
|
||||
settingsViewModel.shouldReloadSettingsList.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { settingsViewModel.setShouldReloadSettingsList(false) }
|
||||
) { if (it) presenter.loadSettingsList() }
|
||||
settingsViewModel.adapterItemChanged.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { settingsViewModel.setAdapterItemChanged(-1) }
|
||||
) { if (it != -1) settingsAdapter?.notifyItemChanged(it) }
|
||||
settingsViewModel.datasetChanged.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { settingsViewModel.setDatasetChanged(false) }
|
||||
) { if (it) settingsAdapter?.notifyDataSetChanged() }
|
||||
settingsViewModel.reloadListAndNotifyDataset.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { settingsViewModel.setReloadListAndNotifyDataset(false) }
|
||||
) { if (it) presenter.loadSettingsList(true) }
|
||||
settingsViewModel.shouldShowResetInputDialog.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { settingsViewModel.setShouldShowResetInputDialog(false) }
|
||||
) {
|
||||
if (it) {
|
||||
MessageDialogFragment.newInstance(
|
||||
activity = requireActivity(),
|
||||
titleId = R.string.reset_mapping,
|
||||
descriptionId = R.string.reset_mapping_description,
|
||||
positiveAction = {
|
||||
NativeInput.resetControllerMappings(getPlayerIndex())
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
},
|
||||
negativeAction = {}
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldReloadSettingsList.collectLatest {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldReloadSettingsList(false)
|
||||
presenter.loadSettingsList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
settingsViewModel.adapterItemChanged.collect {
|
||||
if (it != -1) {
|
||||
settingsAdapter?.notifyItemChanged(it)
|
||||
settingsViewModel.setAdapterItemChanged(-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
settingsViewModel.datasetChanged.collect {
|
||||
if (it) {
|
||||
settingsAdapter?.notifyDataSetChanged()
|
||||
settingsViewModel.setDatasetChanged(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.reloadListAndNotifyDataset.collectLatest {
|
||||
if (it) {
|
||||
settingsViewModel.setReloadListAndNotifyDataset(false)
|
||||
presenter.loadSettingsList(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldShowResetInputDialog.collectLatest {
|
||||
if (it) {
|
||||
MessageDialogFragment.newInstance(
|
||||
activity = requireActivity(),
|
||||
titleId = R.string.reset_mapping,
|
||||
descriptionId = R.string.reset_mapping_description,
|
||||
positiveAction = {
|
||||
NativeInput.resetControllerMappings(getPlayerIndex())
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
},
|
||||
negativeAction = {}
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
settingsViewModel.setShouldShowResetInputDialog(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,17 +15,19 @@ import androidx.core.view.updatePadding
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.divider.MaterialDividerItemDecoration
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import info.debatty.java.stringsimilarity.Cosine
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class SettingsSearchFragment : Fragment() {
|
||||
private var _binding: FragmentSettingsSearchBinding? = null
|
||||
@ -81,10 +83,14 @@ class SettingsSearchFragment : Fragment() {
|
||||
search()
|
||||
binding.settingsList.smoothScrollToPosition(0)
|
||||
}
|
||||
settingsViewModel.shouldReloadSettingsList.collect(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldReloadSettingsList(false)
|
||||
search()
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldReloadSettingsList.collect {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldReloadSettingsList(false)
|
||||
search()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,9 +106,10 @@ class SettingsSearchFragment : Fragment() {
|
||||
|
||||
private fun search() {
|
||||
val searchTerm = binding.searchText.text.toString().lowercase()
|
||||
binding.clearButton.setVisible(visible = searchTerm.isNotEmpty(), gone = false)
|
||||
binding.clearButton.visibility =
|
||||
if (searchTerm.isEmpty()) View.INVISIBLE else View.VISIBLE
|
||||
if (searchTerm.isEmpty()) {
|
||||
binding.noResultsView.setVisible(visible = false, gone = false)
|
||||
binding.noResultsView.visibility = View.VISIBLE
|
||||
settingsAdapter?.submitList(emptyList())
|
||||
return
|
||||
}
|
||||
@ -129,7 +136,8 @@ class SettingsSearchFragment : Fragment() {
|
||||
optionalSetting
|
||||
}
|
||||
settingsAdapter?.submitList(sortedList)
|
||||
binding.noResultsView.setVisible(visible = sortedList.isEmpty(), gone = false)
|
||||
binding.noResultsView.visibility =
|
||||
if (sortedList.isEmpty()) View.VISIBLE else View.INVISIBLE
|
||||
}
|
||||
|
||||
private fun focusSearch() {
|
||||
|
@ -14,7 +14,6 @@ import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
@ -23,18 +22,27 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item as DateTimeSetting
|
||||
binding.textSettingName.text = item.title
|
||||
binding.textSettingDescription.setVisible(item.description.isNotEmpty())
|
||||
binding.textSettingDescription.text = item.description
|
||||
binding.textSettingValue.setVisible(true)
|
||||
if (setting.description.isNotEmpty()) {
|
||||
binding.textSettingDescription.text = item.description
|
||||
binding.textSettingDescription.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.textSettingValue.visibility = View.VISIBLE
|
||||
val epochTime = setting.getValue()
|
||||
val instant = Instant.ofEpochMilli(epochTime * 1000)
|
||||
val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
|
||||
val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
|
||||
binding.textSettingValue.text = dateFormatter.format(zonedTime)
|
||||
|
||||
binding.buttonClear.setVisible(
|
||||
!setting.setting.global || NativeConfig.isPerGameConfigLoaded()
|
||||
)
|
||||
binding.buttonClear.visibility = if (setting.setting.global ||
|
||||
!NativeConfig.isPerGameConfigLoaded()
|
||||
) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
binding.buttonClear.setOnClickListener {
|
||||
adapter.onClearClick(setting, bindingAdapterPosition)
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import org.yuzu.yuzu_emu.features.settings.model.view.InputProfileSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class InputProfileViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
@ -21,10 +20,10 @@ class InputProfileViewHolder(val binding: ListItemSettingBinding, adapter: Setti
|
||||
binding.textSettingValue.text =
|
||||
setting.getCurrentProfile().ifEmpty { binding.root.context.getString(R.string.not_set) }
|
||||
|
||||
binding.textSettingDescription.setVisible(false)
|
||||
binding.buttonClear.setVisible(false)
|
||||
binding.icon.setVisible(false)
|
||||
binding.buttonClear.setVisible(false)
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
binding.buttonClear.visibility = View.GONE
|
||||
binding.icon.visibility = View.GONE
|
||||
binding.buttonClear.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun onClick(clicked: View) =
|
||||
|
@ -12,7 +12,6 @@ import org.yuzu.yuzu_emu.features.settings.model.view.InputSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.ModifierInputSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class InputViewHolder(val binding: ListItemSettingInputBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
@ -23,26 +22,38 @@ class InputViewHolder(val binding: ListItemSettingInputBinding, adapter: Setting
|
||||
binding.textSettingName.text = setting.title
|
||||
binding.textSettingValue.text = setting.getSelectedValue()
|
||||
|
||||
when (item) {
|
||||
binding.buttonOptions.visibility = when (item) {
|
||||
is AnalogInputSetting -> {
|
||||
val param = NativeInput.getStickParam(item.playerIndex, item.nativeAnalog)
|
||||
binding.buttonOptions.setVisible(
|
||||
if (
|
||||
param.get("engine", "") == "analog_from_button" ||
|
||||
param.has("axis_x") || param.has("axis_y")
|
||||
)
|
||||
param.has("axis_x") || param.has("axis_y")
|
||||
) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
is ButtonInputSetting -> {
|
||||
val param = NativeInput.getButtonParam(item.playerIndex, item.nativeButton)
|
||||
binding.buttonOptions.setVisible(
|
||||
if (
|
||||
param.has("code") || param.has("button") || param.has("hat") ||
|
||||
param.has("axis")
|
||||
)
|
||||
param.has("axis")
|
||||
) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
is ModifierInputSetting -> {
|
||||
val params = NativeInput.getStickParam(item.playerIndex, item.nativeAnalog)
|
||||
binding.buttonOptions.setVisible(params.has("modifier"))
|
||||
if (params.has("modifier")) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,6 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.RunnableSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
@ -17,8 +16,8 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
|
||||
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item as RunnableSetting
|
||||
binding.icon.setVisible(setting.iconId != 0)
|
||||
if (setting.iconId != 0) {
|
||||
binding.icon.visibility = View.VISIBLE
|
||||
binding.icon.setImageDrawable(
|
||||
ResourcesCompat.getDrawable(
|
||||
binding.icon.resources,
|
||||
@ -26,13 +25,19 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
|
||||
binding.icon.context.theme
|
||||
)
|
||||
)
|
||||
} else {
|
||||
binding.icon.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.textSettingName.text = setting.title
|
||||
binding.textSettingDescription.setVisible(setting.description.isNotEmpty())
|
||||
binding.textSettingDescription.text = item.description
|
||||
binding.textSettingValue.setVisible(false)
|
||||
binding.buttonClear.setVisible(false)
|
||||
if (setting.description.isNotEmpty()) {
|
||||
binding.textSettingDescription.setText(item.descriptionId)
|
||||
binding.textSettingDescription.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
binding.textSettingValue.visibility = View.GONE
|
||||
binding.buttonClear.visibility = View.GONE
|
||||
|
||||
setStyle(setting.isEditable, binding)
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
@ -20,10 +19,14 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item
|
||||
binding.textSettingName.text = setting.title
|
||||
binding.textSettingDescription.setVisible(item.description.isNotEmpty())
|
||||
binding.textSettingDescription.text = item.description
|
||||
if (item.description.isNotEmpty()) {
|
||||
binding.textSettingDescription.text = item.description
|
||||
binding.textSettingDescription.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.textSettingValue.setVisible(true)
|
||||
binding.textSettingValue.visibility = View.VISIBLE
|
||||
when (item) {
|
||||
is SingleChoiceSetting -> {
|
||||
val resMgr = binding.textSettingValue.context.resources
|
||||
@ -45,12 +48,16 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
|
||||
}
|
||||
}
|
||||
if (binding.textSettingValue.text.isEmpty()) {
|
||||
binding.textSettingValue.setVisible(false)
|
||||
binding.textSettingValue.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.buttonClear.setVisible(
|
||||
!setting.setting.global || NativeConfig.isPerGameConfigLoaded()
|
||||
)
|
||||
binding.buttonClear.visibility = if (setting.setting.global ||
|
||||
!NativeConfig.isPerGameConfigLoaded()
|
||||
) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
binding.buttonClear.setOnClickListener {
|
||||
adapter.onClearClick(setting, bindingAdapterPosition)
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
@ -19,18 +18,26 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item as SliderSetting
|
||||
binding.textSettingName.text = setting.title
|
||||
binding.textSettingDescription.setVisible(item.description.isNotEmpty())
|
||||
binding.textSettingDescription.text = setting.description
|
||||
binding.textSettingValue.setVisible(true)
|
||||
if (item.description.isNotEmpty()) {
|
||||
binding.textSettingDescription.text = setting.description
|
||||
binding.textSettingDescription.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
binding.textSettingValue.visibility = View.VISIBLE
|
||||
binding.textSettingValue.text = String.format(
|
||||
binding.textSettingValue.context.getString(R.string.value_with_units),
|
||||
setting.getSelectedValue(),
|
||||
setting.units
|
||||
)
|
||||
|
||||
binding.buttonClear.setVisible(
|
||||
!setting.setting.global || NativeConfig.isPerGameConfigLoaded()
|
||||
)
|
||||
binding.buttonClear.visibility = if (setting.setting.global ||
|
||||
!NativeConfig.isPerGameConfigLoaded()
|
||||
) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
binding.buttonClear.setOnClickListener {
|
||||
adapter.onClearClick(setting, bindingAdapterPosition)
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SubmenuSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
@ -17,8 +16,8 @@ class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAd
|
||||
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item as SubmenuSetting
|
||||
binding.icon.setVisible(setting.iconId != 0)
|
||||
if (setting.iconId != 0) {
|
||||
binding.icon.visibility = View.VISIBLE
|
||||
binding.icon.setImageDrawable(
|
||||
ResourcesCompat.getDrawable(
|
||||
binding.icon.resources,
|
||||
@ -26,13 +25,19 @@ class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAd
|
||||
binding.icon.context.theme
|
||||
)
|
||||
)
|
||||
} else {
|
||||
binding.icon.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.textSettingName.text = setting.title
|
||||
binding.textSettingDescription.setVisible(setting.description.isNotEmpty())
|
||||
binding.textSettingDescription.text = setting.description
|
||||
binding.textSettingValue.setVisible(false)
|
||||
binding.buttonClear.setVisible(false)
|
||||
if (setting.description.isNotEmpty()) {
|
||||
binding.textSettingDescription.text = setting.description
|
||||
binding.textSettingDescription.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
binding.textSettingValue.visibility = View.GONE
|
||||
binding.buttonClear.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun onClick(clicked: View) {
|
||||
|
@ -10,7 +10,6 @@ import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
@ -20,8 +19,12 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item as SwitchSetting
|
||||
binding.textSettingName.text = setting.title
|
||||
binding.textSettingDescription.setVisible(setting.description.isNotEmpty())
|
||||
binding.textSettingDescription.text = setting.description
|
||||
if (setting.description.isNotEmpty()) {
|
||||
binding.textSettingDescription.text = setting.description
|
||||
binding.textSettingDescription.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.switchWidget.setOnCheckedChangeListener(null)
|
||||
binding.switchWidget.isChecked = setting.getIsChecked(setting.needsRuntimeGlobal)
|
||||
@ -29,9 +32,13 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
|
||||
adapter.onBooleanClick(setting, binding.switchWidget.isChecked, bindingAdapterPosition)
|
||||
}
|
||||
|
||||
binding.buttonClear.setVisible(
|
||||
!setting.setting.global || NativeConfig.isPerGameConfigLoaded()
|
||||
)
|
||||
binding.buttonClear.visibility = if (setting.setting.global ||
|
||||
!NativeConfig.isPerGameConfigLoaded()
|
||||
) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
binding.buttonClear.setOnClickListener {
|
||||
adapter.onClearClick(setting, bindingAdapterPosition)
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
@ -15,6 +16,9 @@ import androidx.core.view.updatePadding
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
@ -28,7 +32,6 @@ import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.utils.AddonUtil
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil.copyFilesTo
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
import java.io.File
|
||||
|
||||
class AddonsFragment : Fragment() {
|
||||
@ -57,6 +60,8 @@ class AddonsFragment : Fragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
homeViewModel.setNavigationVisibility(visible = false, animated = false)
|
||||
@ -73,41 +78,57 @@ class AddonsFragment : Fragment() {
|
||||
adapter = AddonAdapter(addonViewModel)
|
||||
}
|
||||
|
||||
addonViewModel.addonList.collect(viewLifecycleOwner) {
|
||||
(binding.listAddons.adapter as AddonAdapter).submitList(it)
|
||||
}
|
||||
addonViewModel.showModInstallPicker.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { addonViewModel.showModInstallPicker(false) }
|
||||
) { if (it) installAddon.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) }
|
||||
addonViewModel.showModNoticeDialog.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { addonViewModel.showModNoticeDialog(false) }
|
||||
) {
|
||||
if (it) {
|
||||
MessageDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
titleId = R.string.addon_notice,
|
||||
descriptionId = R.string.addon_notice_description,
|
||||
dismissible = false,
|
||||
positiveAction = { addonViewModel.showModInstallPicker(true) },
|
||||
negativeAction = {},
|
||||
negativeButtonTitleId = R.string.close
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
addonViewModel.addonList.collect {
|
||||
(binding.listAddons.adapter as AddonAdapter).submitList(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
addonViewModel.addonToDelete.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { addonViewModel.setAddonToDelete(null) }
|
||||
) {
|
||||
if (it != null) {
|
||||
MessageDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
titleId = R.string.confirm_uninstall,
|
||||
descriptionId = R.string.confirm_uninstall_description,
|
||||
positiveAction = { addonViewModel.onDeleteAddon(it) },
|
||||
negativeAction = {}
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
addonViewModel.showModInstallPicker.collect {
|
||||
if (it) {
|
||||
installAddon.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
|
||||
addonViewModel.showModInstallPicker(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
addonViewModel.showModNoticeDialog.collect {
|
||||
if (it) {
|
||||
MessageDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
titleId = R.string.addon_notice,
|
||||
descriptionId = R.string.addon_notice_description,
|
||||
dismissible = false,
|
||||
positiveAction = { addonViewModel.showModInstallPicker(true) },
|
||||
negativeAction = {},
|
||||
negativeButtonTitleId = R.string.close
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
addonViewModel.showModNoticeDialog(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
addonViewModel.addonToDelete.collect {
|
||||
if (it != null) {
|
||||
MessageDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
titleId = R.string.confirm_uninstall,
|
||||
descriptionId = R.string.confirm_uninstall_description,
|
||||
positiveAction = { addonViewModel.onDeleteAddon(it) },
|
||||
negativeAction = {}
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
addonViewModel.setAddonToDelete(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,47 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
|
||||
class CoreErrorDialogFragment : DialogFragment() {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
|
||||
MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(requireArguments().getString(TITLE))
|
||||
.setMessage(requireArguments().getString(MESSAGE))
|
||||
.setPositiveButton(R.string.continue_button, null)
|
||||
.setNegativeButton(R.string.abort_button) { _: DialogInterface?, _: Int ->
|
||||
NativeLibrary.coreErrorAlertResult = false
|
||||
synchronized(NativeLibrary.coreErrorAlertLock) {
|
||||
NativeLibrary.coreErrorAlertLock.notify()
|
||||
}
|
||||
}
|
||||
.create()
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
super.onDismiss(dialog)
|
||||
NativeLibrary.coreErrorAlertResult = true
|
||||
synchronized(NativeLibrary.coreErrorAlertLock) { NativeLibrary.coreErrorAlertLock.notify() }
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TITLE = "Title"
|
||||
const val MESSAGE = "Message"
|
||||
|
||||
fun newInstance(title: String, message: String): CoreErrorDialogFragment {
|
||||
val frag = CoreErrorDialogFragment()
|
||||
val args = Bundle()
|
||||
args.putString(TITLE, title)
|
||||
args.putString(MESSAGE, message)
|
||||
frag.arguments = args
|
||||
return frag
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@ -13,6 +14,9 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
@ -31,7 +35,6 @@ import org.yuzu.yuzu_emu.utils.FileUtil
|
||||
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
@ -60,6 +63,8 @@ class DriverManagerFragment : Fragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
homeViewModel.setNavigationVisibility(visible = false, animated = true)
|
||||
@ -84,8 +89,15 @@ class DriverManagerFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
driverViewModel.showClearButton.collect(viewLifecycleOwner) {
|
||||
binding.toolbarDrivers.menu.findItem(R.id.menu_driver_use_global).isVisible = it
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
driverViewModel.showClearButton.collect {
|
||||
binding.toolbarDrivers.menu
|
||||
.findItem(R.id.menu_driver_use_global).isVisible = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,11 +10,14 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class DriversLoadingDialogFragment : DialogFragment() {
|
||||
private val driverViewModel: DriverViewModel by activityViewModels()
|
||||
@ -41,7 +44,13 @@ class DriversLoadingDialogFragment : DialogFragment() {
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) { if (it) dismiss() }
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
driverViewModel.isInteractionAllowed.collect { if (it) dismiss() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -32,6 +32,9 @@ import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.drawerlayout.widget.DrawerLayout.DrawerListener
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.window.layout.FoldingFeature
|
||||
@ -39,6 +42,9 @@ import androidx.window.layout.WindowInfoTracker
|
||||
import androidx.window.layout.WindowLayoutInfo
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.slider.Slider
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
@ -57,7 +63,6 @@ import org.yuzu.yuzu_emu.model.EmulationViewModel
|
||||
import org.yuzu.yuzu_emu.overlay.model.OverlayControl
|
||||
import org.yuzu.yuzu_emu.overlay.model.OverlayLayout
|
||||
import org.yuzu.yuzu_emu.utils.*
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import java.lang.NullPointerException
|
||||
|
||||
class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
@ -85,6 +90,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
if (context is EmulationActivity) {
|
||||
emulationActivity = context
|
||||
NativeLibrary.setEmulationActivity(context)
|
||||
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
WindowInfoTracker.getOrCreate(context)
|
||||
.windowLayoutInfo(context)
|
||||
.collect { updateFoldableLayout(context, it) }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw IllegalStateException("EmulationFragment must have EmulationActivity parent")
|
||||
}
|
||||
@ -155,6 +168,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
if (requireActivity().isFinishing) {
|
||||
@ -335,85 +350,128 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
binding.loadingTitle.isSelected = true
|
||||
binding.loadingText.isSelected = true
|
||||
|
||||
WindowInfoTracker.getOrCreate(requireContext())
|
||||
.windowLayoutInfo(requireActivity()).collect(viewLifecycleOwner) {
|
||||
updateFoldableLayout(requireActivity() as EmulationActivity, it)
|
||||
}
|
||||
emulationViewModel.shaderProgress.collect(viewLifecycleOwner) {
|
||||
if (it > 0 && it != emulationViewModel.totalShaders.value) {
|
||||
binding.loadingProgressIndicator.isIndeterminate = false
|
||||
|
||||
if (it < binding.loadingProgressIndicator.max) {
|
||||
binding.loadingProgressIndicator.progress = it
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
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 == emulationViewModel.totalShaders.value) {
|
||||
binding.loadingText.setText(R.string.loading)
|
||||
binding.loadingProgressIndicator.isIndeterminate = true
|
||||
}
|
||||
}
|
||||
emulationViewModel.totalShaders.collect(viewLifecycleOwner) {
|
||||
binding.loadingProgressIndicator.max = it
|
||||
}
|
||||
emulationViewModel.shaderMessage.collect(viewLifecycleOwner) {
|
||||
if (it.isNotEmpty()) {
|
||||
binding.loadingText.text = it
|
||||
}
|
||||
}
|
||||
if (it < binding.loadingProgressIndicator.max) {
|
||||
binding.loadingProgressIndicator.progress = it
|
||||
}
|
||||
}
|
||||
|
||||
emulationViewModel.emulationStarted.collect(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
binding.drawerLayout.setDrawerLockMode(IntSetting.LOCK_DRAWER.getInt())
|
||||
ViewUtils.showView(binding.surfaceInputOverlay)
|
||||
ViewUtils.hideView(binding.loadingIndicator)
|
||||
|
||||
emulationState.updateSurface()
|
||||
|
||||
// Setup overlays
|
||||
updateShowFpsOverlay()
|
||||
updateThermalOverlay()
|
||||
}
|
||||
}
|
||||
emulationViewModel.isEmulationStopping.collect(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
binding.loadingText.setText(R.string.shutting_down)
|
||||
ViewUtils.showView(binding.loadingIndicator)
|
||||
ViewUtils.hideView(binding.inputContainer)
|
||||
ViewUtils.hideView(binding.showFpsText)
|
||||
}
|
||||
}
|
||||
emulationViewModel.drawerOpen.collect(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
binding.drawerLayout.open()
|
||||
binding.inGameMenu.requestFocus()
|
||||
} else {
|
||||
binding.drawerLayout.close()
|
||||
}
|
||||
}
|
||||
emulationViewModel.programChanged.collect(viewLifecycleOwner) {
|
||||
if (it != 0) {
|
||||
emulationViewModel.setEmulationStarted(false)
|
||||
binding.drawerLayout.close()
|
||||
binding.drawerLayout
|
||||
.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
|
||||
ViewUtils.hideView(binding.surfaceInputOverlay)
|
||||
ViewUtils.showView(binding.loadingIndicator)
|
||||
}
|
||||
}
|
||||
emulationViewModel.emulationStopped.collect(viewLifecycleOwner) {
|
||||
if (it && emulationViewModel.programChanged.value != -1) {
|
||||
if (perfStatsUpdater != null) {
|
||||
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
|
||||
if (it == emulationViewModel.totalShaders.value) {
|
||||
binding.loadingText.setText(R.string.loading)
|
||||
binding.loadingProgressIndicator.isIndeterminate = true
|
||||
}
|
||||
}
|
||||
}
|
||||
emulationState.changeProgram(emulationViewModel.programChanged.value)
|
||||
emulationViewModel.setProgramChanged(-1)
|
||||
emulationViewModel.setEmulationStopped(false)
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.totalShaders.collectLatest {
|
||||
binding.loadingProgressIndicator.max = it
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.shaderMessage.collectLatest {
|
||||
if (it.isNotEmpty()) {
|
||||
binding.loadingText.text = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
driverViewModel.isInteractionAllowed.collect {
|
||||
if (it) {
|
||||
startEmulation()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.emulationStarted.collectLatest {
|
||||
if (it) {
|
||||
binding.drawerLayout.setDrawerLockMode(IntSetting.LOCK_DRAWER.getInt())
|
||||
ViewUtils.showView(binding.surfaceInputOverlay)
|
||||
ViewUtils.hideView(binding.loadingIndicator)
|
||||
|
||||
driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) {
|
||||
if (it) startEmulation()
|
||||
emulationState.updateSurface()
|
||||
|
||||
// Setup overlays
|
||||
updateShowFpsOverlay()
|
||||
updateThermalOverlay()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.isEmulationStopping.collectLatest {
|
||||
if (it) {
|
||||
binding.loadingText.setText(R.string.shutting_down)
|
||||
ViewUtils.showView(binding.loadingIndicator)
|
||||
ViewUtils.hideView(binding.inputContainer)
|
||||
ViewUtils.hideView(binding.showFpsText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.drawerOpen.collect {
|
||||
if (it) {
|
||||
binding.drawerLayout.open()
|
||||
binding.inGameMenu.requestFocus()
|
||||
} else {
|
||||
binding.drawerLayout.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.programChanged.collect {
|
||||
if (it != 0) {
|
||||
emulationViewModel.setEmulationStarted(false)
|
||||
binding.drawerLayout.close()
|
||||
binding.drawerLayout
|
||||
.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
|
||||
ViewUtils.hideView(binding.surfaceInputOverlay)
|
||||
ViewUtils.showView(binding.loadingIndicator)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.emulationStopped.collect {
|
||||
if (it && emulationViewModel.programChanged.value != -1) {
|
||||
if (perfStatsUpdater != null) {
|
||||
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
|
||||
}
|
||||
emulationState.changeProgram(emulationViewModel.programChanged.value)
|
||||
emulationViewModel.setProgramChanged(-1)
|
||||
emulationViewModel.setEmulationStopped(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -442,12 +500,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
binding.drawerLayout.close()
|
||||
}
|
||||
if (showInputOverlay) {
|
||||
binding.surfaceInputOverlay.setVisible(visible = false, gone = false)
|
||||
binding.surfaceInputOverlay.visibility = View.INVISIBLE
|
||||
}
|
||||
} else {
|
||||
binding.surfaceInputOverlay.setVisible(
|
||||
showInputOverlay && emulationViewModel.emulationStarted.value
|
||||
)
|
||||
if (showInputOverlay && emulationViewModel.emulationStarted.value) {
|
||||
binding.surfaceInputOverlay.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.surfaceInputOverlay.visibility = View.INVISIBLE
|
||||
}
|
||||
if (!isInFoldableLayout) {
|
||||
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
binding.surfaceInputOverlay.layout = OverlayLayout.Portrait
|
||||
@ -484,9 +544,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
|
||||
private fun updateShowFpsOverlay() {
|
||||
val showOverlay = BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()
|
||||
binding.showFpsText.setVisible(showOverlay)
|
||||
if (showOverlay) {
|
||||
if (BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()) {
|
||||
val SYSTEM_FPS = 0
|
||||
val FPS = 1
|
||||
val FRAMETIME = 2
|
||||
@ -506,17 +564,17 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
}
|
||||
perfStatsUpdateHandler.post(perfStatsUpdater!!)
|
||||
binding.showFpsText.visibility = View.VISIBLE
|
||||
} else {
|
||||
if (perfStatsUpdater != null) {
|
||||
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
|
||||
}
|
||||
binding.showFpsText.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateThermalOverlay() {
|
||||
val showOverlay = BooleanSetting.SHOW_THERMAL_OVERLAY.getBoolean()
|
||||
binding.showThermalsText.setVisible(showOverlay)
|
||||
if (showOverlay) {
|
||||
if (BooleanSetting.SHOW_THERMAL_OVERLAY.getBoolean()) {
|
||||
thermalStatsUpdater = {
|
||||
if (emulationViewModel.emulationStarted.value &&
|
||||
!emulationViewModel.isEmulationStopping.value
|
||||
@ -538,10 +596,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
}
|
||||
thermalStatsUpdateHandler.post(thermalStatsUpdater!!)
|
||||
binding.showThermalsText.visibility = View.VISIBLE
|
||||
} else {
|
||||
if (thermalStatsUpdater != null) {
|
||||
thermalStatsUpdateHandler.removeCallbacks(thermalStatsUpdater!!)
|
||||
}
|
||||
binding.showThermalsText.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
@ -810,12 +870,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.doneControlConfig.setVisible(false)
|
||||
binding.doneControlConfig.visibility = View.VISIBLE
|
||||
binding.surfaceInputOverlay.setIsInEditMode(true)
|
||||
}
|
||||
|
||||
private fun stopConfiguringControls() {
|
||||
binding.doneControlConfig.setVisible(false)
|
||||
binding.doneControlConfig.visibility = View.GONE
|
||||
binding.surfaceInputOverlay.setIsInEditMode(false)
|
||||
// Unlock the orientation if it was locked for editing
|
||||
if (IntSetting.RENDERER_SCREEN_LAYOUT.getInt() == EmulationOrientation.Unspecified.int) {
|
||||
|
@ -13,6 +13,9 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
@ -24,7 +27,6 @@ import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class GameFoldersFragment : Fragment() {
|
||||
private var _binding: FragmentFoldersBinding? = null
|
||||
@ -68,8 +70,12 @@ class GameFoldersFragment : Fragment() {
|
||||
adapter = FolderAdapter(requireActivity(), gamesViewModel)
|
||||
}
|
||||
|
||||
gamesViewModel.folders.collect(viewLifecycleOwner) {
|
||||
(binding.listFolders.adapter as FolderAdapter).submitList(it)
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
gamesViewModel.folders.collect {
|
||||
(binding.listFolders.adapter as FolderAdapter).submitList(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val mainActivity = requireActivity() as MainActivity
|
||||
|
@ -27,7 +27,6 @@ import org.yuzu.yuzu_emu.databinding.FragmentGameInfoBinding
|
||||
import org.yuzu.yuzu_emu.model.GameVerificationResult
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.utils.GameMetadata
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
|
||||
class GameInfoFragment : Fragment() {
|
||||
@ -86,7 +85,7 @@ class GameInfoFragment : Fragment() {
|
||||
copyToClipboard(getString(R.string.developer), args.game.developer)
|
||||
}
|
||||
} else {
|
||||
developer.setVisible(false)
|
||||
developer.visibility = View.GONE
|
||||
}
|
||||
|
||||
version.setHint(R.string.version)
|
||||
|
@ -3,9 +3,11 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.pm.ShortcutInfo
|
||||
import android.content.pm.ShortcutManager
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@ -16,7 +18,9 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
@ -42,9 +46,7 @@ import org.yuzu.yuzu_emu.utils.FileUtil
|
||||
import org.yuzu.yuzu_emu.utils.GameIconUtils
|
||||
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||
import org.yuzu.yuzu_emu.utils.MemoryUtil
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
|
||||
@ -74,6 +76,8 @@ class GamePropertiesFragment : Fragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
homeViewModel.setNavigationVisibility(visible = false, animated = true)
|
||||
@ -103,7 +107,13 @@ class GamePropertiesFragment : Fragment() {
|
||||
|
||||
GameIconUtils.loadGameIcon(args.game, binding.imageGameScreen)
|
||||
binding.title.text = args.game.title
|
||||
binding.title.marquee()
|
||||
binding.title.postDelayed(
|
||||
{
|
||||
binding.title.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
binding.title.isSelected = true
|
||||
},
|
||||
3000
|
||||
)
|
||||
|
||||
binding.buttonStart.setOnClickListener {
|
||||
LaunchGameDialogFragment.newInstance(args.game)
|
||||
@ -112,14 +122,28 @@ class GamePropertiesFragment : Fragment() {
|
||||
|
||||
reloadList()
|
||||
|
||||
homeViewModel.openImportSaves.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { homeViewModel.setOpenImportSaves(false) }
|
||||
) { if (it) importSaves.launch(arrayOf("application/zip")) }
|
||||
homeViewModel.reloadPropertiesList.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { homeViewModel.reloadPropertiesList(false) }
|
||||
) { if (it) reloadList() }
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
homeViewModel.openImportSaves.collect {
|
||||
if (it) {
|
||||
importSaves.launch(arrayOf("application/zip"))
|
||||
homeViewModel.setOpenImportSaves(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
homeViewModel.reloadPropertiesList.collect {
|
||||
if (it) {
|
||||
reloadList()
|
||||
homeViewModel.reloadPropertiesList(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setInsets()
|
||||
}
|
||||
|
@ -14,6 +14,9 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
@ -32,7 +35,6 @@ import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.math.BigInteger
|
||||
@ -73,10 +75,14 @@ class InstallableFragment : Fragment() {
|
||||
binding.root.findNavController().popBackStack()
|
||||
}
|
||||
|
||||
homeViewModel.openImportSaves.collect(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
importSaves.launch(arrayOf("application/zip"))
|
||||
homeViewModel.setOpenImportSaves(false)
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
homeViewModel.openImportSaves.collect {
|
||||
if (it) {
|
||||
importSaves.launch(arrayOf("application/zip"))
|
||||
homeViewModel.setOpenImportSaves(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,13 +13,15 @@ import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
||||
import org.yuzu.yuzu_emu.model.TaskViewModel
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class ProgressDialogFragment : DialogFragment() {
|
||||
private val taskViewModel: TaskViewModel by activityViewModels()
|
||||
@ -62,49 +64,71 @@ class ProgressDialogFragment : DialogFragment() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding.message.isSelected = true
|
||||
taskViewModel.isComplete.collect(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
dismiss()
|
||||
when (val result = taskViewModel.result.value) {
|
||||
is String -> Toast.makeText(
|
||||
requireContext(),
|
||||
result,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
taskViewModel.isComplete.collect {
|
||||
if (it) {
|
||||
dismiss()
|
||||
when (val result = taskViewModel.result.value) {
|
||||
is String -> Toast.makeText(
|
||||
requireContext(),
|
||||
result,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
|
||||
is MessageDialogFragment -> result.show(
|
||||
requireActivity().supportFragmentManager,
|
||||
MessageDialogFragment.TAG
|
||||
)
|
||||
is MessageDialogFragment -> result.show(
|
||||
requireActivity().supportFragmentManager,
|
||||
MessageDialogFragment.TAG
|
||||
)
|
||||
|
||||
else -> {
|
||||
// Do nothing
|
||||
else -> {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
taskViewModel.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
taskViewModel.clear()
|
||||
}
|
||||
}
|
||||
taskViewModel.cancelled.collect(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
dialog?.setTitle(R.string.cancelling)
|
||||
}
|
||||
}
|
||||
taskViewModel.progress.collect(viewLifecycleOwner) {
|
||||
if (it != 0.0) {
|
||||
binding.progressBar.apply {
|
||||
isIndeterminate = false
|
||||
progress = (
|
||||
(it / taskViewModel.maxProgress.value) *
|
||||
PROGRESS_BAR_RESOLUTION
|
||||
).toInt()
|
||||
min = 0
|
||||
max = PROGRESS_BAR_RESOLUTION
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
taskViewModel.cancelled.collect {
|
||||
if (it) {
|
||||
dialog?.setTitle(R.string.cancelling)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
taskViewModel.progress.collect {
|
||||
if (it != 0.0) {
|
||||
binding.progressBar.apply {
|
||||
isIndeterminate = false
|
||||
progress = (
|
||||
(it / taskViewModel.maxProgress.value) *
|
||||
PROGRESS_BAR_RESOLUTION
|
||||
).toInt()
|
||||
min = 0
|
||||
max = PROGRESS_BAR_RESOLUTION
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
taskViewModel.message.collect {
|
||||
if (it.isEmpty()) {
|
||||
binding.message.visibility = View.GONE
|
||||
} else {
|
||||
binding.message.visibility = View.VISIBLE
|
||||
binding.message.text = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
taskViewModel.message.collect(viewLifecycleOwner) {
|
||||
binding.message.setVisible(it.isNotEmpty())
|
||||
binding.message.text = it
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
@ -17,9 +18,14 @@ import androidx.core.view.updatePadding
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.preference.PreferenceManager
|
||||
import info.debatty.java.stringsimilarity.Jaccard
|
||||
import info.debatty.java.stringsimilarity.JaroWinkler
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Locale
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
@ -29,8 +35,6 @@ import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class SearchFragment : Fragment() {
|
||||
private var _binding: FragmentSearchBinding? = null
|
||||
@ -54,6 +58,8 @@ class SearchFragment : Fragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
homeViewModel.setNavigationVisibility(visible = true, animated = true)
|
||||
@ -75,18 +81,42 @@ class SearchFragment : Fragment() {
|
||||
binding.chipGroup.setOnCheckedStateChangeListener { _, _ -> filterAndSearch() }
|
||||
|
||||
binding.searchText.doOnTextChanged { text: CharSequence?, _: Int, _: Int, _: Int ->
|
||||
binding.clearButton.setVisible(text.toString().isNotEmpty())
|
||||
if (text.toString().isNotEmpty()) {
|
||||
binding.clearButton.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.clearButton.visibility = View.INVISIBLE
|
||||
}
|
||||
filterAndSearch()
|
||||
}
|
||||
|
||||
gamesViewModel.searchFocused.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { gamesViewModel.setSearchFocused(false) }
|
||||
) { if (it) focusSearch() }
|
||||
gamesViewModel.games.collect(viewLifecycleOwner) { filterAndSearch() }
|
||||
gamesViewModel.searchedGames.collect(viewLifecycleOwner) {
|
||||
(binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
|
||||
binding.noResultsView.setVisible(it.isNotEmpty())
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
gamesViewModel.searchFocused.collect {
|
||||
if (it) {
|
||||
focusSearch()
|
||||
gamesViewModel.setSearchFocused(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
gamesViewModel.games.collectLatest { filterAndSearch() }
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
gamesViewModel.searchedGames.collect {
|
||||
(binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
|
||||
if (it.isEmpty()) {
|
||||
binding.noResultsView.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.noResultsView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.clearButton.setOnClickListener { binding.searchText.setText("") }
|
||||
|
@ -4,6 +4,7 @@
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@ -22,6 +23,9 @@ import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
||||
@ -42,8 +46,6 @@ import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class SetupFragment : Fragment() {
|
||||
private var _binding: FragmentSetupBinding? = null
|
||||
@ -75,6 +77,8 @@ class SetupFragment : Fragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
mainActivity = requireActivity() as MainActivity
|
||||
|
||||
@ -206,14 +210,28 @@ class SetupFragment : Fragment() {
|
||||
)
|
||||
}
|
||||
|
||||
homeViewModel.shouldPageForward.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { homeViewModel.setShouldPageForward(false) }
|
||||
) { if (it) pageForward() }
|
||||
homeViewModel.gamesDirSelected.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { homeViewModel.setGamesDirSelected(false) }
|
||||
) { if (it) gamesDirCallback.onStepCompleted() }
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
homeViewModel.shouldPageForward.collect {
|
||||
if (it) {
|
||||
pageForward()
|
||||
homeViewModel.setShouldPageForward(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
homeViewModel.gamesDirSelected.collect {
|
||||
if (it) {
|
||||
gamesDirCallback.onStepCompleted()
|
||||
homeViewModel.setGamesDirSelected(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.viewPager2.apply {
|
||||
adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages)
|
||||
@ -274,8 +292,12 @@ class SetupFragment : Fragment() {
|
||||
val backIsVisible = savedInstanceState.getBoolean(KEY_BACK_VISIBILITY)
|
||||
hasBeenWarned = savedInstanceState.getBooleanArray(KEY_HAS_BEEN_WARNED)!!
|
||||
|
||||
binding.buttonNext.setVisible(nextIsVisible)
|
||||
binding.buttonBack.setVisible(backIsVisible)
|
||||
if (nextIsVisible) {
|
||||
binding.buttonNext.visibility = View.VISIBLE
|
||||
}
|
||||
if (backIsVisible) {
|
||||
binding.buttonBack.visibility = View.VISIBLE
|
||||
}
|
||||
} else {
|
||||
hasBeenWarned = BooleanArray(pages.size)
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@ -13,16 +14,19 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
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 kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
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.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class GamesFragment : Fragment() {
|
||||
private var _binding: FragmentGamesBinding? = null
|
||||
@ -40,6 +44,8 @@ class GamesFragment : Fragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
homeViewModel.setNavigationVisibility(visible = true, animated = true)
|
||||
@ -82,28 +88,49 @@ class GamesFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
gamesViewModel.isReloading.collect(viewLifecycleOwner) {
|
||||
binding.swipeRefresh.isRefreshing = it
|
||||
binding.noticeText.setVisible(
|
||||
visible = gamesViewModel.games.value.isEmpty() && !it,
|
||||
gone = false
|
||||
)
|
||||
}
|
||||
gamesViewModel.games.collect(viewLifecycleOwner) {
|
||||
(binding.gridGames.adapter as GameAdapter).submitList(it)
|
||||
}
|
||||
gamesViewModel.shouldSwapData.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { gamesViewModel.setShouldSwapData(false) }
|
||||
) {
|
||||
if (it) {
|
||||
(binding.gridGames.adapter as GameAdapter).submitList(gamesViewModel.games.value)
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
gamesViewModel.isReloading.collect {
|
||||
binding.swipeRefresh.isRefreshing = it
|
||||
if (gamesViewModel.games.value.isEmpty() && !it) {
|
||||
binding.noticeText.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.noticeText.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
gamesViewModel.games.collectLatest {
|
||||
(binding.gridGames.adapter as GameAdapter).submitList(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
gamesViewModel.shouldSwapData.collect {
|
||||
if (it) {
|
||||
(binding.gridGames.adapter as GameAdapter).submitList(
|
||||
gamesViewModel.games.value
|
||||
)
|
||||
gamesViewModel.setShouldSwapData(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
gamesViewModel.shouldScrollToTop.collect {
|
||||
if (it) {
|
||||
scrollToTop()
|
||||
gamesViewModel.setShouldScrollToTop(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
gamesViewModel.shouldScrollToTop.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { gamesViewModel.setShouldScrollToTop(false) }
|
||||
) { if (it) scrollToTop() }
|
||||
|
||||
setInsets()
|
||||
}
|
||||
|
@ -19,6 +19,9 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
@ -27,6 +30,7 @@ import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.navigation.NavigationBarView
|
||||
import java.io.File
|
||||
import java.io.FilenameFilter
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
@ -43,7 +47,6 @@ import org.yuzu.yuzu_emu.model.InstallResult
|
||||
import org.yuzu.yuzu_emu.model.TaskState
|
||||
import org.yuzu.yuzu_emu.model.TaskViewModel
|
||||
import org.yuzu.yuzu_emu.utils.*
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.BufferedOutputStream
|
||||
import java.util.zip.ZipEntry
|
||||
@ -136,22 +139,41 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
||||
// Prevents navigation from being drawn for a short time on recreation if set to hidden
|
||||
if (!homeViewModel.navigationVisible.value.first) {
|
||||
binding.navigationView.setVisible(visible = false, gone = false)
|
||||
binding.statusBarShade.setVisible(visible = false, gone = false)
|
||||
binding.navigationView.visibility = View.INVISIBLE
|
||||
binding.statusBarShade.visibility = View.INVISIBLE
|
||||
}
|
||||
|
||||
homeViewModel.navigationVisible.collect(this) { showNavigation(it.first, it.second) }
|
||||
homeViewModel.statusBarShadeVisible.collect(this) { showStatusBarShade(it) }
|
||||
homeViewModel.contentToInstall.collect(
|
||||
this,
|
||||
resetState = { homeViewModel.setContentToInstall(null) }
|
||||
) {
|
||||
if (it != null) {
|
||||
installContent(it)
|
||||
lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
homeViewModel.navigationVisible.collect { showNavigation(it.first, it.second) }
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
homeViewModel.statusBarShadeVisible.collect { showStatusBarShade(it) }
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
homeViewModel.contentToInstall.collect {
|
||||
if (it != null) {
|
||||
installContent(it)
|
||||
homeViewModel.setContentToInstall(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
homeViewModel.checkKeys.collect {
|
||||
if (it) {
|
||||
checkKeys()
|
||||
homeViewModel.setCheckKeys(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
homeViewModel.checkKeys.collect(this, resetState = { homeViewModel.setCheckKeys(false) }) {
|
||||
if (it) checkKeys()
|
||||
}
|
||||
|
||||
setInsets()
|
||||
@ -192,14 +214,18 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
||||
private fun showNavigation(visible: Boolean, animated: Boolean) {
|
||||
if (!animated) {
|
||||
binding.navigationView.setVisible(visible)
|
||||
if (visible) {
|
||||
binding.navigationView.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.navigationView.visibility = View.INVISIBLE
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val smallLayout = resources.getBoolean(R.bool.small_layout)
|
||||
binding.navigationView.animate().apply {
|
||||
if (visible) {
|
||||
binding.navigationView.setVisible(true)
|
||||
binding.navigationView.visibility = View.VISIBLE
|
||||
duration = 300
|
||||
interpolator = PathInterpolator(0.05f, 0.7f, 0.1f, 1f)
|
||||
|
||||
@ -238,7 +264,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
}
|
||||
}.withEndAction {
|
||||
if (!visible) {
|
||||
binding.navigationView.setVisible(visible = false, gone = false)
|
||||
binding.navigationView.visibility = View.INVISIBLE
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
@ -246,7 +272,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
private fun showStatusBarShade(visible: Boolean) {
|
||||
binding.statusBarShade.animate().apply {
|
||||
if (visible) {
|
||||
binding.statusBarShade.setVisible(true)
|
||||
binding.statusBarShade.visibility = View.VISIBLE
|
||||
binding.statusBarShade.translationY = binding.statusBarShade.height.toFloat() * -2
|
||||
duration = 300
|
||||
translationY(0f)
|
||||
@ -258,7 +284,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
}
|
||||
}.withEndAction {
|
||||
if (!visible) {
|
||||
binding.statusBarShade.setVisible(visible = false, gone = false)
|
||||
binding.statusBarShade.visibility = View.INVISIBLE
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
@ -498,8 +524,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
this@MainActivity,
|
||||
titleId = R.string.content_install_notice,
|
||||
descriptionId = R.string.content_install_notice_description,
|
||||
positiveAction = { homeViewModel.setContentToInstall(documents) },
|
||||
negativeAction = {}
|
||||
positiveAction = { homeViewModel.setContentToInstall(documents) }
|
||||
)
|
||||
}
|
||||
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
|
||||
|
@ -1,38 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Collects this [Flow] with a given [LifecycleOwner].
|
||||
* @param scope [LifecycleOwner] that this [Flow] will be collected with.
|
||||
* @param repeatState When to repeat collection on this [Flow].
|
||||
* @param resetState Optional lambda to reset state of an underlying [MutableStateFlow] after
|
||||
* [stateCollector] has been run.
|
||||
* @param stateCollector Lambda that receives new state.
|
||||
*/
|
||||
inline fun <reified T> Flow<T>.collect(
|
||||
scope: LifecycleOwner,
|
||||
repeatState: Lifecycle.State = Lifecycle.State.CREATED,
|
||||
crossinline resetState: () -> Unit = {},
|
||||
crossinline stateCollector: (state: T) -> Unit
|
||||
) {
|
||||
scope.apply {
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(repeatState) {
|
||||
this@collect.collect {
|
||||
stateCollector(it)
|
||||
resetState()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,10 +3,8 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
|
||||
object ViewUtils {
|
||||
fun showView(view: View, length: Long = 300) {
|
||||
@ -59,35 +57,4 @@ object ViewUtils {
|
||||
}
|
||||
this.layoutParams = layoutParams
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows or hides a view.
|
||||
* @param visible Whether a view will be made View.VISIBLE or View.INVISIBLE/GONE.
|
||||
* @param gone Optional parameter for hiding a view. Uses View.GONE if true and View.INVISIBLE otherwise.
|
||||
*/
|
||||
fun View.setVisible(visible: Boolean, gone: Boolean = true) {
|
||||
visibility = if (visible) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
if (gone) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.INVISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a marquee on some text.
|
||||
* @param delay Optional parameter for changing the start delay. 3 seconds of delay by default.
|
||||
*/
|
||||
fun TextView.marquee(delay: Long = 3000) {
|
||||
ellipsize = null
|
||||
marqueeRepeatLimit = -1
|
||||
isSingleLine = true
|
||||
postDelayed({
|
||||
ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
isSelected = true
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
@ -23,22 +23,6 @@ void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
|
||||
window_info.render_surface = reinterpret_cast<void*>(surface);
|
||||
}
|
||||
|
||||
void EmuWindow_Android::OnTouchPressed(int id, float x, float y) {
|
||||
const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
|
||||
EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchPressed(touch_x,
|
||||
touch_y, id);
|
||||
}
|
||||
|
||||
void EmuWindow_Android::OnTouchMoved(int id, float x, float y) {
|
||||
const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
|
||||
EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchMoved(touch_x,
|
||||
touch_y, id);
|
||||
}
|
||||
|
||||
void EmuWindow_Android::OnTouchReleased(int id) {
|
||||
EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchReleased(id);
|
||||
}
|
||||
|
||||
void EmuWindow_Android::OnFrameDisplayed() {
|
||||
if (!m_first_frame) {
|
||||
Common::Android::RunJNIOnFiber<void>(
|
||||
|
@ -38,10 +38,6 @@ public:
|
||||
void OnSurfaceChanged(ANativeWindow* surface);
|
||||
void OnFrameDisplayed() override;
|
||||
|
||||
void OnTouchPressed(int id, float x, float y);
|
||||
void OnTouchMoved(int id, float x, float y);
|
||||
void OnTouchReleased(int id);
|
||||
|
||||
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override {
|
||||
return {std::make_unique<GraphicsContext_Android>(m_driver_library)};
|
||||
}
|
||||
|
@ -190,7 +190,8 @@ void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onTouchPressed(JNIEnv* e
|
||||
jint j_id, jfloat j_x_axis,
|
||||
jfloat j_y_axis) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().Window().OnTouchPressed(j_id, j_x_axis, j_y_axis);
|
||||
EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchPressed(
|
||||
j_id, j_x_axis, j_y_axis);
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,14 +199,15 @@ void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onTouchMoved(JNIEnv* env
|
||||
jint j_id, jfloat j_x_axis,
|
||||
jfloat j_y_axis) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().Window().OnTouchMoved(j_id, j_x_axis, j_y_axis);
|
||||
EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchMoved(
|
||||
j_id, j_x_axis, j_y_axis);
|
||||
}
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onTouchReleased(JNIEnv* env, jobject j_obj,
|
||||
jint j_id) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().Window().OnTouchReleased(j_id);
|
||||
EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchReleased(j_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,10 @@
|
||||
style="@style/TextAppearance.Material3.TitleMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="none"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:requiresFadingEdge="horizontal"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="@string/select_gpu_driver_default" />
|
||||
|
||||
@ -49,7 +52,10 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:ellipsize="none"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:requiresFadingEdge="horizontal"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="@string/install_gpu_driver_description" />
|
||||
|
||||
@ -59,7 +65,10 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:ellipsize="none"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:requiresFadingEdge="horizontal"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="@string/install_gpu_driver_description" />
|
||||
|
||||
|
@ -21,7 +21,10 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|start"
|
||||
android:ellipsize="none"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:requiresFadingEdge="horizontal"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/button_layout"
|
||||
|
@ -40,7 +40,10 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:ellipsize="none"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:requiresFadingEdge="horizontal"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="center"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintEnd_toEndOf="@+id/image_game_screen"
|
||||
|
@ -59,6 +59,9 @@
|
||||
android:textAlignment="viewStart"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:singleLine="true"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:ellipsize="none"
|
||||
android:requiresFadingEdge="horizontal"
|
||||
android:layout_marginTop="6dp"
|
||||
android:visibility="gone"
|
||||
|
@ -76,7 +76,10 @@
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:ellipsize="none"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:requiresFadingEdge="horizontal"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="center"
|
||||
tools:text="deko_basic" />
|
||||
|
||||
|
@ -64,8 +64,8 @@ struct RawNACP {
|
||||
u64_le cache_storage_size;
|
||||
u64_le cache_storage_journal_size;
|
||||
u64_le cache_storage_data_and_journal_max_size;
|
||||
u16_le cache_storage_max_index;
|
||||
INSERT_PADDING_BYTES(0xE76);
|
||||
u64_le cache_storage_max_index;
|
||||
INSERT_PADDING_BYTES(0xE70);
|
||||
};
|
||||
static_assert(sizeof(RawNACP) == 0x4000, "RawNACP has incorrect size.");
|
||||
|
||||
|
@ -70,7 +70,7 @@ public:
|
||||
Result Read(s64 offset, void* buffer, size_t size) override {
|
||||
R_TRY(ValidateOffset(offset, size, m_size));
|
||||
|
||||
m_memory.ReadBlock(m_trmem->GetSourceAddress() + offset, buffer, size);
|
||||
m_memory.ReadBlock(m_trmem->GetSourceAddress(), buffer, size);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
@ -79,7 +79,7 @@ public:
|
||||
R_UNLESS(m_is_writable, ResultUnknown);
|
||||
R_TRY(ValidateOffset(offset, size, m_size));
|
||||
|
||||
m_memory.WriteBlock(m_trmem->GetSourceAddress() + offset, buffer, size);
|
||||
m_memory.WriteBlock(m_trmem->GetSourceAddress(), buffer, size);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
@ -15,7 +15,6 @@
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/filesystem/save_data_controller.h"
|
||||
#include "core/hle/service/glue/glue_manager.h"
|
||||
#include "core/hle/service/ns/application_manager_interface.h"
|
||||
#include "core/hle/service/ns/service_getter_interface.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
@ -42,7 +41,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_, std::shared_
|
||||
{26, D<&IApplicationFunctions::GetSaveDataSize>, "GetSaveDataSize"},
|
||||
{27, D<&IApplicationFunctions::CreateCacheStorage>, "CreateCacheStorage"},
|
||||
{28, D<&IApplicationFunctions::GetSaveDataSizeMax>, "GetSaveDataSizeMax"},
|
||||
{29, D<&IApplicationFunctions::GetCacheStorageMax>, "GetCacheStorageMax"},
|
||||
{29, nullptr, "GetCacheStorageMax"},
|
||||
{30, D<&IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed>, "BeginBlockingHomeButtonShortAndLongPressed"},
|
||||
{31, D<&IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed>, "EndBlockingHomeButtonShortAndLongPressed"},
|
||||
{32, D<&IApplicationFunctions::BeginBlockingHomeButton>, "BeginBlockingHomeButton"},
|
||||
@ -271,22 +270,6 @@ Result IApplicationFunctions::GetSaveDataSizeMax(Out<u64> out_max_normal_size,
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IApplicationFunctions::GetCacheStorageMax(Out<u32> out_cache_storage_index_max,
|
||||
Out<u64> out_max_journal_size) {
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
|
||||
std::vector<u8> nacp;
|
||||
R_TRY(system.GetARPManager().GetControlProperty(&nacp, m_applet->program_id));
|
||||
|
||||
auto raw_nacp = std::make_unique<FileSys::RawNACP>();
|
||||
std::memcpy(raw_nacp.get(), nacp.data(), std::min(sizeof(*raw_nacp), nacp.size()));
|
||||
|
||||
*out_cache_storage_index_max = static_cast<u32>(raw_nacp->cache_storage_max_index);
|
||||
*out_max_journal_size = static_cast<u64>(raw_nacp->cache_storage_data_and_journal_max_size);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed(s64 unused) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
|
||||
|
@ -40,7 +40,6 @@ private:
|
||||
Result CreateCacheStorage(Out<u32> out_target_media, Out<u64> out_required_size, u16 index,
|
||||
u64 normal_size, u64 journal_size);
|
||||
Result GetSaveDataSizeMax(Out<u64> out_max_normal_size, Out<u64> out_max_journal_size);
|
||||
Result GetCacheStorageMax(Out<u32> out_cache_storage_index_max, Out<u64> out_max_journal_size);
|
||||
Result BeginBlockingHomeButtonShortAndLongPressed(s64 unused);
|
||||
Result EndBlockingHomeButtonShortAndLongPressed();
|
||||
Result BeginBlockingHomeButton(s64 timeout_ns);
|
||||
|
@ -336,7 +336,7 @@ FSP_SRV::FSP_SRV(Core::System& system_)
|
||||
{1012, nullptr, "GetFsStackUsage"},
|
||||
{1013, nullptr, "UnsetSaveDataRootPath"},
|
||||
{1014, nullptr, "OutputMultiProgramTagAccessLog"},
|
||||
{1016, &FSP_SRV::FlushAccessLogOnSdCard, "FlushAccessLogOnSdCard"},
|
||||
{1016, nullptr, "FlushAccessLogOnSdCard"},
|
||||
{1017, nullptr, "OutputApplicationInfoAccessLog"},
|
||||
{1018, nullptr, "SetDebugOption"},
|
||||
{1019, nullptr, "UnsetDebugOption"},
|
||||
@ -706,13 +706,6 @@ void FSP_SRV::GetProgramIndexForAccessLog(HLERequestContext& ctx) {
|
||||
rb.Push(access_log_program_index);
|
||||
}
|
||||
|
||||
void FSP_SRV::FlushAccessLogOnSdCard(HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_FS, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void FSP_SRV::GetCacheStorageSize(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto index{rp.Pop<s32>()};
|
||||
|
@ -58,7 +58,6 @@ private:
|
||||
void SetGlobalAccessLogMode(HLERequestContext& ctx);
|
||||
void GetGlobalAccessLogMode(HLERequestContext& ctx);
|
||||
void OutputAccessLogToSdCard(HLERequestContext& ctx);
|
||||
void FlushAccessLogOnSdCard(HLERequestContext& ctx);
|
||||
void GetProgramIndexForAccessLog(HLERequestContext& ctx);
|
||||
void OpenMultiCommitManager(HLERequestContext& ctx);
|
||||
void GetCacheStorageSize(HLERequestContext& ctx);
|
||||
|
@ -1,6 +1,8 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/ns/develop_interface.h"
|
||||
|
||||
namespace Service::NS {
|
||||
|
@ -44,10 +44,6 @@ struct Display {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool HasLayers() {
|
||||
return !stack.layers.empty();
|
||||
}
|
||||
|
||||
u64 id;
|
||||
LayerStack stack;
|
||||
};
|
||||
|
@ -33,17 +33,16 @@ void SurfaceFlinger::RemoveDisplay(u64 display_id) {
|
||||
std::erase_if(m_displays, [&](auto& display) { return display.id == display_id; });
|
||||
}
|
||||
|
||||
bool SurfaceFlinger::ComposeDisplay(s32* out_swap_interval, f32* out_compose_speed_scale,
|
||||
void SurfaceFlinger::ComposeDisplay(s32* out_swap_interval, f32* out_compose_speed_scale,
|
||||
u64 display_id) {
|
||||
auto* const display = this->FindDisplay(display_id);
|
||||
if (!display || !display->HasLayers()) {
|
||||
return false;
|
||||
if (!display) {
|
||||
return;
|
||||
}
|
||||
|
||||
*out_swap_interval =
|
||||
m_composer.ComposeLocked(out_compose_speed_scale, *display,
|
||||
*nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>(disp_fd));
|
||||
return true;
|
||||
}
|
||||
|
||||
void SurfaceFlinger::AddLayerToDisplayStack(u64 display_id, s32 consumer_binder_id) {
|
||||
|
@ -34,7 +34,7 @@ public:
|
||||
|
||||
void AddDisplay(u64 display_id);
|
||||
void RemoveDisplay(u64 display_id);
|
||||
bool ComposeDisplay(s32* out_swap_interval, f32* out_compose_speed_scale, u64 display_id);
|
||||
void ComposeDisplay(s32* out_swap_interval, f32* out_compose_speed_scale, u64 display_id);
|
||||
|
||||
void AddLayerToDisplayStack(u64 display_id, s32 consumer_binder_id);
|
||||
void RemoveLayerFromDisplayStack(u64 display_id, s32 consumer_binder_id);
|
||||
|
@ -218,11 +218,10 @@ void Container::DestroyBufferQueueLocked(Layer* layer) {
|
||||
layer->GetProducerBinderId());
|
||||
}
|
||||
|
||||
bool Container::ComposeOnDisplay(s32* out_swap_interval, f32* out_compose_speed_scale,
|
||||
void Container::ComposeOnDisplay(s32* out_swap_interval, f32* out_compose_speed_scale,
|
||||
u64 display_id) {
|
||||
std::scoped_lock lk{m_lock};
|
||||
return m_surface_flinger->ComposeDisplay(out_swap_interval, out_compose_speed_scale,
|
||||
display_id);
|
||||
m_surface_flinger->ComposeDisplay(out_swap_interval, out_compose_speed_scale, display_id);
|
||||
}
|
||||
|
||||
} // namespace Service::VI
|
||||
|
@ -76,7 +76,7 @@ private:
|
||||
void DestroyBufferQueueLocked(Layer* layer);
|
||||
|
||||
public:
|
||||
bool ComposeOnDisplay(s32* out_swap_interval, f32* out_compose_speed_scale, u64 display_id);
|
||||
void ComposeOnDisplay(s32* out_swap_interval, f32* out_compose_speed_scale, u64 display_id);
|
||||
|
||||
private:
|
||||
std::mutex m_lock{};
|
||||
|
@ -117,9 +117,9 @@ bool StandardVmCallbacks::IsAddressInRange(VAddr in) const {
|
||||
(in < metadata.heap_extents.base ||
|
||||
in >= metadata.heap_extents.base + metadata.heap_extents.size) &&
|
||||
(in < metadata.alias_extents.base ||
|
||||
in >= metadata.alias_extents.base + metadata.alias_extents.size) &&
|
||||
in >= metadata.heap_extents.base + metadata.alias_extents.size) &&
|
||||
(in < metadata.aslr_extents.base ||
|
||||
in >= metadata.aslr_extents.base + metadata.aslr_extents.size)) {
|
||||
in >= metadata.heap_extents.base + metadata.aslr_extents.size)) {
|
||||
LOG_DEBUG(CheatEngine,
|
||||
"Cheat attempting to access memory at invalid address={:016X}, if this "
|
||||
"persists, "
|
||||
|
@ -251,12 +251,11 @@ inline InstallResult InstallNCA(FileSys::VfsFilesystem& vfs, const std::string&
|
||||
* \param callback Callback to report the progress of the installation. The first size_t
|
||||
* parameter is the total size of the installed contents and the second is the current progress. If
|
||||
* you return true to the callback, it will cancel the installation as soon as possible.
|
||||
* \param firmware_only Set to true to only scan system nand NCAs (firmware), post firmware install.
|
||||
* \return A list of entries that failed to install. Returns an empty vector if successful.
|
||||
*/
|
||||
inline std::vector<std::string> VerifyInstalledContents(
|
||||
Core::System& system, FileSys::ManualContentProvider& provider,
|
||||
const std::function<bool(size_t, size_t)>& callback, bool firmware_only = false) {
|
||||
const std::function<bool(size_t, size_t)>& callback) {
|
||||
// Get content registries.
|
||||
auto bis_contents = system.GetFileSystemController().GetSystemNANDContents();
|
||||
auto user_contents = system.GetFileSystemController().GetUserNANDContents();
|
||||
@ -265,7 +264,7 @@ inline std::vector<std::string> VerifyInstalledContents(
|
||||
if (bis_contents) {
|
||||
content_providers.push_back(bis_contents);
|
||||
}
|
||||
if (user_contents && !firmware_only) {
|
||||
if (user_contents) {
|
||||
content_providers.push_back(user_contents);
|
||||
}
|
||||
|
||||
|
@ -174,13 +174,9 @@ void EmulatedController::LoadDevices() {
|
||||
// Only map virtual devices to the first controller
|
||||
if (npad_id_type == NpadIdType::Player1 || npad_id_type == NpadIdType::Handheld) {
|
||||
camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"};
|
||||
nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
|
||||
#ifdef HAVE_LIBUSB
|
||||
ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"};
|
||||
#endif
|
||||
#ifdef ANDROID
|
||||
nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
|
||||
android_params = Common::ParamPackage{"engine:android,port:100"};
|
||||
#endif
|
||||
}
|
||||
|
||||
output_params[LeftIndex] = left_joycon;
|
||||
|
@ -128,11 +128,10 @@ private:
|
||||
const std::string razer_vid{"1532"};
|
||||
const std::string redmagic_vid{"3537"};
|
||||
const std::string backbone_labs_vid{"358a"};
|
||||
const std::string xbox_vid{"045e"};
|
||||
const std::vector<std::string> flipped_ab_vids{sony_vid, nintendo_vid, razer_vid,
|
||||
redmagic_vid, backbone_labs_vid, xbox_vid};
|
||||
const std::vector<std::string> flipped_ab_vids{sony_vid, nintendo_vid, razer_vid, redmagic_vid,
|
||||
backbone_labs_vid};
|
||||
const std::vector<std::string> flipped_xy_vids{sony_vid, razer_vid, redmagic_vid,
|
||||
backbone_labs_vid, xbox_vid};
|
||||
backbone_labs_vid};
|
||||
};
|
||||
|
||||
} // namespace InputCommon
|
||||
|
@ -1603,7 +1603,6 @@ void GMainWindow::ConnectMenuEvents() {
|
||||
// Help
|
||||
connect_menu(ui->action_Open_yuzu_Folder, &GMainWindow::OnOpenYuzuFolder);
|
||||
connect_menu(ui->action_Verify_installed_contents, &GMainWindow::OnVerifyInstalledContents);
|
||||
connect_menu(ui->action_Install_Firmware, &GMainWindow::OnInstallFirmware);
|
||||
connect_menu(ui->action_About, &GMainWindow::OnAbout);
|
||||
}
|
||||
|
||||
@ -1632,8 +1631,6 @@ void GMainWindow::UpdateMenuState() {
|
||||
action->setEnabled(emulation_running);
|
||||
}
|
||||
|
||||
ui->action_Install_Firmware->setEnabled(!emulation_running);
|
||||
|
||||
for (QAction* action : applet_actions) {
|
||||
action->setEnabled(is_firmware_available && !emulation_running);
|
||||
}
|
||||
@ -4153,146 +4150,6 @@ void GMainWindow::OnVerifyInstalledContents() {
|
||||
}
|
||||
}
|
||||
|
||||
void GMainWindow::OnInstallFirmware() {
|
||||
// Don't do this while emulation is running, that'd probably be a bad idea.
|
||||
if (emu_thread != nullptr && emu_thread->IsRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for installed keys, error out, suggest restart?
|
||||
if (!ContentManager::AreKeysPresent()) {
|
||||
QMessageBox::information(
|
||||
this, tr("Keys not installed"),
|
||||
tr("Install decryption keys and restart yuzu before attempting to install firmware."));
|
||||
return;
|
||||
}
|
||||
|
||||
QString firmware_source_location =
|
||||
QFileDialog::getExistingDirectory(this, tr("Select Dumped Firmware Source Location"),
|
||||
QString::fromStdString(""), QFileDialog::ShowDirsOnly);
|
||||
if (firmware_source_location.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QProgressDialog progress(tr("Installing Firmware..."), tr("Cancel"), 0, 100, this);
|
||||
progress.setWindowModality(Qt::WindowModal);
|
||||
progress.setMinimumDuration(100);
|
||||
progress.setAutoClose(false);
|
||||
progress.setAutoReset(false);
|
||||
progress.show();
|
||||
|
||||
// Declare progress callback.
|
||||
auto QtProgressCallback = [&](size_t total_size, size_t processed_size) {
|
||||
progress.setValue(static_cast<int>((processed_size * 100) / total_size));
|
||||
return progress.wasCanceled();
|
||||
};
|
||||
|
||||
LOG_INFO(Frontend, "Installing firmware from {}", firmware_source_location.toStdString());
|
||||
|
||||
// Check for a reasonable number of .nca files (don't hardcode them, just see if there's some in
|
||||
// there.)
|
||||
std::filesystem::path firmware_source_path = firmware_source_location.toStdString();
|
||||
if (!Common::FS::IsDir(firmware_source_path)) {
|
||||
progress.close();
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> out;
|
||||
const Common::FS::DirEntryCallable callback =
|
||||
[&out](const std::filesystem::directory_entry& entry) {
|
||||
if (entry.path().has_extension() && entry.path().extension() == ".nca")
|
||||
out.emplace_back(entry.path());
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
QtProgressCallback(100, 10);
|
||||
|
||||
Common::FS::IterateDirEntries(firmware_source_path, callback, Common::FS::DirEntryFilter::File);
|
||||
if (out.size() <= 0) {
|
||||
progress.close();
|
||||
QMessageBox::warning(this, tr("Firmware install failed"),
|
||||
tr("Unable to locate potential firmware NCA files"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Locate and erase the content of nand/system/Content/registered/*.nca, if any.
|
||||
auto sysnand_content_vdir = system->GetFileSystemController().GetSystemNANDContentDirectory();
|
||||
if (!sysnand_content_vdir->CleanSubdirectoryRecursive("registered")) {
|
||||
progress.close();
|
||||
QMessageBox::critical(this, tr("Firmware install failed"),
|
||||
tr("Failed to delete one or more firmware file."));
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(Frontend,
|
||||
"Cleaned nand/system/Content/registered folder in preparation for new firmware.");
|
||||
|
||||
QtProgressCallback(100, 20);
|
||||
|
||||
auto firmware_vdir = sysnand_content_vdir->GetDirectoryRelative("registered");
|
||||
|
||||
bool success = true;
|
||||
bool cancelled = false;
|
||||
int i = 0;
|
||||
for (const auto& firmware_src_path : out) {
|
||||
i++;
|
||||
auto firmware_src_vfile =
|
||||
vfs->OpenFile(firmware_src_path.generic_string(), FileSys::OpenMode::Read);
|
||||
auto firmware_dst_vfile =
|
||||
firmware_vdir->CreateFileRelative(firmware_src_path.filename().string());
|
||||
|
||||
if (!VfsRawCopy(firmware_src_vfile, firmware_dst_vfile)) {
|
||||
LOG_ERROR(Frontend, "Failed to copy firmware file {} to {} in registered folder!",
|
||||
firmware_src_path.generic_string(), firmware_src_path.filename().string());
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (QtProgressCallback(100, 20 + (int)(((float)(i) / (float)out.size()) * 70.0))) {
|
||||
success = false;
|
||||
cancelled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!success && !cancelled) {
|
||||
progress.close();
|
||||
QMessageBox::critical(this, tr("Firmware install failed"),
|
||||
tr("One or more firmware files failed to copy into NAND."));
|
||||
return;
|
||||
} else if (cancelled) {
|
||||
progress.close();
|
||||
QMessageBox::warning(this, tr("Firmware install failed"),
|
||||
tr("Firmware installation cancelled, firmware may be in bad state, "
|
||||
"restart yuzu or re-install firmware."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Re-scan VFS for the newly placed firmware files.
|
||||
system->GetFileSystemController().CreateFactories(*vfs);
|
||||
|
||||
auto VerifyFirmwareCallback = [&](size_t total_size, size_t processed_size) {
|
||||
progress.setValue(90 + static_cast<int>((processed_size * 10) / total_size));
|
||||
return progress.wasCanceled();
|
||||
};
|
||||
|
||||
auto result =
|
||||
ContentManager::VerifyInstalledContents(*system, *provider, VerifyFirmwareCallback, true);
|
||||
|
||||
if (result.size() > 0) {
|
||||
const auto failed_names =
|
||||
QString::fromStdString(fmt::format("{}", fmt::join(result, "\n")));
|
||||
progress.close();
|
||||
QMessageBox::critical(
|
||||
this, tr("Firmware integrity verification failed!"),
|
||||
tr("Verification failed for the following files:\n\n%1").arg(failed_names));
|
||||
return;
|
||||
}
|
||||
|
||||
progress.close();
|
||||
OnCheckFirmwareDecryption();
|
||||
}
|
||||
|
||||
void GMainWindow::OnAbout() {
|
||||
AboutDialog aboutDialog(this);
|
||||
aboutDialog.exec();
|
||||
|
@ -380,7 +380,6 @@ private slots:
|
||||
void OnLoadAmiibo();
|
||||
void OnOpenYuzuFolder();
|
||||
void OnVerifyInstalledContents();
|
||||
void OnInstallFirmware();
|
||||
void OnAbout();
|
||||
void OnToggleFilterBar();
|
||||
void OnToggleStatusBar();
|
||||
|
@ -25,16 +25,7 @@
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin" stdset="0">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</layout>
|
||||
@ -165,8 +156,8 @@
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_Configure_Tas"/>
|
||||
</widget>
|
||||
<addaction name="action_Rederive"/>
|
||||
<addaction name="action_Verify_installed_contents"/>
|
||||
<addaction name="action_Install_Firmware"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menu_cabinet_applet"/>
|
||||
<addaction name="action_Load_Album"/>
|
||||
@ -464,11 +455,6 @@
|
||||
<string>Open &Controller Menu</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Install_Firmware">
|
||||
<property name="text">
|
||||
<string>Install Firmware</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="yuzu.qrc"/>
|
||||
|
Reference in New Issue
Block a user