diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt index 573549d84b..5eb2aeaf95 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt @@ -19,7 +19,6 @@ import androidx.navigation.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.transition.MaterialSharedAxis -import kotlinx.coroutines.launch import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.adapters.AddonAdapter import org.yuzu.yuzu_emu.databinding.FragmentAddonsBinding @@ -42,7 +41,7 @@ class AddonsFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - addonViewModel.onOpenAddons(args.game) + addonViewModel.onAddonsViewCreated(args.game) enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) @@ -122,12 +121,14 @@ class AddonsFragment : Fragment() { override fun onResume() { super.onResume() - addonViewModel.refreshAddons() + addonViewModel.onAddonsViewStarted(args.game) } override fun onDestroy() { + if (activity?.isChangingConfigurations != true) { + addonViewModel.onCloseAddons() + } super.onDestroy() - addonViewModel.onCloseAddons() } val installAddon = @@ -167,7 +168,7 @@ class AddonsFragment : Fragment() { } catch (_: Exception) { return@newInstance errorMessage } - addonViewModel.refreshAddons() + addonViewModel.refreshAddons(force = true) return@newInstance getString(R.string.addon_installed_successfully) }.show(parentFragmentManager, ProgressDialogFragment.TAG) } else { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt index 2a0e72be26..2331630c4e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt @@ -18,7 +18,7 @@ import org.yuzu.yuzu_emu.utils.NativeConfig import java.util.concurrent.atomic.AtomicBoolean class AddonViewModel : ViewModel() { - private val _patchList = MutableStateFlow(mutableListOf()) + private val _patchList = MutableStateFlow>(emptyList()) val addonList get() = _patchList.asStateFlow() private val _showModInstallPicker = MutableStateFlow(false) @@ -31,34 +31,62 @@ class AddonViewModel : ViewModel() { val addonToDelete = _addonToDelete.asStateFlow() var game: Game? = null + private var loadedGameKey: String? = null private val isRefreshing = AtomicBoolean(false) + private val pendingRefresh = AtomicBoolean(false) - fun onOpenAddons(game: Game) { + fun onAddonsViewCreated(game: Game) { this.game = game - refreshAddons() + refreshAddons(commitEmpty = false) } - fun refreshAddons() { - if (isRefreshing.get() || game == null) { + fun onAddonsViewStarted(game: Game) { + this.game = game + val hasLoadedCurrentGame = loadedGameKey == gameKey(game) + refreshAddons(force = !hasLoadedCurrentGame) + } + + fun refreshAddons(force: Boolean = false, commitEmpty: Boolean = true) { + val currentGame = game ?: return + val currentGameKey = gameKey(currentGame) + if (!force && loadedGameKey == currentGameKey) { + return + } + if (!isRefreshing.compareAndSet(false, true)) { + if (force) { + pendingRefresh.set(true) + } return } - isRefreshing.set(true) + viewModelScope.launch { - withContext(Dispatchers.IO) { - val patchList = ( - NativeLibrary.getPatchesForFile(game!!.path, game!!.programId) - ?: emptyArray() - ).toMutableList() + try { + val patches = withContext(Dispatchers.IO) { + NativeLibrary.getPatchesForFile(currentGame.path, currentGame.programId) + } ?: return@launch + + val patchList = patches.toMutableList() patchList.sortBy { it.name } // Ensure only one update is enabled ensureSingleUpdateEnabled(patchList) removeDuplicates(patchList) + if (patchList.isEmpty() && !commitEmpty) { + return@launch + } + if (gameKey(game ?: return@launch) != currentGameKey) { + return@launch + } - _patchList.value = patchList + _patchList.value = patchList.toList() + loadedGameKey = currentGameKey + } finally { isRefreshing.set(false) + if (pendingRefresh.compareAndSet(true, false)) { + refreshAddons(force = true) + } } } } @@ -119,17 +147,26 @@ class AddonViewModel : ViewModel() { PatchType.DLC -> NativeLibrary.removeDLC(patch.programId) PatchType.Mod -> NativeLibrary.removeMod(patch.programId, patch.name) } - refreshAddons() + refreshAddons(force = true) } fun onCloseAddons() { - if (_patchList.value.isEmpty()) { + val currentGame = game ?: run { + _patchList.value = emptyList() + loadedGameKey = null + return + } + val currentList = _patchList.value + if (currentList.isEmpty()) { + _patchList.value = emptyList() + loadedGameKey = null + game = null return } NativeConfig.setDisabledAddons( - game!!.programId, - _patchList.value.mapNotNull { + currentGame.programId, + currentList.mapNotNull { if (it.enabled) { null } else { @@ -148,7 +185,8 @@ class AddonViewModel : ViewModel() { }.toTypedArray() ) NativeConfig.saveGlobalConfig() - _patchList.value.clear() + _patchList.value = emptyList() + loadedGameKey = null game = null } @@ -159,4 +197,8 @@ class AddonViewModel : ViewModel() { fun showModNoticeDialog(show: Boolean) { _showModNoticeDialog.value = show } + + private fun gameKey(game: Game): String { + return "${game.programId}|${game.path}" + } } 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 db4cc0f60e..74a171cf1f 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 @@ -642,7 +642,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { } } - addonViewModel.refreshAddons() + addonViewModel.refreshAddons(force = true) val separator = System.lineSeparator() ?: "\n" val installResult = StringBuilder()