android: Replace Picasso with Coil

This commit is contained in:
Charles Lombardo 2023-03-14 20:23:00 -04:00 committed by bunnei
parent 37cc94526b
commit 3fcc6b1104
7 changed files with 41 additions and 138 deletions

View file

@ -135,9 +135,7 @@ dependencies {
implementation 'com.google.android.material:material:1.8.0'
implementation 'androidx.preference:preference:1.2.0'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
// For loading huge screenshots from the disk.
implementation 'com.squareup.picasso:picasso:2.71828'
implementation "io.coil-kt:coil:2.2.2"
// Allows FRP-style asynchronous operations in Android.
implementation 'io.reactivex:rxandroid:1.2.1'

View file

@ -5,17 +5,27 @@ package org.yuzu.yuzu_emu.adapters
import android.database.Cursor
import android.database.DataSetObserver
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import coil.load
import com.google.android.material.color.MaterialColors
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.activities.EmulationActivity.Companion.launch
import org.yuzu.yuzu_emu.model.GameDatabase
import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.PicassoUtils
import org.yuzu.yuzu_emu.viewholders.GameViewHolder
import java.util.*
import java.util.stream.Stream
@ -25,7 +35,8 @@ import java.util.stream.Stream
* ContentProviders and Loaders, allows for efficient display of a limited view into a (possibly)
* large dataset.
*/
class GameAdapter : RecyclerView.Adapter<GameViewHolder>(), View.OnClickListener {
class GameAdapter(private val activity: AppCompatActivity) : RecyclerView.Adapter<GameViewHolder>(),
View.OnClickListener {
private var cursor: Cursor? = null
private val observer: GameDataSetObserver?
private var isDatasetValid = false
@ -51,10 +62,21 @@ class GameAdapter : RecyclerView.Adapter<GameViewHolder>(), View.OnClickListener
override fun onBindViewHolder(holder: GameViewHolder, position: Int) {
if (isDatasetValid) {
if (cursor!!.moveToPosition(position)) {
PicassoUtils.loadGameIcon(
holder.imageIcon,
cursor!!.getString(GameDatabase.GAME_COLUMN_PATH)
)
holder.imageIcon.scaleType = ImageView.ScaleType.CENTER_CROP
activity.lifecycleScope.launch {
withContext(Dispatchers.IO) {
val uri =
Uri.parse(cursor!!.getString(GameDatabase.GAME_COLUMN_PATH)).toString()
val bitmap = decodeGameIcon(uri)
withContext(Dispatchers.Main) {
holder.imageIcon.load(bitmap) {
error(R.drawable.no_icon)
crossfade(true)
}
}
}
}
holder.textGameTitle.text =
cursor!!.getString(GameDatabase.GAME_COLUMN_TITLE)
.replace("[\\t\\n\\r]+".toRegex(), " ")
@ -165,6 +187,16 @@ class GameAdapter : RecyclerView.Adapter<GameViewHolder>(), View.OnClickListener
}
}
private fun decodeGameIcon(uri: String): Bitmap {
val data = NativeLibrary.GetIcon(uri)
return BitmapFactory.decodeByteArray(
data,
0,
data.size,
BitmapFactory.Options()
)
}
private inner class GameDataSetObserver : DataSetObserver() {
override fun onChanged() {
super.onChanged()

View file

@ -57,7 +57,6 @@ class MainActivity : AppCompatActivity(), MainView {
PlatformGamesFragment.TAG
) as PlatformGamesFragment?
}
PicassoUtils.init()
// Dismiss previous notifications (should not happen unless a crash occurred)
EmulationActivity.tryDismissRunningNotification(this)

View file

@ -10,6 +10,7 @@ import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
@ -40,7 +41,7 @@ class PlatformGamesFragment : Fragment(), PlatformGamesView {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
adapter = GameAdapter()
adapter = GameAdapter(requireActivity() as AppCompatActivity)
// Organize our grid layout based on the current view.
if (isAdded) {

View file

@ -1,25 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.utils
import android.graphics.BitmapFactory
import com.squareup.picasso.Picasso
import com.squareup.picasso.Request
import com.squareup.picasso.RequestHandler
import org.yuzu.yuzu_emu.NativeLibrary
class GameIconRequestHandler : RequestHandler() {
override fun canHandleRequest(data: Request): Boolean {
return "content" == data.uri.scheme
}
override fun load(request: Request, networkPolicy: Int): Result {
val gamePath = request.uri.toString()
val data = NativeLibrary.GetIcon(gamePath)
val options = BitmapFactory.Options()
options.inMutable = true
val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size, options)
return Result(bitmap, Picasso.LoadedFrom.DISK)
}
}

View file

@ -1,45 +0,0 @@
package org.yuzu.yuzu_emu.utils;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import com.squareup.picasso.Transformation;
public class PicassoRoundedCornersTransformation implements Transformation {
@Override
public Bitmap transform(Bitmap icon) {
final int width = icon.getWidth();
final int height = icon.getHeight();
final Rect rect = new Rect(0, 0, width, height);
final int size = Math.min(width, height);
final int x = (width - size) / 2;
final int y = (height - size) / 2;
Bitmap squaredBitmap = Bitmap.createBitmap(icon, x, y, size, size);
if (squaredBitmap != icon) {
icon.recycle();
}
Bitmap output = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output);
BitmapShader shader = new BitmapShader(squaredBitmap, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(shader);
canvas.drawRoundRect(new RectF(rect), 10, 10, paint);
squaredBitmap.recycle();
return output;
}
@Override
public String key() {
return "circle";
}
}

View file

@ -1,57 +0,0 @@
package org.yuzu.yuzu_emu.utils;
import android.graphics.Bitmap;
import android.net.Uri;
import android.widget.ImageView;
import com.squareup.picasso.Picasso;
import org.yuzu.yuzu_emu.YuzuApplication;
import org.yuzu.yuzu_emu.R;
import java.io.IOException;
import androidx.annotation.Nullable;
public class PicassoUtils {
private static boolean mPicassoInitialized = false;
public static void init() {
if (mPicassoInitialized) {
return;
}
Picasso picassoInstance = new Picasso.Builder(YuzuApplication.getAppContext())
.addRequestHandler(new GameIconRequestHandler())
.build();
Picasso.setSingletonInstance(picassoInstance);
mPicassoInitialized = true;
}
public static void loadGameIcon(ImageView imageView, String gamePath) {
Picasso
.get()
.load(Uri.parse(gamePath))
.fit()
.centerInside()
.config(Bitmap.Config.RGB_565)
.error(R.drawable.no_icon)
.transform(new PicassoRoundedCornersTransformation())
.into(imageView);
}
// Blocking call. Load image from file and crop/resize it to fit in width x height.
@Nullable
public static Bitmap LoadBitmapFromFile(String uri, int width, int height) {
try {
return Picasso.get()
.load(Uri.parse(uri))
.config(Bitmap.Config.ARGB_8888)
.centerCrop()
.resize(width, height)
.get();
} catch (IOException e) {
return null;
}
}
}