diff options
Diffstat (limited to 'src')
31 files changed, 554 insertions, 254 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2da983cad8..7bb88c8ea1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -134,7 +134,7 @@ else() endif() # GCC bugs - if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12" AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "11" AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") # These diagnostics would be great if they worked, but are just completely broken # and produce bogus errors on external libraries like fmt. add_compile_options( diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt index 7006651d00..bc6ff13643 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt @@ -49,6 +49,7 @@ class LicenseAdapter(private val activity: AppCompatActivity, var licenses: List val context = YuzuApplication.appContext binding.textSettingName.text = context.getString(license.titleId) binding.textSettingDescription.text = context.getString(license.descriptionId) + binding.textSettingValue.visibility = View.GONE } } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt index 481ddd5a5f..6b46d359e5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt @@ -5,13 +5,19 @@ 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 +import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.RecyclerView import com.google.android.material.button.MaterialButton import org.yuzu.yuzu_emu.databinding.PageSetupBinding +import org.yuzu.yuzu_emu.model.HomeViewModel +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 class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) : RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() { @@ -26,7 +32,7 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) holder.bind(pages[position]) inner class SetupPageViewHolder(val binding: PageSetupBinding) : - RecyclerView.ViewHolder(binding.root) { + RecyclerView.ViewHolder(binding.root), SetupCallback { lateinit var page: SetupPage init { @@ -35,6 +41,12 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) fun bind(page: SetupPage) { this.page = page + + if (page.stepCompleted.invoke() == StepState.COMPLETE) { + binding.buttonAction.visibility = View.INVISIBLE + binding.textConfirmation.visibility = View.VISIBLE + } + binding.icon.setImageDrawable( ResourcesCompat.getDrawable( activity.resources, @@ -62,9 +74,15 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) MaterialButton.ICON_GRAVITY_END } setOnClickListener { - page.buttonAction.invoke() + page.buttonAction.invoke(this@SetupPageViewHolder) } } } + + override fun onStepCompleted() { + ViewUtils.hideView(binding.buttonAction, 200) + ViewUtils.showView(binding.textConfirmation, 200) + ViewModelProvider(activity)[HomeViewModel::class.java].setShouldPageForward(true) + } } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt index ce0b92c90e..9711e2c51e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt @@ -207,8 +207,11 @@ class SettingsAdapter( val sliderBinding = DialogSliderBinding.inflate(inflater) textSliderValue = sliderBinding.textValue - textSliderValue!!.text = sliderProgress.toString() - sliderBinding.textUnits.text = item.units + textSliderValue!!.text = String.format( + context.getString(R.string.value_with_units), + sliderProgress.toString(), + item.units + ) sliderBinding.slider.apply { valueFrom = item.min.toFloat() @@ -216,7 +219,11 @@ class SettingsAdapter( value = sliderProgress.toFloat() addOnChangeListener { _: Slider, value: Float, _: Boolean -> sliderProgress = value.toInt() - textSliderValue!!.text = sliderProgress.toString() + textSliderValue!!.text = String.format( + context.getString(R.string.value_with_units), + sliderProgress.toString(), + item.units + ) } } @@ -225,10 +232,6 @@ class SettingsAdapter( .setView(sliderBinding.root) .setPositiveButton(android.R.string.ok, this) .setNegativeButton(android.R.string.cancel, defaultCancelListener) - .setNeutralButton(R.string.slider_default) { dialog: DialogInterface, which: Int -> - sliderBinding.slider.value = item.defaultValue!!.toFloat() - onClick(dialog, which) - } .show() } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt index 7955532eee..79572fc06b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt @@ -25,12 +25,17 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA binding.textSettingDescription.setText(item.descriptionId) binding.textSettingDescription.visibility = View.VISIBLE } else { - val epochTime = setting.value.toLong() - val instant = Instant.ofEpochMilli(epochTime * 1000) - val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC")) - val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) - binding.textSettingDescription.text = dateFormatter.format(zonedTime) + binding.textSettingDescription.visibility = View.GONE } + + binding.textSettingValue.visibility = View.VISIBLE + val epochTime = setting.value.toLong() + 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) + + setStyle(setting.isEditable, binding) } override fun onClick(clicked: View) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt index 5dad5945f2..83a2e94f13 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt @@ -23,6 +23,9 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA } else { binding.textSettingDescription.visibility = View.GONE } + binding.textSettingValue.visibility = View.GONE + + setStyle(setting.isEditable, binding) } override fun onClick(clicked: View) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt index f564608938..0fd1d2eaaa 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt @@ -5,6 +5,8 @@ package org.yuzu.yuzu_emu.features.settings.ui.viewholder import android.view.View import androidx.recyclerview.widget.RecyclerView +import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding +import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter @@ -33,4 +35,18 @@ abstract class SettingViewHolder(itemView: View, protected val adapter: Settings abstract override fun onClick(clicked: View) abstract override fun onLongClick(clicked: View): Boolean + + fun setStyle(isEditable: Boolean, binding: ListItemSettingBinding) { + val opacity = if (isEditable) 1.0f else 0.5f + binding.textSettingName.alpha = opacity + binding.textSettingDescription.alpha = opacity + binding.textSettingValue.alpha = opacity + } + + fun setStyle(isEditable: Boolean, binding: ListItemSettingSwitchBinding) { + binding.switchWidget.isEnabled = isEditable + val opacity = if (isEditable) 1.0f else 0.5f + binding.textSettingName.alpha = opacity + binding.textSettingDescription.alpha = opacity + } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt index e4e321bd36..b42d955aa7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt @@ -17,28 +17,33 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti override fun bind(item: SettingsItem) { setting = item binding.textSettingName.setText(item.nameId) - binding.textSettingDescription.visibility = View.VISIBLE if (item.descriptionId != 0) { binding.textSettingDescription.setText(item.descriptionId) - } else if (item is SingleChoiceSetting) { - val resMgr = binding.textSettingDescription.context.resources + binding.textSettingDescription.visibility = View.VISIBLE + } else { + binding.textSettingDescription.visibility = View.GONE + } + + binding.textSettingValue.visibility = View.VISIBLE + if (item is SingleChoiceSetting) { + val resMgr = binding.textSettingValue.context.resources val values = resMgr.getIntArray(item.valuesId) for (i in values.indices) { if (values[i] == item.selectedValue) { - binding.textSettingDescription.text = resMgr.getStringArray(item.choicesId)[i] - return + binding.textSettingValue.text = resMgr.getStringArray(item.choicesId)[i] + break } } } else if (item is StringSingleChoiceSetting) { for (i in item.values!!.indices) { if (item.values[i] == item.selectedValue) { - binding.textSettingDescription.text = item.choices[i] - return + binding.textSettingValue.text = item.choices[i] + break } } - } else { - binding.textSettingDescription.visibility = View.GONE } + + setStyle(setting.isEditable, binding) } override fun onClick(clicked: View) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt index cc3f39aa54..a23b5d1099 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt @@ -4,6 +4,7 @@ package org.yuzu.yuzu_emu.features.settings.ui.viewholder import android.view.View +import org.yuzu.yuzu_emu.R 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.SliderSetting @@ -22,6 +23,14 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda } 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.selectedValue, + setting.units + ) + + setStyle(setting.isEditable, binding) } override fun onClick(clicked: View) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt index c545b41743..1cf581a9d4 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt @@ -22,6 +22,7 @@ class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAd } else { binding.textSettingDescription.visibility = View.GONE } + binding.textSettingValue.visibility = View.GONE } override fun onClick(clicked: View) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt index 54f531795c..ef34bf5f44 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt @@ -25,12 +25,12 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter binding.textSettingDescription.text = "" binding.textSettingDescription.visibility = View.GONE } - binding.switchWidget.isChecked = setting.isChecked binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean -> adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked) } + binding.switchWidget.isChecked = setting.isChecked - binding.switchWidget.isEnabled = setting.isEditable + setStyle(setting.isEditable, binding) } override fun onClick(clicked: View) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt index 6c4ddaf6b6..d50c421a01 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt @@ -19,6 +19,7 @@ import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.isVisible +import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.navigation.findNavController @@ -32,10 +33,13 @@ import org.yuzu.yuzu_emu.adapters.SetupAdapter import org.yuzu.yuzu_emu.databinding.FragmentSetupBinding import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.model.HomeViewModel +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.ui.main.MainActivity import org.yuzu.yuzu_emu.utils.DirectoryInitialization import org.yuzu.yuzu_emu.utils.GameHelper +import org.yuzu.yuzu_emu.utils.ViewUtils class SetupFragment : Fragment() { private var _binding: FragmentSetupBinding? = null @@ -112,14 +116,22 @@ class SetupFragment : Fragment() { 0, false, R.string.give_permission, - { permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) }, + { + notificationCallback = it + permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + }, true, R.string.notification_warning, R.string.notification_warning_description, 0, { - NotificationManagerCompat.from(requireContext()) + if (NotificationManagerCompat.from(requireContext()) .areNotificationsEnabled() + ) { + StepState.COMPLETE + } else { + StepState.INCOMPLETE + } } ) ) @@ -133,12 +145,22 @@ class SetupFragment : Fragment() { R.drawable.ic_add, true, R.string.select_keys, - { mainActivity.getProdKey.launch(arrayOf("*/*")) }, + { + keyCallback = it + getProdKey.launch(arrayOf("*/*")) + }, true, R.string.install_prod_keys_warning, R.string.install_prod_keys_warning_description, R.string.install_prod_keys_warning_help, - { File(DirectoryInitialization.userDirectory + "/keys/prod.keys").exists() } + { + val file = File(DirectoryInitialization.userDirectory + "/keys/prod.keys") + if (file.exists()) { + StepState.COMPLETE + } else { + StepState.INCOMPLETE + } + } ) ) add( @@ -150,9 +172,8 @@ class SetupFragment : Fragment() { true, R.string.add_games, { - mainActivity.getGamesDirectory.launch( - Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data - ) + gamesDirCallback = it + getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) }, true, R.string.add_games_warning, @@ -163,7 +184,11 @@ class SetupFragment : Fragment() { PreferenceManager.getDefaultSharedPreferences( YuzuApplication.appContext ) - preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty() + if (preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()) { + StepState.COMPLETE + } else { + StepState.INCOMPLETE + } } ) ) @@ -181,6 +206,13 @@ class SetupFragment : Fragment() { ) } + homeViewModel.shouldPageForward.observe(viewLifecycleOwner) { + if (it) { + pageForward() + homeViewModel.setShouldPageForward(false) + } + } + binding.viewPager2.apply { adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages) offscreenPageLimit = 2 @@ -194,15 +226,15 @@ class SetupFragment : Fragment() { super.onPageSelected(position) if (position == 1 && previousPosition == 0) { - showView(binding.buttonNext) - showView(binding.buttonBack) + ViewUtils.showView(binding.buttonNext) + ViewUtils.showView(binding.buttonBack) } else if (position == 0 && previousPosition == 1) { - hideView(binding.buttonBack) - hideView(binding.buttonNext) + ViewUtils.hideView(binding.buttonBack) + ViewUtils.hideView(binding.buttonNext) } else if (position == pages.size - 1 && previousPosition == pages.size - 2) { - hideView(binding.buttonNext) + ViewUtils.hideView(binding.buttonNext) } else if (position == pages.size - 2 && previousPosition == pages.size - 1) { - showView(binding.buttonNext) + ViewUtils.showView(binding.buttonNext) } previousPosition = position @@ -215,7 +247,8 @@ class SetupFragment : Fragment() { // Checks if the user has completed the task on the current page if (currentPage.hasWarning) { - if (currentPage.taskCompleted.invoke()) { + val stepState = currentPage.stepCompleted.invoke() + if (stepState != StepState.INCOMPLETE) { pageForward() return@setOnClickListener } @@ -264,9 +297,15 @@ class SetupFragment : Fragment() { _binding = null } + private lateinit var notificationCallback: SetupCallback + @RequiresApi(Build.VERSION_CODES.TIRAMISU) private val permissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { + if (it) { + notificationCallback.onStepCompleted() + } + if (!it && !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) ) { @@ -277,6 +316,27 @@ class SetupFragment : Fragment() { } } + private lateinit var keyCallback: SetupCallback + + val getProdKey = + registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> + if (result != null) { + if (mainActivity.processKey(result)) { + keyCallback.onStepCompleted() + } + } + } + + private lateinit var gamesDirCallback: SetupCallback + + val getGamesDirectory = + registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> + if (result != null) { + mainActivity.processGamesDir(result) + gamesDirCallback.onStepCompleted() + } + } + private fun finishSetup() { PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() .putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false) @@ -284,33 +344,6 @@ class SetupFragment : Fragment() { mainActivity.finishSetup(binding.root.findNavController()) } - private fun showView(view: View) { - view.apply { - alpha = 0f - visibility = View.VISIBLE - isClickable = true - }.animate().apply { - duration = 300 - alpha(1f) - }.start() - } - - private fun hideView(view: View) { - if (view.visibility == View.INVISIBLE) { - return - } - - view.apply { - alpha = 1f - isClickable = false - }.animate().apply { - duration = 300 - alpha(0f) - }.withEndAction { - view.visibility = View.INVISIBLE - } - } - fun pageForward() { binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1 } @@ -326,15 +359,29 @@ class SetupFragment : Fragment() { private fun setInsets() = ViewCompat.setOnApplyWindowInsetsListener( binding.root - ) { view: View, windowInsets: WindowInsetsCompat -> + ) { _: View, windowInsets: WindowInsetsCompat -> val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) - view.setPadding( - barInsets.left + cutoutInsets.left, - barInsets.top + cutoutInsets.top, - barInsets.right + cutoutInsets.right, - barInsets.bottom + cutoutInsets.bottom - ) + + val leftPadding = barInsets.left + cutoutInsets.left + val topPadding = barInsets.top + cutoutInsets.top + val rightPadding = barInsets.right + cutoutInsets.right + val bottomPadding = barInsets.bottom + cutoutInsets.bottom + + if (resources.getBoolean(R.bool.small_layout)) { + binding.viewPager2 + .updatePadding(left = leftPadding, top = topPadding, right = rightPadding) + binding.constraintButtons + .updatePadding(left = leftPadding, right = rightPadding, bottom = bottomPadding) + } else { + binding.viewPager2.updatePadding(top = topPadding, bottom = bottomPadding) + binding.constraintButtons + .updatePadding( + left = leftPadding, + right = rightPadding, + bottom = bottomPadding + ) + } windowInsets } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt index 263ee7144d..e13d84c9ca 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt @@ -14,6 +14,9 @@ class HomeViewModel : ViewModel() { private val _statusBarShadeVisible = MutableLiveData(true) val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible + private val _shouldPageForward = MutableLiveData(false) + val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward + var navigatedToSetup = false init { @@ -33,4 +36,8 @@ class HomeViewModel : ViewModel() { } _statusBarShadeVisible.value = visible } + + fun setShouldPageForward(pageForward: Boolean) { + _shouldPageForward.value = pageForward + } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt index a0c878e1ca..09a128ae65 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt @@ -10,10 +10,20 @@ data class SetupPage( val buttonIconId: Int, val leftAlignedIcon: Boolean, val buttonTextId: Int, - val buttonAction: () -> Unit, + val buttonAction: (callback: SetupCallback) -> Unit, val hasWarning: Boolean, val warningTitleId: Int = 0, val warningDescriptionId: Int = 0, val warningHelpLinkId: Int = 0, - val taskCompleted: () -> Boolean = { true } + val stepCompleted: () -> StepState = { StepState.UNDEFINED } ) + +interface SetupCallback { + fun onStepCompleted() +} + +enum class StepState { + COMPLETE, + INCOMPLETE, + UNDEFINED +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt index f7d7aed1e4..f77d062620 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt @@ -266,73 +266,80 @@ class MainActivity : AppCompatActivity(), ThemeProvider { val getGamesDirectory = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> - if (result == null) { - return@registerForActivityResult + if (result != null) { + processGamesDir(result) } + } - contentResolver.takePersistableUriPermission( - result, - Intent.FLAG_GRANT_READ_URI_PERMISSION - ) + fun processGamesDir(result: Uri) { + contentResolver.takePersistableUriPermission( + result, + Intent.FLAG_GRANT_READ_URI_PERMISSION + ) - // When a new directory is picked, we currently will reset the existing games - // database. This effectively means that only one game directory is supported. - PreferenceManager.getDefaultSharedPreferences(applicationContext).edit() - .putString(GameHelper.KEY_GAME_PATH, result.toString()) - .apply() + // When a new directory is picked, we currently will reset the existing games + // database. This effectively means that only one game directory is supported. + PreferenceManager.getDefaultSharedPreferences(applicationContext).edit() + .putString(GameHelper.KEY_GAME_PATH, result.toString()) + .apply() - Toast.makeText( - applicationContext, - R.string.games_dir_selected, - Toast.LENGTH_LONG - ).show() + Toast.makeText( + applicationContext, + R.string.games_dir_selected, + Toast.LENGTH_LONG + ).show() - gamesViewModel.reloadGames(true) - } + gamesViewModel.reloadGames(true) + } val getProdKey = registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> - if (result == null) { - return@registerForActivityResult + if (result != null) { + processKey(result) } + } - if (FileUtil.getExtension(result) != "keys") { - MessageDialogFragment.newInstance( - R.string.reading_keys_failure, - R.string.install_prod_keys_failure_extension_description - ).show(supportFragmentManager, MessageDialogFragment.TAG) - return@registerForActivityResult - } + fun processKey(result: Uri): Boolean { + if (FileUtil.getExtension(result) != "keys") { + MessageDialogFragment.newInstance( + R.string.reading_keys_failure, + R.string.install_prod_keys_failure_extension_description + ).show(supportFragmentManager, MessageDialogFragment.TAG) + return false + } - contentResolver.takePersistableUriPermission( + contentResolver.takePersistableUriPermission( + result, + Intent.FLAG_GRANT_READ_URI_PERMISSION + ) + + val dstPath = DirectoryInitialization.userDirectory + "/keys/" + if (FileUtil.copyUriToInternalStorage( + applicationContext, result, - Intent.FLAG_GRANT_READ_URI_PERMISSION + dstPath, + "prod.keys" ) - - val dstPath = DirectoryInitialization.userDirectory + "/keys/" - if (FileUtil.copyUriToInternalStorage( + ) { + if (NativeLibrary.reloadKeys()) { + Toast.makeText( applicationContext, - result, - dstPath, - "prod.keys" - ) - ) { - if (NativeLibrary.reloadKeys()) { - Toast.makeText( - applicationContext, - R.string.install_keys_success, - Toast.LENGTH_SHORT - ).show() - gamesViewModel.reloadGames(true) - } else { - MessageDialogFragment.newInstance( - R.string.invalid_keys_error, - R.string.install_keys_failure_description, - R.string.dumping_keys_quickstart_link - ).show(supportFragmentManager, MessageDialogFragment.TAG) - } + R.string.install_keys_success, + Toast.LENGTH_SHORT + ).show() + gamesViewModel.reloadGames(true) + return true + } else { + MessageDialogFragment.newInstance( + R.string.invalid_keys_error, + R.string.install_keys_failure_description, + R.string.dumping_keys_quickstart_link + ).show(supportFragmentManager, MessageDialogFragment.TAG) + return false } } + return false + } val getFirmware = registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt new file mode 100644 index 0000000000..f9a3e4126b --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.utils + +import android.view.View + +object ViewUtils { + fun showView(view: View, length: Long = 300) { + view.apply { + alpha = 0f + visibility = View.VISIBLE + isClickable = true + }.animate().apply { + duration = length + alpha(1f) + }.start() + } + + fun hideView(view: View, length: Long = 300) { + if (view.visibility == View.INVISIBLE) { + return + } + + view.apply { + alpha = 1f + isClickable = false + }.animate().apply { + duration = length + alpha(0f) + }.withEndAction { + view.visibility = View.INVISIBLE + }.start() + } +} diff --git a/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml b/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml index cbe631d888..406df9eabd 100644 --- a/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml +++ b/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<androidx.constraintlayout.widget.ConstraintLayout +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/setup_root" @@ -8,33 +8,39 @@ <androidx.viewpager2.widget.ViewPager2 android:id="@+id/viewPager2" - android:layout_width="0dp" - android:layout_height="0dp" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentTop="true" + android:layout_alignParentBottom="true" + android:clipToPadding="false" /> - <com.google.android.material.button.MaterialButton - style="@style/Widget.Material3.Button.TextButton" - android:id="@+id/button_next" - android:layout_width="wrap_content" + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/constraint_buttons" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_margin="16dp" - android:text="@string/next" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" /> + android:layout_alignParentBottom="true" + android:layout_margin="8dp"> - <com.google.android.material.button.MaterialButton - android:id="@+id/button_back" - style="@style/Widget.Material3.Button.TextButton" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_margin="16dp" - android:text="@string/back" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintStart_toStartOf="parent" /> + <com.google.android.material.button.MaterialButton + android:id="@+id/button_next" + style="@style/Widget.Material3.Button.TextButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/next" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/button_back" + style="@style/Widget.Material3.Button.TextButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/back" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + + </androidx.constraintlayout.widget.ConstraintLayout> -</androidx.constraintlayout.widget.ConstraintLayout> +</RelativeLayout> diff --git a/src/android/app/src/main/res/layout-w600dp/page_setup.xml b/src/android/app/src/main/res/layout-w600dp/page_setup.xml index e1c26b2f8d..9e0ab8ecb1 100644 --- a/src/android/app/src/main/res/layout-w600dp/page_setup.xml +++ b/src/android/app/src/main/res/layout-w600dp/page_setup.xml @@ -21,45 +21,76 @@ </LinearLayout> - <LinearLayout + <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_weight="1" - android:orientation="vertical" - android:gravity="center"> + android:layout_weight="1"> <com.google.android.material.textview.MaterialTextView - style="@style/TextAppearance.Material3.DisplaySmall" android:id="@+id/text_title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:textAlignment="center" + style="@style/TextAppearance.Material3.DisplaySmall" + android:layout_width="0dp" + android:layout_height="0dp" + android:gravity="center" android:textColor="?attr/colorOnSurface" android:textStyle="bold" + app:layout_constraintBottom_toTopOf="@+id/text_description" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_weight="2" tools:text="@string/welcome" /> <com.google.android.material.textview.MaterialTextView - style="@style/TextAppearance.Material3.TitleLarge" android:id="@+id/text_description" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:paddingHorizontal="32dp" - android:textAlignment="center" - android:textSize="26sp" - app:lineHeight="40sp" + style="@style/TextAppearance.Material3.TitleLarge" + android:layout_width="0dp" + android:layout_height="0dp" + android:gravity="center" + android:textSize="20sp" + android:paddingHorizontal="16dp" + app:layout_constraintBottom_toTopOf="@+id/button_action" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_title" + app:layout_constraintVertical_weight="2" + app:lineHeight="30sp" tools:text="@string/welcome_description" /> + <com.google.android.material.textview.MaterialTextView + android:id="@+id/text_confirmation" + style="@style/TextAppearance.Material3.TitleLarge" + android:layout_width="0dp" + android:layout_height="0dp" + android:paddingHorizontal="16dp" + android:paddingBottom="20dp" + android:gravity="center" + android:textSize="30sp" + android:visibility="invisible" + android:text="@string/step_complete" + android:textStyle="bold" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_description" + app:layout_constraintVertical_weight="1" + app:lineHeight="30sp" /> + <com.google.android.material.button.MaterialButton android:id="@+id/button_action" android:layout_width="wrap_content" android:layout_height="56dp" - android:layout_marginTop="32dp" + android:layout_marginTop="16dp" + android:layout_marginBottom="48dp" android:textSize="20sp" - app:iconSize="24sp" app:iconGravity="end" + app:iconSize="24sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_description" tools:text="Get started" /> - </LinearLayout> + </androidx.constraintlayout.widget.ConstraintLayout> </LinearLayout> diff --git a/src/android/app/src/main/res/layout/dialog_slider.xml b/src/android/app/src/main/res/layout/dialog_slider.xml index 8c84cb6065..d1cb317399 100644 --- a/src/android/app/src/main/res/layout/dialog_slider.xml +++ b/src/android/app/src/main/res/layout/dialog_slider.xml @@ -5,23 +5,16 @@ android:layout_height="wrap_content" android:orientation="vertical"> - <TextView + <com.google.android.material.textview.MaterialTextView android:id="@+id/text_value" + style="@style/TextAppearance.Material3.LabelMedium" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_marginBottom="@dimen/spacing_medlarge" android:layout_marginTop="@dimen/spacing_medlarge" - tools:text="75" /> - - <TextView - android:id="@+id/text_units" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignTop="@+id/text_value" - android:layout_toEndOf="@+id/text_value" - tools:text="%" /> + tools:text="75%" /> <com.google.android.material.slider.Slider android:id="@+id/slider" diff --git a/src/android/app/src/main/res/layout/fragment_setup.xml b/src/android/app/src/main/res/layout/fragment_setup.xml index d7bafaea24..9499f64636 100644 --- a/src/android/app/src/main/res/layout/fragment_setup.xml +++ b/src/android/app/src/main/res/layout/fragment_setup.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<androidx.constraintlayout.widget.ConstraintLayout +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/setup_root" @@ -8,35 +8,39 @@ <androidx.viewpager2.widget.ViewPager2 android:id="@+id/viewPager2" - android:layout_width="0dp" - android:layout_height="0dp" - android:clipToPadding="false" - android:layout_marginBottom="16dp" - app:layout_constraintBottom_toTopOf="@+id/button_next" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - - <com.google.android.material.button.MaterialButton - style="@style/Widget.Material3.Button.TextButton" - android:id="@+id/button_next" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_margin="12dp" - android:text="@string/next" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" /> + android:layout_above="@+id/constraint_buttons" + android:layout_alignParentTop="true" + android:clipToPadding="false" /> - <com.google.android.material.button.MaterialButton - style="@style/Widget.Material3.Button.TextButton" - android:id="@+id/button_back" - android:layout_width="wrap_content" + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/constraint_buttons" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_margin="12dp" - android:text="@string/back" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintStart_toStartOf="parent" /> + android:layout_margin="8dp" + android:layout_alignParentBottom="true"> + + <com.google.android.material.button.MaterialButton + android:id="@+id/button_next" + style="@style/Widget.Material3.Button.TextButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/next" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/button_back" + style="@style/Widget.Material3.Button.TextButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/back" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + + </androidx.constraintlayout.widget.ConstraintLayout> -</androidx.constraintlayout.widget.ConstraintLayout> +</RelativeLayout> diff --git a/src/android/app/src/main/res/layout/list_item_setting.xml b/src/android/app/src/main/res/layout/list_item_setting.xml index ec896342b4..f1037a7401 100644 --- a/src/android/app/src/main/res/layout/list_item_setting.xml +++ b/src/android/app/src/main/res/layout/list_item_setting.xml @@ -1,9 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - xmlns:app="http://schemas.android.com/apk/res-auto" android:background="?android:attr/selectableItemBackground" android:clickable="true" android:focusable="true" @@ -11,31 +12,40 @@ android:minHeight="72dp" android:padding="@dimen/spacing_large"> - <com.google.android.material.textview.MaterialTextView - style="@style/TextAppearance.Material3.HeadlineMedium" - android:id="@+id/text_setting_name" - android:layout_width="0dp" + <LinearLayout + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_alignParentEnd="true" - android:layout_alignParentStart="true" - android:layout_alignParentTop="true" - android:textSize="16sp" - android:textAlignment="viewStart" - app:lineHeight="28dp" - tools:text="Setting Name" /> + android:orientation="vertical"> - <TextView - style="@style/TextAppearance.Material3.BodySmall" - android:id="@+id/text_setting_description" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentEnd="true" - android:layout_alignParentStart="true" - android:layout_alignStart="@+id/text_setting_name" - android:layout_below="@+id/text_setting_name" - android:layout_marginTop="@dimen/spacing_small" - android:visibility="visible" - android:textAlignment="viewStart" - tools:text="@string/app_disclaimer" /> + <com.google.android.material.textview.MaterialTextView + android:id="@+id/text_setting_name" + style="@style/TextAppearance.Material3.HeadlineMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAlignment="viewStart" + android:textSize="16sp" + app:lineHeight="22dp" + tools:text="Setting Name" /> + + <com.google.android.material.textview.MaterialTextView + android:id="@+id/text_setting_description" + style="@style/TextAppearance.Material3.BodySmall" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_small" + android:textAlignment="viewStart" + tools:text="@string/app_disclaimer" /> + + <com.google.android.material.textview.MaterialTextView + android:id="@+id/text_setting_value" + style="@style/TextAppearance.Material3.LabelMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_small" + android:textAlignment="viewStart" + android:textStyle="bold" + tools:text="1x" /> + + </LinearLayout> </RelativeLayout> diff --git a/src/android/app/src/main/res/layout/page_setup.xml b/src/android/app/src/main/res/layout/page_setup.xml index 1436ef308a..535abcf029 100644 --- a/src/android/app/src/main/res/layout/page_setup.xml +++ b/src/android/app/src/main/res/layout/page_setup.xml @@ -21,11 +21,12 @@ app:layout_constraintVertical_chainStyle="spread" app:layout_constraintWidth_max="220dp" app:layout_constraintWidth_min="110dp" - app:layout_constraintVertical_weight="3" /> + app:layout_constraintVertical_weight="3" + tools:src="@drawable/ic_notification" /> <com.google.android.material.textview.MaterialTextView android:id="@+id/text_title" - style="@style/TextAppearance.Material3.DisplayMedium" + style="@style/TextAppearance.Material3.DisplaySmall" android:layout_width="0dp" android:layout_height="0dp" android:textAlignment="center" @@ -44,23 +45,42 @@ android:layout_width="0dp" android:layout_height="0dp" android:textAlignment="center" - android:textSize="26sp" + android:textSize="20sp" android:paddingHorizontal="16dp" app:layout_constraintBottom_toTopOf="@+id/button_action" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/text_title" app:layout_constraintVertical_weight="2" - app:lineHeight="40sp" + app:lineHeight="30sp" tools:text="@string/welcome_description" /> + <com.google.android.material.textview.MaterialTextView + android:id="@+id/text_confirmation" + style="@style/TextAppearance.Material3.TitleLarge" + android:layout_width="wrap_content" + android:layout_height="0dp" + android:paddingHorizontal="16dp" + android:paddingTop="24dp" + android:textAlignment="center" + android:textSize="30sp" + android:visibility="invisible" + android:text="@string/step_complete" + android:textStyle="bold" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_description" + app:layout_constraintVertical_weight="1" + app:lineHeight="30sp" /> + <com.google.android.material.button.MaterialButton android:id="@+id/button_action" android:layout_width="wrap_content" android:layout_height="56dp" - android:textSize="20sp" android:layout_marginTop="16dp" android:layout_marginBottom="48dp" + android:textSize="20sp" app:iconGravity="end" app:iconSize="24sp" app:layout_constraintBottom_toBottomOf="parent" diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 02e25504de..de1b2909b5 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -29,6 +29,7 @@ <string name="back">Back</string> <string name="add_games">Add Games</string> <string name="add_games_description">Select your games folder</string> + <string name="step_complete">Complete!</string> <!-- Home strings --> <string name="home_games">Games</string> @@ -149,6 +150,7 @@ <string name="frame_limit_slider">Limit speed percent</string> <string name="frame_limit_slider_description">Specifies the percentage to limit emulation speed. 100% is the normal speed. Values higher or lower will increase or decrease the speed limit.</string> <string name="cpu_accuracy">CPU accuracy</string> + <string name="value_with_units">%1$s%2$s</string> <!-- System settings strings --> <string name="use_docked_mode">Docked Mode</string> diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp index 0f839d5b4c..e55831f27d 100644 --- a/src/core/debugger/gdbstub.cpp +++ b/src/core/debugger/gdbstub.cpp @@ -263,6 +263,23 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction std::vector<u8> mem(size); if (system.ApplicationMemory().ReadBlock(addr, mem.data(), size)) { + // Restore any bytes belonging to replaced instructions. + auto it = replaced_instructions.lower_bound(addr); + for (; it != replaced_instructions.end() && it->first < addr + size; it++) { + // Get the bytes of the instruction we previously replaced. + const u32 original_bytes = it->second; + + // Calculate where to start writing to the output buffer. + const size_t output_offset = it->first - addr; + + // Calculate how many bytes to write. + // The loop condition ensures output_offset < size. + const size_t n = std::min<size_t>(size - output_offset, sizeof(u32)); + + // Write the bytes to the output buffer. + std::memcpy(mem.data() + output_offset, &original_bytes, n); + } + SendReply(Common::HexToString(mem)); } else { SendReply(GDB_STUB_REPLY_ERR); diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h index 77426c46e3..f86af01a43 100644 --- a/src/core/hle/service/sockets/sockets.h +++ b/src/core/hle/service/sockets/sockets.h @@ -18,7 +18,9 @@ enum class Errno : u32 { AGAIN = 11, INVAL = 22, MFILE = 24, + PIPE = 32, MSGSIZE = 90, + CONNABORTED = 103, CONNRESET = 104, NOTCONN = 107, TIMEDOUT = 110, diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp index c1187209ff..aed05250c7 100644 --- a/src/core/hle/service/sockets/sockets_translate.cpp +++ b/src/core/hle/service/sockets/sockets_translate.cpp @@ -23,10 +23,14 @@ Errno Translate(Network::Errno value) { return Errno::INVAL; case Network::Errno::MFILE: return Errno::MFILE; + case Network::Errno::PIPE: + return Errno::PIPE; case Network::Errno::NOTCONN: return Errno::NOTCONN; case Network::Errno::TIMEDOUT: return Errno::TIMEDOUT; + case Network::Errno::CONNABORTED: + return Errno::CONNABORTED; case Network::Errno::CONNRESET: return Errno::CONNRESET; case Network::Errno::INPROGRESS: diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp index 28f89c599f..5d28300e6e 100644 --- a/src/core/internal_network/network.cpp +++ b/src/core/internal_network/network.cpp @@ -39,6 +39,11 @@ namespace Network { namespace { +enum class CallType { + Send, + Other, +}; + #ifdef _WIN32 using socklen_t = int; @@ -96,7 +101,7 @@ bool EnableNonBlock(SOCKET fd, bool enable) { return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR; } -Errno TranslateNativeError(int e) { +Errno TranslateNativeError(int e, CallType call_type = CallType::Other) { switch (e) { case 0: return Errno::SUCCESS; @@ -112,6 +117,14 @@ Errno TranslateNativeError(int e) { return Errno::AGAIN; case WSAECONNREFUSED: return Errno::CONNREFUSED; + case WSAECONNABORTED: + if (call_type == CallType::Send) { + // Winsock yields WSAECONNABORTED from `send` in situations where Unix + // systems, and actual Switches, yield EPIPE. + return Errno::PIPE; + } else { + return Errno::CONNABORTED; + } case WSAECONNRESET: return Errno::CONNRESET; case WSAEHOSTUNREACH: @@ -198,7 +211,7 @@ bool EnableNonBlock(int fd, bool enable) { return fcntl(fd, F_SETFL, flags) == 0; } -Errno TranslateNativeError(int e) { +Errno TranslateNativeError(int e, CallType call_type = CallType::Other) { switch (e) { case 0: return Errno::SUCCESS; @@ -208,6 +221,10 @@ Errno TranslateNativeError(int e) { return Errno::INVAL; case EMFILE: return Errno::MFILE; + case EPIPE: + return Errno::PIPE; + case ECONNABORTED: + return Errno::CONNABORTED; case ENOTCONN: return Errno::NOTCONN; case EAGAIN: @@ -236,13 +253,13 @@ Errno TranslateNativeError(int e) { #endif -Errno GetAndLogLastError() { +Errno GetAndLogLastError(CallType call_type = CallType::Other) { #ifdef _WIN32 int e = WSAGetLastError(); #else int e = errno; #endif - const Errno err = TranslateNativeError(e); + const Errno err = TranslateNativeError(e, call_type); if (err == Errno::AGAIN || err == Errno::TIMEDOUT || err == Errno::INPROGRESS) { // These happen during normal operation, so only log them at debug level. LOG_DEBUG(Network, "Socket operation error: {}", Common::NativeErrorToString(e)); @@ -476,7 +493,13 @@ NetworkInstance::~NetworkInstance() { std::optional<IPv4Address> GetHostIPv4Address() { const auto network_interface = Network::GetSelectedNetworkInterface(); if (!network_interface.has_value()) { - LOG_DEBUG(Network, "GetSelectedNetworkInterface returned no interface"); + // Only print the error once to avoid log spam + static bool print_error = true; + if (print_error) { + LOG_ERROR(Network, "GetSelectedNetworkInterface returned no interface"); + print_error = false; + } + return {}; } @@ -725,13 +748,17 @@ std::pair<s32, Errno> Socket::Send(std::span<const u8> message, int flags) { ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); ASSERT(flags == 0); + int native_flags = 0; +#if YUZU_UNIX + native_flags |= MSG_NOSIGNAL; // do not send us SIGPIPE +#endif const auto result = send(fd, reinterpret_cast<const char*>(message.data()), - static_cast<int>(message.size()), 0); + static_cast<int>(message.size()), native_flags); if (result != SOCKET_ERROR) { return {static_cast<s32>(result), Errno::SUCCESS}; } - return {-1, GetAndLogLastError()}; + return {-1, GetAndLogLastError(CallType::Send)}; } std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message, @@ -753,7 +780,7 @@ std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message, return {static_cast<s32>(result), Errno::SUCCESS}; } - return {-1, GetAndLogLastError()}; + return {-1, GetAndLogLastError(CallType::Send)}; } Errno Socket::Close() { diff --git a/src/core/internal_network/network.h b/src/core/internal_network/network.h index badcb83698..c7e20ae348 100644 --- a/src/core/internal_network/network.h +++ b/src/core/internal_network/network.h @@ -33,10 +33,12 @@ enum class Errno { BADF, INVAL, MFILE, + PIPE, NOTCONN, AGAIN, CONNREFUSED, CONNRESET, + CONNABORTED, HOSTUNREACH, NETDOWN, NETUNREACH, diff --git a/src/core/internal_network/network_interface.cpp b/src/core/internal_network/network_interface.cpp index 4c909a6d3c..7c37f660bb 100644 --- a/src/core/internal_network/network_interface.cpp +++ b/src/core/internal_network/network_interface.cpp @@ -200,7 +200,14 @@ std::optional<NetworkInterface> GetSelectedNetworkInterface() { }); if (res == network_interfaces.end()) { - LOG_DEBUG(Network, "Couldn't find selected interface \"{}\"", selected_network_interface); + // Only print the error once to avoid log spam + static bool print_error = true; + if (print_error) { + LOG_ERROR(Network, "Couldn't find selected interface \"{}\"", + selected_network_interface); + print_error = false; + } + return std::nullopt; } diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp index a8540339db..35bf80ea31 100644 --- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp +++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp @@ -126,7 +126,7 @@ struct FormatTuple { {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1R5G5B5_UNORM {VK_FORMAT_A2B10G10R10_UNORM_PACK32, Attachable | Storage}, // A2B10G10R10_UNORM {VK_FORMAT_A2B10G10R10_UINT_PACK32, Attachable | Storage}, // A2B10G10R10_UINT - {VK_FORMAT_A2R10G10B10_UNORM_PACK32, Attachable | Storage}, // A2R10G10B10_UNORM + {VK_FORMAT_A2R10G10B10_UNORM_PACK32, Attachable}, // A2R10G10B10_UNORM {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1B5G5R5_UNORM (flipped with swizzle) {VK_FORMAT_R5G5B5A1_UNORM_PACK16}, // A5B5G5R1_UNORM (specially swizzled) {VK_FORMAT_R8_UNORM, Attachable | Storage}, // R8_UNORM diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index adde96aa5f..6174170407 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -71,6 +71,11 @@ constexpr std::array R8G8B8_SSCALED{ VK_FORMAT_UNDEFINED, }; +constexpr std::array VK_FORMAT_R32G32B32_SFLOAT{ + VK_FORMAT_R32G32B32A32_SFLOAT, + VK_FORMAT_UNDEFINED, +}; + } // namespace Alternatives enum class NvidiaArchitecture { @@ -103,6 +108,8 @@ constexpr const VkFormat* GetFormatAlternatives(VkFormat format) { return Alternatives::R16G16B16_SSCALED.data(); case VK_FORMAT_R8G8B8_SSCALED: return Alternatives::R8G8B8_SSCALED.data(); + case VK_FORMAT_R32G32B32_SFLOAT: + return Alternatives::VK_FORMAT_R32G32B32_SFLOAT.data(); default: return nullptr; } @@ -130,6 +137,7 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica VK_FORMAT_A2B10G10R10_UINT_PACK32, VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_FORMAT_A2B10G10R10_USCALED_PACK32, + VK_FORMAT_A2R10G10B10_UNORM_PACK32, VK_FORMAT_A8B8G8R8_SINT_PACK32, VK_FORMAT_A8B8G8R8_SNORM_PACK32, VK_FORMAT_A8B8G8R8_SRGB_PACK32, |