summaryrefslogtreecommitdiff
path: root/Source/Android/app/src/main/java
diff options
context:
space:
mode:
authorCharles Lombardo <clombardo169@gmail.com>2023-09-20 14:12:22 -0400
committerCharles Lombardo <clombardo169@gmail.com>2023-09-20 14:36:58 -0400
commitf13b29196d25ba8c36f40903ad5d025b09be9ba0 (patch)
tree1a6511d5efb5866840178084b384222afabcdf07 /Source/Android/app/src/main/java
parent579ccb0710935bac3e4751e6a1671f927d81de57 (diff)
Android: Use custom image loader for game covers
This fixes a bug where custom cover loading was initiated but would finish by the time another image view would be in the place of the previous one.
Diffstat (limited to 'Source/Android/app/src/main/java')
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt17
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt17
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.kt2
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.kt2
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoilUtils.kt113
5 files changed, 82 insertions, 69 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 520c96d812..c1ef9c2b8f 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
@@ -25,7 +25,7 @@ import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
import org.dolphinemu.dolphinemu.utils.CoilUtils
import java.util.ArrayList
-class GameAdapter(private val mActivity: FragmentActivity) : RecyclerView.Adapter<GameViewHolder>(),
+class GameAdapter : RecyclerView.Adapter<GameViewHolder>(),
View.OnClickListener, OnLongClickListener {
private var mGameFiles: List<GameFile> = ArrayList()
@@ -72,20 +72,7 @@ class GameAdapter(private val mActivity: FragmentActivity) : RecyclerView.Adapte
binding.textGameCaption.visibility = View.GONE
}
}
-
- mActivity.lifecycleScope.launch {
- withContext(Dispatchers.IO) {
- val customCoverUri = CoilUtils.findCustomCover(gameFile)
- withContext(Dispatchers.Main) {
- CoilUtils.loadGameCover(
- holder,
- holder.binding.imageGameScreen,
- gameFile,
- customCoverUri
- )
- }
- }
- }
+ CoilUtils.loadGameCover(holder, holder.binding.imageGameScreen, gameFile)
val animateIn = AnimationUtils.loadAnimation(context, R.anim.anim_card_game_in)
animateIn.fillAfter = true
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 b355dc8f6d..754c089876 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
@@ -24,7 +24,7 @@ import org.dolphinemu.dolphinemu.utils.CoilUtils
* The Leanback library / docs call this a Presenter, but it works very
* similarly to a RecyclerView.Adapter.
*/
-class GameRowPresenter(private val mActivity: FragmentActivity) : Presenter() {
+class GameRowPresenter : Presenter() {
override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
// Create a new view.
@@ -69,20 +69,7 @@ class GameRowPresenter(private val mActivity: FragmentActivity) : Presenter() {
holder.cardParent.contentText = gameFile.getCompany()
}
}
-
- mActivity.lifecycleScope.launch {
- withContext(Dispatchers.IO) {
- val customCoverUri = CoilUtils.findCustomCover(gameFile)
- withContext(Dispatchers.Main) {
- CoilUtils.loadGameCover(
- null,
- holder.imageScreenshot,
- gameFile,
- customCoverUri
- )
- }
- }
- }
+ CoilUtils.loadGameCover(null, holder.imageScreenshot, gameFile)
}
override fun onUnbindViewHolder(viewHolder: ViewHolder) {
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.kt
index 79ec2f2943..9beec74fc3 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.kt
@@ -268,7 +268,7 @@ class TvMainActivity : FragmentActivity(), MainView, OnRefreshListener {
}
// Create an adapter for this row.
- val row = ArrayObjectAdapter(GameRowPresenter(this))
+ val row = ArrayObjectAdapter(GameRowPresenter())
row.addAll(0, gameFiles)
// Keep a reference to the row in case we need to refresh it.
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.kt
index 5f1139da8a..c23eeb4fe0 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.kt
@@ -37,7 +37,7 @@ class PlatformGamesFragment : Fragment(), PlatformGamesView {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
swipeRefresh = binding.swipeRefresh
- val gameAdapter = GameAdapter(requireActivity())
+ val gameAdapter = GameAdapter()
gameAdapter.stateRestorationPolicy =
RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoilUtils.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoilUtils.kt
index dd2e2e68fb..350306b572 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoilUtils.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoilUtils.kt
@@ -2,11 +2,22 @@
package org.dolphinemu.dolphinemu.utils
+import android.graphics.drawable.Drawable
import android.net.Uri
import android.view.View
import android.widget.ImageView
-import coil.load
-import coil.target.ImageViewTarget
+import coil.ImageLoader
+import coil.decode.DataSource
+import coil.executeBlocking
+import coil.fetch.DrawableResult
+import coil.fetch.FetchResult
+import coil.fetch.Fetcher
+import coil.imageLoader
+import coil.key.Keyer
+import coil.memory.MemoryCache
+import coil.request.ImageRequest
+import coil.request.Options
+import org.dolphinemu.dolphinemu.DolphinApplication
import org.dolphinemu.dolphinemu.R
import org.dolphinemu.dolphinemu.adapters.GameAdapter.GameViewHolder
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
@@ -14,47 +25,75 @@ import org.dolphinemu.dolphinemu.model.GameFile
import java.io.File
import java.io.FileNotFoundException
+class GameCoverFetcher(
+ private val game: GameFile,
+ private val options: Options
+) : Fetcher {
+ override suspend fun fetch(): FetchResult {
+ val customCoverUri = CoilUtils.findCustomCover(game)
+ val builder = ImageRequest.Builder(DolphinApplication.getAppContext())
+ var dataSource = DataSource.DISK
+ val drawable: Drawable? = if (customCoverUri != null) {
+ val request = builder.data(customCoverUri).error(R.drawable.no_banner).build()
+ DolphinApplication.getAppContext().imageLoader.executeBlocking(request).drawable
+ } else if (BooleanSetting.MAIN_USE_GAME_COVERS.boolean) {
+ val request = builder.data(
+ CoverHelper.buildGameTDBUrl(game, CoverHelper.getRegion(game))
+ ).error(R.drawable.no_banner).build()
+ dataSource = DataSource.NETWORK
+ DolphinApplication.getAppContext().imageLoader.executeBlocking(request).drawable
+ } else {
+ null
+ }
+
+ return DrawableResult(
+ // In the case where the drawable is null, intentionally throw an NPE. This tells Coil
+ // to load the error drawable.
+ drawable = drawable!!,
+ isSampled = false,
+ dataSource = dataSource
+ )
+ }
+
+ class Factory : Fetcher.Factory<GameFile> {
+ override fun create(data: GameFile, options: Options, imageLoader: ImageLoader): Fetcher =
+ GameCoverFetcher(data, options)
+ }
+}
+
+class GameCoverKeyer : Keyer<GameFile> {
+ override fun key(data: GameFile, options: Options): String = data.getGameId() + data.getPath()
+}
+
object CoilUtils {
+ private val imageLoader = ImageLoader.Builder(DolphinApplication.getAppContext())
+ .components {
+ add(GameCoverKeyer())
+ add(GameCoverFetcher.Factory())
+ }
+ .memoryCache {
+ MemoryCache.Builder(DolphinApplication.getAppContext())
+ .maxSizePercent(0.25)
+ .build()
+ }
+ .build()
+
fun loadGameCover(
gameViewHolder: GameViewHolder?,
imageView: ImageView,
- gameFile: GameFile,
- customCoverUri: Uri?
+ gameFile: GameFile
) {
imageView.scaleType = ImageView.ScaleType.FIT_CENTER
- val imageTarget = ImageViewTarget(imageView)
- if (customCoverUri != null) {
- imageView.load(customCoverUri) {
- error(R.drawable.no_banner)
- target(
- onSuccess = { success ->
- disableInnerTitle(gameViewHolder)
- imageTarget.drawable = success
- },
- onError = { error ->
- enableInnerTitle(gameViewHolder)
- imageTarget.drawable = error
- }
- )
- }
- } else if (BooleanSetting.MAIN_USE_GAME_COVERS.boolean) {
- imageView.load(CoverHelper.buildGameTDBUrl(gameFile, CoverHelper.getRegion(gameFile))) {
- error(R.drawable.no_banner)
- target(
- onSuccess = { success ->
- disableInnerTitle(gameViewHolder)
- imageTarget.drawable = success
- },
- onError = { error ->
- enableInnerTitle(gameViewHolder)
- imageTarget.drawable = error
- }
- )
- }
- } else {
- imageView.load(R.drawable.no_banner)
- enableInnerTitle(gameViewHolder)
- }
+ val imageRequest = ImageRequest.Builder(imageView.context)
+ .data(gameFile)
+ .error(R.drawable.no_banner)
+ .target(imageView)
+ .listener(
+ onSuccess = { _, _ -> disableInnerTitle(gameViewHolder) },
+ onError = { _, _ -> enableInnerTitle(gameViewHolder) }
+ )
+ .build()
+ imageLoader.enqueue(imageRequest)
}
private fun enableInnerTitle(gameViewHolder: GameViewHolder?) {