Merge pull request #13054 from t895/lifecycle-utils

android: Create lifecycle utility to simplify common StateFlow operations
This commit is contained in:
liamwhite 2024-02-17 23:17:49 -05:00 committed by GitHub
commit acfc4d6dfb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 329 additions and 628 deletions

View file

@ -6,11 +6,7 @@ package org.yuzu.yuzu_emu.adapters
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding
import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
import org.yuzu.yuzu_emu.model.GameProperty 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.model.SubmenuProperty
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
import org.yuzu.yuzu_emu.utils.collect
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class GamePropertiesAdapter( class GamePropertiesAdapter(
@ -82,11 +79,7 @@ class GamePropertiesAdapter(
binding.details.text = submenuProperty.details.invoke() binding.details.text = submenuProperty.details.invoke()
} else if (submenuProperty.detailsFlow != null) { } else if (submenuProperty.detailsFlow != null) {
binding.details.setVisible(true) binding.details.setVisible(true)
viewLifecycle.lifecycleScope.launch { submenuProperty.detailsFlow.collect(viewLifecycle) { binding.details.text = it }
viewLifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
submenuProperty.detailsFlow.collect { binding.details.text = it }
}
}
} else { } else {
binding.details.setVisible(false) binding.details.setVisible(false)
} }

View file

@ -8,17 +8,14 @@ import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.HomeSetting import org.yuzu.yuzu_emu.model.HomeSetting
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
import org.yuzu.yuzu_emu.utils.collect
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class HomeSettingAdapter( class HomeSettingAdapter(
@ -59,11 +56,7 @@ class HomeSettingAdapter(
binding.optionIcon.alpha = 0.5f binding.optionIcon.alpha = 0.5f
} }
viewLifecycle.lifecycleScope.launch { model.details.collect(viewLifecycle) { updateOptionDetails(it) }
viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
model.details.collect { updateOptionDetails(it) }
}
}
binding.optionDetail.marquee() binding.optionDetail.marquee()
binding.root.setOnClickListener { onClick(model) } binding.root.setOnClickListener { onClick(model) }

View file

@ -11,16 +11,13 @@ import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogInputProfilesBinding import org.yuzu.yuzu_emu.databinding.DialogInputProfilesBinding
import org.yuzu.yuzu_emu.features.settings.model.view.InputProfileSetting import org.yuzu.yuzu_emu.features.settings.model.view.InputProfileSetting
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.utils.collect
class InputProfileDialogFragment : DialogFragment() { class InputProfileDialogFragment : DialogFragment() {
private var position = 0 private var position = 0
@ -110,25 +107,21 @@ class InputProfileDialogFragment : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch { settingsViewModel.shouldShowDeleteProfileDialog.collect(viewLifecycleOwner) {
repeatOnLifecycle(Lifecycle.State.CREATED) { if (it.isNotEmpty()) {
settingsViewModel.shouldShowDeleteProfileDialog.collect { MessageDialogFragment.newInstance(
if (it.isNotEmpty()) { activity = requireActivity(),
MessageDialogFragment.newInstance( titleId = R.string.delete_input_profile,
activity = requireActivity(), descriptionId = R.string.delete_input_profile_description,
titleId = R.string.delete_input_profile, positiveAction = {
descriptionId = R.string.delete_input_profile_description, setting.deleteProfile(it)
positiveAction = { settingsViewModel.setReloadListAndNotifyDataset(true)
setting.deleteProfile(it) },
settingsViewModel.setReloadListAndNotifyDataset(true) negativeAction = {},
}, negativeButtonTitleId = android.R.string.cancel
negativeAction = {}, ).show(parentFragmentManager, MessageDialogFragment.TAG)
negativeButtonTitleId = android.R.string.cancel settingsViewModel.setShouldShowDeleteProfileDialog("")
).show(parentFragmentManager, MessageDialogFragment.TAG) dismiss()
settingsViewModel.setShouldShowDeleteProfileDialog("")
dismiss()
}
}
} }
} }
} }

View file

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

View file

@ -11,12 +11,8 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.Slider import com.google.android.material.slider.Slider
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogSliderBinding import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
import org.yuzu.yuzu_emu.features.input.NativeInput 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.SliderSetting
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
import org.yuzu.yuzu_emu.utils.ParamPackage import org.yuzu.yuzu_emu.utils.ParamPackage
import org.yuzu.yuzu_emu.utils.collect
class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener { class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener {
private var type = 0 private var type = 0
@ -169,17 +166,11 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
when (type) { when (type) {
SettingsItem.TYPE_SLIDER -> { SettingsItem.TYPE_SLIDER -> {
viewLifecycleOwner.lifecycleScope.launch { settingsViewModel.sliderTextValue.collect(viewLifecycleOwner) {
repeatOnLifecycle(Lifecycle.State.CREATED) { sliderBinding.textValue.text = it
settingsViewModel.sliderTextValue.collect { }
sliderBinding.textValue.text = it settingsViewModel.sliderProgress.collect(viewLifecycleOwner) {
} sliderBinding.slider.value = it.toFloat()
}
repeatOnLifecycle(Lifecycle.State.CREATED) {
settingsViewModel.sliderProgress.collect {
sliderBinding.slider.value = it.toFloat()
}
}
} }
} }
} }

View file

@ -13,21 +13,17 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
import org.yuzu.yuzu_emu.features.input.NativeInput import org.yuzu.yuzu_emu.features.input.NativeInput
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
import org.yuzu.yuzu_emu.utils.collect
class SettingsFragment : Fragment() { class SettingsFragment : Fragment() {
private lateinit var presenter: SettingsFragmentPresenter private lateinit var presenter: SettingsFragmentPresenter
@ -63,8 +59,7 @@ class SettingsFragment : Fragment() {
return binding.root return binding.root
} }
// This is using the correct scope, lint is just acting up @SuppressLint("NotifyDataSetChanged")
@SuppressLint("UnsafeRepeatOnLifecycleDetector", "NotifyDataSetChanged")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
settingsAdapter = SettingsAdapter(this, requireContext()) settingsAdapter = SettingsAdapter(this, requireContext())
@ -100,65 +95,37 @@ class SettingsFragment : Fragment() {
settingsViewModel.setShouldNavigateBack(true) settingsViewModel.setShouldNavigateBack(true)
} }
viewLifecycleOwner.lifecycleScope.apply { settingsViewModel.shouldReloadSettingsList.collect(
launch { viewLifecycleOwner,
repeatOnLifecycle(Lifecycle.State.CREATED) { resetState = { settingsViewModel.setShouldReloadSettingsList(false) }
settingsViewModel.shouldReloadSettingsList.collectLatest { ) { if (it) presenter.loadSettingsList() }
if (it) { settingsViewModel.adapterItemChanged.collect(
settingsViewModel.setShouldReloadSettingsList(false) viewLifecycleOwner,
presenter.loadSettingsList() resetState = { settingsViewModel.setAdapterItemChanged(-1) }
} ) { if (it != -1) settingsAdapter?.notifyItemChanged(it) }
} settingsViewModel.datasetChanged.collect(
} viewLifecycleOwner,
} resetState = { settingsViewModel.setDatasetChanged(false) }
launch { ) { if (it) settingsAdapter?.notifyDataSetChanged() }
repeatOnLifecycle(Lifecycle.State.STARTED) { settingsViewModel.reloadListAndNotifyDataset.collect(
settingsViewModel.adapterItemChanged.collect { viewLifecycleOwner,
if (it != -1) { resetState = { settingsViewModel.setReloadListAndNotifyDataset(false) }
settingsAdapter?.notifyItemChanged(it) ) { if (it) presenter.loadSettingsList(true) }
settingsViewModel.setAdapterItemChanged(-1) settingsViewModel.shouldShowResetInputDialog.collect(
} viewLifecycleOwner,
} resetState = { settingsViewModel.setShouldShowResetInputDialog(false) }
} ) {
} if (it) {
launch { MessageDialogFragment.newInstance(
repeatOnLifecycle(Lifecycle.State.STARTED) { activity = requireActivity(),
settingsViewModel.datasetChanged.collect { titleId = R.string.reset_mapping,
if (it) { descriptionId = R.string.reset_mapping_description,
settingsAdapter?.notifyDataSetChanged() positiveAction = {
settingsViewModel.setDatasetChanged(false) NativeInput.resetControllerMappings(getPlayerIndex())
} settingsViewModel.setReloadListAndNotifyDataset(true)
} },
} negativeAction = {}
} ).show(parentFragmentManager, MessageDialogFragment.TAG)
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)
}
}
}
} }
} }

View file

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

View file

@ -3,7 +3,6 @@
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments
import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
@ -16,9 +15,6 @@ import androidx.core.view.updatePadding
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -32,6 +28,7 @@ import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.utils.AddonUtil import org.yuzu.yuzu_emu.utils.AddonUtil
import org.yuzu.yuzu_emu.utils.FileUtil.copyFilesTo import org.yuzu.yuzu_emu.utils.FileUtil.copyFilesTo
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
import org.yuzu.yuzu_emu.utils.collect
import java.io.File import java.io.File
class AddonsFragment : Fragment() { class AddonsFragment : Fragment() {
@ -60,8 +57,6 @@ class AddonsFragment : Fragment() {
return binding.root return binding.root
} }
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = false, animated = false) homeViewModel.setNavigationVisibility(visible = false, animated = false)
@ -78,57 +73,41 @@ class AddonsFragment : Fragment() {
adapter = AddonAdapter(addonViewModel) adapter = AddonAdapter(addonViewModel)
} }
viewLifecycleOwner.lifecycleScope.apply { addonViewModel.addonList.collect(viewLifecycleOwner) {
launch { (binding.listAddons.adapter as AddonAdapter).submitList(it)
repeatOnLifecycle(Lifecycle.State.STARTED) { }
addonViewModel.addonList.collect { addonViewModel.showModInstallPicker.collect(
(binding.listAddons.adapter as AddonAdapter).submitList(it) 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.addonToDelete.collect(
addonViewModel.showModInstallPicker.collect { viewLifecycleOwner,
if (it) { resetState = { addonViewModel.setAddonToDelete(null) }
installAddon.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) ) {
addonViewModel.showModInstallPicker(false) if (it != null) {
} MessageDialogFragment.newInstance(
} requireActivity(),
} titleId = R.string.confirm_uninstall,
} descriptionId = R.string.confirm_uninstall_description,
launch { positiveAction = { addonViewModel.onDeleteAddon(it) },
repeatOnLifecycle(Lifecycle.State.STARTED) { negativeAction = {}
addonViewModel.showModNoticeDialog.collect { ).show(parentFragmentManager, MessageDialogFragment.TAG)
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)
}
}
}
} }
} }

View file

@ -3,7 +3,6 @@
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments
import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -14,9 +13,6 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.GridLayoutManager 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.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.NativeConfig import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
import org.yuzu.yuzu_emu.utils.collect
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@ -63,8 +60,6 @@ class DriverManagerFragment : Fragment() {
return binding.root return binding.root
} }
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = false, animated = true) homeViewModel.setNavigationVisibility(visible = false, animated = true)
@ -89,15 +84,8 @@ class DriverManagerFragment : Fragment() {
} }
} }
viewLifecycleOwner.lifecycleScope.apply { driverViewModel.showClearButton.collect(viewLifecycleOwner) {
launch { binding.toolbarDrivers.menu.findItem(R.id.menu_driver_use_global).isVisible = it
repeatOnLifecycle(Lifecycle.State.STARTED) {
driverViewModel.showClearButton.collect {
binding.toolbarDrivers.menu
.findItem(R.id.menu_driver_use_global).isVisible = it
}
}
}
} }
} }

View file

@ -10,14 +10,11 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.model.DriverViewModel import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.utils.collect
class DriversLoadingDialogFragment : DialogFragment() { class DriversLoadingDialogFragment : DialogFragment() {
private val driverViewModel: DriverViewModel by activityViewModels() private val driverViewModel: DriverViewModel by activityViewModels()
@ -44,13 +41,7 @@ class DriversLoadingDialogFragment : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.apply { driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) { if (it) dismiss() }
launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
driverViewModel.isInteractionAllowed.collect { if (it) dismiss() }
}
}
}
} }
companion object { companion object {

View file

@ -32,9 +32,6 @@ import androidx.drawerlayout.widget.DrawerLayout
import androidx.drawerlayout.widget.DrawerLayout.DrawerListener import androidx.drawerlayout.widget.DrawerLayout.DrawerListener
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.window.layout.FoldingFeature import androidx.window.layout.FoldingFeature
@ -42,9 +39,6 @@ import androidx.window.layout.WindowInfoTracker
import androidx.window.layout.WindowLayoutInfo import androidx.window.layout.WindowLayoutInfo
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.Slider import com.google.android.material.slider.Slider
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.HomeNavigationDirections import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
@ -91,14 +85,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
if (context is EmulationActivity) { if (context is EmulationActivity) {
emulationActivity = context emulationActivity = context
NativeLibrary.setEmulationActivity(context) NativeLibrary.setEmulationActivity(context)
lifecycleScope.launch(Dispatchers.Main) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
WindowInfoTracker.getOrCreate(context)
.windowLayoutInfo(context)
.collect { updateFoldableLayout(context, it) }
}
}
} else { } else {
throw IllegalStateException("EmulationFragment must have EmulationActivity parent") throw IllegalStateException("EmulationFragment must have EmulationActivity parent")
} }
@ -169,8 +155,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
return binding.root return binding.root
} }
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
if (requireActivity().isFinishing) { if (requireActivity().isFinishing) {
@ -351,129 +335,86 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
binding.loadingTitle.isSelected = true binding.loadingTitle.isSelected = true
binding.loadingText.isSelected = true binding.loadingText.isSelected = true
viewLifecycleOwner.lifecycleScope.apply { WindowInfoTracker.getOrCreate(requireContext())
launch { .windowLayoutInfo(requireActivity()).collect(viewLifecycleOwner) {
repeatOnLifecycle(Lifecycle.State.STARTED) { updateFoldableLayout(requireActivity() as EmulationActivity, it)
WindowInfoTracker.getOrCreate(requireContext())
.windowLayoutInfo(requireActivity())
.collect {
updateFoldableLayout(requireActivity() as EmulationActivity, it)
}
}
} }
launch { emulationViewModel.shaderProgress.collect(viewLifecycleOwner) {
repeatOnLifecycle(Lifecycle.State.CREATED) { if (it > 0 && it != emulationViewModel.totalShaders.value) {
emulationViewModel.shaderProgress.collectLatest { binding.loadingProgressIndicator.isIndeterminate = false
if (it > 0 && it != emulationViewModel.totalShaders.value) {
binding.loadingProgressIndicator.isIndeterminate = false
if (it < binding.loadingProgressIndicator.max) { if (it < binding.loadingProgressIndicator.max) {
binding.loadingProgressIndicator.progress = it binding.loadingProgressIndicator.progress = it
} }
} }
if (it == emulationViewModel.totalShaders.value) { if (it == emulationViewModel.totalShaders.value) {
binding.loadingText.setText(R.string.loading) binding.loadingText.setText(R.string.loading)
binding.loadingProgressIndicator.isIndeterminate = true binding.loadingProgressIndicator.isIndeterminate = true
}
}
}
} }
launch { }
repeatOnLifecycle(Lifecycle.State.CREATED) { emulationViewModel.totalShaders.collect(viewLifecycleOwner) {
emulationViewModel.totalShaders.collectLatest { binding.loadingProgressIndicator.max = it
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 emulationState.updateSurface()
updateShowFpsOverlay()
updateThermalOverlay() // Setup overlays
} updateShowFpsOverlay()
} updateThermalOverlay()
}
} }
launch { }
repeatOnLifecycle(Lifecycle.State.CREATED) { emulationViewModel.isEmulationStopping.collect(viewLifecycleOwner) {
emulationViewModel.isEmulationStopping.collectLatest { if (it) {
if (it) { binding.loadingText.setText(R.string.shutting_down)
binding.loadingText.setText(R.string.shutting_down) ViewUtils.showView(binding.loadingIndicator)
ViewUtils.showView(binding.loadingIndicator) ViewUtils.hideView(binding.inputContainer)
ViewUtils.hideView(binding.inputContainer) ViewUtils.hideView(binding.showFpsText)
ViewUtils.hideView(binding.showFpsText)
}
}
}
} }
launch { }
repeatOnLifecycle(Lifecycle.State.CREATED) { emulationViewModel.drawerOpen.collect(viewLifecycleOwner) {
emulationViewModel.drawerOpen.collect { if (it) {
if (it) { binding.drawerLayout.open()
binding.drawerLayout.open() binding.inGameMenu.requestFocus()
binding.inGameMenu.requestFocus() } else {
} else { binding.drawerLayout.close()
binding.drawerLayout.close()
}
}
}
} }
launch { }
repeatOnLifecycle(Lifecycle.State.CREATED) { emulationViewModel.programChanged.collect(viewLifecycleOwner) {
emulationViewModel.programChanged.collect { if (it != 0) {
if (it != 0) { emulationViewModel.setEmulationStarted(false)
emulationViewModel.setEmulationStarted(false) binding.drawerLayout.close()
binding.drawerLayout.close() binding.drawerLayout
binding.drawerLayout .setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) ViewUtils.hideView(binding.surfaceInputOverlay)
ViewUtils.hideView(binding.surfaceInputOverlay) ViewUtils.showView(binding.loadingIndicator)
ViewUtils.showView(binding.loadingIndicator)
}
}
}
} }
launch { }
repeatOnLifecycle(Lifecycle.State.CREATED) { emulationViewModel.emulationStopped.collect(viewLifecycleOwner) {
emulationViewModel.emulationStopped.collect { if (it && emulationViewModel.programChanged.value != -1) {
if (it && emulationViewModel.programChanged.value != -1) { if (perfStatsUpdater != null) {
if (perfStatsUpdater != null) { perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
}
emulationState.changeProgram(emulationViewModel.programChanged.value)
emulationViewModel.setProgramChanged(-1)
emulationViewModel.setEmulationStopped(false)
}
}
} }
emulationState.changeProgram(emulationViewModel.programChanged.value)
emulationViewModel.setProgramChanged(-1)
emulationViewModel.setEmulationStopped(false)
} }
} }
driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) {
if (it) startEmulation()
}
} }
private fun startEmulation(programIndex: Int = 0) { private fun startEmulation(programIndex: Int = 0) {

View file

@ -13,9 +13,6 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis 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.model.HomeViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
import org.yuzu.yuzu_emu.utils.collect
class GameFoldersFragment : Fragment() { class GameFoldersFragment : Fragment() {
private var _binding: FragmentFoldersBinding? = null private var _binding: FragmentFoldersBinding? = null
@ -70,12 +68,8 @@ class GameFoldersFragment : Fragment() {
adapter = FolderAdapter(requireActivity(), gamesViewModel) adapter = FolderAdapter(requireActivity(), gamesViewModel)
} }
viewLifecycleOwner.lifecycleScope.launch { gamesViewModel.folders.collect(viewLifecycleOwner) {
repeatOnLifecycle(Lifecycle.State.CREATED) { (binding.listFolders.adapter as FolderAdapter).submitList(it)
gamesViewModel.folders.collect {
(binding.listFolders.adapter as FolderAdapter).submitList(it)
}
}
} }
val mainActivity = requireActivity() as MainActivity val mainActivity = requireActivity() as MainActivity

View file

@ -3,7 +3,6 @@
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments
import android.annotation.SuppressLint
import android.content.pm.ShortcutInfo import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager import android.content.pm.ShortcutManager
import android.os.Bundle import android.os.Bundle
@ -17,9 +16,7 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.GridLayoutManager 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.MemoryUtil
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
import org.yuzu.yuzu_emu.utils.collect
import java.io.BufferedOutputStream import java.io.BufferedOutputStream
import java.io.File import java.io.File
@ -76,8 +74,6 @@ class GamePropertiesFragment : Fragment() {
return binding.root return binding.root
} }
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = false, animated = true) homeViewModel.setNavigationVisibility(visible = false, animated = true)
@ -116,28 +112,14 @@ class GamePropertiesFragment : Fragment() {
reloadList() reloadList()
viewLifecycleOwner.lifecycleScope.apply { homeViewModel.openImportSaves.collect(
launch { viewLifecycleOwner,
repeatOnLifecycle(Lifecycle.State.STARTED) { resetState = { homeViewModel.setOpenImportSaves(false) }
homeViewModel.openImportSaves.collect { ) { if (it) importSaves.launch(arrayOf("application/zip")) }
if (it) { homeViewModel.reloadPropertiesList.collect(
importSaves.launch(arrayOf("application/zip")) viewLifecycleOwner,
homeViewModel.setOpenImportSaves(false) resetState = { homeViewModel.reloadPropertiesList(false) }
} ) { if (it) reloadList() }
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
homeViewModel.reloadPropertiesList.collect {
if (it) {
reloadList()
homeViewModel.reloadPropertiesList(false)
}
}
}
}
}
setInsets() setInsets()
} }

View file

@ -14,9 +14,6 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis 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.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.FileUtil import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
import org.yuzu.yuzu_emu.utils.collect
import java.io.BufferedOutputStream import java.io.BufferedOutputStream
import java.io.File import java.io.File
import java.math.BigInteger import java.math.BigInteger
@ -75,14 +73,10 @@ class InstallableFragment : Fragment() {
binding.root.findNavController().popBackStack() binding.root.findNavController().popBackStack()
} }
viewLifecycleOwner.lifecycleScope.launch { homeViewModel.openImportSaves.collect(viewLifecycleOwner) {
repeatOnLifecycle(Lifecycle.State.CREATED) { if (it) {
homeViewModel.openImportSaves.collect { importSaves.launch(arrayOf("application/zip"))
if (it) { homeViewModel.setOpenImportSaves(false)
importSaves.launch(arrayOf("application/zip"))
homeViewModel.setOpenImportSaves(false)
}
}
} }
} }

View file

@ -13,16 +13,13 @@ import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.model.TaskViewModel import org.yuzu.yuzu_emu.model.TaskViewModel
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
import org.yuzu.yuzu_emu.utils.collect
class ProgressDialogFragment : DialogFragment() { class ProgressDialogFragment : DialogFragment() {
private val taskViewModel: TaskViewModel by activityViewModels() private val taskViewModel: TaskViewModel by activityViewModels()
@ -65,70 +62,50 @@ class ProgressDialogFragment : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.message.isSelected = true binding.message.isSelected = true
viewLifecycleOwner.lifecycleScope.apply { taskViewModel.isComplete.collect(viewLifecycleOwner) {
launch { if (it) {
repeatOnLifecycle(Lifecycle.State.CREATED) { dismiss()
taskViewModel.isComplete.collect { when (val result = taskViewModel.result.value) {
if (it) { is String -> Toast.makeText(
dismiss() requireContext(),
when (val result = taskViewModel.result.value) { result,
is String -> Toast.makeText( Toast.LENGTH_LONG
requireContext(), ).show()
result,
Toast.LENGTH_LONG
).show()
is MessageDialogFragment -> result.show( is MessageDialogFragment -> result.show(
requireActivity().supportFragmentManager, requireActivity().supportFragmentManager,
MessageDialogFragment.TAG MessageDialogFragment.TAG
) )
else -> { else -> {
// Do nothing // Do nothing
}
}
taskViewModel.clear()
}
} }
} }
taskViewModel.clear()
} }
launch { }
repeatOnLifecycle(Lifecycle.State.CREATED) { taskViewModel.cancelled.collect(viewLifecycleOwner) {
taskViewModel.cancelled.collect { if (it) {
if (it) { dialog?.setTitle(R.string.cancelling)
dialog?.setTitle(R.string.cancelling) }
} }
} taskViewModel.progress.collect(viewLifecycleOwner) {
} if (it != 0.0) {
} binding.progressBar.apply {
launch { isIndeterminate = false
repeatOnLifecycle(Lifecycle.State.CREATED) { progress = (
taskViewModel.progress.collect { (it / taskViewModel.maxProgress.value) *
if (it != 0.0) { PROGRESS_BAR_RESOLUTION
binding.progressBar.apply { ).toInt()
isIndeterminate = false min = 0
progress = ( max = PROGRESS_BAR_RESOLUTION
(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.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. // By default, the ProgressDialog will immediately dismiss itself upon a button being pressed.

View file

@ -3,7 +3,6 @@
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
@ -18,14 +17,9 @@ import androidx.core.view.updatePadding
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import info.debatty.java.stringsimilarity.Jaccard import info.debatty.java.stringsimilarity.Jaccard
import info.debatty.java.stringsimilarity.JaroWinkler import info.debatty.java.stringsimilarity.JaroWinkler
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import java.util.Locale import java.util.Locale
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
@ -36,6 +30,7 @@ import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.GamesViewModel import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
import org.yuzu.yuzu_emu.utils.collect
class SearchFragment : Fragment() { class SearchFragment : Fragment() {
private var _binding: FragmentSearchBinding? = null private var _binding: FragmentSearchBinding? = null
@ -59,8 +54,6 @@ class SearchFragment : Fragment() {
return binding.root return binding.root
} }
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = true, animated = true) homeViewModel.setNavigationVisibility(visible = true, animated = true)
@ -86,30 +79,14 @@ class SearchFragment : Fragment() {
filterAndSearch() filterAndSearch()
} }
viewLifecycleOwner.lifecycleScope.apply { gamesViewModel.searchFocused.collect(
launch { viewLifecycleOwner,
repeatOnLifecycle(Lifecycle.State.CREATED) { resetState = { gamesViewModel.setSearchFocused(false) }
gamesViewModel.searchFocused.collect { ) { if (it) focusSearch() }
if (it) { gamesViewModel.games.collect(viewLifecycleOwner) { filterAndSearch() }
focusSearch() gamesViewModel.searchedGames.collect(viewLifecycleOwner) {
gamesViewModel.setSearchFocused(false) (binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
} binding.noResultsView.setVisible(it.isNotEmpty())
}
}
}
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())
}
}
}
} }
binding.clearButton.setOnClickListener { binding.searchText.setText("") } binding.clearButton.setOnClickListener { binding.searchText.setText("") }

View file

@ -4,7 +4,6 @@
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments
import android.Manifest import android.Manifest
import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@ -23,9 +22,6 @@ import androidx.core.view.isVisible
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
@ -47,6 +43,7 @@ import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.NativeConfig import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.ViewUtils import org.yuzu.yuzu_emu.utils.ViewUtils
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
import org.yuzu.yuzu_emu.utils.collect
class SetupFragment : Fragment() { class SetupFragment : Fragment() {
private var _binding: FragmentSetupBinding? = null private var _binding: FragmentSetupBinding? = null
@ -78,8 +75,6 @@ class SetupFragment : Fragment() {
return binding.root return binding.root
} }
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
mainActivity = requireActivity() as MainActivity mainActivity = requireActivity() as MainActivity
@ -211,28 +206,14 @@ class SetupFragment : Fragment() {
) )
} }
viewLifecycleOwner.lifecycleScope.apply { homeViewModel.shouldPageForward.collect(
launch { viewLifecycleOwner,
repeatOnLifecycle(Lifecycle.State.CREATED) { resetState = { homeViewModel.setShouldPageForward(false) }
homeViewModel.shouldPageForward.collect { ) { if (it) pageForward() }
if (it) { homeViewModel.gamesDirSelected.collect(
pageForward() viewLifecycleOwner,
homeViewModel.setShouldPageForward(false) resetState = { homeViewModel.setGamesDirSelected(false) }
} ) { if (it) gamesDirCallback.onStepCompleted() }
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
homeViewModel.gamesDirSelected.collect {
if (it) {
gamesDirCallback.onStepCompleted()
homeViewModel.setGamesDirSelected(false)
}
}
}
}
}
binding.viewPager2.apply { binding.viewPager2.apply {
adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages) adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages)

View file

@ -3,7 +3,6 @@
package org.yuzu.yuzu_emu.ui package org.yuzu.yuzu_emu.ui
import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -14,12 +13,7 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.GameAdapter import org.yuzu.yuzu_emu.adapters.GameAdapter
import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding 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.model.HomeViewModel
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
import org.yuzu.yuzu_emu.utils.collect
class GamesFragment : Fragment() { class GamesFragment : Fragment() {
private var _binding: FragmentGamesBinding? = null private var _binding: FragmentGamesBinding? = null
@ -45,8 +40,6 @@ class GamesFragment : Fragment() {
return binding.root return binding.root
} }
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = true, animated = true) homeViewModel.setNavigationVisibility(visible = true, animated = true)
@ -89,48 +82,28 @@ class GamesFragment : Fragment() {
} }
} }
viewLifecycleOwner.lifecycleScope.apply { gamesViewModel.isReloading.collect(viewLifecycleOwner) {
launch { binding.swipeRefresh.isRefreshing = it
repeatOnLifecycle(Lifecycle.State.RESUMED) { binding.noticeText.setVisible(
gamesViewModel.isReloading.collect { visible = gamesViewModel.games.value.isEmpty() && !it,
binding.swipeRefresh.isRefreshing = it gone = false
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,
launch { resetState = { gamesViewModel.setShouldSwapData(false) }
repeatOnLifecycle(Lifecycle.State.RESUMED) { ) {
gamesViewModel.games.collectLatest { if (it) {
(binding.gridGames.adapter as GameAdapter).submitList(it) (binding.gridGames.adapter as GameAdapter).submitList(gamesViewModel.games.value)
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
gamesViewModel.shouldSwapData.collect {
if (it) {
(binding.gridGames.adapter as GameAdapter).submitList(
gamesViewModel.games.value
)
gamesViewModel.setShouldSwapData(false)
}
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
gamesViewModel.shouldScrollToTop.collect {
if (it) {
scrollToTop()
gamesViewModel.setShouldScrollToTop(false)
}
}
}
} }
} }
gamesViewModel.shouldScrollToTop.collect(
viewLifecycleOwner,
resetState = { gamesViewModel.setShouldScrollToTop(false) }
) { if (it) scrollToTop() }
setInsets() setInsets()
} }

View file

@ -19,9 +19,6 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
@ -30,7 +27,6 @@ import com.google.android.material.color.MaterialColors
import com.google.android.material.navigation.NavigationBarView import com.google.android.material.navigation.NavigationBarView
import java.io.File import java.io.File
import java.io.FilenameFilter import java.io.FilenameFilter
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.HomeNavigationDirections import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
@ -144,38 +140,19 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
binding.statusBarShade.setVisible(visible = false, gone = false) binding.statusBarShade.setVisible(visible = false, gone = false)
} }
lifecycleScope.apply { homeViewModel.navigationVisible.collect(this) { showNavigation(it.first, it.second) }
launch { homeViewModel.statusBarShadeVisible.collect(this) { showStatusBarShade(it) }
repeatOnLifecycle(Lifecycle.State.CREATED) { homeViewModel.contentToInstall.collect(
homeViewModel.navigationVisible.collect { showNavigation(it.first, it.second) } this,
} resetState = { homeViewModel.setContentToInstall(null) }
} ) {
launch { if (it != null) {
repeatOnLifecycle(Lifecycle.State.CREATED) { installContent(it)
homeViewModel.statusBarShadeVisible.collect { showStatusBarShade(it) }
}
}
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
homeViewModel.contentToInstall.collect {
if (it != null) {
installContent(it)
homeViewModel.setContentToInstall(null)
}
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
homeViewModel.checkKeys.collect {
if (it) {
checkKeys()
homeViewModel.setCheckKeys(false)
}
}
}
} }
} }
homeViewModel.checkKeys.collect(this, resetState = { homeViewModel.setCheckKeys(false) }) {
if (it) checkKeys()
}
setInsets() setInsets()
} }

View file

@ -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 <reified T> Flow<T>.collect(
scope: LifecycleOwner,
repeatState: Lifecycle.State = Lifecycle.State.CREATED,
crossinline resetState: () -> Unit = {},
crossinline stateCollector: (state: T) -> Unit
) {
scope.apply {
lifecycleScope.launch {
repeatOnLifecycle(repeatState) {
this@collect.collect {
stateCollector(it)
resetState()
}
}
}
}
}