diff options
Diffstat (limited to 'src/android/app')
32 files changed, 459 insertions, 336 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index c8706d7a6..21f67f32a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt @@ -308,21 +308,6 @@ object NativeLibrary {      external fun isPaused(): Boolean      /** -     * Mutes emulation sound -     */ -    external fun muteAudio(): Boolean - -    /** -     * Unmutes emulation sound -     */ -    external fun unmuteAudio(): Boolean - -    /** -     * Returns true if emulation audio is muted. -     */ -    external fun isMuted(): Boolean - -    /**       * Returns the performance stats for the current game       */      external fun getPerfStats(): DoubleArray diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt index bbd328c71..d4ae39661 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt @@ -332,7 +332,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {              pictureInPictureActions.add(pauseRemoteAction)          } -        if (NativeLibrary.isMuted()) { +        if (BooleanSetting.AUDIO_MUTED.boolean) {              val unmuteIcon = Icon.createWithResource(                  this@EmulationActivity,                  R.drawable.ic_pip_unmute @@ -389,9 +389,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {                  if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation()              }              if (intent.action == actionUnmute) { -                if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio() +                if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false)              } else if (intent.action == actionMute) { -                if (!NativeLibrary.isMuted()) NativeLibrary.muteAudio() +                if (!BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(true)              }              buildPictureInPictureParams()          } @@ -417,7 +417,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {              } catch (ignored: Exception) {              }              // Always resume audio, since there is no UI button -            if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio() +            if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false)          }      } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt index 0013e8512..f9f88a1d2 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt @@ -4,7 +4,8 @@  package org.yuzu.yuzu_emu.adapters  import android.content.Intent -import android.graphics.drawable.BitmapDrawable +import android.graphics.Bitmap +import android.graphics.drawable.LayerDrawable  import android.net.Uri  import android.text.TextUtils  import android.view.LayoutInflater @@ -15,7 +16,10 @@ import android.widget.Toast  import androidx.appcompat.app.AppCompatActivity  import androidx.core.content.pm.ShortcutInfoCompat  import androidx.core.content.pm.ShortcutManagerCompat +import androidx.core.content.res.ResourcesCompat  import androidx.core.graphics.drawable.IconCompat +import androidx.core.graphics.drawable.toBitmap +import androidx.core.graphics.drawable.toDrawable  import androidx.documentfile.provider.DocumentFile  import androidx.lifecycle.ViewModelProvider  import androidx.navigation.findNavController @@ -87,11 +91,24 @@ class GameAdapter(private val activity: AppCompatActivity) :              action = Intent.ACTION_VIEW              data = Uri.parse(holder.game.path)          } + +        val layerDrawable = ResourcesCompat.getDrawable( +            YuzuApplication.appContext.resources, +            R.drawable.shortcut, +            null +        ) as LayerDrawable +        layerDrawable.setDrawableByLayerId( +            R.id.shortcut_foreground, +            GameIconUtils.getGameIcon(holder.game).toDrawable(YuzuApplication.appContext.resources) +        ) +        val inset = YuzuApplication.appContext.resources +            .getDimensionPixelSize(R.dimen.icon_inset) +        layerDrawable.setLayerInset(1, inset, inset, inset, inset)          val shortcut = ShortcutInfoCompat.Builder(YuzuApplication.appContext, holder.game.path)              .setShortLabel(holder.game.title)              .setIcon( -                IconCompat.createWithBitmap( -                    (holder.binding.imageGameScreen.drawable as BitmapDrawable).bitmap +                IconCompat.createWithAdaptiveBitmap( +                    layerDrawable.toBitmap(config = Bitmap.Config.ARGB_8888)                  )              )              .setIntent(openIntent) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt index 8d87d3bd7..1675627a1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt @@ -10,8 +10,12 @@ 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 androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.launch  import org.yuzu.yuzu_emu.R  import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding  import org.yuzu.yuzu_emu.fragments.MessageDialogFragment @@ -86,7 +90,11 @@ class HomeSettingAdapter(                  binding.optionIcon.alpha = 0.5f              } -            option.details.observe(viewLifecycle) { updateOptionDetails(it) } +            viewLifecycle.lifecycleScope.launch { +                viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) { +                    option.details.collect { updateOptionDetails(it) } +                } +            }              binding.optionDetail.postDelayed(                  {                      binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index e0c0538c7..8476ce867 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -10,6 +10,7 @@ enum class BooleanSetting(      override val category: Settings.Category,      override val androidDefault: Boolean? = null  ) : AbstractBooleanSetting { +    AUDIO_MUTED("audio_muted", Settings.Category.Audio),      CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu),      FASTMEM("cpuopt_fastmem", Settings.Category.Cpu),      FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt index 0702236e8..08e2a973d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt @@ -80,6 +80,17 @@ object Settings {      const val SECTION_THEME = "Theme"      const val SECTION_DEBUG = "Debug" +    enum class MenuTag(val titleId: Int) { +        SECTION_ROOT(R.string.advanced_settings), +        SECTION_GENERAL(R.string.preferences_general), +        SECTION_SYSTEM(R.string.preferences_system), +        SECTION_RENDERER(R.string.preferences_graphics), +        SECTION_AUDIO(R.string.preferences_audio), +        SECTION_CPU(R.string.cpu), +        SECTION_THEME(R.string.preferences_theme), +        SECTION_DEBUG(R.string.preferences_debug); +    } +      const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"      const val PREF_OVERLAY_VERSION = "OverlayVersion" diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt index 91c273964..b343e527e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt @@ -3,10 +3,12 @@  package org.yuzu.yuzu_emu.features.settings.model.view +import org.yuzu.yuzu_emu.features.settings.model.Settings +  class SubmenuSetting(      titleId: Int,      descriptionId: Int, -    val menuKey: String +    val menuKey: Settings.MenuTag  ) : SettingsItem(emptySetting, titleId, descriptionId) {      override val type = TYPE_SUBMENU  } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt index 908c01265..4d2f2f604 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt @@ -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 java.io.IOException  import org.yuzu.yuzu_emu.R  import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding @@ -66,25 +71,39 @@ class SettingsActivity : AppCompatActivity() {              )          } -        settingsViewModel.shouldRecreate.observe(this) { -            if (it) { -                settingsViewModel.setShouldRecreate(false) -                recreate() +        lifecycleScope.apply { +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    settingsViewModel.shouldRecreate.collectLatest { +                        if (it) { +                            settingsViewModel.setShouldRecreate(false) +                            recreate() +                        } +                    } +                }              } -        } -        settingsViewModel.shouldNavigateBack.observe(this) { -            if (it) { -                settingsViewModel.setShouldNavigateBack(false) -                navigateBack() +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    settingsViewModel.shouldNavigateBack.collectLatest { +                        if (it) { +                            settingsViewModel.setShouldNavigateBack(false) +                            navigateBack() +                        } +                    } +                }              } -        } -        settingsViewModel.shouldShowResetSettingsDialog.observe(this) { -            if (it) { -                settingsViewModel.setShouldShowResetSettingsDialog(false) -                ResetSettingsDialogFragment().show( -                    supportFragmentManager, -                    ResetSettingsDialogFragment.TAG -                ) +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    settingsViewModel.shouldShowResetSettingsDialog.collectLatest { +                        if (it) { +                            settingsViewModel.setShouldShowResetSettingsDialog(false) +                            ResetSettingsDialogFragment().show( +                                supportFragmentManager, +                                ResetSettingsDialogFragment.TAG +                            ) +                        } +                    } +                }              }          } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt index bc319714c..70d8ec14b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt @@ -3,6 +3,7 @@  package org.yuzu.yuzu_emu.features.settings.ui +import android.annotation.SuppressLint  import android.os.Bundle  import android.view.LayoutInflater  import android.view.View @@ -13,14 +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 androidx.navigation.findNavController  import androidx.navigation.fragment.navArgs  import androidx.recyclerview.widget.LinearLayoutManager  import com.google.android.material.divider.MaterialDividerItemDecoration  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.settings.utils.SettingsFile +import org.yuzu.yuzu_emu.features.settings.model.Settings  import org.yuzu.yuzu_emu.model.SettingsViewModel  class SettingsFragment : Fragment() { @@ -51,15 +57,17 @@ class SettingsFragment : Fragment() {          return binding.root      } +    // This is using the correct scope, lint is just acting up +    @SuppressLint("UnsafeRepeatOnLifecycleDetector")      override fun onViewCreated(view: View, savedInstanceState: Bundle?) {          settingsAdapter = SettingsAdapter(this, requireContext())          presenter = SettingsFragmentPresenter(              settingsViewModel,              settingsAdapter!!, -            args.menuTag, -            args.game?.gameId ?: "" +            args.menuTag          ) +        binding.toolbarSettingsLayout.title = getString(args.menuTag.titleId)          val dividerDecoration = MaterialDividerItemDecoration(              requireContext(),              LinearLayoutManager.VERTICAL @@ -75,28 +83,31 @@ class SettingsFragment : Fragment() {              settingsViewModel.setShouldNavigateBack(true)          } -        settingsViewModel.toolbarTitle.observe(viewLifecycleOwner) { -            if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it -        } - -        settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) { -            if (it) { -                settingsViewModel.setShouldReloadSettingsList(false) -                presenter.loadSettingsList() +        viewLifecycleOwner.lifecycleScope.apply { +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    settingsViewModel.shouldReloadSettingsList.collectLatest { +                        if (it) { +                            settingsViewModel.setShouldReloadSettingsList(false) +                            presenter.loadSettingsList() +                        } +                    } +                }              } -        } - -        settingsViewModel.isUsingSearch.observe(viewLifecycleOwner) { -            if (it) { -                reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) -                exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) -            } else { -                reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) -                exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) +            launch { +                settingsViewModel.isUsingSearch.collectLatest { +                    if (it) { +                        reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) +                        exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) +                    } else { +                        reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) +                        exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) +                    } +                }              }          } -        if (args.menuTag == SettingsFile.FILE_NAME_CONFIG) { +        if (args.menuTag == Settings.MenuTag.SECTION_ROOT) {              binding.toolbarSettings.inflateMenu(R.menu.menu_settings)              binding.toolbarSettings.setOnMenuItemClickListener {                  when (it.itemId) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 22a529b1b..766414a6c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.features.settings.ui  import android.content.Context  import android.content.SharedPreferences  import android.os.Build -import android.text.TextUtils  import android.widget.Toast  import androidx.preference.PreferenceManager  import org.yuzu.yuzu_emu.R @@ -20,15 +19,13 @@ import org.yuzu.yuzu_emu.features.settings.model.LongSetting  import org.yuzu.yuzu_emu.features.settings.model.Settings  import org.yuzu.yuzu_emu.features.settings.model.ShortSetting  import org.yuzu.yuzu_emu.features.settings.model.view.* -import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile  import org.yuzu.yuzu_emu.model.SettingsViewModel  import org.yuzu.yuzu_emu.utils.NativeConfig  class SettingsFragmentPresenter(      private val settingsViewModel: SettingsViewModel,      private val adapter: SettingsAdapter, -    private var menuTag: String, -    private var gameId: String +    private var menuTag: Settings.MenuTag  ) {      private var settingsList = ArrayList<SettingsItem>() @@ -53,24 +50,15 @@ class SettingsFragmentPresenter(      }      fun loadSettingsList() { -        if (!TextUtils.isEmpty(gameId)) { -            settingsViewModel.setToolbarTitle( -                context.getString( -                    R.string.advanced_settings_game, -                    gameId -                ) -            ) -        } -          val sl = ArrayList<SettingsItem>()          when (menuTag) { -            SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl) -            Settings.SECTION_GENERAL -> addGeneralSettings(sl) -            Settings.SECTION_SYSTEM -> addSystemSettings(sl) -            Settings.SECTION_RENDERER -> addGraphicsSettings(sl) -            Settings.SECTION_AUDIO -> addAudioSettings(sl) -            Settings.SECTION_THEME -> addThemeSettings(sl) -            Settings.SECTION_DEBUG -> addDebugSettings(sl) +            Settings.MenuTag.SECTION_ROOT -> addConfigSettings(sl) +            Settings.MenuTag.SECTION_GENERAL -> addGeneralSettings(sl) +            Settings.MenuTag.SECTION_SYSTEM -> addSystemSettings(sl) +            Settings.MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl) +            Settings.MenuTag.SECTION_AUDIO -> addAudioSettings(sl) +            Settings.MenuTag.SECTION_THEME -> addThemeSettings(sl) +            Settings.MenuTag.SECTION_DEBUG -> addDebugSettings(sl)              else -> {                  val context = YuzuApplication.appContext                  Toast.makeText( @@ -86,13 +74,12 @@ class SettingsFragmentPresenter(      }      private fun addConfigSettings(sl: ArrayList<SettingsItem>) { -        settingsViewModel.setToolbarTitle(context.getString(R.string.advanced_settings))          sl.apply { -            add(SubmenuSetting(R.string.preferences_general, 0, Settings.SECTION_GENERAL)) -            add(SubmenuSetting(R.string.preferences_system, 0, Settings.SECTION_SYSTEM)) -            add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.SECTION_RENDERER)) -            add(SubmenuSetting(R.string.preferences_audio, 0, Settings.SECTION_AUDIO)) -            add(SubmenuSetting(R.string.preferences_debug, 0, Settings.SECTION_DEBUG)) +            add(SubmenuSetting(R.string.preferences_general, 0, Settings.MenuTag.SECTION_GENERAL)) +            add(SubmenuSetting(R.string.preferences_system, 0, Settings.MenuTag.SECTION_SYSTEM)) +            add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.MenuTag.SECTION_RENDERER)) +            add(SubmenuSetting(R.string.preferences_audio, 0, Settings.MenuTag.SECTION_AUDIO)) +            add(SubmenuSetting(R.string.preferences_debug, 0, Settings.MenuTag.SECTION_DEBUG))              add(                  RunnableSetting(R.string.reset_to_default, 0, false) {                      settingsViewModel.setShouldShowResetSettingsDialog(true) @@ -102,7 +89,6 @@ class SettingsFragmentPresenter(      }      private fun addGeneralSettings(sl: ArrayList<SettingsItem>) { -        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_general))          sl.apply {              add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key)              add(ShortSetting.RENDERER_SPEED_LIMIT.key) @@ -112,7 +98,6 @@ class SettingsFragmentPresenter(      }      private fun addSystemSettings(sl: ArrayList<SettingsItem>) { -        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_system))          sl.apply {              add(BooleanSetting.USE_DOCKED_MODE.key)              add(IntSetting.REGION_INDEX.key) @@ -123,7 +108,6 @@ class SettingsFragmentPresenter(      }      private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { -        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_graphics))          sl.apply {              add(IntSetting.RENDERER_ACCURACY.key)              add(IntSetting.RENDERER_RESOLUTION.key) @@ -140,7 +124,6 @@ class SettingsFragmentPresenter(      }      private fun addAudioSettings(sl: ArrayList<SettingsItem>) { -        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_audio))          sl.apply {              add(IntSetting.AUDIO_OUTPUT_ENGINE.key)              add(ByteSetting.AUDIO_VOLUME.key) @@ -148,7 +131,6 @@ class SettingsFragmentPresenter(      }      private fun addThemeSettings(sl: ArrayList<SettingsItem>) { -        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_theme))          sl.apply {              val theme: AbstractIntSetting = object : AbstractIntSetting {                  override val int: Int @@ -261,7 +243,6 @@ class SettingsFragmentPresenter(      }      private fun addDebugSettings(sl: ArrayList<SettingsItem>) { -        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_debug))          sl.apply {              add(HeaderSetting(R.string.gpu))              add(IntSetting.RENDERER_BACKEND.key) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 944ae652e..3e6c157c7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -39,6 +39,7 @@ 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 @@ -49,7 +50,6 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding  import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding  import org.yuzu.yuzu_emu.features.settings.model.IntSetting  import org.yuzu.yuzu_emu.features.settings.model.Settings -import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile  import org.yuzu.yuzu_emu.model.Game  import org.yuzu.yuzu_emu.model.EmulationViewModel  import org.yuzu.yuzu_emu.overlay.InputOverlay @@ -129,6 +129,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?) {          binding.surfaceEmulation.holder.addCallback(this)          binding.showFpsText.setTextColor(Color.YELLOW) @@ -163,7 +165,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {                  R.id.menu_settings -> {                      val action = HomeNavigationDirections.actionGlobalSettingsActivity(                          null, -                        SettingsFile.FILE_NAME_CONFIG +                        Settings.MenuTag.SECTION_ROOT                      )                      binding.root.findNavController().navigate(action)                      true @@ -205,59 +207,80 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {              }          ) -        viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { -            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { -                WindowInfoTracker.getOrCreate(requireContext()) -                    .windowLayoutInfo(requireActivity()) -                    .collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) } -            } -        } -          GameIconUtils.loadGameIcon(game, binding.loadingImage)          binding.loadingTitle.text = game.title          binding.loadingTitle.isSelected = true          binding.loadingText.isSelected = true -        emulationViewModel.shaderProgress.observe(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 < binding.loadingProgressIndicator.max) { +                                binding.loadingProgressIndicator.progress = it +                            } +                        } -            if (it == emulationViewModel.totalShaders.value!!) { -                binding.loadingText.setText(R.string.loading) -                binding.loadingProgressIndicator.isIndeterminate = true +                        if (it == emulationViewModel.totalShaders.value) { +                            binding.loadingText.setText(R.string.loading) +                            binding.loadingProgressIndicator.isIndeterminate = true +                        } +                    } +                }              } -        } -        emulationViewModel.totalShaders.observe(viewLifecycleOwner) { -            binding.loadingProgressIndicator.max = it -        } -        emulationViewModel.shaderMessage.observe(viewLifecycleOwner) { -            if (it.isNotEmpty()) { -                binding.loadingText.text = it +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    emulationViewModel.totalShaders.collectLatest { +                        binding.loadingProgressIndicator.max = it +                    } +                }              } -        } - -        emulationViewModel.emulationStarted.observe(viewLifecycleOwner) { started -> -            if (started) { -                binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) -                ViewUtils.showView(binding.surfaceInputOverlay) -                ViewUtils.hideView(binding.loadingIndicator) - -                // Setup overlay -                updateShowFpsOverlay() +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    emulationViewModel.shaderMessage.collectLatest { +                        if (it.isNotEmpty()) { +                            binding.loadingText.text = it +                        } +                    } +                }              } -        } +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    emulationViewModel.emulationStarted.collectLatest { +                        if (it) { +                            binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) +                            ViewUtils.showView(binding.surfaceInputOverlay) +                            ViewUtils.hideView(binding.loadingIndicator) -        emulationViewModel.isEmulationStopping.observe(viewLifecycleOwner) { -            if (it) { -                binding.loadingText.setText(R.string.shutting_down) -                ViewUtils.showView(binding.loadingIndicator) -                ViewUtils.hideView(binding.inputContainer) -                ViewUtils.hideView(binding.showFpsText) +                            // Setup overlay +                            updateShowFpsOverlay() +                        } +                    } +                } +            } +            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) +                        } +                    } +                }              }          }      } @@ -274,9 +297,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {                  }              }          } else { -            if (EmulationMenuSettings.showOverlay && -                emulationViewModel.emulationStarted.value == true -            ) { +            if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) {                  binding.surfaceInputOverlay.post {                      binding.surfaceInputOverlay.visibility = View.VISIBLE                  } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt index cbbe14d22..c119e69c9 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt @@ -37,7 +37,6 @@ import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter  import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding  import org.yuzu.yuzu_emu.features.DocumentProvider  import org.yuzu.yuzu_emu.features.settings.model.Settings -import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile  import org.yuzu.yuzu_emu.model.HomeSetting  import org.yuzu.yuzu_emu.model.HomeViewModel  import org.yuzu.yuzu_emu.ui.main.MainActivity @@ -78,7 +77,7 @@ class HomeSettingsFragment : Fragment() {                      {                          val action = HomeNavigationDirections.actionGlobalSettingsActivity(                              null, -                            SettingsFile.FILE_NAME_CONFIG +                            Settings.MenuTag.SECTION_ROOT                          )                          binding.root.findNavController().navigate(action)                      } @@ -100,7 +99,7 @@ class HomeSettingsFragment : Fragment() {                      {                          val action = HomeNavigationDirections.actionGlobalSettingsActivity(                              null, -                            Settings.SECTION_THEME +                            Settings.MenuTag.SECTION_THEME                          )                          binding.root.findNavController().navigate(action)                      } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt index 181bd983a..ea8eb073a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt @@ -9,8 +9,12 @@ import android.widget.Toast  import androidx.appcompat.app.AppCompatActivity  import androidx.fragment.app.DialogFragment  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.databinding.DialogProgressBarBinding  import org.yuzu.yuzu_emu.model.TaskViewModel @@ -28,21 +32,27 @@ class IndeterminateProgressDialogFragment : DialogFragment() {              .create()          dialog.setCanceledOnTouchOutside(false) -        taskViewModel.isComplete.observe(this) { complete -> -            if (complete) { -                dialog.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 -                    ) +        viewLifecycleOwner.lifecycleScope.launch { +            repeatOnLifecycle(Lifecycle.State.CREATED) { +                taskViewModel.isComplete.collect { +                    if (it) { +                        dialog.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 +                            ) +                        } +                        taskViewModel.clear() +                    }                  } -                taskViewModel.clear()              }          } -        if (taskViewModel.isRunning.value == false) { +        if (!taskViewModel.isRunning.value) {              taskViewModel.runTask()          }          return dialog diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt index f54dccc69..2dbca76a5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt @@ -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,13 @@ 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.launch  import java.util.Locale  import org.yuzu.yuzu_emu.R  import org.yuzu.yuzu_emu.YuzuApplication @@ -52,6 +57,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?) {          homeViewModel.setNavigationVisibility(visible = true, animated = false)          preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) @@ -79,21 +86,32 @@ class SearchFragment : Fragment() {              filterAndSearch()          } -        gamesViewModel.apply { -            searchFocused.observe(viewLifecycleOwner) { searchFocused -> -                if (searchFocused) { -                    focusSearch() -                    gamesViewModel.setSearchFocused(false) +        viewLifecycleOwner.lifecycleScope.apply { +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    gamesViewModel.searchFocused.collect { +                        if (it) { +                            focusSearch() +                            gamesViewModel.setSearchFocused(false) +                        } +                    }                  }              } - -            games.observe(viewLifecycleOwner) { filterAndSearch() } -            searchedGames.observe(viewLifecycleOwner) { -                (binding.gridGamesSearch.adapter as GameAdapter).submitList(it) -                if (it.isEmpty()) { -                    binding.noResultsView.visibility = View.VISIBLE -                } else { -                    binding.noResultsView.visibility = View.GONE +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    gamesViewModel.games.collect { 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 +                        } +                    }                  }              }          } @@ -109,7 +127,7 @@ class SearchFragment : Fragment() {      private inner class ScoredGame(val score: Double, val item: Game)      private fun filterAndSearch() { -        val baseList = gamesViewModel.games.value!! +        val baseList = gamesViewModel.games.value          val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) {              R.id.chip_recently_played -> {                  baseList.filter { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt index 55b6a0367..9d0594c6e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt @@ -15,10 +15,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.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 @@ -79,10 +83,14 @@ class SettingsSearchFragment : Fragment() {              search()              binding.settingsList.smoothScrollToPosition(0)          } -        settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) { -            if (it) { -                settingsViewModel.setShouldReloadSettingsList(false) -                search() +        viewLifecycleOwner.lifecycleScope.launch { +            repeatOnLifecycle(Lifecycle.State.CREATED) { +                settingsViewModel.shouldReloadSettingsList.collect { +                    if (it) { +                        settingsViewModel.setShouldReloadSettingsList(false) +                        search() +                    } +                }              }          } 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 d50c421a0..fbb2f6e18 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 @@ -22,10 +22,14 @@ 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  import com.google.android.material.transition.MaterialFadeThrough +import kotlinx.coroutines.launch  import java.io.File  import org.yuzu.yuzu_emu.R  import org.yuzu.yuzu_emu.YuzuApplication @@ -206,10 +210,14 @@ class SetupFragment : Fragment() {              )          } -        homeViewModel.shouldPageForward.observe(viewLifecycleOwner) { -            if (it) { -                pageForward() -                homeViewModel.setShouldPageForward(false) +        viewLifecycleOwner.lifecycleScope.launch { +            repeatOnLifecycle(Lifecycle.State.CREATED) { +                homeViewModel.shouldPageForward.collect { +                    if (it) { +                        pageForward() +                        homeViewModel.setShouldPageForward(false) +                    } +                }              }          } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt index e35f51bc3..f34870c2d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt @@ -3,28 +3,28 @@  package org.yuzu.yuzu_emu.model -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData  import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow  class EmulationViewModel : ViewModel() { -    private val _emulationStarted = MutableLiveData(false) -    val emulationStarted: LiveData<Boolean> get() = _emulationStarted +    val emulationStarted: StateFlow<Boolean> get() = _emulationStarted +    private val _emulationStarted = MutableStateFlow(false) -    private val _isEmulationStopping = MutableLiveData(false) -    val isEmulationStopping: LiveData<Boolean> get() = _isEmulationStopping +    val isEmulationStopping: StateFlow<Boolean> get() = _isEmulationStopping +    private val _isEmulationStopping = MutableStateFlow(false) -    private val _shaderProgress = MutableLiveData(0) -    val shaderProgress: LiveData<Int> get() = _shaderProgress +    val shaderProgress: StateFlow<Int> get() = _shaderProgress +    private val _shaderProgress = MutableStateFlow(0) -    private val _totalShaders = MutableLiveData(0) -    val totalShaders: LiveData<Int> get() = _totalShaders +    val totalShaders: StateFlow<Int> get() = _totalShaders +    private val _totalShaders = MutableStateFlow(0) -    private val _shaderMessage = MutableLiveData("") -    val shaderMessage: LiveData<String> get() = _shaderMessage +    val shaderMessage: StateFlow<String> get() = _shaderMessage +    private val _shaderMessage = MutableStateFlow("")      fun setEmulationStarted(started: Boolean) { -        _emulationStarted.postValue(started) +        _emulationStarted.value = started      }      fun setIsEmulationStopping(value: Boolean) { @@ -50,10 +50,18 @@ class EmulationViewModel : ViewModel() {      }      fun clear() { -        _emulationStarted.value = false -        _isEmulationStopping.value = false -        _shaderProgress.value = 0 -        _totalShaders.value = 0 -        _shaderMessage.value = "" +        setEmulationStarted(false) +        setIsEmulationStopping(false) +        setShaderProgress(0) +        setTotalShaders(0) +        setShaderMessage("") +    } + +    companion object { +        const val KEY_EMULATION_STARTED = "EmulationStarted" +        const val KEY_IS_EMULATION_STOPPING = "IsEmulationStarting" +        const val KEY_SHADER_PROGRESS = "ShaderProgress" +        const val KEY_TOTAL_SHADERS = "TotalShaders" +        const val KEY_SHADER_MESSAGE = "ShaderMessage"      }  } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt index 1fe42f922..6e09fa81d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt @@ -5,13 +5,13 @@ package org.yuzu.yuzu_emu.model  import android.net.Uri  import androidx.documentfile.provider.DocumentFile -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData  import androidx.lifecycle.ViewModel  import androidx.lifecycle.viewModelScope  import androidx.preference.PreferenceManager  import java.util.Locale  import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow  import kotlinx.coroutines.launch  import kotlinx.coroutines.withContext  import kotlinx.serialization.ExperimentalSerializationApi @@ -24,23 +24,23 @@ import org.yuzu.yuzu_emu.utils.GameHelper  @OptIn(ExperimentalSerializationApi::class)  class GamesViewModel : ViewModel() { -    private val _games = MutableLiveData<List<Game>>(emptyList()) -    val games: LiveData<List<Game>> get() = _games +    val games: StateFlow<List<Game>> get() = _games +    private val _games = MutableStateFlow(emptyList<Game>()) -    private val _searchedGames = MutableLiveData<List<Game>>(emptyList()) -    val searchedGames: LiveData<List<Game>> get() = _searchedGames +    val searchedGames: StateFlow<List<Game>> get() = _searchedGames +    private val _searchedGames = MutableStateFlow(emptyList<Game>()) -    private val _isReloading = MutableLiveData(false) -    val isReloading: LiveData<Boolean> get() = _isReloading +    val isReloading: StateFlow<Boolean> get() = _isReloading +    private val _isReloading = MutableStateFlow(false) -    private val _shouldSwapData = MutableLiveData(false) -    val shouldSwapData: LiveData<Boolean> get() = _shouldSwapData +    val shouldSwapData: StateFlow<Boolean> get() = _shouldSwapData +    private val _shouldSwapData = MutableStateFlow(false) -    private val _shouldScrollToTop = MutableLiveData(false) -    val shouldScrollToTop: LiveData<Boolean> get() = _shouldScrollToTop +    val shouldScrollToTop: StateFlow<Boolean> get() = _shouldScrollToTop +    private val _shouldScrollToTop = MutableStateFlow(false) -    private val _searchFocused = MutableLiveData(false) -    val searchFocused: LiveData<Boolean> get() = _searchFocused +    val searchFocused: StateFlow<Boolean> get() = _searchFocused +    private val _searchFocused = MutableStateFlow(false)      init {          // Ensure keys are loaded so that ROM metadata can be decrypted. @@ -79,36 +79,36 @@ class GamesViewModel : ViewModel() {              )          ) -        _games.postValue(sortedList) +        _games.value = sortedList      }      fun setSearchedGames(games: List<Game>) { -        _searchedGames.postValue(games) +        _searchedGames.value = games      }      fun setShouldSwapData(shouldSwap: Boolean) { -        _shouldSwapData.postValue(shouldSwap) +        _shouldSwapData.value = shouldSwap      }      fun setShouldScrollToTop(shouldScroll: Boolean) { -        _shouldScrollToTop.postValue(shouldScroll) +        _shouldScrollToTop.value = shouldScroll      }      fun setSearchFocused(searchFocused: Boolean) { -        _searchFocused.postValue(searchFocused) +        _searchFocused.value = searchFocused      }      fun reloadGames(directoryChanged: Boolean) { -        if (isReloading.value == true) { +        if (isReloading.value) {              return          } -        _isReloading.postValue(true) +        _isReloading.value = true          viewModelScope.launch {              withContext(Dispatchers.IO) {                  NativeLibrary.resetRomMetadata()                  setGames(GameHelper.getGames()) -                _isReloading.postValue(false) +                _isReloading.value = false                  if (directoryChanged) {                      setShouldSwapData(true) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt index 498c222fa..b32e19373 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt @@ -3,8 +3,8 @@  package org.yuzu.yuzu_emu.model -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow  data class HomeSetting(      val titleId: Int, @@ -14,5 +14,5 @@ data class HomeSetting(      val isEnabled: () -> Boolean = { true },      val disabledTitleId: Int = 0,      val disabledMessageId: Int = 0, -    val details: LiveData<String> = MutableLiveData("") +    val details: StateFlow<String> = MutableStateFlow("")  ) 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 a48ef7a88..756f76721 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 @@ -5,47 +5,43 @@ package org.yuzu.yuzu_emu.model  import android.net.Uri  import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData  import androidx.lifecycle.ViewModel  import androidx.lifecycle.ViewModelProvider  import androidx.preference.PreferenceManager +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow  import org.yuzu.yuzu_emu.YuzuApplication  import org.yuzu.yuzu_emu.utils.GameHelper  class HomeViewModel : ViewModel() { -    private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>() -    val navigationVisible: LiveData<Pair<Boolean, Boolean>> get() = _navigationVisible +    val navigationVisible: StateFlow<Pair<Boolean, Boolean>> get() = _navigationVisible +    private val _navigationVisible = MutableStateFlow(Pair(false, false)) -    private val _statusBarShadeVisible = MutableLiveData(true) -    val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible +    val statusBarShadeVisible: StateFlow<Boolean> get() = _statusBarShadeVisible +    private val _statusBarShadeVisible = MutableStateFlow(true) -    private val _shouldPageForward = MutableLiveData(false) -    val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward +    val shouldPageForward: StateFlow<Boolean> get() = _shouldPageForward +    private val _shouldPageForward = MutableStateFlow(false) -    private val _gamesDir = MutableLiveData( +    val gamesDir: StateFlow<String> get() = _gamesDir +    private val _gamesDir = MutableStateFlow(          Uri.parse(              PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)                  .getString(GameHelper.KEY_GAME_PATH, "")          ).path ?: ""      ) -    val gamesDir: LiveData<String> get() = _gamesDir      var navigatedToSetup = false -    init { -        _navigationVisible.value = Pair(false, false) -    } -      fun setNavigationVisibility(visible: Boolean, animated: Boolean) { -        if (_navigationVisible.value?.first == visible) { +        if (navigationVisible.value.first == visible) {              return          }          _navigationVisible.value = Pair(visible, animated)      }      fun setStatusBarShadeVisibility(visible: Boolean) { -        if (_statusBarShadeVisible.value == visible) { +        if (statusBarShadeVisible.value == visible) {              return          }          _statusBarShadeVisible.value = visible diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt index d16d15fa6..53fa7a8de 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt @@ -3,48 +3,43 @@  package org.yuzu.yuzu_emu.model -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.SavedStateHandle  import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow  import org.yuzu.yuzu_emu.R  import org.yuzu.yuzu_emu.YuzuApplication  import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem -class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { +class SettingsViewModel : ViewModel() {      var game: Game? = null      var shouldSave = false      var clickedItem: SettingsItem? = null -    private val _toolbarTitle = MutableLiveData("") -    val toolbarTitle: LiveData<String> get() = _toolbarTitle +    val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate +    private val _shouldRecreate = MutableStateFlow(false) -    private val _shouldRecreate = MutableLiveData(false) -    val shouldRecreate: LiveData<Boolean> get() = _shouldRecreate +    val shouldNavigateBack: StateFlow<Boolean> get() = _shouldNavigateBack +    private val _shouldNavigateBack = MutableStateFlow(false) -    private val _shouldNavigateBack = MutableLiveData(false) -    val shouldNavigateBack: LiveData<Boolean> get() = _shouldNavigateBack +    val shouldShowResetSettingsDialog: StateFlow<Boolean> get() = _shouldShowResetSettingsDialog +    private val _shouldShowResetSettingsDialog = MutableStateFlow(false) -    private val _shouldShowResetSettingsDialog = MutableLiveData(false) -    val shouldShowResetSettingsDialog: LiveData<Boolean> get() = _shouldShowResetSettingsDialog +    val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList +    private val _shouldReloadSettingsList = MutableStateFlow(false) -    private val _shouldReloadSettingsList = MutableLiveData(false) -    val shouldReloadSettingsList: LiveData<Boolean> get() = _shouldReloadSettingsList +    val isUsingSearch: StateFlow<Boolean> get() = _isUsingSearch +    private val _isUsingSearch = MutableStateFlow(false) -    private val _isUsingSearch = MutableLiveData(false) -    val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch +    val sliderProgress: StateFlow<Int> get() = _sliderProgress +    private val _sliderProgress = MutableStateFlow(-1) -    val sliderProgress = savedStateHandle.getStateFlow(KEY_SLIDER_PROGRESS, -1) +    val sliderTextValue: StateFlow<String> get() = _sliderTextValue +    private val _sliderTextValue = MutableStateFlow("") -    val sliderTextValue = savedStateHandle.getStateFlow(KEY_SLIDER_TEXT_VALUE, "") - -    val adapterItemChanged = savedStateHandle.getStateFlow(KEY_ADAPTER_ITEM_CHANGED, -1) - -    fun setToolbarTitle(value: String) { -        _toolbarTitle.value = value -    } +    val adapterItemChanged: StateFlow<Int> get() = _adapterItemChanged +    private val _adapterItemChanged = MutableStateFlow(-1)      fun setShouldRecreate(value: Boolean) {          _shouldRecreate.value = value @@ -67,8 +62,8 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo      }      fun setSliderTextValue(value: Float, units: String) { -        savedStateHandle[KEY_SLIDER_PROGRESS] = value -        savedStateHandle[KEY_SLIDER_TEXT_VALUE] = String.format( +        _sliderProgress.value = value.toInt() +        _sliderTextValue.value = String.format(              YuzuApplication.appContext.getString(R.string.value_with_units),              value.toInt().toString(),              units @@ -76,21 +71,15 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo      }      fun setSliderProgress(value: Float) { -        savedStateHandle[KEY_SLIDER_PROGRESS] = value +        _sliderProgress.value = value.toInt()      }      fun setAdapterItemChanged(value: Int) { -        savedStateHandle[KEY_ADAPTER_ITEM_CHANGED] = value +        _adapterItemChanged.value = value      }      fun clear() {          game = null          shouldSave = false      } - -    companion object { -        const val KEY_SLIDER_TEXT_VALUE = "SliderTextValue" -        const val KEY_SLIDER_PROGRESS = "SliderProgress" -        const val KEY_ADAPTER_ITEM_CHANGED = "AdapterItemChanged" -    }  } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt index 27ea725a5..531c2aaf0 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt @@ -3,29 +3,25 @@  package org.yuzu.yuzu_emu.model -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData  import androidx.lifecycle.ViewModel  import androidx.lifecycle.viewModelScope  import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow  import kotlinx.coroutines.launch  class TaskViewModel : ViewModel() { -    private val _result = MutableLiveData<Any>() -    val result: LiveData<Any> = _result +    val result: StateFlow<Any> get() = _result +    private val _result = MutableStateFlow(Any()) -    private val _isComplete = MutableLiveData<Boolean>() -    val isComplete: LiveData<Boolean> = _isComplete +    val isComplete: StateFlow<Boolean> get() = _isComplete +    private val _isComplete = MutableStateFlow(false) -    private val _isRunning = MutableLiveData<Boolean>() -    val isRunning: LiveData<Boolean> = _isRunning +    val isRunning: StateFlow<Boolean> get() = _isRunning +    private val _isRunning = MutableStateFlow(false)      lateinit var task: () -> Any -    init { -        clear() -    } -      fun clear() {          _result.value = Any()          _isComplete.value = false @@ -33,15 +29,16 @@ class TaskViewModel : ViewModel() {      }      fun runTask() { -        if (_isRunning.value == true) { +        if (isRunning.value) {              return          }          _isRunning.value = true          viewModelScope.launch(Dispatchers.IO) {              val res = task() -            _result.postValue(res) -            _isComplete.postValue(true) +            _result.value = res +            _isComplete.value = true +            _isRunning.value = false          }      }  } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt index b0156dca5..805b89b31 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt @@ -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 @@ -14,8 +15,12 @@ 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 com.google.android.material.transition.MaterialFadeThrough +import kotlinx.coroutines.launch  import org.yuzu.yuzu_emu.R  import org.yuzu.yuzu_emu.adapters.GameAdapter  import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding @@ -44,6 +49,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?) {          homeViewModel.setNavigationVisibility(visible = true, animated = false) @@ -80,37 +87,48 @@ class GamesFragment : Fragment() {                  if (_binding == null) {                      return@post                  } -                binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value!! +                binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value              }          } -        gamesViewModel.apply { -            // Watch for when we get updates to any of our games lists -            isReloading.observe(viewLifecycleOwner) { isReloading -> -                binding.swipeRefresh.isRefreshing = isReloading +        viewLifecycleOwner.lifecycleScope.apply { +            launch { +                repeatOnLifecycle(Lifecycle.State.RESUMED) { +                    gamesViewModel.isReloading.collect { binding.swipeRefresh.isRefreshing = it } +                }              } -            games.observe(viewLifecycleOwner) { -                (binding.gridGames.adapter as GameAdapter).submitList(it) -                if (it.isEmpty()) { -                    binding.noticeText.visibility = View.VISIBLE -                } else { -                    binding.noticeText.visibility = View.GONE +            launch { +                repeatOnLifecycle(Lifecycle.State.RESUMED) { +                    gamesViewModel.games.collect { +                        (binding.gridGames.adapter as GameAdapter).submitList(it) +                        if (it.isEmpty()) { +                            binding.noticeText.visibility = View.VISIBLE +                        } else { +                            binding.noticeText.visibility = View.GONE +                        } +                    }                  }              } -            shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData -> -                if (shouldSwapData) { -                    (binding.gridGames.adapter as GameAdapter).submitList( -                        gamesViewModel.games.value!! -                    ) -                    gamesViewModel.setShouldSwapData(false) +            launch { +                repeatOnLifecycle(Lifecycle.State.RESUMED) { +                    gamesViewModel.shouldSwapData.collect { +                        if (it) { +                            (binding.gridGames.adapter as GameAdapter).submitList( +                                gamesViewModel.games.value +                            ) +                            gamesViewModel.setShouldSwapData(false) +                        } +                    }                  }              } - -            // Check if the user reselected the games menu item and then scroll to top of the list -            shouldScrollToTop.observe(viewLifecycleOwner) { shouldScroll -> -                if (shouldScroll) { -                    scrollToTop() -                    gamesViewModel.setShouldScrollToTop(false) +            launch { +                repeatOnLifecycle(Lifecycle.State.RESUMED) { +                    gamesViewModel.shouldScrollToTop.collect { +                        if (it) { +                            scrollToTop() +                            gamesViewModel.setShouldScrollToTop(false) +                        } +                    }                  }              }          } 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 7d8e06ad8..b6b6c6c17 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 @@ -19,7 +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 @@ -40,7 +42,6 @@ import org.yuzu.yuzu_emu.activities.EmulationActivity  import org.yuzu.yuzu_emu.databinding.ActivityMainBinding  import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding  import org.yuzu.yuzu_emu.features.settings.model.Settings -import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile  import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment  import org.yuzu.yuzu_emu.fragments.MessageDialogFragment  import org.yuzu.yuzu_emu.model.GamesViewModel @@ -107,7 +108,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {                  R.id.homeSettingsFragment -> {                      val action = HomeNavigationDirections.actionGlobalSettingsActivity(                          null, -                        SettingsFile.FILE_NAME_CONFIG +                        Settings.MenuTag.SECTION_ROOT                      )                      navHostFragment.navController.navigate(action)                  } @@ -115,16 +116,22 @@ class MainActivity : AppCompatActivity(), ThemeProvider {          }          // Prevents navigation from being drawn for a short time on recreation if set to hidden -        if (!homeViewModel.navigationVisible.value?.first!!) { +        if (!homeViewModel.navigationVisible.value.first) {              binding.navigationView.visibility = View.INVISIBLE              binding.statusBarShade.visibility = View.INVISIBLE          } -        homeViewModel.navigationVisible.observe(this) { -            showNavigation(it.first, it.second) -        } -        homeViewModel.statusBarShadeVisible.observe(this) { visible -> -            showStatusBarShade(visible) +        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) } +                } +            }          }          // Dismiss previous notifications (should not happen unless a crash occurred) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt index c0fe596d7..9fe99fab1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt @@ -6,9 +6,11 @@ package org.yuzu.yuzu_emu.utils  import android.graphics.Bitmap  import android.graphics.BitmapFactory  import android.widget.ImageView +import androidx.core.graphics.drawable.toBitmap  import androidx.core.graphics.drawable.toDrawable  import coil.ImageLoader  import coil.decode.DataSource +import coil.executeBlocking  import coil.fetch.DrawableResult  import coil.fetch.FetchResult  import coil.fetch.Fetcher @@ -74,4 +76,13 @@ object GameIconUtils {              .build()          imageLoader.enqueue(request)      } + +    fun getGameIcon(game: Game): Bitmap { +        val request = ImageRequest.Builder(YuzuApplication.appContext) +            .data(game) +            .error(R.drawable.default_icon) +            .build() +        return imageLoader.executeBlocking(request) +            .drawable!!.toBitmap(config = Bitmap.Config.ARGB_8888) +    }  } diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index 34b425cb4..81120ab0f 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -282,7 +282,7 @@ void Config::ReadValues() {      std::stringstream ss(title_list);      std::string line;      while (std::getline(ss, line, '|')) { -        const auto title_id = std::stoul(line, nullptr, 16); +        const auto title_id = std::strtoul(line.c_str(), nullptr, 16);          const auto disabled_list = config->Get("AddOns", "disabled_" + line, "");          std::stringstream inner_ss(disabled_list); diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index b9ecefa74..f31fe054b 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -262,9 +262,6 @@ public:      Core::SystemResultStatus InitializeEmulation(const std::string& filepath) {          std::scoped_lock lock(m_mutex); -        // Loads the configuration. -        Config{}; -          // Create the render window.          m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window,                                                         m_vulkan_library); @@ -330,12 +327,13 @@ public:              m_system.ShutdownMainProcess();              m_detached_tasks.WaitForAllTasks();              m_load_result = Core::SystemResultStatus::ErrorNotInitialized; +            m_window.reset(); +            OnEmulationStopped(Core::SystemResultStatus::Success); +            return;          }          // Tear down the render window.          m_window.reset(); - -        OnEmulationStopped(m_load_result);      }      void PauseEmulation() { @@ -672,18 +670,6 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass claz      return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());  } -void Java_org_yuzu_yuzu_1emu_NativeLibrary_muteAduio(JNIEnv* env, jclass clazz) { -    Settings::values.audio_muted = true; -} - -void Java_org_yuzu_yuzu_1emu_NativeLibrary_unmuteAudio(JNIEnv* env, jclass clazz) { -    Settings::values.audio_muted = false; -} - -jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isMuted(JNIEnv* env, jclass clazz) { -    return static_cast<jboolean>(Settings::values.audio_muted.GetValue()); -} -  jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env, jclass clazz) {      return EmulationSession::GetInstance().IsHandheldOnly();  } diff --git a/src/android/app/src/main/res/drawable/shortcut.xml b/src/android/app/src/main/res/drawable/shortcut.xml new file mode 100644 index 000000000..c749e5d72 --- /dev/null +++ b/src/android/app/src/main/res/drawable/shortcut.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + +    <item> +        <color android:color="@android:color/white" /> +    </item> +    <item android:id="@+id/shortcut_foreground"> +        <bitmap android:src="@drawable/default_icon" /> +    </item> + +</layer-list> diff --git a/src/android/app/src/main/res/navigation/emulation_navigation.xml b/src/android/app/src/main/res/navigation/emulation_navigation.xml index c7be37f9b..cfc494b3f 100644 --- a/src/android/app/src/main/res/navigation/emulation_navigation.xml +++ b/src/android/app/src/main/res/navigation/emulation_navigation.xml @@ -27,7 +27,7 @@              app:nullable="true" />          <argument              android:name="menuTag" -            app:argType="string" /> +            app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />      </activity>      <action diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml index 2085430bf..2e0ce7a3d 100644 --- a/src/android/app/src/main/res/navigation/home_navigation.xml +++ b/src/android/app/src/main/res/navigation/home_navigation.xml @@ -82,7 +82,7 @@              app:nullable="true" />          <argument              android:name="menuTag" -            app:argType="string" /> +            app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />      </activity>      <action diff --git a/src/android/app/src/main/res/navigation/settings_navigation.xml b/src/android/app/src/main/res/navigation/settings_navigation.xml index 88e1b4587..1d87d36b3 100644 --- a/src/android/app/src/main/res/navigation/settings_navigation.xml +++ b/src/android/app/src/main/res/navigation/settings_navigation.xml @@ -10,7 +10,7 @@          android:label="SettingsFragment">          <argument              android:name="menuTag" -            app:argType="string" /> +            app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />          <argument              android:name="game"              app:argType="org.yuzu.yuzu_emu.model.Game" diff --git a/src/android/app/src/main/res/values/dimens.xml b/src/android/app/src/main/res/values/dimens.xml index 00757e5e8..7b2296d95 100644 --- a/src/android/app/src/main/res/values/dimens.xml +++ b/src/android/app/src/main/res/values/dimens.xml @@ -12,6 +12,7 @@      <dimen name="spacing_refresh_end">72dp</dimen>      <dimen name="menu_width">256dp</dimen>      <dimen name="card_width">165dp</dimen> +    <dimen name="icon_inset">24dp</dimen>      <dimen name="dialog_margin">20dp</dimen>      <dimen name="elevated_app_bar">3dp</dimen>  | 
