summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt53
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt43
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt110
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt32
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt46
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt16
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt16
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt44
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt44
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt28
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt57
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt27
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt64
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt20
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
10import androidx.appcompat.app.AppCompatActivity 10import androidx.appcompat.app.AppCompatActivity
11import androidx.core.content.ContextCompat 11import androidx.core.content.ContextCompat
12import androidx.core.content.res.ResourcesCompat 12import androidx.core.content.res.ResourcesCompat
13import androidx.lifecycle.Lifecycle
13import androidx.lifecycle.LifecycleOwner 14import androidx.lifecycle.LifecycleOwner
15import androidx.lifecycle.lifecycleScope
16import androidx.lifecycle.repeatOnLifecycle
14import androidx.recyclerview.widget.RecyclerView 17import androidx.recyclerview.widget.RecyclerView
18import kotlinx.coroutines.launch
15import org.yuzu.yuzu_emu.R 19import org.yuzu.yuzu_emu.R
16import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding 20import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
17import org.yuzu.yuzu_emu.fragments.MessageDialogFragment 21import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
@@ -86,7 +90,11 @@ class HomeSettingAdapter(
86 binding.optionIcon.alpha = 0.5f 90 binding.optionIcon.alpha = 0.5f
87 } 91 }
88 92
89 option.details.observe(viewLifecycle) { updateOptionDetails(it) } 93 viewLifecycle.lifecycleScope.launch {
94 viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
95 option.details.collect { updateOptionDetails(it) }
96 }
97 }
90 binding.optionDetail.postDelayed( 98 binding.optionDetail.postDelayed(
91 { 99 {
92 binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE 100 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
13import androidx.core.view.ViewCompat 13import androidx.core.view.ViewCompat
14import androidx.core.view.WindowCompat 14import androidx.core.view.WindowCompat
15import androidx.core.view.WindowInsetsCompat 15import androidx.core.view.WindowInsetsCompat
16import androidx.lifecycle.Lifecycle
17import androidx.lifecycle.lifecycleScope
18import androidx.lifecycle.repeatOnLifecycle
16import androidx.navigation.fragment.NavHostFragment 19import androidx.navigation.fragment.NavHostFragment
17import androidx.navigation.navArgs 20import androidx.navigation.navArgs
18import com.google.android.material.color.MaterialColors 21import com.google.android.material.color.MaterialColors
22import kotlinx.coroutines.flow.collectLatest
23import kotlinx.coroutines.launch
19import java.io.IOException 24import java.io.IOException
20import org.yuzu.yuzu_emu.R 25import org.yuzu.yuzu_emu.R
21import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding 26import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
@@ -66,25 +71,39 @@ class SettingsActivity : AppCompatActivity() {
66 ) 71 )
67 } 72 }
68 73
69 settingsViewModel.shouldRecreate.observe(this) { 74 lifecycleScope.apply {
70 if (it) { 75 launch {
71 settingsViewModel.setShouldRecreate(false) 76 repeatOnLifecycle(Lifecycle.State.CREATED) {
72 recreate() 77 settingsViewModel.shouldRecreate.collectLatest {
78 if (it) {
79 settingsViewModel.setShouldRecreate(false)
80 recreate()
81 }
82 }
83 }
73 } 84 }
74 } 85 launch {
75 settingsViewModel.shouldNavigateBack.observe(this) { 86 repeatOnLifecycle(Lifecycle.State.CREATED) {
76 if (it) { 87 settingsViewModel.shouldNavigateBack.collectLatest {
77 settingsViewModel.setShouldNavigateBack(false) 88 if (it) {
78 navigateBack() 89 settingsViewModel.setShouldNavigateBack(false)
90 navigateBack()
91 }
92 }
93 }
79 } 94 }
80 } 95 launch {
81 settingsViewModel.shouldShowResetSettingsDialog.observe(this) { 96 repeatOnLifecycle(Lifecycle.State.CREATED) {
82 if (it) { 97 settingsViewModel.shouldShowResetSettingsDialog.collectLatest {
83 settingsViewModel.setShouldShowResetSettingsDialog(false) 98 if (it) {
84 ResetSettingsDialogFragment().show( 99 settingsViewModel.setShouldShowResetSettingsDialog(false)
85 supportFragmentManager, 100 ResetSettingsDialogFragment().show(
86 ResetSettingsDialogFragment.TAG 101 supportFragmentManager,
87 ) 102 ResetSettingsDialogFragment.TAG
103 )
104 }
105 }
106 }
88 } 107 }
89 } 108 }
90 109
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
13import androidx.core.view.updatePadding 13import androidx.core.view.updatePadding
14import androidx.fragment.app.Fragment 14import androidx.fragment.app.Fragment
15import androidx.fragment.app.activityViewModels 15import androidx.fragment.app.activityViewModels
16import androidx.lifecycle.Lifecycle
17import androidx.lifecycle.lifecycleScope
18import androidx.lifecycle.repeatOnLifecycle
16import androidx.navigation.findNavController 19import androidx.navigation.findNavController
17import androidx.navigation.fragment.navArgs 20import androidx.navigation.fragment.navArgs
18import androidx.recyclerview.widget.LinearLayoutManager 21import androidx.recyclerview.widget.LinearLayoutManager
19import com.google.android.material.divider.MaterialDividerItemDecoration 22import com.google.android.material.divider.MaterialDividerItemDecoration
20import com.google.android.material.transition.MaterialSharedAxis 23import com.google.android.material.transition.MaterialSharedAxis
24import kotlinx.coroutines.launch
21import org.yuzu.yuzu_emu.R 25import org.yuzu.yuzu_emu.R
22import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding 26import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
23import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile 27import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
@@ -51,6 +55,8 @@ class SettingsFragment : Fragment() {
51 return binding.root 55 return binding.root
52 } 56 }
53 57
58 // This is using the correct scope, lint is just acting up
59 @SuppressLint("UnsafeRepeatOnLifecycleDetector")
54 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 60 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
55 settingsAdapter = SettingsAdapter(this, requireContext()) 61 settingsAdapter = SettingsAdapter(this, requireContext())
56 presenter = SettingsFragmentPresenter( 62 presenter = SettingsFragmentPresenter(
@@ -75,24 +81,27 @@ class SettingsFragment : Fragment() {
75 settingsViewModel.setShouldNavigateBack(true) 81 settingsViewModel.setShouldNavigateBack(true)
76 } 82 }
77 83
78 settingsViewModel.toolbarTitle.observe(viewLifecycleOwner) { 84 viewLifecycleOwner.lifecycleScope.apply {
79 if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it 85 launch {
80 } 86 repeatOnLifecycle(Lifecycle.State.CREATED) {
81 87 settingsViewModel.shouldReloadSettingsList.collectLatest {
82 settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) { 88 if (it) {
83 if (it) { 89 settingsViewModel.setShouldReloadSettingsList(false)
84 settingsViewModel.setShouldReloadSettingsList(false) 90 presenter.loadSettingsList()
85 presenter.loadSettingsList() 91 }
92 }
93 }
86 } 94 }
87 } 95 launch {
88 96 settingsViewModel.isUsingSearch.collectLatest {
89 settingsViewModel.isUsingSearch.observe(viewLifecycleOwner) { 97 if (it) {
90 if (it) { 98 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
91 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) 99 exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
92 exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) 100 } else {
93 } else { 101 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
94 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) 102 exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
95 exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) 103 }
104 }
96 } 105 }
97 } 106 }
98 107
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
39import com.google.android.material.dialog.MaterialAlertDialogBuilder 39import com.google.android.material.dialog.MaterialAlertDialogBuilder
40import com.google.android.material.slider.Slider 40import com.google.android.material.slider.Slider
41import kotlinx.coroutines.Dispatchers 41import kotlinx.coroutines.Dispatchers
42import kotlinx.coroutines.flow.collectLatest
42import kotlinx.coroutines.launch 43import kotlinx.coroutines.launch
43import org.yuzu.yuzu_emu.HomeNavigationDirections 44import org.yuzu.yuzu_emu.HomeNavigationDirections
44import org.yuzu.yuzu_emu.NativeLibrary 45import org.yuzu.yuzu_emu.NativeLibrary
@@ -129,6 +130,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
129 return binding.root 130 return binding.root
130 } 131 }
131 132
133 // This is using the correct scope, lint is just acting up
134 @SuppressLint("UnsafeRepeatOnLifecycleDetector")
132 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 135 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
133 binding.surfaceEmulation.holder.addCallback(this) 136 binding.surfaceEmulation.holder.addCallback(this)
134 binding.showFpsText.setTextColor(Color.YELLOW) 137 binding.showFpsText.setTextColor(Color.YELLOW)
@@ -205,59 +208,80 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
205 } 208 }
206 ) 209 )
207 210
208 viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
209 lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
210 WindowInfoTracker.getOrCreate(requireContext())
211 .windowLayoutInfo(requireActivity())
212 .collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) }
213 }
214 }
215
216 GameIconUtils.loadGameIcon(game, binding.loadingImage) 211 GameIconUtils.loadGameIcon(game, binding.loadingImage)
217 binding.loadingTitle.text = game.title 212 binding.loadingTitle.text = game.title
218 binding.loadingTitle.isSelected = true 213 binding.loadingTitle.isSelected = true
219 binding.loadingText.isSelected = true 214 binding.loadingText.isSelected = true
220 215
221 emulationViewModel.shaderProgress.observe(viewLifecycleOwner) { 216 viewLifecycleOwner.lifecycleScope.apply {
222 if (it > 0 && it != emulationViewModel.totalShaders.value!!) { 217 launch {
223 binding.loadingProgressIndicator.isIndeterminate = false 218 repeatOnLifecycle(Lifecycle.State.STARTED) {
224 219 WindowInfoTracker.getOrCreate(requireContext())
225 if (it < binding.loadingProgressIndicator.max) { 220 .windowLayoutInfo(requireActivity())
226 binding.loadingProgressIndicator.progress = it 221 .collect {
222 updateFoldableLayout(requireActivity() as EmulationActivity, it)
223 }
227 } 224 }
228 } 225 }
226 launch {
227 repeatOnLifecycle(Lifecycle.State.CREATED) {
228 emulationViewModel.shaderProgress.collectLatest {
229 if (it > 0 && it != emulationViewModel.totalShaders.value) {
230 binding.loadingProgressIndicator.isIndeterminate = false
231
232 if (it < binding.loadingProgressIndicator.max) {
233 binding.loadingProgressIndicator.progress = it
234 }
235 }
229 236
230 if (it == emulationViewModel.totalShaders.value!!) { 237 if (it == emulationViewModel.totalShaders.value) {
231 binding.loadingText.setText(R.string.loading) 238 binding.loadingText.setText(R.string.loading)
232 binding.loadingProgressIndicator.isIndeterminate = true 239 binding.loadingProgressIndicator.isIndeterminate = true
240 }
241 }
242 }
233 } 243 }
234 } 244 launch {
235 emulationViewModel.totalShaders.observe(viewLifecycleOwner) { 245 repeatOnLifecycle(Lifecycle.State.CREATED) {
236 binding.loadingProgressIndicator.max = it 246 emulationViewModel.totalShaders.collectLatest {
237 } 247 binding.loadingProgressIndicator.max = it
238 emulationViewModel.shaderMessage.observe(viewLifecycleOwner) { 248 }
239 if (it.isNotEmpty()) { 249 }
240 binding.loadingText.text = it
241 } 250 }
242 } 251 launch {
243 252 repeatOnLifecycle(Lifecycle.State.CREATED) {
244 emulationViewModel.emulationStarted.observe(viewLifecycleOwner) { started -> 253 emulationViewModel.shaderMessage.collectLatest {
245 if (started) { 254 if (it.isNotEmpty()) {
246 binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) 255 binding.loadingText.text = it
247 ViewUtils.showView(binding.surfaceInputOverlay) 256 }
248 ViewUtils.hideView(binding.loadingIndicator) 257 }
249 258 }
250 // Setup overlay
251 updateShowFpsOverlay()
252 } 259 }
253 } 260 launch {
261 repeatOnLifecycle(Lifecycle.State.CREATED) {
262 emulationViewModel.emulationStarted.collectLatest {
263 if (it) {
264 binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
265 ViewUtils.showView(binding.surfaceInputOverlay)
266 ViewUtils.hideView(binding.loadingIndicator)
254 267
255 emulationViewModel.isEmulationStopping.observe(viewLifecycleOwner) { 268 // Setup overlay
256 if (it) { 269 updateShowFpsOverlay()
257 binding.loadingText.setText(R.string.shutting_down) 270 }
258 ViewUtils.showView(binding.loadingIndicator) 271 }
259 ViewUtils.hideView(binding.inputContainer) 272 }
260 ViewUtils.hideView(binding.showFpsText) 273 }
274 launch {
275 repeatOnLifecycle(Lifecycle.State.CREATED) {
276 emulationViewModel.isEmulationStopping.collectLatest {
277 if (it) {
278 binding.loadingText.setText(R.string.shutting_down)
279 ViewUtils.showView(binding.loadingIndicator)
280 ViewUtils.hideView(binding.inputContainer)
281 ViewUtils.hideView(binding.showFpsText)
282 }
283 }
284 }
261 } 285 }
262 } 286 }
263 } 287 }
@@ -274,9 +298,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
274 } 298 }
275 } 299 }
276 } else { 300 } else {
277 if (EmulationMenuSettings.showOverlay && 301 if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) {
278 emulationViewModel.emulationStarted.value == true
279 ) {
280 binding.surfaceInputOverlay.post { 302 binding.surfaceInputOverlay.post {
281 binding.surfaceInputOverlay.visibility = View.VISIBLE 303 binding.surfaceInputOverlay.visibility = View.VISIBLE
282 } 304 }
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
9import androidx.appcompat.app.AppCompatActivity 9import androidx.appcompat.app.AppCompatActivity
10import androidx.fragment.app.DialogFragment 10import androidx.fragment.app.DialogFragment
11import androidx.fragment.app.activityViewModels 11import androidx.fragment.app.activityViewModels
12import androidx.lifecycle.Lifecycle
12import androidx.lifecycle.ViewModelProvider 13import androidx.lifecycle.ViewModelProvider
14import androidx.lifecycle.lifecycleScope
15import androidx.lifecycle.repeatOnLifecycle
13import com.google.android.material.dialog.MaterialAlertDialogBuilder 16import com.google.android.material.dialog.MaterialAlertDialogBuilder
17import kotlinx.coroutines.launch
14import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding 18import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
15import org.yuzu.yuzu_emu.model.TaskViewModel 19import org.yuzu.yuzu_emu.model.TaskViewModel
16 20
@@ -28,21 +32,27 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
28 .create() 32 .create()
29 dialog.setCanceledOnTouchOutside(false) 33 dialog.setCanceledOnTouchOutside(false)
30 34
31 taskViewModel.isComplete.observe(this) { complete -> 35 viewLifecycleOwner.lifecycleScope.launch {
32 if (complete) { 36 repeatOnLifecycle(Lifecycle.State.CREATED) {
33 dialog.dismiss() 37 taskViewModel.isComplete.collect {
34 when (val result = taskViewModel.result.value) { 38 if (it) {
35 is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG).show() 39 dialog.dismiss()
36 is MessageDialogFragment -> result.show( 40 when (val result = taskViewModel.result.value) {
37 requireActivity().supportFragmentManager, 41 is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG)
38 MessageDialogFragment.TAG 42 .show()
39 ) 43
44 is MessageDialogFragment -> result.show(
45 requireActivity().supportFragmentManager,
46 MessageDialogFragment.TAG
47 )
48 }
49 taskViewModel.clear()
50 }
40 } 51 }
41 taskViewModel.clear()
42 } 52 }
43 } 53 }
44 54
45 if (taskViewModel.isRunning.value == false) { 55 if (!taskViewModel.isRunning.value) {
46 taskViewModel.runTask() 56 taskViewModel.runTask()
47 } 57 }
48 return dialog 58 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 @@
3 3
4package org.yuzu.yuzu_emu.fragments 4package org.yuzu.yuzu_emu.fragments
5 5
6import android.annotation.SuppressLint
6import android.content.Context 7import android.content.Context
7import android.content.SharedPreferences 8import android.content.SharedPreferences
8import android.os.Bundle 9import android.os.Bundle
@@ -17,9 +18,13 @@ import androidx.core.view.updatePadding
17import androidx.core.widget.doOnTextChanged 18import androidx.core.widget.doOnTextChanged
18import androidx.fragment.app.Fragment 19import androidx.fragment.app.Fragment
19import androidx.fragment.app.activityViewModels 20import androidx.fragment.app.activityViewModels
21import androidx.lifecycle.Lifecycle
22import androidx.lifecycle.lifecycleScope
23import androidx.lifecycle.repeatOnLifecycle
20import androidx.preference.PreferenceManager 24import androidx.preference.PreferenceManager
21import info.debatty.java.stringsimilarity.Jaccard 25import info.debatty.java.stringsimilarity.Jaccard
22import info.debatty.java.stringsimilarity.JaroWinkler 26import info.debatty.java.stringsimilarity.JaroWinkler
27import kotlinx.coroutines.launch
23import java.util.Locale 28import java.util.Locale
24import org.yuzu.yuzu_emu.R 29import org.yuzu.yuzu_emu.R
25import org.yuzu.yuzu_emu.YuzuApplication 30import org.yuzu.yuzu_emu.YuzuApplication
@@ -52,6 +57,8 @@ class SearchFragment : Fragment() {
52 return binding.root 57 return binding.root
53 } 58 }
54 59
60 // This is using the correct scope, lint is just acting up
61 @SuppressLint("UnsafeRepeatOnLifecycleDetector")
55 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 62 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
56 homeViewModel.setNavigationVisibility(visible = true, animated = false) 63 homeViewModel.setNavigationVisibility(visible = true, animated = false)
57 preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 64 preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
@@ -79,21 +86,32 @@ class SearchFragment : Fragment() {
79 filterAndSearch() 86 filterAndSearch()
80 } 87 }
81 88
82 gamesViewModel.apply { 89 viewLifecycleOwner.lifecycleScope.apply {
83 searchFocused.observe(viewLifecycleOwner) { searchFocused -> 90 launch {
84 if (searchFocused) { 91 repeatOnLifecycle(Lifecycle.State.CREATED) {
85 focusSearch() 92 gamesViewModel.searchFocused.collect {
86 gamesViewModel.setSearchFocused(false) 93 if (it) {
94 focusSearch()
95 gamesViewModel.setSearchFocused(false)
96 }
97 }
87 } 98 }
88 } 99 }
89 100 launch {
90 games.observe(viewLifecycleOwner) { filterAndSearch() } 101 repeatOnLifecycle(Lifecycle.State.CREATED) {
91 searchedGames.observe(viewLifecycleOwner) { 102 gamesViewModel.games.collect { filterAndSearch() }
92 (binding.gridGamesSearch.adapter as GameAdapter).submitList(it) 103 }
93 if (it.isEmpty()) { 104 }
94 binding.noResultsView.visibility = View.VISIBLE 105 launch {
95 } else { 106 repeatOnLifecycle(Lifecycle.State.CREATED) {
96 binding.noResultsView.visibility = View.GONE 107 gamesViewModel.searchedGames.collect {
108 (binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
109 if (it.isEmpty()) {
110 binding.noResultsView.visibility = View.VISIBLE
111 } else {
112 binding.noResultsView.visibility = View.GONE
113 }
114 }
97 } 115 }
98 } 116 }
99 } 117 }
@@ -109,7 +127,7 @@ class SearchFragment : Fragment() {
109 private inner class ScoredGame(val score: Double, val item: Game) 127 private inner class ScoredGame(val score: Double, val item: Game)
110 128
111 private fun filterAndSearch() { 129 private fun filterAndSearch() {
112 val baseList = gamesViewModel.games.value!! 130 val baseList = gamesViewModel.games.value
113 val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) { 131 val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) {
114 R.id.chip_recently_played -> { 132 R.id.chip_recently_played -> {
115 baseList.filter { 133 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
15import androidx.core.widget.doOnTextChanged 15import androidx.core.widget.doOnTextChanged
16import androidx.fragment.app.Fragment 16import androidx.fragment.app.Fragment
17import androidx.fragment.app.activityViewModels 17import androidx.fragment.app.activityViewModels
18import androidx.lifecycle.Lifecycle
19import androidx.lifecycle.lifecycleScope
20import androidx.lifecycle.repeatOnLifecycle
18import androidx.recyclerview.widget.LinearLayoutManager 21import androidx.recyclerview.widget.LinearLayoutManager
19import com.google.android.material.divider.MaterialDividerItemDecoration 22import com.google.android.material.divider.MaterialDividerItemDecoration
20import com.google.android.material.transition.MaterialSharedAxis 23import com.google.android.material.transition.MaterialSharedAxis
21import info.debatty.java.stringsimilarity.Cosine 24import info.debatty.java.stringsimilarity.Cosine
25import kotlinx.coroutines.launch
22import org.yuzu.yuzu_emu.R 26import org.yuzu.yuzu_emu.R
23import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding 27import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
24import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem 28import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
@@ -79,10 +83,14 @@ class SettingsSearchFragment : Fragment() {
79 search() 83 search()
80 binding.settingsList.smoothScrollToPosition(0) 84 binding.settingsList.smoothScrollToPosition(0)
81 } 85 }
82 settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) { 86 viewLifecycleOwner.lifecycleScope.launch {
83 if (it) { 87 repeatOnLifecycle(Lifecycle.State.CREATED) {
84 settingsViewModel.setShouldReloadSettingsList(false) 88 settingsViewModel.shouldReloadSettingsList.collect {
85 search() 89 if (it) {
90 settingsViewModel.setShouldReloadSettingsList(false)
91 search()
92 }
93 }
86 } 94 }
87 } 95 }
88 96
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
22import androidx.core.view.updatePadding 22import androidx.core.view.updatePadding
23import androidx.fragment.app.Fragment 23import androidx.fragment.app.Fragment
24import androidx.fragment.app.activityViewModels 24import androidx.fragment.app.activityViewModels
25import androidx.lifecycle.Lifecycle
26import androidx.lifecycle.lifecycleScope
27import androidx.lifecycle.repeatOnLifecycle
25import androidx.navigation.findNavController 28import androidx.navigation.findNavController
26import androidx.preference.PreferenceManager 29import androidx.preference.PreferenceManager
27import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback 30import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
28import com.google.android.material.transition.MaterialFadeThrough 31import com.google.android.material.transition.MaterialFadeThrough
32import kotlinx.coroutines.launch
29import java.io.File 33import java.io.File
30import org.yuzu.yuzu_emu.R 34import org.yuzu.yuzu_emu.R
31import org.yuzu.yuzu_emu.YuzuApplication 35import org.yuzu.yuzu_emu.YuzuApplication
@@ -206,10 +210,14 @@ class SetupFragment : Fragment() {
206 ) 210 )
207 } 211 }
208 212
209 homeViewModel.shouldPageForward.observe(viewLifecycleOwner) { 213 viewLifecycleOwner.lifecycleScope.launch {
210 if (it) { 214 repeatOnLifecycle(Lifecycle.State.CREATED) {
211 pageForward() 215 homeViewModel.shouldPageForward.collect {
212 homeViewModel.setShouldPageForward(false) 216 if (it) {
217 pageForward()
218 homeViewModel.setShouldPageForward(false)
219 }
220 }
213 } 221 }
214 } 222 }
215 223
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 @@
3 3
4package org.yuzu.yuzu_emu.model 4package org.yuzu.yuzu_emu.model
5 5
6import androidx.lifecycle.LiveData
7import androidx.lifecycle.MutableLiveData
8import androidx.lifecycle.ViewModel 6import androidx.lifecycle.ViewModel
7import kotlinx.coroutines.flow.MutableStateFlow
8import kotlinx.coroutines.flow.StateFlow
9 9
10class EmulationViewModel : ViewModel() { 10class EmulationViewModel : ViewModel() {
11 private val _emulationStarted = MutableLiveData(false) 11 val emulationStarted: StateFlow<Boolean> get() = _emulationStarted
12 val emulationStarted: LiveData<Boolean> get() = _emulationStarted 12 private val _emulationStarted = MutableStateFlow(false)
13 13
14 private val _isEmulationStopping = MutableLiveData(false) 14 val isEmulationStopping: StateFlow<Boolean> get() = _isEmulationStopping
15 val isEmulationStopping: LiveData<Boolean> get() = _isEmulationStopping 15 private val _isEmulationStopping = MutableStateFlow(false)
16 16
17 private val _shaderProgress = MutableLiveData(0) 17 val shaderProgress: StateFlow<Int> get() = _shaderProgress
18 val shaderProgress: LiveData<Int> get() = _shaderProgress 18 private val _shaderProgress = MutableStateFlow(0)
19 19
20 private val _totalShaders = MutableLiveData(0) 20 val totalShaders: StateFlow<Int> get() = _totalShaders
21 val totalShaders: LiveData<Int> get() = _totalShaders 21 private val _totalShaders = MutableStateFlow(0)
22 22
23 private val _shaderMessage = MutableLiveData("") 23 val shaderMessage: StateFlow<String> get() = _shaderMessage
24 val shaderMessage: LiveData<String> get() = _shaderMessage 24 private val _shaderMessage = MutableStateFlow("")
25 25
26 fun setEmulationStarted(started: Boolean) { 26 fun setEmulationStarted(started: Boolean) {
27 _emulationStarted.postValue(started) 27 _emulationStarted.value = started
28 } 28 }
29 29
30 fun setIsEmulationStopping(value: Boolean) { 30 fun setIsEmulationStopping(value: Boolean) {
@@ -50,10 +50,18 @@ class EmulationViewModel : ViewModel() {
50 } 50 }
51 51
52 fun clear() { 52 fun clear() {
53 _emulationStarted.value = false 53 setEmulationStarted(false)
54 _isEmulationStopping.value = false 54 setIsEmulationStopping(false)
55 _shaderProgress.value = 0 55 setShaderProgress(0)
56 _totalShaders.value = 0 56 setTotalShaders(0)
57 _shaderMessage.value = "" 57 setShaderMessage("")
58 }
59
60 companion object {
61 const val KEY_EMULATION_STARTED = "EmulationStarted"
62 const val KEY_IS_EMULATION_STOPPING = "IsEmulationStarting"
63 const val KEY_SHADER_PROGRESS = "ShaderProgress"
64 const val KEY_TOTAL_SHADERS = "TotalShaders"
65 const val KEY_SHADER_MESSAGE = "ShaderMessage"
58 } 66 }
59} 67}
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
5 5
6import android.net.Uri 6import android.net.Uri
7import androidx.documentfile.provider.DocumentFile 7import androidx.documentfile.provider.DocumentFile
8import androidx.lifecycle.LiveData
9import androidx.lifecycle.MutableLiveData
10import androidx.lifecycle.ViewModel 8import androidx.lifecycle.ViewModel
11import androidx.lifecycle.viewModelScope 9import androidx.lifecycle.viewModelScope
12import androidx.preference.PreferenceManager 10import androidx.preference.PreferenceManager
13import java.util.Locale 11import java.util.Locale
14import kotlinx.coroutines.Dispatchers 12import kotlinx.coroutines.Dispatchers
13import kotlinx.coroutines.flow.MutableStateFlow
14import kotlinx.coroutines.flow.StateFlow
15import kotlinx.coroutines.launch 15import kotlinx.coroutines.launch
16import kotlinx.coroutines.withContext 16import kotlinx.coroutines.withContext
17import kotlinx.serialization.ExperimentalSerializationApi 17import kotlinx.serialization.ExperimentalSerializationApi
@@ -24,23 +24,23 @@ import org.yuzu.yuzu_emu.utils.GameHelper
24 24
25@OptIn(ExperimentalSerializationApi::class) 25@OptIn(ExperimentalSerializationApi::class)
26class GamesViewModel : ViewModel() { 26class GamesViewModel : ViewModel() {
27 private val _games = MutableLiveData<List<Game>>(emptyList()) 27 val games: StateFlow<List<Game>> get() = _games
28 val games: LiveData<List<Game>> get() = _games 28 private val _games = MutableStateFlow(emptyList<Game>())
29 29
30 private val _searchedGames = MutableLiveData<List<Game>>(emptyList()) 30 val searchedGames: StateFlow<List<Game>> get() = _searchedGames
31 val searchedGames: LiveData<List<Game>> get() = _searchedGames 31 private val _searchedGames = MutableStateFlow(emptyList<Game>())
32 32
33 private val _isReloading = MutableLiveData(false) 33 val isReloading: StateFlow<Boolean> get() = _isReloading
34 val isReloading: LiveData<Boolean> get() = _isReloading 34 private val _isReloading = MutableStateFlow(false)
35 35
36 private val _shouldSwapData = MutableLiveData(false) 36 val shouldSwapData: StateFlow<Boolean> get() = _shouldSwapData
37 val shouldSwapData: LiveData<Boolean> get() = _shouldSwapData 37 private val _shouldSwapData = MutableStateFlow(false)
38 38
39 private val _shouldScrollToTop = MutableLiveData(false) 39 val shouldScrollToTop: StateFlow<Boolean> get() = _shouldScrollToTop
40 val shouldScrollToTop: LiveData<Boolean> get() = _shouldScrollToTop 40 private val _shouldScrollToTop = MutableStateFlow(false)
41 41
42 private val _searchFocused = MutableLiveData(false) 42 val searchFocused: StateFlow<Boolean> get() = _searchFocused
43 val searchFocused: LiveData<Boolean> get() = _searchFocused 43 private val _searchFocused = MutableStateFlow(false)
44 44
45 init { 45 init {
46 // Ensure keys are loaded so that ROM metadata can be decrypted. 46 // Ensure keys are loaded so that ROM metadata can be decrypted.
@@ -79,36 +79,36 @@ class GamesViewModel : ViewModel() {
79 ) 79 )
80 ) 80 )
81 81
82 _games.postValue(sortedList) 82 _games.value = sortedList
83 } 83 }
84 84
85 fun setSearchedGames(games: List<Game>) { 85 fun setSearchedGames(games: List<Game>) {
86 _searchedGames.postValue(games) 86 _searchedGames.value = games
87 } 87 }
88 88
89 fun setShouldSwapData(shouldSwap: Boolean) { 89 fun setShouldSwapData(shouldSwap: Boolean) {
90 _shouldSwapData.postValue(shouldSwap) 90 _shouldSwapData.value = shouldSwap
91 } 91 }
92 92
93 fun setShouldScrollToTop(shouldScroll: Boolean) { 93 fun setShouldScrollToTop(shouldScroll: Boolean) {
94 _shouldScrollToTop.postValue(shouldScroll) 94 _shouldScrollToTop.value = shouldScroll
95 } 95 }
96 96
97 fun setSearchFocused(searchFocused: Boolean) { 97 fun setSearchFocused(searchFocused: Boolean) {
98 _searchFocused.postValue(searchFocused) 98 _searchFocused.value = searchFocused
99 } 99 }
100 100
101 fun reloadGames(directoryChanged: Boolean) { 101 fun reloadGames(directoryChanged: Boolean) {
102 if (isReloading.value == true) { 102 if (isReloading.value) {
103 return 103 return
104 } 104 }
105 _isReloading.postValue(true) 105 _isReloading.value = true
106 106
107 viewModelScope.launch { 107 viewModelScope.launch {
108 withContext(Dispatchers.IO) { 108 withContext(Dispatchers.IO) {
109 NativeLibrary.resetRomMetadata() 109 NativeLibrary.resetRomMetadata()
110 setGames(GameHelper.getGames()) 110 setGames(GameHelper.getGames())
111 _isReloading.postValue(false) 111 _isReloading.value = false
112 112
113 if (directoryChanged) { 113 if (directoryChanged) {
114 setShouldSwapData(true) 114 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 @@
3 3
4package org.yuzu.yuzu_emu.model 4package org.yuzu.yuzu_emu.model
5 5
6import androidx.lifecycle.LiveData 6import kotlinx.coroutines.flow.MutableStateFlow
7import androidx.lifecycle.MutableLiveData 7import kotlinx.coroutines.flow.StateFlow
8 8
9data class HomeSetting( 9data class HomeSetting(
10 val titleId: Int, 10 val titleId: Int,
@@ -14,5 +14,5 @@ data class HomeSetting(
14 val isEnabled: () -> Boolean = { true }, 14 val isEnabled: () -> Boolean = { true },
15 val disabledTitleId: Int = 0, 15 val disabledTitleId: Int = 0,
16 val disabledMessageId: Int = 0, 16 val disabledMessageId: Int = 0,
17 val details: LiveData<String> = MutableLiveData("") 17 val details: StateFlow<String> = MutableStateFlow("")
18) 18)
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
5 5
6import android.net.Uri 6import android.net.Uri
7import androidx.fragment.app.FragmentActivity 7import androidx.fragment.app.FragmentActivity
8import androidx.lifecycle.LiveData
9import androidx.lifecycle.MutableLiveData
10import androidx.lifecycle.ViewModel 8import androidx.lifecycle.ViewModel
11import androidx.lifecycle.ViewModelProvider 9import androidx.lifecycle.ViewModelProvider
12import androidx.preference.PreferenceManager 10import androidx.preference.PreferenceManager
11import kotlinx.coroutines.flow.MutableStateFlow
12import kotlinx.coroutines.flow.StateFlow
13import org.yuzu.yuzu_emu.YuzuApplication 13import org.yuzu.yuzu_emu.YuzuApplication
14import org.yuzu.yuzu_emu.utils.GameHelper 14import org.yuzu.yuzu_emu.utils.GameHelper
15 15
16class HomeViewModel : ViewModel() { 16class HomeViewModel : ViewModel() {
17 private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>() 17 val navigationVisible: StateFlow<Pair<Boolean, Boolean>> get() = _navigationVisible
18 val navigationVisible: LiveData<Pair<Boolean, Boolean>> get() = _navigationVisible 18 private val _navigationVisible = MutableStateFlow(Pair(false, false))
19 19
20 private val _statusBarShadeVisible = MutableLiveData(true) 20 val statusBarShadeVisible: StateFlow<Boolean> get() = _statusBarShadeVisible
21 val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible 21 private val _statusBarShadeVisible = MutableStateFlow(true)
22 22
23 private val _shouldPageForward = MutableLiveData(false) 23 val shouldPageForward: StateFlow<Boolean> get() = _shouldPageForward
24 val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward 24 private val _shouldPageForward = MutableStateFlow(false)
25 25
26 private val _gamesDir = MutableLiveData( 26 val gamesDir: StateFlow<String> get() = _gamesDir
27 private val _gamesDir = MutableStateFlow(
27 Uri.parse( 28 Uri.parse(
28 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 29 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
29 .getString(GameHelper.KEY_GAME_PATH, "") 30 .getString(GameHelper.KEY_GAME_PATH, "")
30 ).path ?: "" 31 ).path ?: ""
31 ) 32 )
32 val gamesDir: LiveData<String> get() = _gamesDir
33 33
34 var navigatedToSetup = false 34 var navigatedToSetup = false
35 35
36 init {
37 _navigationVisible.value = Pair(false, false)
38 }
39
40 fun setNavigationVisibility(visible: Boolean, animated: Boolean) { 36 fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
41 if (_navigationVisible.value?.first == visible) { 37 if (navigationVisible.value.first == visible) {
42 return 38 return
43 } 39 }
44 _navigationVisible.value = Pair(visible, animated) 40 _navigationVisible.value = Pair(visible, animated)
45 } 41 }
46 42
47 fun setStatusBarShadeVisibility(visible: Boolean) { 43 fun setStatusBarShadeVisibility(visible: Boolean) {
48 if (_statusBarShadeVisible.value == visible) { 44 if (statusBarShadeVisible.value == visible) {
49 return 45 return
50 } 46 }
51 _statusBarShadeVisible.value = visible 47 _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 @@
3 3
4package org.yuzu.yuzu_emu.model 4package org.yuzu.yuzu_emu.model
5 5
6import androidx.lifecycle.LiveData
7import androidx.lifecycle.MutableLiveData
8import androidx.lifecycle.SavedStateHandle
9import androidx.lifecycle.ViewModel 6import androidx.lifecycle.ViewModel
7import kotlinx.coroutines.flow.MutableStateFlow
8import kotlinx.coroutines.flow.StateFlow
10import org.yuzu.yuzu_emu.R 9import org.yuzu.yuzu_emu.R
11import org.yuzu.yuzu_emu.YuzuApplication 10import org.yuzu.yuzu_emu.YuzuApplication
12import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem 11import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
13 12
14class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { 13class SettingsViewModel : ViewModel() {
15 var game: Game? = null 14 var game: Game? = null
16 15
17 var shouldSave = false 16 var shouldSave = false
18 17
19 var clickedItem: SettingsItem? = null 18 var clickedItem: SettingsItem? = null
20 19
21 private val _toolbarTitle = MutableLiveData("") 20 val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate
22 val toolbarTitle: LiveData<String> get() = _toolbarTitle 21 private val _shouldRecreate = MutableStateFlow(false)
23 22
24 private val _shouldRecreate = MutableLiveData(false) 23 val shouldNavigateBack: StateFlow<Boolean> get() = _shouldNavigateBack
25 val shouldRecreate: LiveData<Boolean> get() = _shouldRecreate 24 private val _shouldNavigateBack = MutableStateFlow(false)
26 25
27 private val _shouldNavigateBack = MutableLiveData(false) 26 val shouldShowResetSettingsDialog: StateFlow<Boolean> get() = _shouldShowResetSettingsDialog
28 val shouldNavigateBack: LiveData<Boolean> get() = _shouldNavigateBack 27 private val _shouldShowResetSettingsDialog = MutableStateFlow(false)
29 28
30 private val _shouldShowResetSettingsDialog = MutableLiveData(false) 29 val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList
31 val shouldShowResetSettingsDialog: LiveData<Boolean> get() = _shouldShowResetSettingsDialog 30 private val _shouldReloadSettingsList = MutableStateFlow(false)
32 31
33 private val _shouldReloadSettingsList = MutableLiveData(false) 32 val isUsingSearch: StateFlow<Boolean> get() = _isUsingSearch
34 val shouldReloadSettingsList: LiveData<Boolean> get() = _shouldReloadSettingsList 33 private val _isUsingSearch = MutableStateFlow(false)
35 34
36 private val _isUsingSearch = MutableLiveData(false) 35 val sliderProgress: StateFlow<Int> get() = _sliderProgress
37 val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch 36 private val _sliderProgress = MutableStateFlow(-1)
38 37
39 val sliderProgress = savedStateHandle.getStateFlow(KEY_SLIDER_PROGRESS, -1) 38 val sliderTextValue: StateFlow<String> get() = _sliderTextValue
39 private val _sliderTextValue = MutableStateFlow("")
40 40
41 val sliderTextValue = savedStateHandle.getStateFlow(KEY_SLIDER_TEXT_VALUE, "") 41 val adapterItemChanged: StateFlow<Int> get() = _adapterItemChanged
42 42 private val _adapterItemChanged = MutableStateFlow(-1)
43 val adapterItemChanged = savedStateHandle.getStateFlow(KEY_ADAPTER_ITEM_CHANGED, -1)
44
45 fun setToolbarTitle(value: String) {
46 _toolbarTitle.value = value
47 }
48 43
49 fun setShouldRecreate(value: Boolean) { 44 fun setShouldRecreate(value: Boolean) {
50 _shouldRecreate.value = value 45 _shouldRecreate.value = value
@@ -67,8 +62,8 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo
67 } 62 }
68 63
69 fun setSliderTextValue(value: Float, units: String) { 64 fun setSliderTextValue(value: Float, units: String) {
70 savedStateHandle[KEY_SLIDER_PROGRESS] = value 65 _sliderProgress.value = value.toInt()
71 savedStateHandle[KEY_SLIDER_TEXT_VALUE] = String.format( 66 _sliderTextValue.value = String.format(
72 YuzuApplication.appContext.getString(R.string.value_with_units), 67 YuzuApplication.appContext.getString(R.string.value_with_units),
73 value.toInt().toString(), 68 value.toInt().toString(),
74 units 69 units
@@ -76,21 +71,15 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo
76 } 71 }
77 72
78 fun setSliderProgress(value: Float) { 73 fun setSliderProgress(value: Float) {
79 savedStateHandle[KEY_SLIDER_PROGRESS] = value 74 _sliderProgress.value = value.toInt()
80 } 75 }
81 76
82 fun setAdapterItemChanged(value: Int) { 77 fun setAdapterItemChanged(value: Int) {
83 savedStateHandle[KEY_ADAPTER_ITEM_CHANGED] = value 78 _adapterItemChanged.value = value
84 } 79 }
85 80
86 fun clear() { 81 fun clear() {
87 game = null 82 game = null
88 shouldSave = false 83 shouldSave = false
89 } 84 }
90
91 companion object {
92 const val KEY_SLIDER_TEXT_VALUE = "SliderTextValue"
93 const val KEY_SLIDER_PROGRESS = "SliderProgress"
94 const val KEY_ADAPTER_ITEM_CHANGED = "AdapterItemChanged"
95 }
96} 85}
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 @@
3 3
4package org.yuzu.yuzu_emu.model 4package org.yuzu.yuzu_emu.model
5 5
6import androidx.lifecycle.LiveData
7import androidx.lifecycle.MutableLiveData
8import androidx.lifecycle.ViewModel 6import androidx.lifecycle.ViewModel
9import androidx.lifecycle.viewModelScope 7import androidx.lifecycle.viewModelScope
10import kotlinx.coroutines.Dispatchers 8import kotlinx.coroutines.Dispatchers
9import kotlinx.coroutines.flow.MutableStateFlow
10import kotlinx.coroutines.flow.StateFlow
11import kotlinx.coroutines.launch 11import kotlinx.coroutines.launch
12 12
13class TaskViewModel : ViewModel() { 13class TaskViewModel : ViewModel() {
14 private val _result = MutableLiveData<Any>() 14 val result: StateFlow<Any> get() = _result
15 val result: LiveData<Any> = _result 15 private val _result = MutableStateFlow(Any())
16 16
17 private val _isComplete = MutableLiveData<Boolean>() 17 val isComplete: StateFlow<Boolean> get() = _isComplete
18 val isComplete: LiveData<Boolean> = _isComplete 18 private val _isComplete = MutableStateFlow(false)
19 19
20 private val _isRunning = MutableLiveData<Boolean>() 20 val isRunning: StateFlow<Boolean> get() = _isRunning
21 val isRunning: LiveData<Boolean> = _isRunning 21 private val _isRunning = MutableStateFlow(false)
22 22
23 lateinit var task: () -> Any 23 lateinit var task: () -> Any
24 24
25 init {
26 clear()
27 }
28
29 fun clear() { 25 fun clear() {
30 _result.value = Any() 26 _result.value = Any()
31 _isComplete.value = false 27 _isComplete.value = false
@@ -33,15 +29,16 @@ class TaskViewModel : ViewModel() {
33 } 29 }
34 30
35 fun runTask() { 31 fun runTask() {
36 if (_isRunning.value == true) { 32 if (isRunning.value) {
37 return 33 return
38 } 34 }
39 _isRunning.value = true 35 _isRunning.value = true
40 36
41 viewModelScope.launch(Dispatchers.IO) { 37 viewModelScope.launch(Dispatchers.IO) {
42 val res = task() 38 val res = task()
43 _result.postValue(res) 39 _result.value = res
44 _isComplete.postValue(true) 40 _isComplete.value = true
41 _isRunning.value = false
45 } 42 }
46 } 43 }
47} 44}
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 @@
3 3
4package org.yuzu.yuzu_emu.ui 4package org.yuzu.yuzu_emu.ui
5 5
6import android.annotation.SuppressLint
6import android.os.Bundle 7import android.os.Bundle
7import android.view.LayoutInflater 8import android.view.LayoutInflater
8import android.view.View 9import android.view.View
@@ -14,8 +15,12 @@ import androidx.core.view.WindowInsetsCompat
14import androidx.core.view.updatePadding 15import androidx.core.view.updatePadding
15import androidx.fragment.app.Fragment 16import androidx.fragment.app.Fragment
16import androidx.fragment.app.activityViewModels 17import androidx.fragment.app.activityViewModels
18import androidx.lifecycle.Lifecycle
19import androidx.lifecycle.lifecycleScope
20import androidx.lifecycle.repeatOnLifecycle
17import com.google.android.material.color.MaterialColors 21import com.google.android.material.color.MaterialColors
18import com.google.android.material.transition.MaterialFadeThrough 22import com.google.android.material.transition.MaterialFadeThrough
23import kotlinx.coroutines.launch
19import org.yuzu.yuzu_emu.R 24import org.yuzu.yuzu_emu.R
20import org.yuzu.yuzu_emu.adapters.GameAdapter 25import org.yuzu.yuzu_emu.adapters.GameAdapter
21import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding 26import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding
@@ -44,6 +49,8 @@ class GamesFragment : Fragment() {
44 return binding.root 49 return binding.root
45 } 50 }
46 51
52 // This is using the correct scope, lint is just acting up
53 @SuppressLint("UnsafeRepeatOnLifecycleDetector")
47 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 54 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
48 homeViewModel.setNavigationVisibility(visible = true, animated = false) 55 homeViewModel.setNavigationVisibility(visible = true, animated = false)
49 56
@@ -80,37 +87,48 @@ class GamesFragment : Fragment() {
80 if (_binding == null) { 87 if (_binding == null) {
81 return@post 88 return@post
82 } 89 }
83 binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value!! 90 binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value
84 } 91 }
85 } 92 }
86 93
87 gamesViewModel.apply { 94 viewLifecycleOwner.lifecycleScope.apply {
88 // Watch for when we get updates to any of our games lists 95 launch {
89 isReloading.observe(viewLifecycleOwner) { isReloading -> 96 repeatOnLifecycle(Lifecycle.State.CREATED) {
90 binding.swipeRefresh.isRefreshing = isReloading 97 gamesViewModel.isReloading.collect { binding.swipeRefresh.isRefreshing = it }
98 }
91 } 99 }
92 games.observe(viewLifecycleOwner) { 100 launch {
93 (binding.gridGames.adapter as GameAdapter).submitList(it) 101 repeatOnLifecycle(Lifecycle.State.CREATED) {
94 if (it.isEmpty()) { 102 gamesViewModel.games.collect {
95 binding.noticeText.visibility = View.VISIBLE 103 (binding.gridGames.adapter as GameAdapter).submitList(it)
96 } else { 104 if (it.isEmpty()) {
97 binding.noticeText.visibility = View.GONE 105 binding.noticeText.visibility = View.VISIBLE
106 } else {
107 binding.noticeText.visibility = View.GONE
108 }
109 }
98 } 110 }
99 } 111 }
100 shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData -> 112 launch {
101 if (shouldSwapData) { 113 repeatOnLifecycle(Lifecycle.State.CREATED) {
102 (binding.gridGames.adapter as GameAdapter).submitList( 114 gamesViewModel.shouldSwapData.collect {
103 gamesViewModel.games.value!! 115 if (it) {
104 ) 116 (binding.gridGames.adapter as GameAdapter).submitList(
105 gamesViewModel.setShouldSwapData(false) 117 gamesViewModel.games.value
118 )
119 gamesViewModel.setShouldSwapData(false)
120 }
121 }
106 } 122 }
107 } 123 }
108 124 launch {
109 // Check if the user reselected the games menu item and then scroll to top of the list 125 repeatOnLifecycle(Lifecycle.State.CREATED) {
110 shouldScrollToTop.observe(viewLifecycleOwner) { shouldScroll -> 126 gamesViewModel.shouldScrollToTop.collect {
111 if (shouldScroll) { 127 if (it) {
112 scrollToTop() 128 scrollToTop()
113 gamesViewModel.setShouldScrollToTop(false) 129 gamesViewModel.setShouldScrollToTop(false)
130 }
131 }
114 } 132 }
115 } 133 }
116 } 134 }
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
19import androidx.core.view.ViewCompat 19import androidx.core.view.ViewCompat
20import androidx.core.view.WindowCompat 20import androidx.core.view.WindowCompat
21import androidx.core.view.WindowInsetsCompat 21import androidx.core.view.WindowInsetsCompat
22import androidx.lifecycle.Lifecycle
22import androidx.lifecycle.lifecycleScope 23import androidx.lifecycle.lifecycleScope
24import androidx.lifecycle.repeatOnLifecycle
23import androidx.navigation.NavController 25import androidx.navigation.NavController
24import androidx.navigation.fragment.NavHostFragment 26import androidx.navigation.fragment.NavHostFragment
25import androidx.navigation.ui.setupWithNavController 27import androidx.navigation.ui.setupWithNavController
@@ -115,16 +117,22 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
115 } 117 }
116 118
117 // Prevents navigation from being drawn for a short time on recreation if set to hidden 119 // Prevents navigation from being drawn for a short time on recreation if set to hidden
118 if (!homeViewModel.navigationVisible.value?.first!!) { 120 if (!homeViewModel.navigationVisible.value.first) {
119 binding.navigationView.visibility = View.INVISIBLE 121 binding.navigationView.visibility = View.INVISIBLE
120 binding.statusBarShade.visibility = View.INVISIBLE 122 binding.statusBarShade.visibility = View.INVISIBLE
121 } 123 }
122 124
123 homeViewModel.navigationVisible.observe(this) { 125 lifecycleScope.apply {
124 showNavigation(it.first, it.second) 126 launch {
125 } 127 repeatOnLifecycle(Lifecycle.State.CREATED) {
126 homeViewModel.statusBarShadeVisible.observe(this) { visible -> 128 homeViewModel.navigationVisible.collect { showNavigation(it.first, it.second) }
127 showStatusBarShade(visible) 129 }
130 }
131 launch {
132 repeatOnLifecycle(Lifecycle.State.CREATED) {
133 homeViewModel.statusBarShadeVisible.collect { showStatusBarShade(it) }
134 }
135 }
128 } 136 }
129 137
130 // Dismiss previous notifications (should not happen unless a crash occurred) 138 // Dismiss previous notifications (should not happen unless a crash occurred)