android: Replace Picasso with Coil
This commit is contained in:
parent
37cc94526b
commit
3fcc6b1104
7 changed files with 41 additions and 138 deletions
|
@ -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'
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue