From 35a3c7226a796ae7205753c13924fe0becd8fe60 Mon Sep 17 00:00:00 2001 From: t895 Date: Sat, 17 Feb 2024 21:23:38 -0500 Subject: [PATCH] android: Create lifecycle utility to simplify common StateFlow operations --- .../adapters/GamePropertiesAdapter.kt | 11 +- .../yuzu_emu/adapters/HomeSettingAdapter.kt | 11 +- .../settings/ui/InputProfileDialogFragment.kt | 39 ++-- .../features/settings/ui/SettingsActivity.kt | 55 ++--- .../settings/ui/SettingsDialogFragment.kt | 21 +- .../features/settings/ui/SettingsFragment.kt | 99 +++------ .../settings/ui/SettingsSearchFragment.kt | 17 +- .../yuzu/yuzu_emu/fragments/AddonsFragment.kt | 91 ++++----- .../fragments/DriverManagerFragment.kt | 18 +- .../fragments/DriversLoadingDialogFragment.kt | 13 +- .../yuzu_emu/fragments/EmulationFragment.kt | 191 ++++++------------ .../yuzu_emu/fragments/GameFoldersFragment.kt | 12 +- .../fragments/GamePropertiesFragment.kt | 36 +--- .../yuzu_emu/fragments/InstallableFragment.kt | 16 +- .../fragments/ProgressDialogFragment.kt | 97 ++++----- .../yuzu/yuzu_emu/fragments/SearchFragment.kt | 41 +--- .../yuzu/yuzu_emu/fragments/SetupFragment.kt | 37 +--- .../org/yuzu/yuzu_emu/ui/GamesFragment.kt | 69 ++----- .../org/yuzu/yuzu_emu/ui/main/MainActivity.kt | 45 +---- .../org/yuzu/yuzu_emu/utils/LifecycleUtils.kt | 38 ++++ 20 files changed, 329 insertions(+), 628 deletions(-) create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/LifecycleUtils.kt diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt index 017306875c..7366e2c778 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt @@ -6,11 +6,7 @@ package org.yuzu.yuzu_emu.adapters import android.view.LayoutInflater import android.view.ViewGroup import androidx.core.content.res.ResourcesCompat -import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import kotlinx.coroutines.launch import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding import org.yuzu.yuzu_emu.model.GameProperty @@ -18,6 +14,7 @@ import org.yuzu.yuzu_emu.model.InstallableProperty import org.yuzu.yuzu_emu.model.SubmenuProperty import org.yuzu.yuzu_emu.utils.ViewUtils.marquee import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible +import org.yuzu.yuzu_emu.utils.collect import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder class GamePropertiesAdapter( @@ -82,11 +79,7 @@ class GamePropertiesAdapter( binding.details.text = submenuProperty.details.invoke() } else if (submenuProperty.detailsFlow != null) { binding.details.setVisible(true) - viewLifecycle.lifecycleScope.launch { - viewLifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { - submenuProperty.detailsFlow.collect { binding.details.text = it } - } - } + submenuProperty.detailsFlow.collect(viewLifecycle) { binding.details.text = it } } else { binding.details.setVisible(false) } 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 9234a49014..0bd196673c 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 @@ -8,17 +8,14 @@ import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.core.content.res.ResourcesCompat -import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import kotlinx.coroutines.launch import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding import org.yuzu.yuzu_emu.fragments.MessageDialogFragment import org.yuzu.yuzu_emu.model.HomeSetting import org.yuzu.yuzu_emu.utils.ViewUtils.marquee import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible +import org.yuzu.yuzu_emu.utils.collect import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder class HomeSettingAdapter( @@ -59,11 +56,7 @@ class HomeSettingAdapter( binding.optionIcon.alpha = 0.5f } - viewLifecycle.lifecycleScope.launch { - viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) { - model.details.collect { updateOptionDetails(it) } - } - } + model.details.collect(viewLifecycle) { updateOptionDetails(it) } binding.optionDetail.marquee() binding.root.setOnClickListener { onClick(model) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputProfileDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputProfileDialogFragment.kt index 9b24d41c16..1bae593ae0 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputProfileDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputProfileDialogFragment.kt @@ -11,16 +11,13 @@ import android.view.ViewGroup import android.widget.Toast import androidx.fragment.app.DialogFragment import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.dialog.MaterialAlertDialogBuilder -import kotlinx.coroutines.launch import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.databinding.DialogInputProfilesBinding import org.yuzu.yuzu_emu.features.settings.model.view.InputProfileSetting import org.yuzu.yuzu_emu.fragments.MessageDialogFragment +import org.yuzu.yuzu_emu.utils.collect class InputProfileDialogFragment : DialogFragment() { private var position = 0 @@ -110,25 +107,21 @@ class InputProfileDialogFragment : DialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewLifecycleOwner.lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - settingsViewModel.shouldShowDeleteProfileDialog.collect { - if (it.isNotEmpty()) { - MessageDialogFragment.newInstance( - activity = requireActivity(), - titleId = R.string.delete_input_profile, - descriptionId = R.string.delete_input_profile_description, - positiveAction = { - setting.deleteProfile(it) - settingsViewModel.setReloadListAndNotifyDataset(true) - }, - negativeAction = {}, - negativeButtonTitleId = android.R.string.cancel - ).show(parentFragmentManager, MessageDialogFragment.TAG) - settingsViewModel.setShouldShowDeleteProfileDialog("") - dismiss() - } - } + settingsViewModel.shouldShowDeleteProfileDialog.collect(viewLifecycleOwner) { + if (it.isNotEmpty()) { + MessageDialogFragment.newInstance( + activity = requireActivity(), + titleId = R.string.delete_input_profile, + descriptionId = R.string.delete_input_profile_description, + positiveAction = { + setting.deleteProfile(it) + settingsViewModel.setReloadListAndNotifyDataset(true) + }, + negativeAction = {}, + negativeButtonTitleId = android.R.string.cancel + ).show(parentFragmentManager, MessageDialogFragment.TAG) + settingsViewModel.setShouldShowDeleteProfileDialog("") + dismiss() } } } 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 681a18b3b1..455b3b5ff1 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,14 +13,9 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.NavHostFragment import androidx.navigation.navArgs import com.google.android.material.color.MaterialColors -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch import org.yuzu.yuzu_emu.NativeLibrary import java.io.IOException import org.yuzu.yuzu_emu.R @@ -70,39 +65,23 @@ class SettingsActivity : AppCompatActivity() { ) } - lifecycleScope.apply { - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - settingsViewModel.shouldRecreate.collectLatest { - if (it) { - settingsViewModel.setShouldRecreate(false) - recreate() - } - } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - settingsViewModel.shouldNavigateBack.collectLatest { - if (it) { - settingsViewModel.setShouldNavigateBack(false) - navigateBack() - } - } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - settingsViewModel.shouldShowResetSettingsDialog.collectLatest { - if (it) { - settingsViewModel.setShouldShowResetSettingsDialog(false) - ResetSettingsDialogFragment().show( - supportFragmentManager, - ResetSettingsDialogFragment.TAG - ) - } - } - } + settingsViewModel.shouldRecreate.collect( + this, + resetState = { settingsViewModel.setShouldRecreate(false) } + ) { if (it) recreate() } + settingsViewModel.shouldNavigateBack.collect( + this, + resetState = { settingsViewModel.setShouldNavigateBack(false) } + ) { if (it) navigateBack() } + settingsViewModel.shouldShowResetSettingsDialog.collect( + this, + resetState = { settingsViewModel.setShouldShowResetSettingsDialog(false) } + ) { + if (it) { + ResetSettingsDialogFragment().show( + supportFragmentManager, + ResetSettingsDialogFragment.TAG + ) } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt index 5d1ea5d295..a81ff6b1a9 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt @@ -11,12 +11,8 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.DialogFragment import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.slider.Slider -import kotlinx.coroutines.launch import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.databinding.DialogSliderBinding import org.yuzu.yuzu_emu.features.input.NativeInput @@ -29,6 +25,7 @@ import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting import org.yuzu.yuzu_emu.utils.ParamPackage +import org.yuzu.yuzu_emu.utils.collect class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener { private var type = 0 @@ -169,17 +166,11 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener super.onViewCreated(view, savedInstanceState) when (type) { SettingsItem.TYPE_SLIDER -> { - viewLifecycleOwner.lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - settingsViewModel.sliderTextValue.collect { - sliderBinding.textValue.text = it - } - } - repeatOnLifecycle(Lifecycle.State.CREATED) { - settingsViewModel.sliderProgress.collect { - sliderBinding.slider.value = it.toFloat() - } - } + settingsViewModel.sliderTextValue.collect(viewLifecycleOwner) { + sliderBinding.textValue.text = it + } + settingsViewModel.sliderProgress.collect(viewLifecycleOwner) { + sliderBinding.slider.value = it.toFloat() } } } 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 0cf944b431..ec16f16c46 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 @@ -13,21 +13,17 @@ import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.transition.MaterialSharedAxis -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding import org.yuzu.yuzu_emu.features.input.NativeInput import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.fragments.MessageDialogFragment import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins +import org.yuzu.yuzu_emu.utils.collect class SettingsFragment : Fragment() { private lateinit var presenter: SettingsFragmentPresenter @@ -63,8 +59,7 @@ class SettingsFragment : Fragment() { return binding.root } - // This is using the correct scope, lint is just acting up - @SuppressLint("UnsafeRepeatOnLifecycleDetector", "NotifyDataSetChanged") + @SuppressLint("NotifyDataSetChanged") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) settingsAdapter = SettingsAdapter(this, requireContext()) @@ -100,65 +95,37 @@ class SettingsFragment : Fragment() { settingsViewModel.setShouldNavigateBack(true) } - viewLifecycleOwner.lifecycleScope.apply { - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - settingsViewModel.shouldReloadSettingsList.collectLatest { - if (it) { - settingsViewModel.setShouldReloadSettingsList(false) - presenter.loadSettingsList() - } - } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - settingsViewModel.adapterItemChanged.collect { - if (it != -1) { - settingsAdapter?.notifyItemChanged(it) - settingsViewModel.setAdapterItemChanged(-1) - } - } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - settingsViewModel.datasetChanged.collect { - if (it) { - settingsAdapter?.notifyDataSetChanged() - settingsViewModel.setDatasetChanged(false) - } - } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - settingsViewModel.reloadListAndNotifyDataset.collectLatest { - if (it) { - settingsViewModel.setReloadListAndNotifyDataset(false) - presenter.loadSettingsList(true) - } - } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - settingsViewModel.shouldShowResetInputDialog.collectLatest { - if (it) { - MessageDialogFragment.newInstance( - activity = requireActivity(), - titleId = R.string.reset_mapping, - descriptionId = R.string.reset_mapping_description, - positiveAction = { - NativeInput.resetControllerMappings(getPlayerIndex()) - settingsViewModel.setReloadListAndNotifyDataset(true) - }, - negativeAction = {} - ).show(parentFragmentManager, MessageDialogFragment.TAG) - settingsViewModel.setShouldShowResetInputDialog(false) - } - } - } + settingsViewModel.shouldReloadSettingsList.collect( + viewLifecycleOwner, + resetState = { settingsViewModel.setShouldReloadSettingsList(false) } + ) { if (it) presenter.loadSettingsList() } + settingsViewModel.adapterItemChanged.collect( + viewLifecycleOwner, + resetState = { settingsViewModel.setAdapterItemChanged(-1) } + ) { if (it != -1) settingsAdapter?.notifyItemChanged(it) } + settingsViewModel.datasetChanged.collect( + viewLifecycleOwner, + resetState = { settingsViewModel.setDatasetChanged(false) } + ) { if (it) settingsAdapter?.notifyDataSetChanged() } + settingsViewModel.reloadListAndNotifyDataset.collect( + viewLifecycleOwner, + resetState = { settingsViewModel.setReloadListAndNotifyDataset(false) } + ) { if (it) presenter.loadSettingsList(true) } + settingsViewModel.shouldShowResetInputDialog.collect( + viewLifecycleOwner, + resetState = { settingsViewModel.setShouldShowResetInputDialog(false) } + ) { + if (it) { + MessageDialogFragment.newInstance( + activity = requireActivity(), + titleId = R.string.reset_mapping, + descriptionId = R.string.reset_mapping_description, + positiveAction = { + NativeInput.resetControllerMappings(getPlayerIndex()) + settingsViewModel.setReloadListAndNotifyDataset(true) + }, + negativeAction = {} + ).show(parentFragmentManager, MessageDialogFragment.TAG) } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsSearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsSearchFragment.kt index c4c1d563ad..ed60cf34f9 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsSearchFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsSearchFragment.kt @@ -15,20 +15,17 @@ import androidx.core.view.updatePadding import androidx.core.widget.doOnTextChanged import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.divider.MaterialDividerItemDecoration import com.google.android.material.transition.MaterialSharedAxis import info.debatty.java.stringsimilarity.Cosine -import kotlinx.coroutines.launch import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem import org.yuzu.yuzu_emu.utils.NativeConfig import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins +import org.yuzu.yuzu_emu.utils.collect class SettingsSearchFragment : Fragment() { private var _binding: FragmentSettingsSearchBinding? = null @@ -84,14 +81,10 @@ class SettingsSearchFragment : Fragment() { search() binding.settingsList.smoothScrollToPosition(0) } - viewLifecycleOwner.lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - settingsViewModel.shouldReloadSettingsList.collect { - if (it) { - settingsViewModel.setShouldReloadSettingsList(false) - search() - } - } + settingsViewModel.shouldReloadSettingsList.collect(viewLifecycleOwner) { + if (it) { + settingsViewModel.setShouldReloadSettingsList(false) + search() } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt index 872553ac43..110aa29600 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt @@ -3,7 +3,6 @@ package org.yuzu.yuzu_emu.fragments -import android.annotation.SuppressLint import android.content.Intent import android.os.Bundle import android.view.LayoutInflater @@ -16,9 +15,6 @@ import androidx.core.view.updatePadding import androidx.documentfile.provider.DocumentFile import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager @@ -32,6 +28,7 @@ import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.utils.AddonUtil import org.yuzu.yuzu_emu.utils.FileUtil.copyFilesTo import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins +import org.yuzu.yuzu_emu.utils.collect import java.io.File class AddonsFragment : Fragment() { @@ -60,8 +57,6 @@ class AddonsFragment : Fragment() { return binding.root } - // This is using the correct scope, lint is just acting up - @SuppressLint("UnsafeRepeatOnLifecycleDetector") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) homeViewModel.setNavigationVisibility(visible = false, animated = false) @@ -78,57 +73,41 @@ class AddonsFragment : Fragment() { adapter = AddonAdapter(addonViewModel) } - viewLifecycleOwner.lifecycleScope.apply { - launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - addonViewModel.addonList.collect { - (binding.listAddons.adapter as AddonAdapter).submitList(it) - } - } + addonViewModel.addonList.collect(viewLifecycleOwner) { + (binding.listAddons.adapter as AddonAdapter).submitList(it) + } + addonViewModel.showModInstallPicker.collect( + viewLifecycleOwner, + resetState = { addonViewModel.showModInstallPicker(false) } + ) { if (it) installAddon.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) } + addonViewModel.showModNoticeDialog.collect( + viewLifecycleOwner, + resetState = { addonViewModel.showModNoticeDialog(false) } + ) { + if (it) { + MessageDialogFragment.newInstance( + requireActivity(), + titleId = R.string.addon_notice, + descriptionId = R.string.addon_notice_description, + dismissible = false, + positiveAction = { addonViewModel.showModInstallPicker(true) }, + negativeAction = {}, + negativeButtonTitleId = R.string.close + ).show(parentFragmentManager, MessageDialogFragment.TAG) } - launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - addonViewModel.showModInstallPicker.collect { - if (it) { - installAddon.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) - addonViewModel.showModInstallPicker(false) - } - } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - addonViewModel.showModNoticeDialog.collect { - if (it) { - MessageDialogFragment.newInstance( - requireActivity(), - titleId = R.string.addon_notice, - descriptionId = R.string.addon_notice_description, - dismissible = false, - positiveAction = { addonViewModel.showModInstallPicker(true) }, - negativeAction = {}, - negativeButtonTitleId = R.string.close - ).show(parentFragmentManager, MessageDialogFragment.TAG) - addonViewModel.showModNoticeDialog(false) - } - } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - addonViewModel.addonToDelete.collect { - if (it != null) { - MessageDialogFragment.newInstance( - requireActivity(), - titleId = R.string.confirm_uninstall, - descriptionId = R.string.confirm_uninstall_description, - positiveAction = { addonViewModel.onDeleteAddon(it) }, - negativeAction = {} - ).show(parentFragmentManager, MessageDialogFragment.TAG) - addonViewModel.setAddonToDelete(null) - } - } - } + } + addonViewModel.addonToDelete.collect( + viewLifecycleOwner, + resetState = { addonViewModel.setAddonToDelete(null) } + ) { + if (it != null) { + MessageDialogFragment.newInstance( + requireActivity(), + titleId = R.string.confirm_uninstall, + descriptionId = R.string.confirm_uninstall_description, + positiveAction = { addonViewModel.onDeleteAddon(it) }, + negativeAction = {} + ).show(parentFragmentManager, MessageDialogFragment.TAG) } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt index 41cff46c17..8b23a10217 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt @@ -3,7 +3,6 @@ package org.yuzu.yuzu_emu.fragments -import android.annotation.SuppressLint import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -14,9 +13,6 @@ import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.GridLayoutManager @@ -35,6 +31,7 @@ import org.yuzu.yuzu_emu.utils.FileUtil import org.yuzu.yuzu_emu.utils.GpuDriverHelper import org.yuzu.yuzu_emu.utils.NativeConfig import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins +import org.yuzu.yuzu_emu.utils.collect import java.io.File import java.io.IOException @@ -63,8 +60,6 @@ class DriverManagerFragment : Fragment() { return binding.root } - // This is using the correct scope, lint is just acting up - @SuppressLint("UnsafeRepeatOnLifecycleDetector") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) homeViewModel.setNavigationVisibility(visible = false, animated = true) @@ -89,15 +84,8 @@ class DriverManagerFragment : Fragment() { } } - viewLifecycleOwner.lifecycleScope.apply { - launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - driverViewModel.showClearButton.collect { - binding.toolbarDrivers.menu - .findItem(R.id.menu_driver_use_global).isVisible = it - } - } - } + driverViewModel.showClearButton.collect(viewLifecycleOwner) { + binding.toolbarDrivers.menu.findItem(R.id.menu_driver_use_global).isVisible = it } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt index 6a47b29f0d..bad56e434a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt @@ -10,14 +10,11 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.DialogFragment import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import com.google.android.material.dialog.MaterialAlertDialogBuilder -import kotlinx.coroutines.launch import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding import org.yuzu.yuzu_emu.model.DriverViewModel +import org.yuzu.yuzu_emu.utils.collect class DriversLoadingDialogFragment : DialogFragment() { private val driverViewModel: DriverViewModel by activityViewModels() @@ -44,13 +41,7 @@ class DriversLoadingDialogFragment : DialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewLifecycleOwner.lifecycleScope.apply { - launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - driverViewModel.isInteractionAllowed.collect { if (it) dismiss() } - } - } - } + driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) { if (it) dismiss() } } companion object { 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 aedc128d63..c3b2b11f80 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 @@ -32,9 +32,6 @@ import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout.DrawerListener import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.findNavController import androidx.navigation.fragment.navArgs import androidx.window.layout.FoldingFeature @@ -42,9 +39,6 @@ import androidx.window.layout.WindowInfoTracker import androidx.window.layout.WindowLayoutInfo import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.slider.Slider -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch import org.yuzu.yuzu_emu.HomeNavigationDirections import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.R @@ -91,14 +85,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { if (context is EmulationActivity) { emulationActivity = context NativeLibrary.setEmulationActivity(context) - - lifecycleScope.launch(Dispatchers.Main) { - lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { - WindowInfoTracker.getOrCreate(context) - .windowLayoutInfo(context) - .collect { updateFoldableLayout(context, it) } - } - } } else { throw IllegalStateException("EmulationFragment must have EmulationActivity parent") } @@ -169,8 +155,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { return binding.root } - // This is using the correct scope, lint is just acting up - @SuppressLint("UnsafeRepeatOnLifecycleDetector") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) if (requireActivity().isFinishing) { @@ -351,129 +335,86 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { binding.loadingTitle.isSelected = true binding.loadingText.isSelected = true - viewLifecycleOwner.lifecycleScope.apply { - launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - WindowInfoTracker.getOrCreate(requireContext()) - .windowLayoutInfo(requireActivity()) - .collect { - updateFoldableLayout(requireActivity() as EmulationActivity, it) - } - } + WindowInfoTracker.getOrCreate(requireContext()) + .windowLayoutInfo(requireActivity()).collect(viewLifecycleOwner) { + updateFoldableLayout(requireActivity() as EmulationActivity, it) } - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - emulationViewModel.shaderProgress.collectLatest { - if (it > 0 && it != emulationViewModel.totalShaders.value) { - binding.loadingProgressIndicator.isIndeterminate = false + emulationViewModel.shaderProgress.collect(viewLifecycleOwner) { + if (it > 0 && it != emulationViewModel.totalShaders.value) { + binding.loadingProgressIndicator.isIndeterminate = false - if (it < binding.loadingProgressIndicator.max) { - binding.loadingProgressIndicator.progress = it - } - } + 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 } - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - emulationViewModel.totalShaders.collectLatest { - binding.loadingProgressIndicator.max = it - } - } + } + emulationViewModel.totalShaders.collect(viewLifecycleOwner) { + binding.loadingProgressIndicator.max = it + } + emulationViewModel.shaderMessage.collect(viewLifecycleOwner) { + if (it.isNotEmpty()) { + binding.loadingText.text = it } - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - emulationViewModel.shaderMessage.collectLatest { - if (it.isNotEmpty()) { - binding.loadingText.text = it - } - } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - driverViewModel.isInteractionAllowed.collect { - if (it) { - startEmulation() - } - } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - emulationViewModel.emulationStarted.collectLatest { - if (it) { - binding.drawerLayout.setDrawerLockMode(IntSetting.LOCK_DRAWER.getInt()) - ViewUtils.showView(binding.surfaceInputOverlay) - ViewUtils.hideView(binding.loadingIndicator) + } - emulationState.updateSurface() + emulationViewModel.emulationStarted.collect(viewLifecycleOwner) { + if (it) { + binding.drawerLayout.setDrawerLockMode(IntSetting.LOCK_DRAWER.getInt()) + ViewUtils.showView(binding.surfaceInputOverlay) + ViewUtils.hideView(binding.loadingIndicator) - // Setup overlays - updateShowFpsOverlay() - updateThermalOverlay() - } - } - } + emulationState.updateSurface() + + // Setup overlays + updateShowFpsOverlay() + updateThermalOverlay() } - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - emulationViewModel.isEmulationStopping.collectLatest { - if (it) { - binding.loadingText.setText(R.string.shutting_down) - ViewUtils.showView(binding.loadingIndicator) - ViewUtils.hideView(binding.inputContainer) - ViewUtils.hideView(binding.showFpsText) - } - } - } + } + emulationViewModel.isEmulationStopping.collect(viewLifecycleOwner) { + if (it) { + binding.loadingText.setText(R.string.shutting_down) + ViewUtils.showView(binding.loadingIndicator) + ViewUtils.hideView(binding.inputContainer) + ViewUtils.hideView(binding.showFpsText) } - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - emulationViewModel.drawerOpen.collect { - if (it) { - binding.drawerLayout.open() - binding.inGameMenu.requestFocus() - } else { - binding.drawerLayout.close() - } - } - } + } + emulationViewModel.drawerOpen.collect(viewLifecycleOwner) { + if (it) { + binding.drawerLayout.open() + binding.inGameMenu.requestFocus() + } else { + binding.drawerLayout.close() } - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - emulationViewModel.programChanged.collect { - if (it != 0) { - emulationViewModel.setEmulationStarted(false) - binding.drawerLayout.close() - binding.drawerLayout - .setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) - ViewUtils.hideView(binding.surfaceInputOverlay) - ViewUtils.showView(binding.loadingIndicator) - } - } - } + } + emulationViewModel.programChanged.collect(viewLifecycleOwner) { + if (it != 0) { + emulationViewModel.setEmulationStarted(false) + binding.drawerLayout.close() + binding.drawerLayout + .setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) + ViewUtils.hideView(binding.surfaceInputOverlay) + ViewUtils.showView(binding.loadingIndicator) } - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - emulationViewModel.emulationStopped.collect { - if (it && emulationViewModel.programChanged.value != -1) { - if (perfStatsUpdater != null) { - perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!) - } - emulationState.changeProgram(emulationViewModel.programChanged.value) - emulationViewModel.setProgramChanged(-1) - emulationViewModel.setEmulationStopped(false) - } - } + } + emulationViewModel.emulationStopped.collect(viewLifecycleOwner) { + if (it && emulationViewModel.programChanged.value != -1) { + if (perfStatsUpdater != null) { + perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!) } + emulationState.changeProgram(emulationViewModel.programChanged.value) + emulationViewModel.setProgramChanged(-1) + emulationViewModel.setEmulationStopped(false) } } + + driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) { + if (it) startEmulation() + } } private fun startEmulation(programIndex: Int = 0) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt index 5c558b1a50..3a6f7a38c4 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt @@ -13,9 +13,6 @@ import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.findNavController import androidx.recyclerview.widget.GridLayoutManager import com.google.android.material.transition.MaterialSharedAxis @@ -27,6 +24,7 @@ import org.yuzu.yuzu_emu.model.GamesViewModel import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.ui.main.MainActivity import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins +import org.yuzu.yuzu_emu.utils.collect class GameFoldersFragment : Fragment() { private var _binding: FragmentFoldersBinding? = null @@ -70,12 +68,8 @@ class GameFoldersFragment : Fragment() { adapter = FolderAdapter(requireActivity(), gamesViewModel) } - viewLifecycleOwner.lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - gamesViewModel.folders.collect { - (binding.listFolders.adapter as FolderAdapter).submitList(it) - } - } + gamesViewModel.folders.collect(viewLifecycleOwner) { + (binding.listFolders.adapter as FolderAdapter).submitList(it) } val mainActivity = requireActivity() as MainActivity diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt index c4da1a65db..c06842c596 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt @@ -3,7 +3,6 @@ package org.yuzu.yuzu_emu.fragments -import android.annotation.SuppressLint import android.content.pm.ShortcutInfo import android.content.pm.ShortcutManager import android.os.Bundle @@ -17,9 +16,7 @@ import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.GridLayoutManager @@ -47,6 +44,7 @@ import org.yuzu.yuzu_emu.utils.GpuDriverHelper import org.yuzu.yuzu_emu.utils.MemoryUtil import org.yuzu.yuzu_emu.utils.ViewUtils.marquee import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins +import org.yuzu.yuzu_emu.utils.collect import java.io.BufferedOutputStream import java.io.File @@ -76,8 +74,6 @@ class GamePropertiesFragment : Fragment() { return binding.root } - // This is using the correct scope, lint is just acting up - @SuppressLint("UnsafeRepeatOnLifecycleDetector") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) homeViewModel.setNavigationVisibility(visible = false, animated = true) @@ -116,28 +112,14 @@ class GamePropertiesFragment : Fragment() { reloadList() - viewLifecycleOwner.lifecycleScope.apply { - launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - homeViewModel.openImportSaves.collect { - if (it) { - importSaves.launch(arrayOf("application/zip")) - homeViewModel.setOpenImportSaves(false) - } - } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - homeViewModel.reloadPropertiesList.collect { - if (it) { - reloadList() - homeViewModel.reloadPropertiesList(false) - } - } - } - } - } + homeViewModel.openImportSaves.collect( + viewLifecycleOwner, + resetState = { homeViewModel.setOpenImportSaves(false) } + ) { if (it) importSaves.launch(arrayOf("application/zip")) } + homeViewModel.reloadPropertiesList.collect( + viewLifecycleOwner, + resetState = { homeViewModel.reloadPropertiesList(false) } + ) { if (it) reloadList() } setInsets() } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt index 63112dc6f0..d218da1c88 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt @@ -14,9 +14,6 @@ import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.findNavController import androidx.recyclerview.widget.GridLayoutManager import com.google.android.material.transition.MaterialSharedAxis @@ -35,6 +32,7 @@ import org.yuzu.yuzu_emu.ui.main.MainActivity import org.yuzu.yuzu_emu.utils.DirectoryInitialization import org.yuzu.yuzu_emu.utils.FileUtil import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins +import org.yuzu.yuzu_emu.utils.collect import java.io.BufferedOutputStream import java.io.File import java.math.BigInteger @@ -75,14 +73,10 @@ class InstallableFragment : Fragment() { binding.root.findNavController().popBackStack() } - viewLifecycleOwner.lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - homeViewModel.openImportSaves.collect { - if (it) { - importSaves.launch(arrayOf("application/zip")) - homeViewModel.setOpenImportSaves(false) - } - } + homeViewModel.openImportSaves.collect(viewLifecycleOwner) { + if (it) { + importSaves.launch(arrayOf("application/zip")) + homeViewModel.setOpenImportSaves(false) } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ProgressDialogFragment.kt index ae29e9cd10..ee3bb0386a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ProgressDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ProgressDialogFragment.kt @@ -13,16 +13,13 @@ import androidx.appcompat.app.AlertDialog import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentActivity import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import com.google.android.material.dialog.MaterialAlertDialogBuilder -import kotlinx.coroutines.launch import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding import org.yuzu.yuzu_emu.model.TaskViewModel import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible +import org.yuzu.yuzu_emu.utils.collect class ProgressDialogFragment : DialogFragment() { private val taskViewModel: TaskViewModel by activityViewModels() @@ -65,70 +62,50 @@ class ProgressDialogFragment : DialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.message.isSelected = true - viewLifecycleOwner.lifecycleScope.apply { - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - taskViewModel.isComplete.collect { - if (it) { - dismiss() - when (val result = taskViewModel.result.value) { - is String -> Toast.makeText( - requireContext(), - result, - Toast.LENGTH_LONG - ).show() + taskViewModel.isComplete.collect(viewLifecycleOwner) { + if (it) { + dismiss() + when (val result = taskViewModel.result.value) { + is String -> Toast.makeText( + requireContext(), + result, + Toast.LENGTH_LONG + ).show() - is MessageDialogFragment -> result.show( - requireActivity().supportFragmentManager, - MessageDialogFragment.TAG - ) + is MessageDialogFragment -> result.show( + requireActivity().supportFragmentManager, + MessageDialogFragment.TAG + ) - else -> { - // Do nothing - } - } - taskViewModel.clear() - } + else -> { + // Do nothing } } + taskViewModel.clear() } - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - taskViewModel.cancelled.collect { - if (it) { - dialog?.setTitle(R.string.cancelling) - } - } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - taskViewModel.progress.collect { - if (it != 0.0) { - binding.progressBar.apply { - isIndeterminate = false - progress = ( - (it / taskViewModel.maxProgress.value) * - PROGRESS_BAR_RESOLUTION - ).toInt() - min = 0 - max = PROGRESS_BAR_RESOLUTION - } - } - } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - taskViewModel.message.collect { - binding.message.setVisible(it.isNotEmpty()) - if (it.isNotEmpty()) { - binding.message.text = it - } - } + } + taskViewModel.cancelled.collect(viewLifecycleOwner) { + if (it) { + dialog?.setTitle(R.string.cancelling) + } + } + taskViewModel.progress.collect(viewLifecycleOwner) { + if (it != 0.0) { + binding.progressBar.apply { + isIndeterminate = false + progress = ( + (it / taskViewModel.maxProgress.value) * + PROGRESS_BAR_RESOLUTION + ).toInt() + min = 0 + max = PROGRESS_BAR_RESOLUTION } } } + taskViewModel.message.collect(viewLifecycleOwner) { + binding.message.setVisible(it.isNotEmpty()) + binding.message.text = it + } } // By default, the ProgressDialog will immediately dismiss itself upon a button being pressed. 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 9f65096052..662ae9760a 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,7 +3,6 @@ package org.yuzu.yuzu_emu.fragments -import android.annotation.SuppressLint import android.content.Context import android.content.SharedPreferences import android.os.Bundle @@ -18,14 +17,9 @@ import androidx.core.view.updatePadding import androidx.core.widget.doOnTextChanged import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.preference.PreferenceManager import info.debatty.java.stringsimilarity.Jaccard import info.debatty.java.stringsimilarity.JaroWinkler -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch import java.util.Locale import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.YuzuApplication @@ -36,6 +30,7 @@ import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.model.GamesViewModel import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible +import org.yuzu.yuzu_emu.utils.collect class SearchFragment : Fragment() { private var _binding: FragmentSearchBinding? = null @@ -59,8 +54,6 @@ class SearchFragment : Fragment() { return binding.root } - // This is using the correct scope, lint is just acting up - @SuppressLint("UnsafeRepeatOnLifecycleDetector") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) homeViewModel.setNavigationVisibility(visible = true, animated = true) @@ -86,30 +79,14 @@ class SearchFragment : Fragment() { filterAndSearch() } - viewLifecycleOwner.lifecycleScope.apply { - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - gamesViewModel.searchFocused.collect { - if (it) { - focusSearch() - gamesViewModel.setSearchFocused(false) - } - } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - gamesViewModel.games.collectLatest { filterAndSearch() } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - gamesViewModel.searchedGames.collect { - (binding.gridGamesSearch.adapter as GameAdapter).submitList(it) - binding.noResultsView.setVisible(it.isEmpty()) - } - } - } + gamesViewModel.searchFocused.collect( + viewLifecycleOwner, + resetState = { gamesViewModel.setSearchFocused(false) } + ) { if (it) focusSearch() } + gamesViewModel.games.collect(viewLifecycleOwner) { filterAndSearch() } + gamesViewModel.searchedGames.collect(viewLifecycleOwner) { + (binding.gridGamesSearch.adapter as GameAdapter).submitList(it) + binding.noResultsView.setVisible(it.isNotEmpty()) } binding.clearButton.setOnClickListener { binding.searchText.setText("") } 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 eb279d309f..4f7548e98e 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 @@ -4,7 +4,6 @@ package org.yuzu.yuzu_emu.fragments import android.Manifest -import android.annotation.SuppressLint import android.content.Intent import android.os.Build import android.os.Bundle @@ -23,9 +22,6 @@ 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 @@ -47,6 +43,7 @@ import org.yuzu.yuzu_emu.utils.DirectoryInitialization import org.yuzu.yuzu_emu.utils.NativeConfig import org.yuzu.yuzu_emu.utils.ViewUtils import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible +import org.yuzu.yuzu_emu.utils.collect class SetupFragment : Fragment() { private var _binding: FragmentSetupBinding? = null @@ -78,8 +75,6 @@ class SetupFragment : Fragment() { return binding.root } - // This is using the correct scope, lint is just acting up - @SuppressLint("UnsafeRepeatOnLifecycleDetector") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { mainActivity = requireActivity() as MainActivity @@ -211,28 +206,14 @@ class SetupFragment : Fragment() { ) } - viewLifecycleOwner.lifecycleScope.apply { - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - homeViewModel.shouldPageForward.collect { - if (it) { - pageForward() - homeViewModel.setShouldPageForward(false) - } - } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - homeViewModel.gamesDirSelected.collect { - if (it) { - gamesDirCallback.onStepCompleted() - homeViewModel.setGamesDirSelected(false) - } - } - } - } - } + homeViewModel.shouldPageForward.collect( + viewLifecycleOwner, + resetState = { homeViewModel.setShouldPageForward(false) } + ) { if (it) pageForward() } + homeViewModel.gamesDirSelected.collect( + viewLifecycleOwner, + resetState = { homeViewModel.setGamesDirSelected(false) } + ) { if (it) gamesDirCallback.onStepCompleted() } binding.viewPager2.apply { adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages) 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 b3248585e6..fadb20e394 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,7 +3,6 @@ package org.yuzu.yuzu_emu.ui -import android.annotation.SuppressLint import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -14,12 +13,7 @@ import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import com.google.android.material.color.MaterialColors -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.adapters.GameAdapter import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding @@ -28,6 +22,7 @@ import org.yuzu.yuzu_emu.model.GamesViewModel import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins +import org.yuzu.yuzu_emu.utils.collect class GamesFragment : Fragment() { private var _binding: FragmentGamesBinding? = null @@ -45,8 +40,6 @@ class GamesFragment : Fragment() { return binding.root } - // This is using the correct scope, lint is just acting up - @SuppressLint("UnsafeRepeatOnLifecycleDetector") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) homeViewModel.setNavigationVisibility(visible = true, animated = true) @@ -89,48 +82,28 @@ class GamesFragment : Fragment() { } } - viewLifecycleOwner.lifecycleScope.apply { - launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - gamesViewModel.isReloading.collect { - binding.swipeRefresh.isRefreshing = it - binding.noticeText.setVisible( - visible = gamesViewModel.games.value.isEmpty() && !it, - gone = false - ) - } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - gamesViewModel.games.collectLatest { - (binding.gridGames.adapter as GameAdapter).submitList(it) - } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - gamesViewModel.shouldSwapData.collect { - if (it) { - (binding.gridGames.adapter as GameAdapter).submitList( - gamesViewModel.games.value - ) - gamesViewModel.setShouldSwapData(false) - } - } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - gamesViewModel.shouldScrollToTop.collect { - if (it) { - scrollToTop() - gamesViewModel.setShouldScrollToTop(false) - } - } - } + gamesViewModel.isReloading.collect(viewLifecycleOwner) { + binding.swipeRefresh.isRefreshing = it + binding.noticeText.setVisible( + visible = gamesViewModel.games.value.isEmpty() && !it, + gone = false + ) + } + gamesViewModel.games.collect(viewLifecycleOwner) { + (binding.gridGames.adapter as GameAdapter).submitList(it) + } + gamesViewModel.shouldSwapData.collect( + viewLifecycleOwner, + resetState = { gamesViewModel.setShouldSwapData(false) } + ) { + if (it) { + (binding.gridGames.adapter as GameAdapter).submitList(gamesViewModel.games.value) } } + gamesViewModel.shouldScrollToTop.collect( + viewLifecycleOwner, + resetState = { gamesViewModel.setShouldScrollToTop(false) } + ) { if (it) scrollToTop() } setInsets() } 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 703fbaf3e7..d16f8a931e 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,9 +19,6 @@ 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 @@ -30,7 +27,6 @@ import com.google.android.material.color.MaterialColors import com.google.android.material.navigation.NavigationBarView import java.io.File import java.io.FilenameFilter -import kotlinx.coroutines.launch import org.yuzu.yuzu_emu.HomeNavigationDirections import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.R @@ -144,38 +140,19 @@ class MainActivity : AppCompatActivity(), ThemeProvider { binding.statusBarShade.setVisible(visible = false, gone = false) } - lifecycleScope.apply { - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - homeViewModel.navigationVisible.collect { showNavigation(it.first, it.second) } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - homeViewModel.statusBarShadeVisible.collect { showStatusBarShade(it) } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - homeViewModel.contentToInstall.collect { - if (it != null) { - installContent(it) - homeViewModel.setContentToInstall(null) - } - } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { - homeViewModel.checkKeys.collect { - if (it) { - checkKeys() - homeViewModel.setCheckKeys(false) - } - } - } + homeViewModel.navigationVisible.collect(this) { showNavigation(it.first, it.second) } + homeViewModel.statusBarShadeVisible.collect(this) { showStatusBarShade(it) } + homeViewModel.contentToInstall.collect( + this, + resetState = { homeViewModel.setContentToInstall(null) } + ) { + if (it != null) { + installContent(it) } } + homeViewModel.checkKeys.collect(this, resetState = { homeViewModel.setCheckKeys(false) }) { + if (it) checkKeys() + } setInsets() } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/LifecycleUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/LifecycleUtils.kt new file mode 100644 index 0000000000..d5c19c681e --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/LifecycleUtils.kt @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.utils + +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch + +/** + * Collects this [Flow] with a given [LifecycleOwner]. + * @param scope [LifecycleOwner] that this [Flow] will be collected with. + * @param repeatState When to repeat collection on this [Flow]. + * @param resetState Optional lambda to reset state of an underlying [MutableStateFlow] after + * [stateCollector] has been run. + * @param stateCollector Lambda that receives new state. + */ +inline fun Flow.collect( + scope: LifecycleOwner, + repeatState: Lifecycle.State = Lifecycle.State.CREATED, + crossinline resetState: () -> Unit = {}, + crossinline stateCollector: (state: T) -> Unit +) { + scope.apply { + lifecycleScope.launch { + repeatOnLifecycle(repeatState) { + this@collect.collect { + stateCollector(it) + resetState() + } + } + } + } +}