diff options
| author | Charles Lombardo <clombardo169@gmail.com> | 2023-08-31 13:32:48 -0400 | 
|---|---|---|
| committer | Charles Lombardo <clombardo169@gmail.com> | 2023-09-14 15:18:56 -0400 | 
| commit | 4a3cbf00216918a53b143f6bbfc4b22a49761770 (patch) | |
| tree | 6e75e7bd037fef538a054d07496f407d2d4a6997 | |
| parent | 04352a9aef52fde375950df8577debbb896c2ae1 (diff) | |
android: Use StateFlow instead of LiveData
16 files changed, 367 insertions, 249 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt index 8d87d3bd7..1675627a1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt @@ -10,8 +10,12 @@ import android.view.ViewGroup  import androidx.appcompat.app.AppCompatActivity  import androidx.core.content.ContextCompat  import androidx.core.content.res.ResourcesCompat +import androidx.lifecycle.Lifecycle  import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle  import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.launch  import org.yuzu.yuzu_emu.R  import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding  import org.yuzu.yuzu_emu.fragments.MessageDialogFragment @@ -86,7 +90,11 @@ class HomeSettingAdapter(                  binding.optionIcon.alpha = 0.5f              } -            option.details.observe(viewLifecycle) { updateOptionDetails(it) } +            viewLifecycle.lifecycleScope.launch { +                viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) { +                    option.details.collect { updateOptionDetails(it) } +                } +            }              binding.optionDetail.postDelayed(                  {                      binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt index 908c01265..4d2f2f604 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt @@ -13,9 +13,14 @@ import androidx.appcompat.app.AppCompatActivity  import androidx.core.view.ViewCompat  import androidx.core.view.WindowCompat  import androidx.core.view.WindowInsetsCompat +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle  import androidx.navigation.fragment.NavHostFragment  import androidx.navigation.navArgs  import com.google.android.material.color.MaterialColors +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch  import java.io.IOException  import org.yuzu.yuzu_emu.R  import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding @@ -66,25 +71,39 @@ class SettingsActivity : AppCompatActivity() {              )          } -        settingsViewModel.shouldRecreate.observe(this) { -            if (it) { -                settingsViewModel.setShouldRecreate(false) -                recreate() +        lifecycleScope.apply { +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    settingsViewModel.shouldRecreate.collectLatest { +                        if (it) { +                            settingsViewModel.setShouldRecreate(false) +                            recreate() +                        } +                    } +                }              } -        } -        settingsViewModel.shouldNavigateBack.observe(this) { -            if (it) { -                settingsViewModel.setShouldNavigateBack(false) -                navigateBack() +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    settingsViewModel.shouldNavigateBack.collectLatest { +                        if (it) { +                            settingsViewModel.setShouldNavigateBack(false) +                            navigateBack() +                        } +                    } +                }              } -        } -        settingsViewModel.shouldShowResetSettingsDialog.observe(this) { -            if (it) { -                settingsViewModel.setShouldShowResetSettingsDialog(false) -                ResetSettingsDialogFragment().show( -                    supportFragmentManager, -                    ResetSettingsDialogFragment.TAG -                ) +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    settingsViewModel.shouldShowResetSettingsDialog.collectLatest { +                        if (it) { +                            settingsViewModel.setShouldShowResetSettingsDialog(false) +                            ResetSettingsDialogFragment().show( +                                supportFragmentManager, +                                ResetSettingsDialogFragment.TAG +                            ) +                        } +                    } +                }              }          } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt index bc319714c..2a816183a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt @@ -13,11 +13,15 @@ import androidx.core.view.WindowInsetsCompat  import androidx.core.view.updatePadding  import androidx.fragment.app.Fragment  import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle  import androidx.navigation.findNavController  import androidx.navigation.fragment.navArgs  import androidx.recyclerview.widget.LinearLayoutManager  import com.google.android.material.divider.MaterialDividerItemDecoration  import com.google.android.material.transition.MaterialSharedAxis +import kotlinx.coroutines.launch  import org.yuzu.yuzu_emu.R  import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding  import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile @@ -51,6 +55,8 @@ class SettingsFragment : Fragment() {          return binding.root      } +    // This is using the correct scope, lint is just acting up +    @SuppressLint("UnsafeRepeatOnLifecycleDetector")      override fun onViewCreated(view: View, savedInstanceState: Bundle?) {          settingsAdapter = SettingsAdapter(this, requireContext())          presenter = SettingsFragmentPresenter( @@ -75,24 +81,27 @@ class SettingsFragment : Fragment() {              settingsViewModel.setShouldNavigateBack(true)          } -        settingsViewModel.toolbarTitle.observe(viewLifecycleOwner) { -            if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it -        } - -        settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) { -            if (it) { -                settingsViewModel.setShouldReloadSettingsList(false) -                presenter.loadSettingsList() +        viewLifecycleOwner.lifecycleScope.apply { +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    settingsViewModel.shouldReloadSettingsList.collectLatest { +                        if (it) { +                            settingsViewModel.setShouldReloadSettingsList(false) +                            presenter.loadSettingsList() +                        } +                    } +                }              } -        } - -        settingsViewModel.isUsingSearch.observe(viewLifecycleOwner) { -            if (it) { -                reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) -                exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) -            } else { -                reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) -                exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) +            launch { +                settingsViewModel.isUsingSearch.collectLatest { +                    if (it) { +                        reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) +                        exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) +                    } else { +                        reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) +                        exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) +                    } +                }              }          } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 944ae652e..1addb2326 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -39,6 +39,7 @@ import androidx.window.layout.WindowLayoutInfo  import com.google.android.material.dialog.MaterialAlertDialogBuilder  import com.google.android.material.slider.Slider  import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.collectLatest  import kotlinx.coroutines.launch  import org.yuzu.yuzu_emu.HomeNavigationDirections  import org.yuzu.yuzu_emu.NativeLibrary @@ -129,6 +130,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {          return binding.root      } +    // This is using the correct scope, lint is just acting up +    @SuppressLint("UnsafeRepeatOnLifecycleDetector")      override fun onViewCreated(view: View, savedInstanceState: Bundle?) {          binding.surfaceEmulation.holder.addCallback(this)          binding.showFpsText.setTextColor(Color.YELLOW) @@ -205,59 +208,80 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {              }          ) -        viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { -            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { -                WindowInfoTracker.getOrCreate(requireContext()) -                    .windowLayoutInfo(requireActivity()) -                    .collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) } -            } -        } -          GameIconUtils.loadGameIcon(game, binding.loadingImage)          binding.loadingTitle.text = game.title          binding.loadingTitle.isSelected = true          binding.loadingText.isSelected = true -        emulationViewModel.shaderProgress.observe(viewLifecycleOwner) { -            if (it > 0 && it != emulationViewModel.totalShaders.value!!) { -                binding.loadingProgressIndicator.isIndeterminate = false - -                if (it < binding.loadingProgressIndicator.max) { -                    binding.loadingProgressIndicator.progress = it +        viewLifecycleOwner.lifecycleScope.apply { +            launch { +                repeatOnLifecycle(Lifecycle.State.STARTED) { +                    WindowInfoTracker.getOrCreate(requireContext()) +                        .windowLayoutInfo(requireActivity()) +                        .collect { +                            updateFoldableLayout(requireActivity() as EmulationActivity, it) +                        }                  }              } +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    emulationViewModel.shaderProgress.collectLatest { +                        if (it > 0 && it != emulationViewModel.totalShaders.value) { +                            binding.loadingProgressIndicator.isIndeterminate = false + +                            if (it < binding.loadingProgressIndicator.max) { +                                binding.loadingProgressIndicator.progress = it +                            } +                        } -            if (it == emulationViewModel.totalShaders.value!!) { -                binding.loadingText.setText(R.string.loading) -                binding.loadingProgressIndicator.isIndeterminate = true +                        if (it == emulationViewModel.totalShaders.value) { +                            binding.loadingText.setText(R.string.loading) +                            binding.loadingProgressIndicator.isIndeterminate = true +                        } +                    } +                }              } -        } -        emulationViewModel.totalShaders.observe(viewLifecycleOwner) { -            binding.loadingProgressIndicator.max = it -        } -        emulationViewModel.shaderMessage.observe(viewLifecycleOwner) { -            if (it.isNotEmpty()) { -                binding.loadingText.text = it +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    emulationViewModel.totalShaders.collectLatest { +                        binding.loadingProgressIndicator.max = it +                    } +                }              } -        } - -        emulationViewModel.emulationStarted.observe(viewLifecycleOwner) { started -> -            if (started) { -                binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) -                ViewUtils.showView(binding.surfaceInputOverlay) -                ViewUtils.hideView(binding.loadingIndicator) - -                // Setup overlay -                updateShowFpsOverlay() +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    emulationViewModel.shaderMessage.collectLatest { +                        if (it.isNotEmpty()) { +                            binding.loadingText.text = it +                        } +                    } +                }              } -        } +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    emulationViewModel.emulationStarted.collectLatest { +                        if (it) { +                            binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) +                            ViewUtils.showView(binding.surfaceInputOverlay) +                            ViewUtils.hideView(binding.loadingIndicator) -        emulationViewModel.isEmulationStopping.observe(viewLifecycleOwner) { -            if (it) { -                binding.loadingText.setText(R.string.shutting_down) -                ViewUtils.showView(binding.loadingIndicator) -                ViewUtils.hideView(binding.inputContainer) -                ViewUtils.hideView(binding.showFpsText) +                            // Setup overlay +                            updateShowFpsOverlay() +                        } +                    } +                } +            } +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    emulationViewModel.isEmulationStopping.collectLatest { +                        if (it) { +                            binding.loadingText.setText(R.string.shutting_down) +                            ViewUtils.showView(binding.loadingIndicator) +                            ViewUtils.hideView(binding.inputContainer) +                            ViewUtils.hideView(binding.showFpsText) +                        } +                    } +                }              }          }      } @@ -274,9 +298,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {                  }              }          } else { -            if (EmulationMenuSettings.showOverlay && -                emulationViewModel.emulationStarted.value == true -            ) { +            if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) {                  binding.surfaceInputOverlay.post {                      binding.surfaceInputOverlay.visibility = View.VISIBLE                  } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt index 181bd983a..ea8eb073a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt @@ -9,8 +9,12 @@ import android.widget.Toast  import androidx.appcompat.app.AppCompatActivity  import androidx.fragment.app.DialogFragment  import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle  import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle  import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.coroutines.launch  import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding  import org.yuzu.yuzu_emu.model.TaskViewModel @@ -28,21 +32,27 @@ class IndeterminateProgressDialogFragment : DialogFragment() {              .create()          dialog.setCanceledOnTouchOutside(false) -        taskViewModel.isComplete.observe(this) { complete -> -            if (complete) { -                dialog.dismiss() -                when (val result = taskViewModel.result.value) { -                    is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG).show() -                    is MessageDialogFragment -> result.show( -                        requireActivity().supportFragmentManager, -                        MessageDialogFragment.TAG -                    ) +        viewLifecycleOwner.lifecycleScope.launch { +            repeatOnLifecycle(Lifecycle.State.CREATED) { +                taskViewModel.isComplete.collect { +                    if (it) { +                        dialog.dismiss() +                        when (val result = taskViewModel.result.value) { +                            is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG) +                                .show() + +                            is MessageDialogFragment -> result.show( +                                requireActivity().supportFragmentManager, +                                MessageDialogFragment.TAG +                            ) +                        } +                        taskViewModel.clear() +                    }                  } -                taskViewModel.clear()              }          } -        if (taskViewModel.isRunning.value == false) { +        if (!taskViewModel.isRunning.value) {              taskViewModel.runTask()          }          return dialog diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt index f54dccc69..2dbca76a5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt @@ -3,6 +3,7 @@  package org.yuzu.yuzu_emu.fragments +import android.annotation.SuppressLint  import android.content.Context  import android.content.SharedPreferences  import android.os.Bundle @@ -17,9 +18,13 @@ import androidx.core.view.updatePadding  import androidx.core.widget.doOnTextChanged  import androidx.fragment.app.Fragment  import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle  import androidx.preference.PreferenceManager  import info.debatty.java.stringsimilarity.Jaccard  import info.debatty.java.stringsimilarity.JaroWinkler +import kotlinx.coroutines.launch  import java.util.Locale  import org.yuzu.yuzu_emu.R  import org.yuzu.yuzu_emu.YuzuApplication @@ -52,6 +57,8 @@ class SearchFragment : Fragment() {          return binding.root      } +    // This is using the correct scope, lint is just acting up +    @SuppressLint("UnsafeRepeatOnLifecycleDetector")      override fun onViewCreated(view: View, savedInstanceState: Bundle?) {          homeViewModel.setNavigationVisibility(visible = true, animated = false)          preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) @@ -79,21 +86,32 @@ class SearchFragment : Fragment() {              filterAndSearch()          } -        gamesViewModel.apply { -            searchFocused.observe(viewLifecycleOwner) { searchFocused -> -                if (searchFocused) { -                    focusSearch() -                    gamesViewModel.setSearchFocused(false) +        viewLifecycleOwner.lifecycleScope.apply { +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    gamesViewModel.searchFocused.collect { +                        if (it) { +                            focusSearch() +                            gamesViewModel.setSearchFocused(false) +                        } +                    }                  }              } - -            games.observe(viewLifecycleOwner) { filterAndSearch() } -            searchedGames.observe(viewLifecycleOwner) { -                (binding.gridGamesSearch.adapter as GameAdapter).submitList(it) -                if (it.isEmpty()) { -                    binding.noResultsView.visibility = View.VISIBLE -                } else { -                    binding.noResultsView.visibility = View.GONE +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    gamesViewModel.games.collect { filterAndSearch() } +                } +            } +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    gamesViewModel.searchedGames.collect { +                        (binding.gridGamesSearch.adapter as GameAdapter).submitList(it) +                        if (it.isEmpty()) { +                            binding.noResultsView.visibility = View.VISIBLE +                        } else { +                            binding.noResultsView.visibility = View.GONE +                        } +                    }                  }              }          } @@ -109,7 +127,7 @@ class SearchFragment : Fragment() {      private inner class ScoredGame(val score: Double, val item: Game)      private fun filterAndSearch() { -        val baseList = gamesViewModel.games.value!! +        val baseList = gamesViewModel.games.value          val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) {              R.id.chip_recently_played -> {                  baseList.filter { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt index 55b6a0367..9d0594c6e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt @@ -15,10 +15,14 @@ import androidx.core.view.updatePadding  import androidx.core.widget.doOnTextChanged  import androidx.fragment.app.Fragment  import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle  import androidx.recyclerview.widget.LinearLayoutManager  import com.google.android.material.divider.MaterialDividerItemDecoration  import com.google.android.material.transition.MaterialSharedAxis  import info.debatty.java.stringsimilarity.Cosine +import kotlinx.coroutines.launch  import org.yuzu.yuzu_emu.R  import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding  import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem @@ -79,10 +83,14 @@ class SettingsSearchFragment : Fragment() {              search()              binding.settingsList.smoothScrollToPosition(0)          } -        settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) { -            if (it) { -                settingsViewModel.setShouldReloadSettingsList(false) -                search() +        viewLifecycleOwner.lifecycleScope.launch { +            repeatOnLifecycle(Lifecycle.State.CREATED) { +                settingsViewModel.shouldReloadSettingsList.collect { +                    if (it) { +                        settingsViewModel.setShouldReloadSettingsList(false) +                        search() +                    } +                }              }          } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt index d50c421a0..fbb2f6e18 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt @@ -22,10 +22,14 @@ import androidx.core.view.isVisible  import androidx.core.view.updatePadding  import androidx.fragment.app.Fragment  import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle  import androidx.navigation.findNavController  import androidx.preference.PreferenceManager  import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback  import com.google.android.material.transition.MaterialFadeThrough +import kotlinx.coroutines.launch  import java.io.File  import org.yuzu.yuzu_emu.R  import org.yuzu.yuzu_emu.YuzuApplication @@ -206,10 +210,14 @@ class SetupFragment : Fragment() {              )          } -        homeViewModel.shouldPageForward.observe(viewLifecycleOwner) { -            if (it) { -                pageForward() -                homeViewModel.setShouldPageForward(false) +        viewLifecycleOwner.lifecycleScope.launch { +            repeatOnLifecycle(Lifecycle.State.CREATED) { +                homeViewModel.shouldPageForward.collect { +                    if (it) { +                        pageForward() +                        homeViewModel.setShouldPageForward(false) +                    } +                }              }          } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt index e35f51bc3..f34870c2d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt @@ -3,28 +3,28 @@  package org.yuzu.yuzu_emu.model -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData  import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow  class EmulationViewModel : ViewModel() { -    private val _emulationStarted = MutableLiveData(false) -    val emulationStarted: LiveData<Boolean> get() = _emulationStarted +    val emulationStarted: StateFlow<Boolean> get() = _emulationStarted +    private val _emulationStarted = MutableStateFlow(false) -    private val _isEmulationStopping = MutableLiveData(false) -    val isEmulationStopping: LiveData<Boolean> get() = _isEmulationStopping +    val isEmulationStopping: StateFlow<Boolean> get() = _isEmulationStopping +    private val _isEmulationStopping = MutableStateFlow(false) -    private val _shaderProgress = MutableLiveData(0) -    val shaderProgress: LiveData<Int> get() = _shaderProgress +    val shaderProgress: StateFlow<Int> get() = _shaderProgress +    private val _shaderProgress = MutableStateFlow(0) -    private val _totalShaders = MutableLiveData(0) -    val totalShaders: LiveData<Int> get() = _totalShaders +    val totalShaders: StateFlow<Int> get() = _totalShaders +    private val _totalShaders = MutableStateFlow(0) -    private val _shaderMessage = MutableLiveData("") -    val shaderMessage: LiveData<String> get() = _shaderMessage +    val shaderMessage: StateFlow<String> get() = _shaderMessage +    private val _shaderMessage = MutableStateFlow("")      fun setEmulationStarted(started: Boolean) { -        _emulationStarted.postValue(started) +        _emulationStarted.value = started      }      fun setIsEmulationStopping(value: Boolean) { @@ -50,10 +50,18 @@ class EmulationViewModel : ViewModel() {      }      fun clear() { -        _emulationStarted.value = false -        _isEmulationStopping.value = false -        _shaderProgress.value = 0 -        _totalShaders.value = 0 -        _shaderMessage.value = "" +        setEmulationStarted(false) +        setIsEmulationStopping(false) +        setShaderProgress(0) +        setTotalShaders(0) +        setShaderMessage("") +    } + +    companion object { +        const val KEY_EMULATION_STARTED = "EmulationStarted" +        const val KEY_IS_EMULATION_STOPPING = "IsEmulationStarting" +        const val KEY_SHADER_PROGRESS = "ShaderProgress" +        const val KEY_TOTAL_SHADERS = "TotalShaders" +        const val KEY_SHADER_MESSAGE = "ShaderMessage"      }  } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt index 1fe42f922..6e09fa81d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt @@ -5,13 +5,13 @@ package org.yuzu.yuzu_emu.model  import android.net.Uri  import androidx.documentfile.provider.DocumentFile -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData  import androidx.lifecycle.ViewModel  import androidx.lifecycle.viewModelScope  import androidx.preference.PreferenceManager  import java.util.Locale  import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow  import kotlinx.coroutines.launch  import kotlinx.coroutines.withContext  import kotlinx.serialization.ExperimentalSerializationApi @@ -24,23 +24,23 @@ import org.yuzu.yuzu_emu.utils.GameHelper  @OptIn(ExperimentalSerializationApi::class)  class GamesViewModel : ViewModel() { -    private val _games = MutableLiveData<List<Game>>(emptyList()) -    val games: LiveData<List<Game>> get() = _games +    val games: StateFlow<List<Game>> get() = _games +    private val _games = MutableStateFlow(emptyList<Game>()) -    private val _searchedGames = MutableLiveData<List<Game>>(emptyList()) -    val searchedGames: LiveData<List<Game>> get() = _searchedGames +    val searchedGames: StateFlow<List<Game>> get() = _searchedGames +    private val _searchedGames = MutableStateFlow(emptyList<Game>()) -    private val _isReloading = MutableLiveData(false) -    val isReloading: LiveData<Boolean> get() = _isReloading +    val isReloading: StateFlow<Boolean> get() = _isReloading +    private val _isReloading = MutableStateFlow(false) -    private val _shouldSwapData = MutableLiveData(false) -    val shouldSwapData: LiveData<Boolean> get() = _shouldSwapData +    val shouldSwapData: StateFlow<Boolean> get() = _shouldSwapData +    private val _shouldSwapData = MutableStateFlow(false) -    private val _shouldScrollToTop = MutableLiveData(false) -    val shouldScrollToTop: LiveData<Boolean> get() = _shouldScrollToTop +    val shouldScrollToTop: StateFlow<Boolean> get() = _shouldScrollToTop +    private val _shouldScrollToTop = MutableStateFlow(false) -    private val _searchFocused = MutableLiveData(false) -    val searchFocused: LiveData<Boolean> get() = _searchFocused +    val searchFocused: StateFlow<Boolean> get() = _searchFocused +    private val _searchFocused = MutableStateFlow(false)      init {          // Ensure keys are loaded so that ROM metadata can be decrypted. @@ -79,36 +79,36 @@ class GamesViewModel : ViewModel() {              )          ) -        _games.postValue(sortedList) +        _games.value = sortedList      }      fun setSearchedGames(games: List<Game>) { -        _searchedGames.postValue(games) +        _searchedGames.value = games      }      fun setShouldSwapData(shouldSwap: Boolean) { -        _shouldSwapData.postValue(shouldSwap) +        _shouldSwapData.value = shouldSwap      }      fun setShouldScrollToTop(shouldScroll: Boolean) { -        _shouldScrollToTop.postValue(shouldScroll) +        _shouldScrollToTop.value = shouldScroll      }      fun setSearchFocused(searchFocused: Boolean) { -        _searchFocused.postValue(searchFocused) +        _searchFocused.value = searchFocused      }      fun reloadGames(directoryChanged: Boolean) { -        if (isReloading.value == true) { +        if (isReloading.value) {              return          } -        _isReloading.postValue(true) +        _isReloading.value = true          viewModelScope.launch {              withContext(Dispatchers.IO) {                  NativeLibrary.resetRomMetadata()                  setGames(GameHelper.getGames()) -                _isReloading.postValue(false) +                _isReloading.value = false                  if (directoryChanged) {                      setShouldSwapData(true) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt index 498c222fa..b32e19373 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt @@ -3,8 +3,8 @@  package org.yuzu.yuzu_emu.model -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow  data class HomeSetting(      val titleId: Int, @@ -14,5 +14,5 @@ data class HomeSetting(      val isEnabled: () -> Boolean = { true },      val disabledTitleId: Int = 0,      val disabledMessageId: Int = 0, -    val details: LiveData<String> = MutableLiveData("") +    val details: StateFlow<String> = MutableStateFlow("")  ) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt index a48ef7a88..756f76721 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt @@ -5,47 +5,43 @@ package org.yuzu.yuzu_emu.model  import android.net.Uri  import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData  import androidx.lifecycle.ViewModel  import androidx.lifecycle.ViewModelProvider  import androidx.preference.PreferenceManager +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow  import org.yuzu.yuzu_emu.YuzuApplication  import org.yuzu.yuzu_emu.utils.GameHelper  class HomeViewModel : ViewModel() { -    private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>() -    val navigationVisible: LiveData<Pair<Boolean, Boolean>> get() = _navigationVisible +    val navigationVisible: StateFlow<Pair<Boolean, Boolean>> get() = _navigationVisible +    private val _navigationVisible = MutableStateFlow(Pair(false, false)) -    private val _statusBarShadeVisible = MutableLiveData(true) -    val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible +    val statusBarShadeVisible: StateFlow<Boolean> get() = _statusBarShadeVisible +    private val _statusBarShadeVisible = MutableStateFlow(true) -    private val _shouldPageForward = MutableLiveData(false) -    val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward +    val shouldPageForward: StateFlow<Boolean> get() = _shouldPageForward +    private val _shouldPageForward = MutableStateFlow(false) -    private val _gamesDir = MutableLiveData( +    val gamesDir: StateFlow<String> get() = _gamesDir +    private val _gamesDir = MutableStateFlow(          Uri.parse(              PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)                  .getString(GameHelper.KEY_GAME_PATH, "")          ).path ?: ""      ) -    val gamesDir: LiveData<String> get() = _gamesDir      var navigatedToSetup = false -    init { -        _navigationVisible.value = Pair(false, false) -    } -      fun setNavigationVisibility(visible: Boolean, animated: Boolean) { -        if (_navigationVisible.value?.first == visible) { +        if (navigationVisible.value.first == visible) {              return          }          _navigationVisible.value = Pair(visible, animated)      }      fun setStatusBarShadeVisibility(visible: Boolean) { -        if (_statusBarShadeVisible.value == visible) { +        if (statusBarShadeVisible.value == visible) {              return          }          _statusBarShadeVisible.value = visible diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt index d16d15fa6..53fa7a8de 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt @@ -3,48 +3,43 @@  package org.yuzu.yuzu_emu.model -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.SavedStateHandle  import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow  import org.yuzu.yuzu_emu.R  import org.yuzu.yuzu_emu.YuzuApplication  import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem -class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { +class SettingsViewModel : ViewModel() {      var game: Game? = null      var shouldSave = false      var clickedItem: SettingsItem? = null -    private val _toolbarTitle = MutableLiveData("") -    val toolbarTitle: LiveData<String> get() = _toolbarTitle +    val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate +    private val _shouldRecreate = MutableStateFlow(false) -    private val _shouldRecreate = MutableLiveData(false) -    val shouldRecreate: LiveData<Boolean> get() = _shouldRecreate +    val shouldNavigateBack: StateFlow<Boolean> get() = _shouldNavigateBack +    private val _shouldNavigateBack = MutableStateFlow(false) -    private val _shouldNavigateBack = MutableLiveData(false) -    val shouldNavigateBack: LiveData<Boolean> get() = _shouldNavigateBack +    val shouldShowResetSettingsDialog: StateFlow<Boolean> get() = _shouldShowResetSettingsDialog +    private val _shouldShowResetSettingsDialog = MutableStateFlow(false) -    private val _shouldShowResetSettingsDialog = MutableLiveData(false) -    val shouldShowResetSettingsDialog: LiveData<Boolean> get() = _shouldShowResetSettingsDialog +    val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList +    private val _shouldReloadSettingsList = MutableStateFlow(false) -    private val _shouldReloadSettingsList = MutableLiveData(false) -    val shouldReloadSettingsList: LiveData<Boolean> get() = _shouldReloadSettingsList +    val isUsingSearch: StateFlow<Boolean> get() = _isUsingSearch +    private val _isUsingSearch = MutableStateFlow(false) -    private val _isUsingSearch = MutableLiveData(false) -    val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch +    val sliderProgress: StateFlow<Int> get() = _sliderProgress +    private val _sliderProgress = MutableStateFlow(-1) -    val sliderProgress = savedStateHandle.getStateFlow(KEY_SLIDER_PROGRESS, -1) +    val sliderTextValue: StateFlow<String> get() = _sliderTextValue +    private val _sliderTextValue = MutableStateFlow("") -    val sliderTextValue = savedStateHandle.getStateFlow(KEY_SLIDER_TEXT_VALUE, "") - -    val adapterItemChanged = savedStateHandle.getStateFlow(KEY_ADAPTER_ITEM_CHANGED, -1) - -    fun setToolbarTitle(value: String) { -        _toolbarTitle.value = value -    } +    val adapterItemChanged: StateFlow<Int> get() = _adapterItemChanged +    private val _adapterItemChanged = MutableStateFlow(-1)      fun setShouldRecreate(value: Boolean) {          _shouldRecreate.value = value @@ -67,8 +62,8 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo      }      fun setSliderTextValue(value: Float, units: String) { -        savedStateHandle[KEY_SLIDER_PROGRESS] = value -        savedStateHandle[KEY_SLIDER_TEXT_VALUE] = String.format( +        _sliderProgress.value = value.toInt() +        _sliderTextValue.value = String.format(              YuzuApplication.appContext.getString(R.string.value_with_units),              value.toInt().toString(),              units @@ -76,21 +71,15 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo      }      fun setSliderProgress(value: Float) { -        savedStateHandle[KEY_SLIDER_PROGRESS] = value +        _sliderProgress.value = value.toInt()      }      fun setAdapterItemChanged(value: Int) { -        savedStateHandle[KEY_ADAPTER_ITEM_CHANGED] = value +        _adapterItemChanged.value = value      }      fun clear() {          game = null          shouldSave = false      } - -    companion object { -        const val KEY_SLIDER_TEXT_VALUE = "SliderTextValue" -        const val KEY_SLIDER_PROGRESS = "SliderProgress" -        const val KEY_ADAPTER_ITEM_CHANGED = "AdapterItemChanged" -    }  } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt index 27ea725a5..531c2aaf0 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt @@ -3,29 +3,25 @@  package org.yuzu.yuzu_emu.model -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData  import androidx.lifecycle.ViewModel  import androidx.lifecycle.viewModelScope  import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow  import kotlinx.coroutines.launch  class TaskViewModel : ViewModel() { -    private val _result = MutableLiveData<Any>() -    val result: LiveData<Any> = _result +    val result: StateFlow<Any> get() = _result +    private val _result = MutableStateFlow(Any()) -    private val _isComplete = MutableLiveData<Boolean>() -    val isComplete: LiveData<Boolean> = _isComplete +    val isComplete: StateFlow<Boolean> get() = _isComplete +    private val _isComplete = MutableStateFlow(false) -    private val _isRunning = MutableLiveData<Boolean>() -    val isRunning: LiveData<Boolean> = _isRunning +    val isRunning: StateFlow<Boolean> get() = _isRunning +    private val _isRunning = MutableStateFlow(false)      lateinit var task: () -> Any -    init { -        clear() -    } -      fun clear() {          _result.value = Any()          _isComplete.value = false @@ -33,15 +29,16 @@ class TaskViewModel : ViewModel() {      }      fun runTask() { -        if (_isRunning.value == true) { +        if (isRunning.value) {              return          }          _isRunning.value = true          viewModelScope.launch(Dispatchers.IO) {              val res = task() -            _result.postValue(res) -            _isComplete.postValue(true) +            _result.value = res +            _isComplete.value = true +            _isRunning.value = false          }      }  } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt index b0156dca5..35e365458 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt @@ -3,6 +3,7 @@  package org.yuzu.yuzu_emu.ui +import android.annotation.SuppressLint  import android.os.Bundle  import android.view.LayoutInflater  import android.view.View @@ -14,8 +15,12 @@ import androidx.core.view.WindowInsetsCompat  import androidx.core.view.updatePadding  import androidx.fragment.app.Fragment  import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle  import com.google.android.material.color.MaterialColors  import com.google.android.material.transition.MaterialFadeThrough +import kotlinx.coroutines.launch  import org.yuzu.yuzu_emu.R  import org.yuzu.yuzu_emu.adapters.GameAdapter  import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding @@ -44,6 +49,8 @@ class GamesFragment : Fragment() {          return binding.root      } +    // This is using the correct scope, lint is just acting up +    @SuppressLint("UnsafeRepeatOnLifecycleDetector")      override fun onViewCreated(view: View, savedInstanceState: Bundle?) {          homeViewModel.setNavigationVisibility(visible = true, animated = false) @@ -80,37 +87,48 @@ class GamesFragment : Fragment() {                  if (_binding == null) {                      return@post                  } -                binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value!! +                binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value              }          } -        gamesViewModel.apply { -            // Watch for when we get updates to any of our games lists -            isReloading.observe(viewLifecycleOwner) { isReloading -> -                binding.swipeRefresh.isRefreshing = isReloading +        viewLifecycleOwner.lifecycleScope.apply { +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    gamesViewModel.isReloading.collect { binding.swipeRefresh.isRefreshing = it } +                }              } -            games.observe(viewLifecycleOwner) { -                (binding.gridGames.adapter as GameAdapter).submitList(it) -                if (it.isEmpty()) { -                    binding.noticeText.visibility = View.VISIBLE -                } else { -                    binding.noticeText.visibility = View.GONE +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    gamesViewModel.games.collect { +                        (binding.gridGames.adapter as GameAdapter).submitList(it) +                        if (it.isEmpty()) { +                            binding.noticeText.visibility = View.VISIBLE +                        } else { +                            binding.noticeText.visibility = View.GONE +                        } +                    }                  }              } -            shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData -> -                if (shouldSwapData) { -                    (binding.gridGames.adapter as GameAdapter).submitList( -                        gamesViewModel.games.value!! -                    ) -                    gamesViewModel.setShouldSwapData(false) +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    gamesViewModel.shouldSwapData.collect { +                        if (it) { +                            (binding.gridGames.adapter as GameAdapter).submitList( +                                gamesViewModel.games.value +                            ) +                            gamesViewModel.setShouldSwapData(false) +                        } +                    }                  }              } - -            // Check if the user reselected the games menu item and then scroll to top of the list -            shouldScrollToTop.observe(viewLifecycleOwner) { shouldScroll -> -                if (shouldScroll) { -                    scrollToTop() -                    gamesViewModel.setShouldScrollToTop(false) +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    gamesViewModel.shouldScrollToTop.collect { +                        if (it) { +                            scrollToTop() +                            gamesViewModel.setShouldScrollToTop(false) +                        } +                    }                  }              }          } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt index 7d8e06ad8..1ee833cf6 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt @@ -19,7 +19,9 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen  import androidx.core.view.ViewCompat  import androidx.core.view.WindowCompat  import androidx.core.view.WindowInsetsCompat +import androidx.lifecycle.Lifecycle  import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle  import androidx.navigation.NavController  import androidx.navigation.fragment.NavHostFragment  import androidx.navigation.ui.setupWithNavController @@ -115,16 +117,22 @@ class MainActivity : AppCompatActivity(), ThemeProvider {          }          // Prevents navigation from being drawn for a short time on recreation if set to hidden -        if (!homeViewModel.navigationVisible.value?.first!!) { +        if (!homeViewModel.navigationVisible.value.first) {              binding.navigationView.visibility = View.INVISIBLE              binding.statusBarShade.visibility = View.INVISIBLE          } -        homeViewModel.navigationVisible.observe(this) { -            showNavigation(it.first, it.second) -        } -        homeViewModel.statusBarShadeVisible.observe(this) { visible -> -            showStatusBarShade(visible) +        lifecycleScope.apply { +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    homeViewModel.navigationVisible.collect { showNavigation(it.first, it.second) } +                } +            } +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    homeViewModel.statusBarShadeVisible.collect { showStatusBarShade(it) } +                } +            }          }          // Dismiss previous notifications (should not happen unless a crash occurred)  | 
