From 65dc35a1a5484f5e7b9a0770fa5b536782a87fbe Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Tue, 2 May 2023 05:58:03 -0400 Subject: [PATCH] android: Game data cache --- src/android/app/build.gradle.kts | 2 ++ .../org/yuzu/yuzu_emu/adapters/GameAdapter.kt | 1 - .../main/java/org/yuzu/yuzu_emu/model/Game.kt | 14 ++++++++++ .../org/yuzu/yuzu_emu/model/GamesViewModel.kt | 28 ++++++++++++++++++- .../org/yuzu/yuzu_emu/ui/GamesFragment.kt | 11 ++------ .../org/yuzu/yuzu_emu/ui/main/MainActivity.kt | 1 - .../org/yuzu/yuzu_emu/utils/GameHelper.kt | 21 +++++++++++--- .../src/main/res/layout/fragment_games.xml | 2 +- 8 files changed, 63 insertions(+), 17 deletions(-) diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index ea13b6d0e..a9790b200 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -2,6 +2,7 @@ plugins { id("com.android.application") id("org.jetbrains.kotlin.android") id("kotlin-parcelize") + kotlin("plugin.serialization") version "1.8.21" } /** @@ -164,6 +165,7 @@ dependencies { implementation("androidx.navigation:navigation-fragment-ktx:2.5.3") implementation("androidx.navigation:navigation-ui-ktx:2.5.3") implementation("info.debatty:java-string-similarity:2.0.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") } fun getVersion(): String { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt index b9f975e2b..a9653475f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt @@ -100,7 +100,6 @@ class GameAdapter(private val activity: AppCompatActivity) : return oldItem.gameId == newItem.gameId } - @SuppressLint("DiffUtilEquals") override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean { return oldItem == newItem } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt index c5cde9d05..2a17653b2 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt @@ -5,9 +5,11 @@ package org.yuzu.yuzu_emu.model import android.os.Parcelable import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Serializable import java.util.HashSet @Parcelize +@Serializable class Game( val title: String, val description: String, @@ -19,6 +21,18 @@ class Game( val keyAddedToLibraryTime get() = "${gameId}_AddedToLibraryTime" val keyLastPlayedTime get() = "${gameId}_LastPlayed" + override fun equals(other: Any?): Boolean { + if (other !is Game) + return false + + return title == other.title + && description == other.description + && regions == other.regions + && path == other.path + && gameId == other.gameId + && company == other.company + } + companion object { val extensions: Set = HashSet( listOf(".xci", ".nsp", ".nca", ".nro") 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 1d0846b08..5a35b14c9 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 @@ -7,11 +7,16 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.preference.PreferenceManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json import org.yuzu.yuzu_emu.NativeLibrary +import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.utils.GameHelper +import java.util.Locale class GamesViewModel : ViewModel() { private val _games = MutableLiveData>(emptyList()) @@ -33,9 +38,30 @@ class GamesViewModel : ViewModel() { val searchFocused: LiveData get() = _searchFocused init { + // Retrieve list of cached games + val storedGames = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) + .getStringSet(GameHelper.KEY_GAMES, emptySet()) + if (storedGames!!.isNotEmpty()) { + val deserializedGames = mutableSetOf() + storedGames.forEach { + deserializedGames.add(Json.decodeFromString(it)) + } + setGames(deserializedGames.toList()) + } reloadGames(false) } + fun setGames(games: List) { + val sortedList = games.sortedWith( + compareBy( + { it.title.lowercase(Locale.getDefault()) }, + { it.path } + ) + ) + + _games.postValue(sortedList) + } + fun setSearchedGames(games: List) { _searchedGames.postValue(games) } @@ -60,7 +86,7 @@ class GamesViewModel : ViewModel() { viewModelScope.launch { withContext(Dispatchers.IO) { NativeLibrary.resetRomMetadata() - _games.postValue(GameHelper.getGames()) + setGames(GameHelper.getGames()) _isReloading.postValue(false) if (directoryChanged) { 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 07d0cd3d8..afabfb2b9 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 @@ -20,10 +20,8 @@ import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.adapters.GameAdapter import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager -import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.model.GamesViewModel import org.yuzu.yuzu_emu.model.HomeViewModel -import java.util.Locale class GamesFragment : Fragment() { private var _binding: FragmentGamesBinding? = null @@ -81,7 +79,7 @@ class GamesFragment : Fragment() { binding.swipeRefresh.isRefreshing = isReloading } gamesViewModel.games.observe(viewLifecycleOwner) { - submitGamesList(it) + (binding.gridGames.adapter as GameAdapter).submitList(it) if (it.isEmpty()) { binding.noticeText.visibility = View.VISIBLE } else { @@ -91,7 +89,7 @@ class GamesFragment : Fragment() { gamesViewModel.shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData -> if (shouldSwapData) { - submitGamesList(gamesViewModel.games.value!!) + (binding.gridGames.adapter as GameAdapter).submitList(gamesViewModel.games.value!!) gamesViewModel.setShouldSwapData(false) } } @@ -115,11 +113,6 @@ class GamesFragment : Fragment() { } } - private fun submitGamesList(gameList: List) { - val sortedList = gameList.sortedBy { it.title.lowercase(Locale.getDefault()) } - (binding.gridGames.adapter as GameAdapter).submitList(sortedList) - } - override fun onDestroyView() { super.onDestroyView() _binding = null 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 b2499168e..f8f275b41 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 @@ -25,7 +25,6 @@ import androidx.navigation.ui.setupWithNavController import androidx.preference.PreferenceManager import com.google.android.material.color.MaterialColors import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.google.android.material.elevation.ElevationOverlayProvider import com.google.android.material.navigation.NavigationBarView import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt index 9dd43343f..ba6b5783e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt @@ -6,19 +6,22 @@ package org.yuzu.yuzu_emu.utils import android.content.SharedPreferences import android.net.Uri import androidx.preference.PreferenceManager +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.model.Game import java.util.* -import kotlin.collections.ArrayList object GameHelper { const val KEY_GAME_PATH = "game_path" + const val KEY_GAMES = "Games" private lateinit var preferences: SharedPreferences - fun getGames(): ArrayList { - val games = ArrayList() + fun getGames(): List { + val games = mutableListOf() val context = YuzuApplication.appContext val gamesDir = PreferenceManager.getDefaultSharedPreferences(context).getString(KEY_GAME_PATH, "") @@ -44,7 +47,17 @@ object GameHelper { } } - return games + // Cache list of games found on disk + val serializedGames = mutableSetOf() + games.forEach { + serializedGames.add(Json.encodeToString(it)) + } + preferences.edit() + .remove(KEY_GAMES) + .putStringSet(KEY_GAMES, serializedGames) + .apply() + + return games.toList() } private fun getGame(filePath: String): Game { diff --git a/src/android/app/src/main/res/layout/fragment_games.xml b/src/android/app/src/main/res/layout/fragment_games.xml index 8b6d0b3b6..a0568668a 100644 --- a/src/android/app/src/main/res/layout/fragment_games.xml +++ b/src/android/app/src/main/res/layout/fragment_games.xml @@ -20,7 +20,7 @@ android:gravity="center" android:padding="@dimen/spacing_large" android:text="@string/empty_gamelist" - tools:visibility="gone" /> + android:visibility="gone" />