mirror of
				https://git.suyu.dev/suyu/suyu
				synced 2025-11-04 00:49:02 -06:00 
			
		
		
		
	android: Create lifecycle utility to simplify common StateFlow operations
This commit is contained in:
		@@ -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)
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -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) }
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 
 | 
			
		||||
@@ -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("") }
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user