diff options
| author | JosJuice <josjuice@gmail.com> | 2026-08-03 20:02:47 +0200 |
|---|---|---|
| committer | JosJuice <josjuice@gmail.com> | 2026-08-15 10:44:28 +0200 |
| commit | 49ebb1cfea589e66134039d4ecca911e0e14af43 (patch) | |
| tree | e962b2bdb71fc41738cfc90b1d02273def13136e /Source/Android/app/src/main/java | |
| parent | c64b02dace5e5e71dae9fbfbd258ffa3562573ef (diff) | |
Android: Use TitleDatabase for game list
By using the title database we ship with Dolphin, we get more uniform
game titles than if we grab what's in each game's opening.bnr file.
It also becomes possible for users to supply a custom titles.txt file.
This has been implemented in DolphinQt since a long time ago.
Diffstat (limited to 'Source/Android/app/src/main/java')
8 files changed, 64 insertions, 12 deletions
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt index dbcfdd42e2..e7ea8e5519 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt @@ -61,13 +61,14 @@ class GameAdapter : RecyclerView.Adapter<GameViewHolder>(), val gameFile = mGameFiles[position] holder.apply { + val title = gameFile.getTitle(GameFileCacheManager.getTitleDatabase()) if (BooleanSetting.MAIN_SHOW_GAME_TITLES.boolean) { - binding.textGameTitle.text = gameFile.getTitle() + binding.textGameTitle.text = title binding.textGameTitle.visibility = View.VISIBLE binding.textGameTitleInner.visibility = View.GONE binding.textGameCaption.visibility = View.VISIBLE } else { - binding.textGameTitleInner.text = gameFile.getTitle() + binding.textGameTitleInner.text = title binding.textGameTitleInner.visibility = View.VISIBLE binding.textGameTitle.visibility = View.GONE binding.textGameCaption.visibility = View.GONE diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt index 4f43601e68..7cbb3000e2 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt @@ -48,7 +48,7 @@ class GameRowPresenter : Presenter() { holder.apply { imageScreenshot.setImageDrawable(null) - cardParent.titleText = gameFile.getTitle() + cardParent.titleText = gameFile.getTitle(GameFileCacheManager.getTitleDatabase()) holder.gameFile = gameFile // Set the background color of the card diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.kt index 0384da89c0..b97fd987e6 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.kt @@ -51,7 +51,7 @@ class GameDetailsDialog : DialogFragment() { textTimePlayed.visibility = View.GONE } - textGameTitle.text = gameFile.getTitle() + textGameTitle.text = gameFile.getTitle(GameFileCacheManager.getTitleDatabase()) textDescription.text = gameFile.getDescription() if (gameFile.getDescription().isEmpty()) { textDescription.visibility = View.GONE @@ -118,7 +118,7 @@ class GameDetailsDialog : DialogFragment() { textTimePlayed.visibility = View.GONE } - textGameTitle.text = gameFile.getTitle() + textGameTitle.text = gameFile.getTitle(GameFileCacheManager.getTitleDatabase()) textDescription.text = gameFile.getDescription() if (gameFile.getDescription().isEmpty()) { tvBinding.textDescription.visibility = View.GONE diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt index 789a3cef17..3c641c6695 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt @@ -104,6 +104,7 @@ import org.dolphinemu.dolphinemu.features.netplay.model.Player import org.dolphinemu.dolphinemu.features.netplay.model.SaveTransferProgress import org.dolphinemu.dolphinemu.features.netplay.model.TraversalState import org.dolphinemu.dolphinemu.model.GameFile +import org.dolphinemu.dolphinemu.services.GameFileCacheManager import org.dolphinemu.dolphinemu.ui.theme.DolphinScaffold import org.dolphinemu.dolphinemu.ui.theme.DolphinTheme import org.dolphinemu.dolphinemu.ui.theme.MenuSpacer @@ -753,6 +754,7 @@ private fun GameGridItem( gameFile: GameFile, onClick: () -> Unit, ) { + val titleDatabase = GameFileCacheManager.getTitleDatabase() Card( onClick = onClick, ) { @@ -762,7 +764,7 @@ private fun GameGridItem( .data(gameFile) .error(R.drawable.no_banner) .build(), - contentDescription = gameFile.getTitle(), + contentDescription = gameFile.getTitle(titleDatabase), contentScale = ContentScale.Crop, imageLoader = CoilUtils.imageLoader, modifier = Modifier @@ -770,7 +772,7 @@ private fun GameGridItem( .aspectRatio(0.7f) ) Text( - text = gameFile.getTitle(), + text = gameFile.getTitle(titleDatabase), style = MaterialTheme.typography.bodySmall, maxLines = 2, minLines = 2, diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.kt index 26da174285..4794f5c1b7 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.kt @@ -12,7 +12,7 @@ class GameFile private constructor(private val pointer: Long) { external fun getPlatform(): Int - external fun getTitle(): String + external fun getTitle(titleDatabase: TitleDatabase?): String external fun getDescription(): String diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TitleDatabase.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TitleDatabase.kt new file mode 100644 index 0000000000..e5089673b8 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TitleDatabase.kt @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.model + +import androidx.annotation.Keep + +@Keep +class TitleDatabase private constructor(private val pointer: Long) { + external fun finalize() + + external fun userTitleMapEquals(other: TitleDatabase?): Boolean + + companion object { + @JvmStatic + external suspend fun load(): TitleDatabase + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.kt index 51ce4a4f2f..a5e54aa0de 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.kt @@ -7,6 +7,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.asFlow import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.MainScope +import kotlinx.coroutines.async import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -14,10 +15,12 @@ import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting import org.dolphinemu.dolphinemu.features.settings.model.ConfigChangedCallback import org.dolphinemu.dolphinemu.model.GameFile import org.dolphinemu.dolphinemu.model.GameFileCache +import org.dolphinemu.dolphinemu.model.TitleDatabase import org.dolphinemu.dolphinemu.ui.platform.Platform import org.dolphinemu.dolphinemu.ui.platform.PlatformTab import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner import java.util.Arrays +import java.util.concurrent.atomic.AtomicReference /** * Loads game list data in the background. @@ -25,6 +28,7 @@ import java.util.Arrays object GameFileCacheManager { private var gameFileCache: GameFileCache? = null private val gameFiles = MutableLiveData(emptyArray<GameFile>()) + private var titleDatabase = AtomicReference<TitleDatabase>(null) private var firstLoadDone = false private var recursiveScanEnabled = false @@ -88,6 +92,8 @@ object GameFileCacheManager { } } + fun getTitleDatabase(): TitleDatabase? = titleDatabase.get() + /** * Returns true if in the process of loading the cache for the first time. */ @@ -171,13 +177,22 @@ object GameFileCacheManager { */ private suspend fun load() { if (!firstLoadDone) { + val titleDatabaseJob = MainScope().async(Dispatchers.IO) { + TitleDatabase.load() + } + withContext(Dispatchers.IO) { setUpAutomaticRescan() + gameFileCache!!.load() + + titleDatabase.set(titleDatabaseJob.await()) + if (gameFileCache!!.getSize() != 0) { updateGameFileArray() } } + firstLoadDone = true } @@ -196,16 +211,29 @@ object GameFileCacheManager { loadInProgress.asFlow().first { !it } } + val titleDatabaseJob = MainScope().async(Dispatchers.IO) { + val newTitleDatabase = TitleDatabase.load() + val titleDatabaseChanged = + !newTitleDatabase.userTitleMapEquals(titleDatabase.get()) + if (titleDatabaseChanged) newTitleDatabase else null + } + withContext(Dispatchers.IO) { val gamePaths = GameFileCache.getAllGamePaths() - val changed = gameFileCache!!.update(gamePaths) if (changed) { updateGameFileArray() } val additionalMetadataChanged = gameFileCache!!.updateAdditionalMetadata() - if (additionalMetadataChanged) { + + val newTitleDatabase = titleDatabaseJob.await() + val titleDatabaseChanged = newTitleDatabase != null + if (titleDatabaseChanged) { + titleDatabase.set(newTitleDatabase) + } + + if (additionalMetadataChanged || titleDatabaseChanged) { updateGameFileArray() } @@ -219,8 +247,9 @@ object GameFileCacheManager { private fun updateGameFileArray() { val gameFilesTemp = gameFileCache!!.getAllGames() + val titleDatabase = titleDatabase.get() Arrays.sort(gameFilesTemp) { lhs, rhs -> - lhs.getTitle().compareTo(rhs.getTitle(), ignoreCase = true) + lhs.getTitle(titleDatabase).compareTo(rhs.getTitle(titleDatabase), ignoreCase = true) } gameFiles.postValue(gameFilesTemp) } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.kt index 2d9ad354fb..fd13ff3175 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.kt @@ -138,7 +138,10 @@ class SyncProgramsJobService : JobService() { TvContractCompat.PreviewPrograms.COLUMN_TYPE, TvContractCompat.PreviewProgramColumns.TYPE_GAME ) - values.put(TvContract.Programs.COLUMN_TITLE, game.getTitle()) + values.put( + TvContract.Programs.COLUMN_TITLE, + game.getTitle(GameFileCacheManager.getTitleDatabase()) + ) values.put(TvContract.Programs.COLUMN_SHORT_DESCRIPTION, game.getDescription()) values.put(TvContract.Programs.COLUMN_POSTER_ART_URI, banner.toString()) values.put( |
