android: Use StateFlow instead of LiveData
This commit is contained in:
		| @@ -10,8 +10,12 @@ import android.view.ViewGroup | |||||||
| import androidx.appcompat.app.AppCompatActivity | import androidx.appcompat.app.AppCompatActivity | ||||||
| import androidx.core.content.ContextCompat | import androidx.core.content.ContextCompat | ||||||
| import androidx.core.content.res.ResourcesCompat | import androidx.core.content.res.ResourcesCompat | ||||||
|  | import androidx.lifecycle.Lifecycle | ||||||
| import androidx.lifecycle.LifecycleOwner | import androidx.lifecycle.LifecycleOwner | ||||||
|  | import androidx.lifecycle.lifecycleScope | ||||||
|  | import androidx.lifecycle.repeatOnLifecycle | ||||||
| import androidx.recyclerview.widget.RecyclerView | import androidx.recyclerview.widget.RecyclerView | ||||||
|  | import kotlinx.coroutines.launch | ||||||
| import org.yuzu.yuzu_emu.R | import org.yuzu.yuzu_emu.R | ||||||
| import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding | import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding | ||||||
| import org.yuzu.yuzu_emu.fragments.MessageDialogFragment | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment | ||||||
| @@ -86,7 +90,11 @@ class HomeSettingAdapter( | |||||||
|                 binding.optionIcon.alpha = 0.5f |                 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.postDelayed( | ||||||
|                 { |                 { | ||||||
|                     binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE |                     binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE | ||||||
|   | |||||||
| @@ -13,9 +13,14 @@ import androidx.appcompat.app.AppCompatActivity | |||||||
| import androidx.core.view.ViewCompat | import androidx.core.view.ViewCompat | ||||||
| import androidx.core.view.WindowCompat | import androidx.core.view.WindowCompat | ||||||
| import androidx.core.view.WindowInsetsCompat | import androidx.core.view.WindowInsetsCompat | ||||||
|  | import androidx.lifecycle.Lifecycle | ||||||
|  | import androidx.lifecycle.lifecycleScope | ||||||
|  | import androidx.lifecycle.repeatOnLifecycle | ||||||
| import androidx.navigation.fragment.NavHostFragment | import androidx.navigation.fragment.NavHostFragment | ||||||
| import androidx.navigation.navArgs | import androidx.navigation.navArgs | ||||||
| import com.google.android.material.color.MaterialColors | import com.google.android.material.color.MaterialColors | ||||||
|  | import kotlinx.coroutines.flow.collectLatest | ||||||
|  | import kotlinx.coroutines.launch | ||||||
| import java.io.IOException | import java.io.IOException | ||||||
| import org.yuzu.yuzu_emu.R | import org.yuzu.yuzu_emu.R | ||||||
| import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding | import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding | ||||||
| @@ -66,19 +71,30 @@ class SettingsActivity : AppCompatActivity() { | |||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         settingsViewModel.shouldRecreate.observe(this) { |         lifecycleScope.apply { | ||||||
|  |             launch { | ||||||
|  |                 repeatOnLifecycle(Lifecycle.State.CREATED) { | ||||||
|  |                     settingsViewModel.shouldRecreate.collectLatest { | ||||||
|                         if (it) { |                         if (it) { | ||||||
|                             settingsViewModel.setShouldRecreate(false) |                             settingsViewModel.setShouldRecreate(false) | ||||||
|                             recreate() |                             recreate() | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|         settingsViewModel.shouldNavigateBack.observe(this) { |                 } | ||||||
|  |             } | ||||||
|  |             launch { | ||||||
|  |                 repeatOnLifecycle(Lifecycle.State.CREATED) { | ||||||
|  |                     settingsViewModel.shouldNavigateBack.collectLatest { | ||||||
|                         if (it) { |                         if (it) { | ||||||
|                             settingsViewModel.setShouldNavigateBack(false) |                             settingsViewModel.setShouldNavigateBack(false) | ||||||
|                             navigateBack() |                             navigateBack() | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|         settingsViewModel.shouldShowResetSettingsDialog.observe(this) { |                 } | ||||||
|  |             } | ||||||
|  |             launch { | ||||||
|  |                 repeatOnLifecycle(Lifecycle.State.CREATED) { | ||||||
|  |                     settingsViewModel.shouldShowResetSettingsDialog.collectLatest { | ||||||
|                         if (it) { |                         if (it) { | ||||||
|                             settingsViewModel.setShouldShowResetSettingsDialog(false) |                             settingsViewModel.setShouldShowResetSettingsDialog(false) | ||||||
|                             ResetSettingsDialogFragment().show( |                             ResetSettingsDialogFragment().show( | ||||||
| @@ -87,6 +103,9 @@ class SettingsActivity : AppCompatActivity() { | |||||||
|                             ) |                             ) | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         onBackPressedDispatcher.addCallback( |         onBackPressedDispatcher.addCallback( | ||||||
|             this, |             this, | ||||||
|   | |||||||
| @@ -13,11 +13,15 @@ import androidx.core.view.WindowInsetsCompat | |||||||
| import androidx.core.view.updatePadding | import androidx.core.view.updatePadding | ||||||
| import androidx.fragment.app.Fragment | import androidx.fragment.app.Fragment | ||||||
| import androidx.fragment.app.activityViewModels | import androidx.fragment.app.activityViewModels | ||||||
|  | import androidx.lifecycle.Lifecycle | ||||||
|  | import androidx.lifecycle.lifecycleScope | ||||||
|  | import androidx.lifecycle.repeatOnLifecycle | ||||||
| import androidx.navigation.findNavController | import androidx.navigation.findNavController | ||||||
| import androidx.navigation.fragment.navArgs | import androidx.navigation.fragment.navArgs | ||||||
| import androidx.recyclerview.widget.LinearLayoutManager | import androidx.recyclerview.widget.LinearLayoutManager | ||||||
| import com.google.android.material.divider.MaterialDividerItemDecoration | import com.google.android.material.divider.MaterialDividerItemDecoration | ||||||
| import com.google.android.material.transition.MaterialSharedAxis | import com.google.android.material.transition.MaterialSharedAxis | ||||||
|  | import kotlinx.coroutines.launch | ||||||
| import org.yuzu.yuzu_emu.R | import org.yuzu.yuzu_emu.R | ||||||
| import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding | import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding | ||||||
| import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | ||||||
| @@ -51,6 +55,8 @@ class SettingsFragment : Fragment() { | |||||||
|         return binding.root |         return binding.root | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // This is using the correct scope, lint is just acting up | ||||||
|  |     @SuppressLint("UnsafeRepeatOnLifecycleDetector") | ||||||
|     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||||||
|         settingsAdapter = SettingsAdapter(this, requireContext()) |         settingsAdapter = SettingsAdapter(this, requireContext()) | ||||||
|         presenter = SettingsFragmentPresenter( |         presenter = SettingsFragmentPresenter( | ||||||
| @@ -75,18 +81,19 @@ class SettingsFragment : Fragment() { | |||||||
|             settingsViewModel.setShouldNavigateBack(true) |             settingsViewModel.setShouldNavigateBack(true) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         settingsViewModel.toolbarTitle.observe(viewLifecycleOwner) { |         viewLifecycleOwner.lifecycleScope.apply { | ||||||
|             if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it |             launch { | ||||||
|         } |                 repeatOnLifecycle(Lifecycle.State.CREATED) { | ||||||
|  |                     settingsViewModel.shouldReloadSettingsList.collectLatest { | ||||||
|         settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) { |  | ||||||
|                         if (it) { |                         if (it) { | ||||||
|                             settingsViewModel.setShouldReloadSettingsList(false) |                             settingsViewModel.setShouldReloadSettingsList(false) | ||||||
|                             presenter.loadSettingsList() |                             presenter.loadSettingsList() | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  |                 } | ||||||
|         settingsViewModel.isUsingSearch.observe(viewLifecycleOwner) { |             } | ||||||
|  |             launch { | ||||||
|  |                 settingsViewModel.isUsingSearch.collectLatest { | ||||||
|                     if (it) { |                     if (it) { | ||||||
|                         reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) |                         reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) | ||||||
|                         exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) |                         exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) | ||||||
| @@ -95,6 +102,8 @@ class SettingsFragment : Fragment() { | |||||||
|                         exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) |                         exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if (args.menuTag == SettingsFile.FILE_NAME_CONFIG) { |         if (args.menuTag == SettingsFile.FILE_NAME_CONFIG) { | ||||||
|             binding.toolbarSettings.inflateMenu(R.menu.menu_settings) |             binding.toolbarSettings.inflateMenu(R.menu.menu_settings) | ||||||
|   | |||||||
| @@ -39,6 +39,7 @@ import androidx.window.layout.WindowLayoutInfo | |||||||
| import com.google.android.material.dialog.MaterialAlertDialogBuilder | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||||||
| import com.google.android.material.slider.Slider | import com.google.android.material.slider.Slider | ||||||
| import kotlinx.coroutines.Dispatchers | import kotlinx.coroutines.Dispatchers | ||||||
|  | import kotlinx.coroutines.flow.collectLatest | ||||||
| import kotlinx.coroutines.launch | import kotlinx.coroutines.launch | ||||||
| import org.yuzu.yuzu_emu.HomeNavigationDirections | import org.yuzu.yuzu_emu.HomeNavigationDirections | ||||||
| import org.yuzu.yuzu_emu.NativeLibrary | import org.yuzu.yuzu_emu.NativeLibrary | ||||||
| @@ -129,6 +130,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||||||
|         return binding.root |         return binding.root | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // This is using the correct scope, lint is just acting up | ||||||
|  |     @SuppressLint("UnsafeRepeatOnLifecycleDetector") | ||||||
|     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||||||
|         binding.surfaceEmulation.holder.addCallback(this) |         binding.surfaceEmulation.holder.addCallback(this) | ||||||
|         binding.showFpsText.setTextColor(Color.YELLOW) |         binding.showFpsText.setTextColor(Color.YELLOW) | ||||||
| @@ -205,21 +208,25 @@ 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) |         GameIconUtils.loadGameIcon(game, binding.loadingImage) | ||||||
|         binding.loadingTitle.text = game.title |         binding.loadingTitle.text = game.title | ||||||
|         binding.loadingTitle.isSelected = true |         binding.loadingTitle.isSelected = true | ||||||
|         binding.loadingText.isSelected = true |         binding.loadingText.isSelected = true | ||||||
|  |  | ||||||
|         emulationViewModel.shaderProgress.observe(viewLifecycleOwner) { |         viewLifecycleOwner.lifecycleScope.apply { | ||||||
|             if (it > 0 && it != emulationViewModel.totalShaders.value!!) { |             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 |                             binding.loadingProgressIndicator.isIndeterminate = false | ||||||
|  |  | ||||||
|                             if (it < binding.loadingProgressIndicator.max) { |                             if (it < binding.loadingProgressIndicator.max) { | ||||||
| @@ -227,22 +234,33 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|  |  | ||||||
|             if (it == emulationViewModel.totalShaders.value!!) { |                         if (it == emulationViewModel.totalShaders.value) { | ||||||
|                             binding.loadingText.setText(R.string.loading) |                             binding.loadingText.setText(R.string.loading) | ||||||
|                             binding.loadingProgressIndicator.isIndeterminate = true |                             binding.loadingProgressIndicator.isIndeterminate = true | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|         emulationViewModel.totalShaders.observe(viewLifecycleOwner) { |                 } | ||||||
|  |             } | ||||||
|  |             launch { | ||||||
|  |                 repeatOnLifecycle(Lifecycle.State.CREATED) { | ||||||
|  |                     emulationViewModel.totalShaders.collectLatest { | ||||||
|                         binding.loadingProgressIndicator.max = it |                         binding.loadingProgressIndicator.max = it | ||||||
|                     } |                     } | ||||||
|         emulationViewModel.shaderMessage.observe(viewLifecycleOwner) { |                 } | ||||||
|  |             } | ||||||
|  |             launch { | ||||||
|  |                 repeatOnLifecycle(Lifecycle.State.CREATED) { | ||||||
|  |                     emulationViewModel.shaderMessage.collectLatest { | ||||||
|                         if (it.isNotEmpty()) { |                         if (it.isNotEmpty()) { | ||||||
|                             binding.loadingText.text = it |                             binding.loadingText.text = it | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  |                 } | ||||||
|         emulationViewModel.emulationStarted.observe(viewLifecycleOwner) { started -> |             } | ||||||
|             if (started) { |             launch { | ||||||
|  |                 repeatOnLifecycle(Lifecycle.State.CREATED) { | ||||||
|  |                     emulationViewModel.emulationStarted.collectLatest { | ||||||
|  |                         if (it) { | ||||||
|                             binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) |                             binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) | ||||||
|                             ViewUtils.showView(binding.surfaceInputOverlay) |                             ViewUtils.showView(binding.surfaceInputOverlay) | ||||||
|                             ViewUtils.hideView(binding.loadingIndicator) |                             ViewUtils.hideView(binding.loadingIndicator) | ||||||
| @@ -251,8 +269,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||||||
|                             updateShowFpsOverlay() |                             updateShowFpsOverlay() | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  |                 } | ||||||
|         emulationViewModel.isEmulationStopping.observe(viewLifecycleOwner) { |             } | ||||||
|  |             launch { | ||||||
|  |                 repeatOnLifecycle(Lifecycle.State.CREATED) { | ||||||
|  |                     emulationViewModel.isEmulationStopping.collectLatest { | ||||||
|                         if (it) { |                         if (it) { | ||||||
|                             binding.loadingText.setText(R.string.shutting_down) |                             binding.loadingText.setText(R.string.shutting_down) | ||||||
|                             ViewUtils.showView(binding.loadingIndicator) |                             ViewUtils.showView(binding.loadingIndicator) | ||||||
| @@ -261,6 +282,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     override fun onConfigurationChanged(newConfig: Configuration) { |     override fun onConfigurationChanged(newConfig: Configuration) { | ||||||
|         super.onConfigurationChanged(newConfig) |         super.onConfigurationChanged(newConfig) | ||||||
| @@ -274,9 +298,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             if (EmulationMenuSettings.showOverlay && |             if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) { | ||||||
|                 emulationViewModel.emulationStarted.value == true |  | ||||||
|             ) { |  | ||||||
|                 binding.surfaceInputOverlay.post { |                 binding.surfaceInputOverlay.post { | ||||||
|                     binding.surfaceInputOverlay.visibility = View.VISIBLE |                     binding.surfaceInputOverlay.visibility = View.VISIBLE | ||||||
|                 } |                 } | ||||||
|   | |||||||
| @@ -9,8 +9,12 @@ import android.widget.Toast | |||||||
| import androidx.appcompat.app.AppCompatActivity | import androidx.appcompat.app.AppCompatActivity | ||||||
| import androidx.fragment.app.DialogFragment | import androidx.fragment.app.DialogFragment | ||||||
| import androidx.fragment.app.activityViewModels | import androidx.fragment.app.activityViewModels | ||||||
|  | import androidx.lifecycle.Lifecycle | ||||||
| import androidx.lifecycle.ViewModelProvider | import androidx.lifecycle.ViewModelProvider | ||||||
|  | import androidx.lifecycle.lifecycleScope | ||||||
|  | import androidx.lifecycle.repeatOnLifecycle | ||||||
| import com.google.android.material.dialog.MaterialAlertDialogBuilder | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||||||
|  | import kotlinx.coroutines.launch | ||||||
| import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding | ||||||
| import org.yuzu.yuzu_emu.model.TaskViewModel | import org.yuzu.yuzu_emu.model.TaskViewModel | ||||||
|  |  | ||||||
| @@ -28,11 +32,15 @@ class IndeterminateProgressDialogFragment : DialogFragment() { | |||||||
|             .create() |             .create() | ||||||
|         dialog.setCanceledOnTouchOutside(false) |         dialog.setCanceledOnTouchOutside(false) | ||||||
|  |  | ||||||
|         taskViewModel.isComplete.observe(this) { complete -> |         viewLifecycleOwner.lifecycleScope.launch { | ||||||
|             if (complete) { |             repeatOnLifecycle(Lifecycle.State.CREATED) { | ||||||
|  |                 taskViewModel.isComplete.collect { | ||||||
|  |                     if (it) { | ||||||
|                         dialog.dismiss() |                         dialog.dismiss() | ||||||
|                         when (val result = taskViewModel.result.value) { |                         when (val result = taskViewModel.result.value) { | ||||||
|                     is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG).show() |                             is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG) | ||||||
|  |                                 .show() | ||||||
|  |  | ||||||
|                             is MessageDialogFragment -> result.show( |                             is MessageDialogFragment -> result.show( | ||||||
|                                 requireActivity().supportFragmentManager, |                                 requireActivity().supportFragmentManager, | ||||||
|                                 MessageDialogFragment.TAG |                                 MessageDialogFragment.TAG | ||||||
| @@ -41,8 +49,10 @@ class IndeterminateProgressDialogFragment : DialogFragment() { | |||||||
|                         taskViewModel.clear() |                         taskViewModel.clear() | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if (taskViewModel.isRunning.value == false) { |         if (!taskViewModel.isRunning.value) { | ||||||
|             taskViewModel.runTask() |             taskViewModel.runTask() | ||||||
|         } |         } | ||||||
|         return dialog |         return dialog | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
|  |  | ||||||
| package org.yuzu.yuzu_emu.fragments | package org.yuzu.yuzu_emu.fragments | ||||||
|  |  | ||||||
|  | import android.annotation.SuppressLint | ||||||
| import android.content.Context | import android.content.Context | ||||||
| import android.content.SharedPreferences | import android.content.SharedPreferences | ||||||
| import android.os.Bundle | import android.os.Bundle | ||||||
| @@ -17,9 +18,13 @@ import androidx.core.view.updatePadding | |||||||
| import androidx.core.widget.doOnTextChanged | import androidx.core.widget.doOnTextChanged | ||||||
| import androidx.fragment.app.Fragment | import androidx.fragment.app.Fragment | ||||||
| import androidx.fragment.app.activityViewModels | import androidx.fragment.app.activityViewModels | ||||||
|  | import androidx.lifecycle.Lifecycle | ||||||
|  | import androidx.lifecycle.lifecycleScope | ||||||
|  | import androidx.lifecycle.repeatOnLifecycle | ||||||
| import androidx.preference.PreferenceManager | import androidx.preference.PreferenceManager | ||||||
| import info.debatty.java.stringsimilarity.Jaccard | import info.debatty.java.stringsimilarity.Jaccard | ||||||
| import info.debatty.java.stringsimilarity.JaroWinkler | import info.debatty.java.stringsimilarity.JaroWinkler | ||||||
|  | import kotlinx.coroutines.launch | ||||||
| import java.util.Locale | import java.util.Locale | ||||||
| import org.yuzu.yuzu_emu.R | import org.yuzu.yuzu_emu.R | ||||||
| import org.yuzu.yuzu_emu.YuzuApplication | import org.yuzu.yuzu_emu.YuzuApplication | ||||||
| @@ -52,6 +57,8 @@ class SearchFragment : Fragment() { | |||||||
|         return binding.root |         return binding.root | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // This is using the correct scope, lint is just acting up | ||||||
|  |     @SuppressLint("UnsafeRepeatOnLifecycleDetector") | ||||||
|     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||||||
|         homeViewModel.setNavigationVisibility(visible = true, animated = false) |         homeViewModel.setNavigationVisibility(visible = true, animated = false) | ||||||
|         preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) |         preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | ||||||
| @@ -79,16 +86,25 @@ class SearchFragment : Fragment() { | |||||||
|             filterAndSearch() |             filterAndSearch() | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         gamesViewModel.apply { |         viewLifecycleOwner.lifecycleScope.apply { | ||||||
|             searchFocused.observe(viewLifecycleOwner) { searchFocused -> |             launch { | ||||||
|                 if (searchFocused) { |                 repeatOnLifecycle(Lifecycle.State.CREATED) { | ||||||
|  |                     gamesViewModel.searchFocused.collect { | ||||||
|  |                         if (it) { | ||||||
|                             focusSearch() |                             focusSearch() | ||||||
|                             gamesViewModel.setSearchFocused(false) |                             gamesViewModel.setSearchFocused(false) | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  |                 } | ||||||
|             games.observe(viewLifecycleOwner) { filterAndSearch() } |             } | ||||||
|             searchedGames.observe(viewLifecycleOwner) { |             launch { | ||||||
|  |                 repeatOnLifecycle(Lifecycle.State.CREATED) { | ||||||
|  |                     gamesViewModel.games.collect { filterAndSearch() } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             launch { | ||||||
|  |                 repeatOnLifecycle(Lifecycle.State.CREATED) { | ||||||
|  |                     gamesViewModel.searchedGames.collect { | ||||||
|                         (binding.gridGamesSearch.adapter as GameAdapter).submitList(it) |                         (binding.gridGamesSearch.adapter as GameAdapter).submitList(it) | ||||||
|                         if (it.isEmpty()) { |                         if (it.isEmpty()) { | ||||||
|                             binding.noResultsView.visibility = View.VISIBLE |                             binding.noResultsView.visibility = View.VISIBLE | ||||||
| @@ -97,6 +113,8 @@ class SearchFragment : Fragment() { | |||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         binding.clearButton.setOnClickListener { binding.searchText.setText("") } |         binding.clearButton.setOnClickListener { binding.searchText.setText("") } | ||||||
|  |  | ||||||
| @@ -109,7 +127,7 @@ class SearchFragment : Fragment() { | |||||||
|     private inner class ScoredGame(val score: Double, val item: Game) |     private inner class ScoredGame(val score: Double, val item: Game) | ||||||
|  |  | ||||||
|     private fun filterAndSearch() { |     private fun filterAndSearch() { | ||||||
|         val baseList = gamesViewModel.games.value!! |         val baseList = gamesViewModel.games.value | ||||||
|         val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) { |         val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) { | ||||||
|             R.id.chip_recently_played -> { |             R.id.chip_recently_played -> { | ||||||
|                 baseList.filter { |                 baseList.filter { | ||||||
|   | |||||||
| @@ -15,10 +15,14 @@ import androidx.core.view.updatePadding | |||||||
| import androidx.core.widget.doOnTextChanged | import androidx.core.widget.doOnTextChanged | ||||||
| import androidx.fragment.app.Fragment | import androidx.fragment.app.Fragment | ||||||
| import androidx.fragment.app.activityViewModels | import androidx.fragment.app.activityViewModels | ||||||
|  | import androidx.lifecycle.Lifecycle | ||||||
|  | import androidx.lifecycle.lifecycleScope | ||||||
|  | import androidx.lifecycle.repeatOnLifecycle | ||||||
| import androidx.recyclerview.widget.LinearLayoutManager | import androidx.recyclerview.widget.LinearLayoutManager | ||||||
| import com.google.android.material.divider.MaterialDividerItemDecoration | import com.google.android.material.divider.MaterialDividerItemDecoration | ||||||
| import com.google.android.material.transition.MaterialSharedAxis | import com.google.android.material.transition.MaterialSharedAxis | ||||||
| import info.debatty.java.stringsimilarity.Cosine | import info.debatty.java.stringsimilarity.Cosine | ||||||
|  | import kotlinx.coroutines.launch | ||||||
| import org.yuzu.yuzu_emu.R | import org.yuzu.yuzu_emu.R | ||||||
| import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding | import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding | ||||||
| import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | ||||||
| @@ -79,12 +83,16 @@ class SettingsSearchFragment : Fragment() { | |||||||
|             search() |             search() | ||||||
|             binding.settingsList.smoothScrollToPosition(0) |             binding.settingsList.smoothScrollToPosition(0) | ||||||
|         } |         } | ||||||
|         settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) { |         viewLifecycleOwner.lifecycleScope.launch { | ||||||
|  |             repeatOnLifecycle(Lifecycle.State.CREATED) { | ||||||
|  |                 settingsViewModel.shouldReloadSettingsList.collect { | ||||||
|                     if (it) { |                     if (it) { | ||||||
|                         settingsViewModel.setShouldReloadSettingsList(false) |                         settingsViewModel.setShouldReloadSettingsList(false) | ||||||
|                         search() |                         search() | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         search() |         search() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,10 +22,14 @@ import androidx.core.view.isVisible | |||||||
| import androidx.core.view.updatePadding | import androidx.core.view.updatePadding | ||||||
| import androidx.fragment.app.Fragment | import androidx.fragment.app.Fragment | ||||||
| import androidx.fragment.app.activityViewModels | import androidx.fragment.app.activityViewModels | ||||||
|  | import androidx.lifecycle.Lifecycle | ||||||
|  | import androidx.lifecycle.lifecycleScope | ||||||
|  | import androidx.lifecycle.repeatOnLifecycle | ||||||
| import androidx.navigation.findNavController | import androidx.navigation.findNavController | ||||||
| import androidx.preference.PreferenceManager | import androidx.preference.PreferenceManager | ||||||
| import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback | import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback | ||||||
| import com.google.android.material.transition.MaterialFadeThrough | import com.google.android.material.transition.MaterialFadeThrough | ||||||
|  | import kotlinx.coroutines.launch | ||||||
| import java.io.File | import java.io.File | ||||||
| import org.yuzu.yuzu_emu.R | import org.yuzu.yuzu_emu.R | ||||||
| import org.yuzu.yuzu_emu.YuzuApplication | import org.yuzu.yuzu_emu.YuzuApplication | ||||||
| @@ -206,12 +210,16 @@ class SetupFragment : Fragment() { | |||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         homeViewModel.shouldPageForward.observe(viewLifecycleOwner) { |         viewLifecycleOwner.lifecycleScope.launch { | ||||||
|  |             repeatOnLifecycle(Lifecycle.State.CREATED) { | ||||||
|  |                 homeViewModel.shouldPageForward.collect { | ||||||
|                     if (it) { |                     if (it) { | ||||||
|                         pageForward() |                         pageForward() | ||||||
|                         homeViewModel.setShouldPageForward(false) |                         homeViewModel.setShouldPageForward(false) | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         binding.viewPager2.apply { |         binding.viewPager2.apply { | ||||||
|             adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages) |             adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages) | ||||||
|   | |||||||
| @@ -3,28 +3,28 @@ | |||||||
|  |  | ||||||
| package org.yuzu.yuzu_emu.model | package org.yuzu.yuzu_emu.model | ||||||
|  |  | ||||||
| import androidx.lifecycle.LiveData |  | ||||||
| import androidx.lifecycle.MutableLiveData |  | ||||||
| import androidx.lifecycle.ViewModel | import androidx.lifecycle.ViewModel | ||||||
|  | import kotlinx.coroutines.flow.MutableStateFlow | ||||||
|  | import kotlinx.coroutines.flow.StateFlow | ||||||
|  |  | ||||||
| class EmulationViewModel : ViewModel() { | class EmulationViewModel : ViewModel() { | ||||||
|     private val _emulationStarted = MutableLiveData(false) |     val emulationStarted: StateFlow<Boolean> get() = _emulationStarted | ||||||
|     val emulationStarted: LiveData<Boolean> get() = _emulationStarted |     private val _emulationStarted = MutableStateFlow(false) | ||||||
|  |  | ||||||
|     private val _isEmulationStopping = MutableLiveData(false) |     val isEmulationStopping: StateFlow<Boolean> get() = _isEmulationStopping | ||||||
|     val isEmulationStopping: LiveData<Boolean> get() = _isEmulationStopping |     private val _isEmulationStopping = MutableStateFlow(false) | ||||||
|  |  | ||||||
|     private val _shaderProgress = MutableLiveData(0) |     val shaderProgress: StateFlow<Int> get() = _shaderProgress | ||||||
|     val shaderProgress: LiveData<Int> get() = _shaderProgress |     private val _shaderProgress = MutableStateFlow(0) | ||||||
|  |  | ||||||
|     private val _totalShaders = MutableLiveData(0) |     val totalShaders: StateFlow<Int> get() = _totalShaders | ||||||
|     val totalShaders: LiveData<Int> get() = _totalShaders |     private val _totalShaders = MutableStateFlow(0) | ||||||
|  |  | ||||||
|     private val _shaderMessage = MutableLiveData("") |     val shaderMessage: StateFlow<String> get() = _shaderMessage | ||||||
|     val shaderMessage: LiveData<String> get() = _shaderMessage |     private val _shaderMessage = MutableStateFlow("") | ||||||
|  |  | ||||||
|     fun setEmulationStarted(started: Boolean) { |     fun setEmulationStarted(started: Boolean) { | ||||||
|         _emulationStarted.postValue(started) |         _emulationStarted.value = started | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun setIsEmulationStopping(value: Boolean) { |     fun setIsEmulationStopping(value: Boolean) { | ||||||
| @@ -50,10 +50,18 @@ class EmulationViewModel : ViewModel() { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun clear() { |     fun clear() { | ||||||
|         _emulationStarted.value = false |         setEmulationStarted(false) | ||||||
|         _isEmulationStopping.value = false |         setIsEmulationStopping(false) | ||||||
|         _shaderProgress.value = 0 |         setShaderProgress(0) | ||||||
|         _totalShaders.value = 0 |         setTotalShaders(0) | ||||||
|         _shaderMessage.value = "" |         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" | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,13 +5,13 @@ package org.yuzu.yuzu_emu.model | |||||||
|  |  | ||||||
| import android.net.Uri | import android.net.Uri | ||||||
| import androidx.documentfile.provider.DocumentFile | import androidx.documentfile.provider.DocumentFile | ||||||
| import androidx.lifecycle.LiveData |  | ||||||
| import androidx.lifecycle.MutableLiveData |  | ||||||
| import androidx.lifecycle.ViewModel | import androidx.lifecycle.ViewModel | ||||||
| import androidx.lifecycle.viewModelScope | import androidx.lifecycle.viewModelScope | ||||||
| import androidx.preference.PreferenceManager | import androidx.preference.PreferenceManager | ||||||
| import java.util.Locale | import java.util.Locale | ||||||
| import kotlinx.coroutines.Dispatchers | import kotlinx.coroutines.Dispatchers | ||||||
|  | import kotlinx.coroutines.flow.MutableStateFlow | ||||||
|  | import kotlinx.coroutines.flow.StateFlow | ||||||
| import kotlinx.coroutines.launch | import kotlinx.coroutines.launch | ||||||
| import kotlinx.coroutines.withContext | import kotlinx.coroutines.withContext | ||||||
| import kotlinx.serialization.ExperimentalSerializationApi | import kotlinx.serialization.ExperimentalSerializationApi | ||||||
| @@ -24,23 +24,23 @@ import org.yuzu.yuzu_emu.utils.GameHelper | |||||||
|  |  | ||||||
| @OptIn(ExperimentalSerializationApi::class) | @OptIn(ExperimentalSerializationApi::class) | ||||||
| class GamesViewModel : ViewModel() { | class GamesViewModel : ViewModel() { | ||||||
|     private val _games = MutableLiveData<List<Game>>(emptyList()) |     val games: StateFlow<List<Game>> get() = _games | ||||||
|     val games: LiveData<List<Game>> get() = _games |     private val _games = MutableStateFlow(emptyList<Game>()) | ||||||
|  |  | ||||||
|     private val _searchedGames = MutableLiveData<List<Game>>(emptyList()) |     val searchedGames: StateFlow<List<Game>> get() = _searchedGames | ||||||
|     val searchedGames: LiveData<List<Game>> get() = _searchedGames |     private val _searchedGames = MutableStateFlow(emptyList<Game>()) | ||||||
|  |  | ||||||
|     private val _isReloading = MutableLiveData(false) |     val isReloading: StateFlow<Boolean> get() = _isReloading | ||||||
|     val isReloading: LiveData<Boolean> get() = _isReloading |     private val _isReloading = MutableStateFlow(false) | ||||||
|  |  | ||||||
|     private val _shouldSwapData = MutableLiveData(false) |     val shouldSwapData: StateFlow<Boolean> get() = _shouldSwapData | ||||||
|     val shouldSwapData: LiveData<Boolean> get() = _shouldSwapData |     private val _shouldSwapData = MutableStateFlow(false) | ||||||
|  |  | ||||||
|     private val _shouldScrollToTop = MutableLiveData(false) |     val shouldScrollToTop: StateFlow<Boolean> get() = _shouldScrollToTop | ||||||
|     val shouldScrollToTop: LiveData<Boolean> get() = _shouldScrollToTop |     private val _shouldScrollToTop = MutableStateFlow(false) | ||||||
|  |  | ||||||
|     private val _searchFocused = MutableLiveData(false) |     val searchFocused: StateFlow<Boolean> get() = _searchFocused | ||||||
|     val searchFocused: LiveData<Boolean> get() = _searchFocused |     private val _searchFocused = MutableStateFlow(false) | ||||||
|  |  | ||||||
|     init { |     init { | ||||||
|         // Ensure keys are loaded so that ROM metadata can be decrypted. |         // 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>) { |     fun setSearchedGames(games: List<Game>) { | ||||||
|         _searchedGames.postValue(games) |         _searchedGames.value = games | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun setShouldSwapData(shouldSwap: Boolean) { |     fun setShouldSwapData(shouldSwap: Boolean) { | ||||||
|         _shouldSwapData.postValue(shouldSwap) |         _shouldSwapData.value = shouldSwap | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun setShouldScrollToTop(shouldScroll: Boolean) { |     fun setShouldScrollToTop(shouldScroll: Boolean) { | ||||||
|         _shouldScrollToTop.postValue(shouldScroll) |         _shouldScrollToTop.value = shouldScroll | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun setSearchFocused(searchFocused: Boolean) { |     fun setSearchFocused(searchFocused: Boolean) { | ||||||
|         _searchFocused.postValue(searchFocused) |         _searchFocused.value = searchFocused | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun reloadGames(directoryChanged: Boolean) { |     fun reloadGames(directoryChanged: Boolean) { | ||||||
|         if (isReloading.value == true) { |         if (isReloading.value) { | ||||||
|             return |             return | ||||||
|         } |         } | ||||||
|         _isReloading.postValue(true) |         _isReloading.value = true | ||||||
|  |  | ||||||
|         viewModelScope.launch { |         viewModelScope.launch { | ||||||
|             withContext(Dispatchers.IO) { |             withContext(Dispatchers.IO) { | ||||||
|                 NativeLibrary.resetRomMetadata() |                 NativeLibrary.resetRomMetadata() | ||||||
|                 setGames(GameHelper.getGames()) |                 setGames(GameHelper.getGames()) | ||||||
|                 _isReloading.postValue(false) |                 _isReloading.value = false | ||||||
|  |  | ||||||
|                 if (directoryChanged) { |                 if (directoryChanged) { | ||||||
|                     setShouldSwapData(true) |                     setShouldSwapData(true) | ||||||
|   | |||||||
| @@ -3,8 +3,8 @@ | |||||||
|  |  | ||||||
| package org.yuzu.yuzu_emu.model | package org.yuzu.yuzu_emu.model | ||||||
|  |  | ||||||
| import androidx.lifecycle.LiveData | import kotlinx.coroutines.flow.MutableStateFlow | ||||||
| import androidx.lifecycle.MutableLiveData | import kotlinx.coroutines.flow.StateFlow | ||||||
|  |  | ||||||
| data class HomeSetting( | data class HomeSetting( | ||||||
|     val titleId: Int, |     val titleId: Int, | ||||||
| @@ -14,5 +14,5 @@ data class HomeSetting( | |||||||
|     val isEnabled: () -> Boolean = { true }, |     val isEnabled: () -> Boolean = { true }, | ||||||
|     val disabledTitleId: Int = 0, |     val disabledTitleId: Int = 0, | ||||||
|     val disabledMessageId: Int = 0, |     val disabledMessageId: Int = 0, | ||||||
|     val details: LiveData<String> = MutableLiveData("") |     val details: StateFlow<String> = MutableStateFlow("") | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -5,47 +5,43 @@ package org.yuzu.yuzu_emu.model | |||||||
|  |  | ||||||
| import android.net.Uri | import android.net.Uri | ||||||
| import androidx.fragment.app.FragmentActivity | import androidx.fragment.app.FragmentActivity | ||||||
| import androidx.lifecycle.LiveData |  | ||||||
| import androidx.lifecycle.MutableLiveData |  | ||||||
| import androidx.lifecycle.ViewModel | import androidx.lifecycle.ViewModel | ||||||
| import androidx.lifecycle.ViewModelProvider | import androidx.lifecycle.ViewModelProvider | ||||||
| import androidx.preference.PreferenceManager | 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.YuzuApplication | ||||||
| import org.yuzu.yuzu_emu.utils.GameHelper | import org.yuzu.yuzu_emu.utils.GameHelper | ||||||
|  |  | ||||||
| class HomeViewModel : ViewModel() { | class HomeViewModel : ViewModel() { | ||||||
|     private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>() |     val navigationVisible: StateFlow<Pair<Boolean, Boolean>> get() = _navigationVisible | ||||||
|     val navigationVisible: LiveData<Pair<Boolean, Boolean>> get() = _navigationVisible |     private val _navigationVisible = MutableStateFlow(Pair(false, false)) | ||||||
|  |  | ||||||
|     private val _statusBarShadeVisible = MutableLiveData(true) |     val statusBarShadeVisible: StateFlow<Boolean> get() = _statusBarShadeVisible | ||||||
|     val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible |     private val _statusBarShadeVisible = MutableStateFlow(true) | ||||||
|  |  | ||||||
|     private val _shouldPageForward = MutableLiveData(false) |     val shouldPageForward: StateFlow<Boolean> get() = _shouldPageForward | ||||||
|     val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward |     private val _shouldPageForward = MutableStateFlow(false) | ||||||
|  |  | ||||||
|     private val _gamesDir = MutableLiveData( |     val gamesDir: StateFlow<String> get() = _gamesDir | ||||||
|  |     private val _gamesDir = MutableStateFlow( | ||||||
|         Uri.parse( |         Uri.parse( | ||||||
|             PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) |             PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | ||||||
|                 .getString(GameHelper.KEY_GAME_PATH, "") |                 .getString(GameHelper.KEY_GAME_PATH, "") | ||||||
|         ).path ?: "" |         ).path ?: "" | ||||||
|     ) |     ) | ||||||
|     val gamesDir: LiveData<String> get() = _gamesDir |  | ||||||
|  |  | ||||||
|     var navigatedToSetup = false |     var navigatedToSetup = false | ||||||
|  |  | ||||||
|     init { |  | ||||||
|         _navigationVisible.value = Pair(false, false) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun setNavigationVisibility(visible: Boolean, animated: Boolean) { |     fun setNavigationVisibility(visible: Boolean, animated: Boolean) { | ||||||
|         if (_navigationVisible.value?.first == visible) { |         if (navigationVisible.value.first == visible) { | ||||||
|             return |             return | ||||||
|         } |         } | ||||||
|         _navigationVisible.value = Pair(visible, animated) |         _navigationVisible.value = Pair(visible, animated) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun setStatusBarShadeVisibility(visible: Boolean) { |     fun setStatusBarShadeVisibility(visible: Boolean) { | ||||||
|         if (_statusBarShadeVisible.value == visible) { |         if (statusBarShadeVisible.value == visible) { | ||||||
|             return |             return | ||||||
|         } |         } | ||||||
|         _statusBarShadeVisible.value = visible |         _statusBarShadeVisible.value = visible | ||||||
|   | |||||||
| @@ -3,48 +3,43 @@ | |||||||
|  |  | ||||||
| package org.yuzu.yuzu_emu.model | package org.yuzu.yuzu_emu.model | ||||||
|  |  | ||||||
| import androidx.lifecycle.LiveData |  | ||||||
| import androidx.lifecycle.MutableLiveData |  | ||||||
| import androidx.lifecycle.SavedStateHandle |  | ||||||
| import androidx.lifecycle.ViewModel | 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.R | ||||||
| import org.yuzu.yuzu_emu.YuzuApplication | import org.yuzu.yuzu_emu.YuzuApplication | ||||||
| import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | 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 game: Game? = null | ||||||
|  |  | ||||||
|     var shouldSave = false |     var shouldSave = false | ||||||
|  |  | ||||||
|     var clickedItem: SettingsItem? = null |     var clickedItem: SettingsItem? = null | ||||||
|  |  | ||||||
|     private val _toolbarTitle = MutableLiveData("") |     val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate | ||||||
|     val toolbarTitle: LiveData<String> get() = _toolbarTitle |     private val _shouldRecreate = MutableStateFlow(false) | ||||||
|  |  | ||||||
|     private val _shouldRecreate = MutableLiveData(false) |     val shouldNavigateBack: StateFlow<Boolean> get() = _shouldNavigateBack | ||||||
|     val shouldRecreate: LiveData<Boolean> get() = _shouldRecreate |     private val _shouldNavigateBack = MutableStateFlow(false) | ||||||
|  |  | ||||||
|     private val _shouldNavigateBack = MutableLiveData(false) |     val shouldShowResetSettingsDialog: StateFlow<Boolean> get() = _shouldShowResetSettingsDialog | ||||||
|     val shouldNavigateBack: LiveData<Boolean> get() = _shouldNavigateBack |     private val _shouldShowResetSettingsDialog = MutableStateFlow(false) | ||||||
|  |  | ||||||
|     private val _shouldShowResetSettingsDialog = MutableLiveData(false) |     val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList | ||||||
|     val shouldShowResetSettingsDialog: LiveData<Boolean> get() = _shouldShowResetSettingsDialog |     private val _shouldReloadSettingsList = MutableStateFlow(false) | ||||||
|  |  | ||||||
|     private val _shouldReloadSettingsList = MutableLiveData(false) |     val isUsingSearch: StateFlow<Boolean> get() = _isUsingSearch | ||||||
|     val shouldReloadSettingsList: LiveData<Boolean> get() = _shouldReloadSettingsList |     private val _isUsingSearch = MutableStateFlow(false) | ||||||
|  |  | ||||||
|     private val _isUsingSearch = MutableLiveData(false) |     val sliderProgress: StateFlow<Int> get() = _sliderProgress | ||||||
|     val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch |     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: StateFlow<Int> get() = _adapterItemChanged | ||||||
|  |     private val _adapterItemChanged = MutableStateFlow(-1) | ||||||
|     val adapterItemChanged = savedStateHandle.getStateFlow(KEY_ADAPTER_ITEM_CHANGED, -1) |  | ||||||
|  |  | ||||||
|     fun setToolbarTitle(value: String) { |  | ||||||
|         _toolbarTitle.value = value |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun setShouldRecreate(value: Boolean) { |     fun setShouldRecreate(value: Boolean) { | ||||||
|         _shouldRecreate.value = value |         _shouldRecreate.value = value | ||||||
| @@ -67,8 +62,8 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun setSliderTextValue(value: Float, units: String) { |     fun setSliderTextValue(value: Float, units: String) { | ||||||
|         savedStateHandle[KEY_SLIDER_PROGRESS] = value |         _sliderProgress.value = value.toInt() | ||||||
|         savedStateHandle[KEY_SLIDER_TEXT_VALUE] = String.format( |         _sliderTextValue.value = String.format( | ||||||
|             YuzuApplication.appContext.getString(R.string.value_with_units), |             YuzuApplication.appContext.getString(R.string.value_with_units), | ||||||
|             value.toInt().toString(), |             value.toInt().toString(), | ||||||
|             units |             units | ||||||
| @@ -76,21 +71,15 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun setSliderProgress(value: Float) { |     fun setSliderProgress(value: Float) { | ||||||
|         savedStateHandle[KEY_SLIDER_PROGRESS] = value |         _sliderProgress.value = value.toInt() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun setAdapterItemChanged(value: Int) { |     fun setAdapterItemChanged(value: Int) { | ||||||
|         savedStateHandle[KEY_ADAPTER_ITEM_CHANGED] = value |         _adapterItemChanged.value = value | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun clear() { |     fun clear() { | ||||||
|         game = null |         game = null | ||||||
|         shouldSave = false |         shouldSave = false | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     companion object { |  | ||||||
|         const val KEY_SLIDER_TEXT_VALUE = "SliderTextValue" |  | ||||||
|         const val KEY_SLIDER_PROGRESS = "SliderProgress" |  | ||||||
|         const val KEY_ADAPTER_ITEM_CHANGED = "AdapterItemChanged" |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,29 +3,25 @@ | |||||||
|  |  | ||||||
| package org.yuzu.yuzu_emu.model | package org.yuzu.yuzu_emu.model | ||||||
|  |  | ||||||
| import androidx.lifecycle.LiveData |  | ||||||
| import androidx.lifecycle.MutableLiveData |  | ||||||
| import androidx.lifecycle.ViewModel | import androidx.lifecycle.ViewModel | ||||||
| import androidx.lifecycle.viewModelScope | import androidx.lifecycle.viewModelScope | ||||||
| import kotlinx.coroutines.Dispatchers | import kotlinx.coroutines.Dispatchers | ||||||
|  | import kotlinx.coroutines.flow.MutableStateFlow | ||||||
|  | import kotlinx.coroutines.flow.StateFlow | ||||||
| import kotlinx.coroutines.launch | import kotlinx.coroutines.launch | ||||||
|  |  | ||||||
| class TaskViewModel : ViewModel() { | class TaskViewModel : ViewModel() { | ||||||
|     private val _result = MutableLiveData<Any>() |     val result: StateFlow<Any> get() = _result | ||||||
|     val result: LiveData<Any> = _result |     private val _result = MutableStateFlow(Any()) | ||||||
|  |  | ||||||
|     private val _isComplete = MutableLiveData<Boolean>() |     val isComplete: StateFlow<Boolean> get() = _isComplete | ||||||
|     val isComplete: LiveData<Boolean> = _isComplete |     private val _isComplete = MutableStateFlow(false) | ||||||
|  |  | ||||||
|     private val _isRunning = MutableLiveData<Boolean>() |     val isRunning: StateFlow<Boolean> get() = _isRunning | ||||||
|     val isRunning: LiveData<Boolean> = _isRunning |     private val _isRunning = MutableStateFlow(false) | ||||||
|  |  | ||||||
|     lateinit var task: () -> Any |     lateinit var task: () -> Any | ||||||
|  |  | ||||||
|     init { |  | ||||||
|         clear() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun clear() { |     fun clear() { | ||||||
|         _result.value = Any() |         _result.value = Any() | ||||||
|         _isComplete.value = false |         _isComplete.value = false | ||||||
| @@ -33,15 +29,16 @@ class TaskViewModel : ViewModel() { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun runTask() { |     fun runTask() { | ||||||
|         if (_isRunning.value == true) { |         if (isRunning.value) { | ||||||
|             return |             return | ||||||
|         } |         } | ||||||
|         _isRunning.value = true |         _isRunning.value = true | ||||||
|  |  | ||||||
|         viewModelScope.launch(Dispatchers.IO) { |         viewModelScope.launch(Dispatchers.IO) { | ||||||
|             val res = task() |             val res = task() | ||||||
|             _result.postValue(res) |             _result.value = res | ||||||
|             _isComplete.postValue(true) |             _isComplete.value = true | ||||||
|  |             _isRunning.value = false | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
|  |  | ||||||
| package org.yuzu.yuzu_emu.ui | package org.yuzu.yuzu_emu.ui | ||||||
|  |  | ||||||
|  | import android.annotation.SuppressLint | ||||||
| import android.os.Bundle | import android.os.Bundle | ||||||
| import android.view.LayoutInflater | import android.view.LayoutInflater | ||||||
| import android.view.View | import android.view.View | ||||||
| @@ -14,8 +15,12 @@ import androidx.core.view.WindowInsetsCompat | |||||||
| import androidx.core.view.updatePadding | import androidx.core.view.updatePadding | ||||||
| import androidx.fragment.app.Fragment | import androidx.fragment.app.Fragment | ||||||
| import androidx.fragment.app.activityViewModels | import androidx.fragment.app.activityViewModels | ||||||
|  | import androidx.lifecycle.Lifecycle | ||||||
|  | import androidx.lifecycle.lifecycleScope | ||||||
|  | import androidx.lifecycle.repeatOnLifecycle | ||||||
| import com.google.android.material.color.MaterialColors | import com.google.android.material.color.MaterialColors | ||||||
| import com.google.android.material.transition.MaterialFadeThrough | import com.google.android.material.transition.MaterialFadeThrough | ||||||
|  | import kotlinx.coroutines.launch | ||||||
| import org.yuzu.yuzu_emu.R | import org.yuzu.yuzu_emu.R | ||||||
| import org.yuzu.yuzu_emu.adapters.GameAdapter | import org.yuzu.yuzu_emu.adapters.GameAdapter | ||||||
| import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding | import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding | ||||||
| @@ -44,6 +49,8 @@ class GamesFragment : Fragment() { | |||||||
|         return binding.root |         return binding.root | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // This is using the correct scope, lint is just acting up | ||||||
|  |     @SuppressLint("UnsafeRepeatOnLifecycleDetector") | ||||||
|     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||||||
|         homeViewModel.setNavigationVisibility(visible = true, animated = false) |         homeViewModel.setNavigationVisibility(visible = true, animated = false) | ||||||
|  |  | ||||||
| @@ -80,16 +87,19 @@ class GamesFragment : Fragment() { | |||||||
|                 if (_binding == null) { |                 if (_binding == null) { | ||||||
|                     return@post |                     return@post | ||||||
|                 } |                 } | ||||||
|                 binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value!! |                 binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         gamesViewModel.apply { |         viewLifecycleOwner.lifecycleScope.apply { | ||||||
|             // Watch for when we get updates to any of our games lists |             launch { | ||||||
|             isReloading.observe(viewLifecycleOwner) { isReloading -> |                 repeatOnLifecycle(Lifecycle.State.CREATED) { | ||||||
|                 binding.swipeRefresh.isRefreshing = isReloading |                     gamesViewModel.isReloading.collect { binding.swipeRefresh.isRefreshing = it } | ||||||
|                 } |                 } | ||||||
|             games.observe(viewLifecycleOwner) { |             } | ||||||
|  |             launch { | ||||||
|  |                 repeatOnLifecycle(Lifecycle.State.CREATED) { | ||||||
|  |                     gamesViewModel.games.collect { | ||||||
|                         (binding.gridGames.adapter as GameAdapter).submitList(it) |                         (binding.gridGames.adapter as GameAdapter).submitList(it) | ||||||
|                         if (it.isEmpty()) { |                         if (it.isEmpty()) { | ||||||
|                             binding.noticeText.visibility = View.VISIBLE |                             binding.noticeText.visibility = View.VISIBLE | ||||||
| @@ -97,23 +107,31 @@ class GamesFragment : Fragment() { | |||||||
|                             binding.noticeText.visibility = View.GONE |                             binding.noticeText.visibility = View.GONE | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|             shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData -> |                 } | ||||||
|                 if (shouldSwapData) { |             } | ||||||
|  |             launch { | ||||||
|  |                 repeatOnLifecycle(Lifecycle.State.CREATED) { | ||||||
|  |                     gamesViewModel.shouldSwapData.collect { | ||||||
|  |                         if (it) { | ||||||
|                             (binding.gridGames.adapter as GameAdapter).submitList( |                             (binding.gridGames.adapter as GameAdapter).submitList( | ||||||
|                         gamesViewModel.games.value!! |                                 gamesViewModel.games.value | ||||||
|                             ) |                             ) | ||||||
|                             gamesViewModel.setShouldSwapData(false) |                             gamesViewModel.setShouldSwapData(false) | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  |                 } | ||||||
|             // Check if the user reselected the games menu item and then scroll to top of the list |             } | ||||||
|             shouldScrollToTop.observe(viewLifecycleOwner) { shouldScroll -> |             launch { | ||||||
|                 if (shouldScroll) { |                 repeatOnLifecycle(Lifecycle.State.CREATED) { | ||||||
|  |                     gamesViewModel.shouldScrollToTop.collect { | ||||||
|  |                         if (it) { | ||||||
|                             scrollToTop() |                             scrollToTop() | ||||||
|                             gamesViewModel.setShouldScrollToTop(false) |                             gamesViewModel.setShouldScrollToTop(false) | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         setInsets() |         setInsets() | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -19,7 +19,9 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen | |||||||
| import androidx.core.view.ViewCompat | import androidx.core.view.ViewCompat | ||||||
| import androidx.core.view.WindowCompat | import androidx.core.view.WindowCompat | ||||||
| import androidx.core.view.WindowInsetsCompat | import androidx.core.view.WindowInsetsCompat | ||||||
|  | import androidx.lifecycle.Lifecycle | ||||||
| import androidx.lifecycle.lifecycleScope | import androidx.lifecycle.lifecycleScope | ||||||
|  | import androidx.lifecycle.repeatOnLifecycle | ||||||
| import androidx.navigation.NavController | import androidx.navigation.NavController | ||||||
| import androidx.navigation.fragment.NavHostFragment | import androidx.navigation.fragment.NavHostFragment | ||||||
| import androidx.navigation.ui.setupWithNavController | import androidx.navigation.ui.setupWithNavController | ||||||
| @@ -115,16 +117,22 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Prevents navigation from being drawn for a short time on recreation if set to hidden |         // 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.navigationView.visibility = View.INVISIBLE | ||||||
|             binding.statusBarShade.visibility = View.INVISIBLE |             binding.statusBarShade.visibility = View.INVISIBLE | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         homeViewModel.navigationVisible.observe(this) { |         lifecycleScope.apply { | ||||||
|             showNavigation(it.first, it.second) |             launch { | ||||||
|  |                 repeatOnLifecycle(Lifecycle.State.CREATED) { | ||||||
|  |                     homeViewModel.navigationVisible.collect { showNavigation(it.first, it.second) } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             launch { | ||||||
|  |                 repeatOnLifecycle(Lifecycle.State.CREATED) { | ||||||
|  |                     homeViewModel.statusBarShadeVisible.collect { showStatusBarShade(it) } | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         homeViewModel.statusBarShadeVisible.observe(this) { visible -> |  | ||||||
|             showStatusBarShade(visible) |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Dismiss previous notifications (should not happen unless a crash occurred) |         // Dismiss previous notifications (should not happen unless a crash occurred) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user