android: Replace Picasso with Coil
This commit is contained in:
parent
37cc94526b
commit
3fcc6b1104
|
@ -135,9 +135,7 @@ dependencies {
|
||||||
implementation 'com.google.android.material:material:1.8.0'
|
implementation 'com.google.android.material:material:1.8.0'
|
||||||
implementation 'androidx.preference:preference:1.2.0'
|
implementation 'androidx.preference:preference:1.2.0'
|
||||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
|
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
|
||||||
|
implementation "io.coil-kt:coil:2.2.2"
|
||||||
// For loading huge screenshots from the disk.
|
|
||||||
implementation 'com.squareup.picasso:picasso:2.71828'
|
|
||||||
|
|
||||||
// Allows FRP-style asynchronous operations in Android.
|
// Allows FRP-style asynchronous operations in Android.
|
||||||
implementation 'io.reactivex:rxandroid:1.2.1'
|
implementation 'io.reactivex:rxandroid:1.2.1'
|
||||||
|
|
|
@ -5,17 +5,27 @@ package org.yuzu.yuzu_emu.adapters
|
||||||
|
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.database.DataSetObserver
|
import android.database.DataSetObserver
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.net.Uri
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import coil.load
|
||||||
import com.google.android.material.color.MaterialColors
|
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.R
|
||||||
import org.yuzu.yuzu_emu.activities.EmulationActivity.Companion.launch
|
import org.yuzu.yuzu_emu.activities.EmulationActivity.Companion.launch
|
||||||
import org.yuzu.yuzu_emu.model.GameDatabase
|
import org.yuzu.yuzu_emu.model.GameDatabase
|
||||||
import org.yuzu.yuzu_emu.utils.Log
|
import org.yuzu.yuzu_emu.utils.Log
|
||||||
import org.yuzu.yuzu_emu.utils.PicassoUtils
|
|
||||||
import org.yuzu.yuzu_emu.viewholders.GameViewHolder
|
import org.yuzu.yuzu_emu.viewholders.GameViewHolder
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.stream.Stream
|
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)
|
* ContentProviders and Loaders, allows for efficient display of a limited view into a (possibly)
|
||||||
* large dataset.
|
* 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 var cursor: Cursor? = null
|
||||||
private val observer: GameDataSetObserver?
|
private val observer: GameDataSetObserver?
|
||||||
private var isDatasetValid = false
|
private var isDatasetValid = false
|
||||||
|
@ -51,10 +62,21 @@ class GameAdapter : RecyclerView.Adapter<GameViewHolder>(), View.OnClickListener
|
||||||
override fun onBindViewHolder(holder: GameViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: GameViewHolder, position: Int) {
|
||||||
if (isDatasetValid) {
|
if (isDatasetValid) {
|
||||||
if (cursor!!.moveToPosition(position)) {
|
if (cursor!!.moveToPosition(position)) {
|
||||||
PicassoUtils.loadGameIcon(
|
holder.imageIcon.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||||
holder.imageIcon,
|
activity.lifecycleScope.launch {
|
||||||
cursor!!.getString(GameDatabase.GAME_COLUMN_PATH)
|
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 =
|
holder.textGameTitle.text =
|
||||||
cursor!!.getString(GameDatabase.GAME_COLUMN_TITLE)
|
cursor!!.getString(GameDatabase.GAME_COLUMN_TITLE)
|
||||||
.replace("[\\t\\n\\r]+".toRegex(), " ")
|
.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() {
|
private inner class GameDataSetObserver : DataSetObserver() {
|
||||||
override fun onChanged() {
|
override fun onChanged() {
|
||||||
super.onChanged()
|
super.onChanged()
|
||||||
|
|
|
@ -57,7 +57,6 @@ class MainActivity : AppCompatActivity(), MainView {
|
||||||
PlatformGamesFragment.TAG
|
PlatformGamesFragment.TAG
|
||||||
) as PlatformGamesFragment?
|
) as PlatformGamesFragment?
|
||||||
}
|
}
|
||||||
PicassoUtils.init()
|
|
||||||
|
|
||||||
// Dismiss previous notifications (should not happen unless a crash occurred)
|
// Dismiss previous notifications (should not happen unless a crash occurred)
|
||||||
EmulationActivity.tryDismissRunningNotification(this)
|
EmulationActivity.tryDismissRunningNotification(this)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewTreeObserver.OnGlobalLayoutListener
|
import android.view.ViewTreeObserver.OnGlobalLayoutListener
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
|
@ -40,7 +41,7 @@ class PlatformGamesFragment : Fragment(), PlatformGamesView {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
adapter = GameAdapter()
|
adapter = GameAdapter(requireActivity() as AppCompatActivity)
|
||||||
|
|
||||||
// Organize our grid layout based on the current view.
|
// Organize our grid layout based on the current view.
|
||||||
if (isAdded) {
|
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 New Issue