Android: Replace Glide with Coil image loading

This commit is contained in:
Charles Lombardo 2023-01-06 15:15:30 -05:00
parent e6583f8bec
commit 28faca63a6
11 changed files with 210 additions and 297 deletions

View File

@ -155,7 +155,7 @@ dependencies {
implementation 'com.android.volley:volley:1.2.1' implementation 'com.android.volley:volley:1.2.1'
// For loading game covers from disk and GameTDB // For loading game covers from disk and GameTDB
implementation 'com.github.bumptech.glide:glide:4.13.2' implementation 'io.coil-kt:coil:2.2.2'
implementation 'com.nononsenseapps:filepicker:4.2.1' implementation 'com.nononsenseapps:filepicker:4.2.1'
} }

View File

@ -3,7 +3,6 @@
package org.dolphinemu.dolphinemu.adapters package org.dolphinemu.dolphinemu.adapters
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.dolphinemu.dolphinemu.adapters.GameAdapter.GameViewHolder import org.dolphinemu.dolphinemu.adapters.GameAdapter.GameViewHolder
import android.view.View.OnLongClickListener import android.view.View.OnLongClickListener
@ -11,29 +10,23 @@ import org.dolphinemu.dolphinemu.model.GameFile
import android.view.ViewGroup import android.view.ViewGroup
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import org.dolphinemu.dolphinemu.utils.GlideUtils
import org.dolphinemu.dolphinemu.services.GameFileCacheManager import org.dolphinemu.dolphinemu.services.GameFileCacheManager
import org.dolphinemu.dolphinemu.R import org.dolphinemu.dolphinemu.R
import android.view.animation.AnimationUtils import android.view.animation.AnimationUtils
import org.dolphinemu.dolphinemu.activities.EmulationActivity import org.dolphinemu.dolphinemu.activities.EmulationActivity
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.dolphinemu.dolphinemu.databinding.CardGameBinding import org.dolphinemu.dolphinemu.databinding.CardGameBinding
import org.dolphinemu.dolphinemu.dialogs.GamePropertiesDialog import org.dolphinemu.dolphinemu.dialogs.GamePropertiesDialog
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
import org.dolphinemu.dolphinemu.utils.CoilUtils
import java.util.ArrayList import java.util.ArrayList
class GameAdapter(activity: Activity) : RecyclerView.Adapter<GameViewHolder>(), class GameAdapter(private val mActivity: FragmentActivity) : RecyclerView.Adapter<GameViewHolder>(),
View.OnClickListener, OnLongClickListener { View.OnClickListener, OnLongClickListener {
private var mGameFiles: List<GameFile> private var mGameFiles: List<GameFile> = ArrayList()
private val mActivity: Activity
/**
* Initializes the adapter's observer, which watches for changes to the dataset. The adapter will
* display no data until swapDataSet is called.
*/
init {
mGameFiles = ArrayList()
mActivity = activity
}
/** /**
* Called by the LayoutManager when it is necessary to create a new view. * Called by the LayoutManager when it is necessary to create a new view.
@ -65,7 +58,33 @@ class GameAdapter(activity: Activity) : RecyclerView.Adapter<GameViewHolder>(),
val context = holder.itemView.context val context = holder.itemView.context
val gameFile = mGameFiles[position] val gameFile = mGameFiles[position]
GlideUtils.loadGameCover(holder, holder.binding.imageGameScreen, gameFile, mActivity) holder.apply {
if (BooleanSetting.MAIN_SHOW_GAME_TITLES.booleanGlobal) {
binding.textGameTitle.text = gameFile.title
binding.textGameTitle.visibility = View.VISIBLE
binding.textGameTitleInner.visibility = View.GONE
binding.textGameCaption.visibility = View.VISIBLE
} else {
binding.textGameTitleInner.text = gameFile.title
binding.textGameTitleInner.visibility = View.VISIBLE
binding.textGameTitle.visibility = View.GONE
binding.textGameCaption.visibility = View.GONE
}
}
mActivity.lifecycleScope.launchWhenStarted {
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
@ -86,15 +105,11 @@ class GameAdapter(activity: Activity) : RecyclerView.Adapter<GameViewHolder>(),
} }
} }
class GameViewHolder(binding: CardGameBinding) : RecyclerView.ViewHolder(binding.root) { class GameViewHolder(var binding: CardGameBinding) : RecyclerView.ViewHolder(binding.root) {
var gameFile: GameFile? = null var gameFile: GameFile? = null
@JvmField
var binding: CardGameBinding
init { init {
binding.root.tag = this binding.root.tag = this
this.binding = binding
} }
} }

View File

@ -7,20 +7,24 @@ import android.view.ViewGroup
import androidx.leanback.widget.ImageCardView import androidx.leanback.widget.ImageCardView
import org.dolphinemu.dolphinemu.viewholders.TvGameViewHolder import org.dolphinemu.dolphinemu.viewholders.TvGameViewHolder
import org.dolphinemu.dolphinemu.model.GameFile import org.dolphinemu.dolphinemu.model.GameFile
import org.dolphinemu.dolphinemu.utils.GlideUtils
import org.dolphinemu.dolphinemu.services.GameFileCacheManager import org.dolphinemu.dolphinemu.services.GameFileCacheManager
import org.dolphinemu.dolphinemu.R import org.dolphinemu.dolphinemu.R
import android.view.View import android.view.View
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import android.widget.ImageView import android.widget.ImageView
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.dolphinemu.dolphinemu.dialogs.GamePropertiesDialog import org.dolphinemu.dolphinemu.dialogs.GamePropertiesDialog
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 : Presenter() { class GameRowPresenter(private val mActivity: FragmentActivity) : Presenter() {
override fun onCreateViewHolder(parent: ViewGroup): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
// Create a new view. // Create a new view.
val gameCard = ImageCardView(parent.context) val gameCard = ImageCardView(parent.context)
@ -63,7 +67,20 @@ class GameRowPresenter : Presenter() {
holder.cardParent.contentText = gameFile.company holder.cardParent.contentText = gameFile.company
} }
} }
GlideUtils.loadGameCover(null, holder.imageScreenshot, gameFile, null)
mActivity.lifecycleScope.launchWhenStarted {
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) {

View File

@ -3,17 +3,23 @@
package org.dolphinemu.dolphinemu.dialogs package org.dolphinemu.dolphinemu.dialogs
import android.app.Dialog import android.app.Dialog
import android.graphics.Bitmap
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.ImageView
import org.dolphinemu.dolphinemu.services.GameFileCacheManager import org.dolphinemu.dolphinemu.services.GameFileCacheManager
import org.dolphinemu.dolphinemu.R import org.dolphinemu.dolphinemu.R
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import coil.imageLoader
import coil.request.ImageRequest
import kotlinx.coroutines.launch
import org.dolphinemu.dolphinemu.NativeLibrary import org.dolphinemu.dolphinemu.NativeLibrary
import org.dolphinemu.dolphinemu.databinding.DialogGameDetailsBinding import org.dolphinemu.dolphinemu.databinding.DialogGameDetailsBinding
import org.dolphinemu.dolphinemu.databinding.DialogGameDetailsTvBinding import org.dolphinemu.dolphinemu.databinding.DialogGameDetailsTvBinding
import org.dolphinemu.dolphinemu.utils.GlideUtils import org.dolphinemu.dolphinemu.model.GameFile
class GameDetailsDialog : DialogFragment() { class GameDetailsDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
@ -73,7 +79,9 @@ class GameDetailsDialog : DialogFragment() {
} }
} }
GlideUtils.loadGameBanner(binding.banner, gameFile) this.lifecycleScope.launch {
loadGameBanner(binding.banner, gameFile)
}
builder.setView(binding.root) builder.setView(binding.root)
} else { } else {
@ -123,13 +131,35 @@ class GameDetailsDialog : DialogFragment() {
} }
} }
GlideUtils.loadGameBanner(tvBinding.banner, gameFile) this.lifecycleScope.launch {
loadGameBanner(tvBinding.banner, gameFile)
}
builder.setView(tvBinding.root) builder.setView(tvBinding.root)
} }
return builder.create() return builder.create()
} }
private suspend fun loadGameBanner(imageView: ImageView, gameFile: GameFile) {
val vector = gameFile.banner
val width = gameFile.bannerWidth
val height = gameFile.bannerHeight
imageView.scaleType = ImageView.ScaleType.FIT_CENTER
val request = ImageRequest.Builder(imageView.context)
.target(imageView)
.error(R.drawable.no_banner)
if (width > 0 && height > 0) {
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
bitmap.setPixels(vector, 0, width, 0, 0, width, height)
request.data(bitmap)
} else {
request.data(R.drawable.no_banner)
}
imageView.context.imageLoader.execute(request.build())
}
companion object { companion object {
private const val ARG_GAME_PATH = "game_path" private const val ARG_GAME_PATH = "game_path"

View File

@ -1,5 +1,6 @@
package org.dolphinemu.dolphinemu.fragments package org.dolphinemu.dolphinemu.fragments
import android.app.Activity
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
@ -88,7 +89,7 @@ class GridOptionDialogFragment : BottomSheetDialogFragment() {
NativeConfig.LAYER_BASE, NativeConfig.LAYER_BASE,
mBindingMobile.switchDownloadCovers.isChecked mBindingMobile.switchDownloadCovers.isChecked
) )
mView.reloadGrid() (mView as Activity).recreate()
} }
} }
@ -118,7 +119,7 @@ class GridOptionDialogFragment : BottomSheetDialogFragment() {
NativeConfig.LAYER_BASE, NativeConfig.LAYER_BASE,
mBindingTv.switchDownloadCovers.isChecked mBindingTv.switchDownloadCovers.isChecked
) )
mView.reloadGrid() (mView as Activity).recreate()
} }
} }
} }

View File

@ -2,12 +2,15 @@
package org.dolphinemu.dolphinemu.model; package org.dolphinemu.dolphinemu.model;
import android.content.Context;
import androidx.annotation.Keep; import androidx.annotation.Keep;
public class GameFile public class GameFile
{ {
public static int REGION_NTSC_J = 0;
public static int REGION_NTSC_U = 1;
public static int REGION_PAL = 2;
public static int REGION_NTSC_K = 4;
@Keep @Keep
private long mPointer; private long mPointer;
@ -68,11 +71,6 @@ public class GameFile
public native int getBannerHeight(); public native int getBannerHeight();
public String getCoverPath(Context context)
{
return context.getExternalCacheDir().getPath() + "/GameCovers/" + getGameTdbId() + ".png";
}
public String getCustomCoverPath() public String getCustomCoverPath()
{ {
return getPath().substring(0, getPath().lastIndexOf(".")) + ".cover.png"; return getPath().substring(0, getPath().lastIndexOf(".")) + ".cover.png";

View File

@ -349,7 +349,7 @@ public final class TvMainActivity extends FragmentActivity
} }
// Create an adapter for this row. // Create an adapter for this row.
ArrayObjectAdapter row = new ArrayObjectAdapter(new GameRowPresenter()); ArrayObjectAdapter row = new ArrayObjectAdapter(new GameRowPresenter(this));
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.

View File

@ -0,0 +1,95 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.utils
import android.net.Uri
import android.view.View
import android.widget.ImageView
import coil.load
import coil.target.ImageViewTarget
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
object CoilUtils {
fun loadGameCover(
gameViewHolder: GameViewHolder?,
imageView: ImageView,
gameFile: GameFile,
customCoverUri: Uri?
) {
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.booleanGlobal) {
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?) {
if (gameViewHolder != null && !BooleanSetting.MAIN_SHOW_GAME_TITLES.booleanGlobal) {
gameViewHolder.binding.textGameTitleInner.visibility = View.VISIBLE
}
}
private fun disableInnerTitle(gameViewHolder: GameViewHolder?) {
if (gameViewHolder != null && !BooleanSetting.MAIN_SHOW_GAME_TITLES.booleanGlobal) {
gameViewHolder.binding.textGameTitleInner.visibility = View.GONE
}
}
fun findCustomCover(gameFile: GameFile): Uri? {
val customCoverPath = gameFile.customCoverPath
var customCoverUri: Uri? = null
var customCoverExists = false
if (ContentHandler.isContentUri(customCoverPath)) {
try {
customCoverUri = ContentHandler.unmangle(customCoverPath)
customCoverExists = true
} catch (ignored: FileNotFoundException) {
} catch (ignored: SecurityException) {
// Let customCoverExists remain false
}
} else {
customCoverUri = Uri.parse(customCoverPath)
customCoverExists = File(customCoverPath).exists()
}
return if (customCoverExists) {
customCoverUri
} else {
null
}
}
}

View File

@ -3,44 +3,34 @@
package org.dolphinemu.dolphinemu.utils package org.dolphinemu.dolphinemu.utils
import org.dolphinemu.dolphinemu.model.GameFile import org.dolphinemu.dolphinemu.model.GameFile
import android.graphics.Bitmap
import java.io.FileOutputStream
import java.lang.Exception
object CoverHelper { object CoverHelper {
@JvmStatic
fun buildGameTDBUrl(game: GameFile, region: String?): String { fun buildGameTDBUrl(game: GameFile, region: String?): String {
val baseUrl = "https://art.gametdb.com/wii/cover/%s/%s.png" val baseUrl = "https://art.gametdb.com/wii/cover/%s/%s.png"
return String.format(baseUrl, region, game.gameTdbId) return String.format(baseUrl, region, game.gameTdbId)
} }
@JvmStatic
fun getRegion(game: GameFile): String { fun getRegion(game: GameFile): String {
val region: String = when (game.region) { val region: String = when (game.region) {
0 -> "JA" GameFile.REGION_NTSC_J -> "JA"
1 -> "US" GameFile.REGION_NTSC_U -> "US"
4 -> "KO" GameFile.REGION_NTSC_K -> "KO"
2 -> when (game.country) { GameFile.REGION_PAL -> when (game.country) {
3 -> "AU" 3 -> "AU" // Australia
4 -> "FR" 4 -> "FR" // France
5 -> "DE" 5 -> "DE" // Germany
6 -> "IT" 6 -> "IT" // Italy
8 -> "NL" 8 -> "NL" // Netherlands
9 -> "RU" 9 -> "RU" // Russia
10 -> "ES" 10 -> "ES" // Spain
0 -> "EN" 0 -> "EN" // Europe
else -> "EN" else -> "EN"
} }
3 -> "EN" 3 -> "EN" // Unknown
else -> "EN" else -> "EN"
} }
return region return region
} }
fun saveCover(cover: Bitmap, path: String?) {
try {
val out = FileOutputStream(path)
cover.compress(Bitmap.CompressFormat.PNG, 100, out)
out.close()
} catch (ignored: Exception) {
}
}
} }

View File

@ -1,233 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.utils
import org.dolphinemu.dolphinemu.utils.CoverHelper.buildGameTDBUrl
import org.dolphinemu.dolphinemu.utils.CoverHelper.getRegion
import org.dolphinemu.dolphinemu.utils.CoverHelper.saveCover
import android.os.Looper
import org.dolphinemu.dolphinemu.model.GameFile
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import org.dolphinemu.dolphinemu.R
import org.dolphinemu.dolphinemu.adapters.GameAdapter.GameViewHolder
import android.app.Activity
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
import com.bumptech.glide.request.RequestListener
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Handler
import android.view.View
import android.widget.ImageView
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.Transition
import java.io.File
import java.io.FileNotFoundException
import java.util.concurrent.Executors
object GlideUtils {
private val saveCoverExecutor = Executors.newSingleThreadExecutor()
private val unmangleExecutor = Executors.newSingleThreadExecutor()
private val unmangleHandler = Handler(Looper.getMainLooper())
fun loadGameBanner(imageView: ImageView, gameFile: GameFile) {
val context = imageView.context
val vector = gameFile.banner
val width = gameFile.bannerWidth
val height = gameFile.bannerHeight
if (width > 0 && height > 0) {
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
bitmap.setPixels(vector, 0, width, 0, 0, width, height)
Glide.with(context)
.load(bitmap)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.centerCrop()
.into(imageView)
} else {
Glide.with(context)
.load(R.drawable.no_banner)
.into(imageView)
}
}
fun loadGameCover(
gameViewHolder: GameViewHolder?,
imageView: ImageView,
gameFile: GameFile,
activity: Activity?
) {
gameViewHolder?.apply {
if (BooleanSetting.MAIN_SHOW_GAME_TITLES.booleanGlobal) {
binding.textGameTitle.text = gameFile.title
binding.textGameTitle.visibility = View.VISIBLE
binding.textGameTitleInner.visibility = View.GONE
binding.textGameCaption.visibility = View.VISIBLE
} else {
binding.textGameTitleInner.text = gameFile.title
binding.textGameTitle.visibility = View.GONE
binding.textGameCaption.visibility = View.GONE
}
}
unmangleExecutor.execute {
val customCoverPath = gameFile.customCoverPath
var customCoverUri: Uri? = null
var customCoverExists = false
if (ContentHandler.isContentUri(customCoverPath)) {
try {
customCoverUri = ContentHandler.unmangle(customCoverPath)
customCoverExists = true
} catch (ignored: FileNotFoundException) {
// Let customCoverExists remain false
} catch (ignored: SecurityException) {
}
} else {
customCoverUri = Uri.parse(customCoverPath)
customCoverExists = File(customCoverPath).exists()
}
val context = imageView.context
val finalCustomCoverExists = customCoverExists
val finalCustomCoverUri = customCoverUri
val cover = File(gameFile.getCoverPath(context))
val cachedCoverExists = cover.exists()
unmangleHandler.post {
// We can't get a reference to the current activity in the TV version.
// Luckily it won't attempt to start loads on destroyed activities.
if (activity != null) {
// We can't start an image load on a destroyed activity
if (activity.isDestroyed) {
return@post
}
}
if (finalCustomCoverExists) {
Glide.with(imageView)
.load(finalCustomCoverUri)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.centerCrop()
.error(R.drawable.no_banner)
.listener(object : RequestListener<Drawable?> {
override fun onLoadFailed(
e: GlideException?,
model: Any,
target: Target<Drawable?>,
isFirstResource: Boolean
): Boolean {
enableInnerTitle(gameViewHolder)
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any,
target: Target<Drawable?>,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
disableInnerTitle(gameViewHolder)
return false
}
})
.into(imageView)
} else if (cachedCoverExists) {
Glide.with(imageView)
.load(cover)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.centerCrop()
.error(R.drawable.no_banner)
.listener(object : RequestListener<Drawable?> {
override fun onLoadFailed(
e: GlideException?,
model: Any,
target: Target<Drawable?>,
isFirstResource: Boolean
): Boolean {
enableInnerTitle(gameViewHolder)
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any,
target: Target<Drawable?>,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
disableInnerTitle(gameViewHolder)
return false
}
})
.into(imageView)
} else if (BooleanSetting.MAIN_USE_GAME_COVERS.booleanGlobal) {
Glide.with(context)
.load(buildGameTDBUrl(gameFile, getRegion(gameFile)))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.centerCrop()
.error(R.drawable.no_banner)
.listener(object : RequestListener<Drawable?> {
override fun onLoadFailed(
e: GlideException?,
model: Any,
target: Target<Drawable?>,
isFirstResource: Boolean
): Boolean {
enableInnerTitle(gameViewHolder)
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any,
target: Target<Drawable?>,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
disableInnerTitle(gameViewHolder)
return false
}
})
.into(object : CustomTarget<Drawable?>() {
override fun onLoadCleared(placeholder: Drawable?) {}
override fun onResourceReady(
resource: Drawable,
transition: Transition<in Drawable?>?
) {
val savedCover = (resource as BitmapDrawable).bitmap
saveCoverExecutor.execute {
saveCover(
savedCover,
gameFile.getCoverPath(context)
)
}
imageView.setImageBitmap(savedCover)
}
})
} else {
Glide.with(imageView.context)
.load(R.drawable.no_banner)
.into(imageView)
enableInnerTitle(gameViewHolder)
}
}
}
}
private fun enableInnerTitle(gameViewHolder: GameViewHolder?) {
if (gameViewHolder != null && !BooleanSetting.MAIN_SHOW_GAME_TITLES.booleanGlobal) {
gameViewHolder.binding.textGameTitleInner.visibility = View.VISIBLE
}
}
private fun disableInnerTitle(gameViewHolder: GameViewHolder?) {
if (gameViewHolder != null && !BooleanSetting.MAIN_SHOW_GAME_TITLES.booleanGlobal) {
gameViewHolder.binding.textGameTitleInner.visibility = View.GONE
}
}
}

View File

@ -186,9 +186,9 @@ public class TvUtil
} }
} }
if (contentUri == null && (cover = new File(game.getCoverPath(context))).exists()) if (contentUri == null)
{ {
contentUri = getUriForFile(context, getFileProvider(context), cover); contentUri = Uri.parse(CoverHelper.buildGameTDBUrl(game, CoverHelper.getRegion(game)));
} }
context.grantUriPermission(LEANBACK_PACKAGE, contentUri, FLAG_GRANT_READ_URI_PERMISSION); context.grantUriPermission(LEANBACK_PACKAGE, contentUri, FLAG_GRANT_READ_URI_PERMISSION);