From 3fcc6b11042e86ef05d074fe287a02a40ed4d76e Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Tue, 14 Mar 2023 20:23:00 -0400 Subject: [PATCH] android: Replace Picasso with Coil --- src/android/app/build.gradle | 4 +- .../org/yuzu/yuzu_emu/adapters/GameAdapter.kt | 44 ++++++++++++-- .../org/yuzu/yuzu_emu/ui/main/MainActivity.kt | 1 - .../ui/platform/PlatformGamesFragment.kt | 3 +- .../yuzu_emu/utils/GameIconRequestHandler.kt | 25 -------- .../PicassoRoundedCornersTransformation.java | 45 --------------- .../org/yuzu/yuzu_emu/utils/PicassoUtils.java | 57 ------------------- 7 files changed, 41 insertions(+), 138 deletions(-) delete mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconRequestHandler.kt delete mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PicassoRoundedCornersTransformation.java delete mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PicassoUtils.java diff --git a/src/android/app/build.gradle b/src/android/app/build.gradle index c0bd7d4595..68e11bb664 100644 --- a/src/android/app/build.gradle +++ b/src/android/app/build.gradle @@ -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' diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt index af4ec63f27..8891705a56 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt @@ -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(), View.OnClickListener { +class GameAdapter(private val activity: AppCompatActivity) : RecyclerView.Adapter(), + View.OnClickListener { private var cursor: Cursor? = null private val observer: GameDataSetObserver? private var isDatasetValid = false @@ -51,10 +62,21 @@ class GameAdapter : RecyclerView.Adapter(), 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(), 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() diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt index ba60a26e8d..7681598571 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt @@ -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) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/platform/PlatformGamesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/platform/PlatformGamesFragment.kt index 73929e186b..40e902a375 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/platform/PlatformGamesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/platform/PlatformGamesFragment.kt @@ -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) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconRequestHandler.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconRequestHandler.kt deleted file mode 100644 index 24ed309a80..0000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconRequestHandler.kt +++ /dev/null @@ -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) - } -} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PicassoRoundedCornersTransformation.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PicassoRoundedCornersTransformation.java deleted file mode 100644 index 03057b0d5a..0000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PicassoRoundedCornersTransformation.java +++ /dev/null @@ -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"; - } -} \ No newline at end of file diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PicassoUtils.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PicassoUtils.java deleted file mode 100644 index 504dc5b6d9..0000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PicassoUtils.java +++ /dev/null @@ -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; - } - } -}