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.
This commit is contained in:
parent
579ccb0710
commit
f13b29196d
|
@ -25,7 +25,7 @@ import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
|
||||||
import org.dolphinemu.dolphinemu.utils.CoilUtils
|
import org.dolphinemu.dolphinemu.utils.CoilUtils
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
|
|
||||||
class GameAdapter(private val mActivity: FragmentActivity) : RecyclerView.Adapter<GameViewHolder>(),
|
class GameAdapter : RecyclerView.Adapter<GameViewHolder>(),
|
||||||
View.OnClickListener, OnLongClickListener {
|
View.OnClickListener, OnLongClickListener {
|
||||||
private var mGameFiles: List<GameFile> = ArrayList()
|
private var mGameFiles: List<GameFile> = ArrayList()
|
||||||
|
|
||||||
|
@ -72,20 +72,7 @@ class GameAdapter(private val mActivity: FragmentActivity) : RecyclerView.Adapte
|
||||||
binding.textGameCaption.visibility = View.GONE
|
binding.textGameCaption.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
CoilUtils.loadGameCover(holder, holder.binding.imageGameScreen, gameFile)
|
||||||
mActivity.lifecycleScope.launch {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
val customCoverUri = CoilUtils.findCustomCover(gameFile)
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
CoilUtils.loadGameCover(
|
|
||||||
holder,
|
|
||||||
holder.binding.imageGameScreen,
|
|
||||||
gameFile,
|
|
||||||
customCoverUri
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val animateIn = AnimationUtils.loadAnimation(context, R.anim.anim_card_game_in)
|
val animateIn = AnimationUtils.loadAnimation(context, R.anim.anim_card_game_in)
|
||||||
animateIn.fillAfter = true
|
animateIn.fillAfter = true
|
||||||
|
|
|
@ -24,7 +24,7 @@ import org.dolphinemu.dolphinemu.utils.CoilUtils
|
||||||
* The Leanback library / docs call this a Presenter, but it works very
|
* The Leanback library / docs call this a Presenter, but it works very
|
||||||
* similarly to a RecyclerView.Adapter.
|
* similarly to a RecyclerView.Adapter.
|
||||||
*/
|
*/
|
||||||
class GameRowPresenter(private val mActivity: FragmentActivity) : Presenter() {
|
class GameRowPresenter : Presenter() {
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
|
||||||
// Create a new view.
|
// Create a new view.
|
||||||
|
@ -69,20 +69,7 @@ class GameRowPresenter(private val mActivity: FragmentActivity) : Presenter() {
|
||||||
holder.cardParent.contentText = gameFile.getCompany()
|
holder.cardParent.contentText = gameFile.getCompany()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
CoilUtils.loadGameCover(null, holder.imageScreenshot, gameFile)
|
||||||
mActivity.lifecycleScope.launch {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
val customCoverUri = CoilUtils.findCustomCover(gameFile)
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
CoilUtils.loadGameCover(
|
|
||||||
null,
|
|
||||||
holder.imageScreenshot,
|
|
||||||
gameFile,
|
|
||||||
customCoverUri
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUnbindViewHolder(viewHolder: ViewHolder) {
|
override fun onUnbindViewHolder(viewHolder: ViewHolder) {
|
||||||
|
|
|
@ -268,7 +268,7 @@ class TvMainActivity : FragmentActivity(), MainView, OnRefreshListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an adapter for this row.
|
// Create an adapter for this row.
|
||||||
val row = ArrayObjectAdapter(GameRowPresenter(this))
|
val row = ArrayObjectAdapter(GameRowPresenter())
|
||||||
row.addAll(0, gameFiles)
|
row.addAll(0, gameFiles)
|
||||||
|
|
||||||
// Keep a reference to the row in case we need to refresh it.
|
// Keep a reference to the row in case we need to refresh it.
|
||||||
|
|
|
@ -37,7 +37,7 @@ class PlatformGamesFragment : Fragment(), PlatformGamesView {
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
swipeRefresh = binding.swipeRefresh
|
swipeRefresh = binding.swipeRefresh
|
||||||
val gameAdapter = GameAdapter(requireActivity())
|
val gameAdapter = GameAdapter()
|
||||||
gameAdapter.stateRestorationPolicy =
|
gameAdapter.stateRestorationPolicy =
|
||||||
RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
|
RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,22 @@
|
||||||
|
|
||||||
package org.dolphinemu.dolphinemu.utils
|
package org.dolphinemu.dolphinemu.utils
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import coil.load
|
import coil.ImageLoader
|
||||||
import coil.target.ImageViewTarget
|
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.R
|
||||||
import org.dolphinemu.dolphinemu.adapters.GameAdapter.GameViewHolder
|
import org.dolphinemu.dolphinemu.adapters.GameAdapter.GameViewHolder
|
||||||
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
|
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.File
|
||||||
import java.io.FileNotFoundException
|
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 {
|
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(
|
fun loadGameCover(
|
||||||
gameViewHolder: GameViewHolder?,
|
gameViewHolder: GameViewHolder?,
|
||||||
imageView: ImageView,
|
imageView: ImageView,
|
||||||
gameFile: GameFile,
|
gameFile: GameFile
|
||||||
customCoverUri: Uri?
|
|
||||||
) {
|
) {
|
||||||
imageView.scaleType = ImageView.ScaleType.FIT_CENTER
|
imageView.scaleType = ImageView.ScaleType.FIT_CENTER
|
||||||
val imageTarget = ImageViewTarget(imageView)
|
val imageRequest = ImageRequest.Builder(imageView.context)
|
||||||
if (customCoverUri != null) {
|
.data(gameFile)
|
||||||
imageView.load(customCoverUri) {
|
.error(R.drawable.no_banner)
|
||||||
error(R.drawable.no_banner)
|
.target(imageView)
|
||||||
target(
|
.listener(
|
||||||
onSuccess = { success ->
|
onSuccess = { _, _ -> disableInnerTitle(gameViewHolder) },
|
||||||
disableInnerTitle(gameViewHolder)
|
onError = { _, _ -> enableInnerTitle(gameViewHolder) }
|
||||||
imageTarget.drawable = success
|
)
|
||||||
},
|
.build()
|
||||||
onError = { error ->
|
imageLoader.enqueue(imageRequest)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun enableInnerTitle(gameViewHolder: GameViewHolder?) {
|
private fun enableInnerTitle(gameViewHolder: GameViewHolder?) {
|
||||||
|
|
Loading…
Reference in New Issue