android: Game data cache

This commit is contained in:
Charles Lombardo 2023-05-02 05:58:03 -04:00 committed by bunnei
parent b0bef6173a
commit 65dc35a1a5
8 changed files with 63 additions and 17 deletions

View file

@ -2,6 +2,7 @@ plugins {
id("com.android.application") id("com.android.application")
id("org.jetbrains.kotlin.android") id("org.jetbrains.kotlin.android")
id("kotlin-parcelize") 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-fragment-ktx:2.5.3")
implementation("androidx.navigation:navigation-ui-ktx:2.5.3") implementation("androidx.navigation:navigation-ui-ktx:2.5.3")
implementation("info.debatty:java-string-similarity:2.0.0") implementation("info.debatty:java-string-similarity:2.0.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
} }
fun getVersion(): String { fun getVersion(): String {

View file

@ -100,7 +100,6 @@ class GameAdapter(private val activity: AppCompatActivity) :
return oldItem.gameId == newItem.gameId return oldItem.gameId == newItem.gameId
} }
@SuppressLint("DiffUtilEquals")
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean { override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
return oldItem == newItem return oldItem == newItem
} }

View file

@ -5,9 +5,11 @@ package org.yuzu.yuzu_emu.model
import android.os.Parcelable import android.os.Parcelable
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable
import java.util.HashSet import java.util.HashSet
@Parcelize @Parcelize
@Serializable
class Game( class Game(
val title: String, val title: String,
val description: String, val description: String,
@ -19,6 +21,18 @@ class Game(
val keyAddedToLibraryTime get() = "${gameId}_AddedToLibraryTime" val keyAddedToLibraryTime get() = "${gameId}_AddedToLibraryTime"
val keyLastPlayedTime get() = "${gameId}_LastPlayed" 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 { companion object {
val extensions: Set<String> = HashSet( val extensions: Set<String> = HashSet(
listOf(".xci", ".nsp", ".nca", ".nro") listOf(".xci", ".nsp", ".nca", ".nro")

View file

@ -7,11 +7,16 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData 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 kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.utils.GameHelper import org.yuzu.yuzu_emu.utils.GameHelper
import java.util.Locale
class GamesViewModel : ViewModel() { class GamesViewModel : ViewModel() {
private val _games = MutableLiveData<List<Game>>(emptyList()) private val _games = MutableLiveData<List<Game>>(emptyList())
@ -33,9 +38,30 @@ class GamesViewModel : ViewModel() {
val searchFocused: LiveData<Boolean> get() = _searchFocused val searchFocused: LiveData<Boolean> get() = _searchFocused
init { init {
// Retrieve list of cached games
val storedGames = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
.getStringSet(GameHelper.KEY_GAMES, emptySet())
if (storedGames!!.isNotEmpty()) {
val deserializedGames = mutableSetOf<Game>()
storedGames.forEach {
deserializedGames.add(Json.decodeFromString(it))
}
setGames(deserializedGames.toList())
}
reloadGames(false) reloadGames(false)
} }
fun setGames(games: List<Game>) {
val sortedList = games.sortedWith(
compareBy(
{ it.title.lowercase(Locale.getDefault()) },
{ it.path }
)
)
_games.postValue(sortedList)
}
fun setSearchedGames(games: List<Game>) { fun setSearchedGames(games: List<Game>) {
_searchedGames.postValue(games) _searchedGames.postValue(games)
} }
@ -60,7 +86,7 @@ class GamesViewModel : ViewModel() {
viewModelScope.launch { viewModelScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
NativeLibrary.resetRomMetadata() NativeLibrary.resetRomMetadata()
_games.postValue(GameHelper.getGames()) setGames(GameHelper.getGames())
_isReloading.postValue(false) _isReloading.postValue(false)
if (directoryChanged) { if (directoryChanged) {

View file

@ -20,10 +20,8 @@ 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
import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager 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.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.HomeViewModel
import java.util.Locale
class GamesFragment : Fragment() { class GamesFragment : Fragment() {
private var _binding: FragmentGamesBinding? = null private var _binding: FragmentGamesBinding? = null
@ -81,7 +79,7 @@ class GamesFragment : Fragment() {
binding.swipeRefresh.isRefreshing = isReloading binding.swipeRefresh.isRefreshing = isReloading
} }
gamesViewModel.games.observe(viewLifecycleOwner) { gamesViewModel.games.observe(viewLifecycleOwner) {
submitGamesList(it) (binding.gridGames.adapter as GameAdapter).submitList(it)
if (it.isEmpty()) { if (it.isEmpty()) {
binding.noticeText.visibility = View.VISIBLE binding.noticeText.visibility = View.VISIBLE
} else { } else {
@ -91,7 +89,7 @@ class GamesFragment : Fragment() {
gamesViewModel.shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData -> gamesViewModel.shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData ->
if (shouldSwapData) { if (shouldSwapData) {
submitGamesList(gamesViewModel.games.value!!) (binding.gridGames.adapter as GameAdapter).submitList(gamesViewModel.games.value!!)
gamesViewModel.setShouldSwapData(false) gamesViewModel.setShouldSwapData(false)
} }
} }
@ -115,11 +113,6 @@ class GamesFragment : Fragment() {
} }
} }
private fun submitGamesList(gameList: List<Game>) {
val sortedList = gameList.sortedBy { it.title.lowercase(Locale.getDefault()) }
(binding.gridGames.adapter as GameAdapter).submitList(sortedList)
}
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
_binding = null _binding = null

View file

@ -25,7 +25,6 @@ import androidx.navigation.ui.setupWithNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.elevation.ElevationOverlayProvider
import com.google.android.material.navigation.NavigationBarView import com.google.android.material.navigation.NavigationBarView
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View file

@ -6,19 +6,22 @@ package org.yuzu.yuzu_emu.utils
import android.content.SharedPreferences import android.content.SharedPreferences
import android.net.Uri import android.net.Uri
import androidx.preference.PreferenceManager 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.NativeLibrary
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.model.Game
import java.util.* import java.util.*
import kotlin.collections.ArrayList
object GameHelper { object GameHelper {
const val KEY_GAME_PATH = "game_path" const val KEY_GAME_PATH = "game_path"
const val KEY_GAMES = "Games"
private lateinit var preferences: SharedPreferences private lateinit var preferences: SharedPreferences
fun getGames(): ArrayList<Game> { fun getGames(): List<Game> {
val games = ArrayList<Game>() val games = mutableListOf<Game>()
val context = YuzuApplication.appContext val context = YuzuApplication.appContext
val gamesDir = val gamesDir =
PreferenceManager.getDefaultSharedPreferences(context).getString(KEY_GAME_PATH, "") 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<String>()
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 { private fun getGame(filePath: String): Game {

View file

@ -20,7 +20,7 @@
android:gravity="center" android:gravity="center"
android:padding="@dimen/spacing_large" android:padding="@dimen/spacing_large"
android:text="@string/empty_gamelist" android:text="@string/empty_gamelist"
tools:visibility="gone" /> android:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/grid_games" android:id="@+id/grid_games"