Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #12188 from t895/coil-loader
Android: Use custom image loader for game covers
  • Loading branch information
JosJuice committed Sep 22, 2023
2 parents 579ccb0 + f13b291 commit 853d93d
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 69 deletions.
Expand Up @@ -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()

Expand Down Expand Up @@ -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
Expand Down
Expand Up @@ -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.
Expand Down Expand Up @@ -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) {
Expand Down
Expand Up @@ -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.
Expand Down
Expand Up @@ -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

Expand Down
Expand Up @@ -2,59 +2,98 @@

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
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?) {
Expand Down

0 comments on commit 853d93d

Please sign in to comment.