summaryrefslogtreecommitdiff
path: root/Source/Android/app/src/main/java
diff options
context:
space:
mode:
authorJosJuice <josjuice@gmail.com>2023-02-20 20:51:36 +0100
committerGitHub <noreply@github.com>2023-02-20 20:51:36 +0100
commit0fb91057006c89d50e42bee7838405149229f03e (patch)
tree1a18adc886f00cf5d86f0f3819cbbe280feabd86 /Source/Android/app/src/main/java
parent6f21ce7d9548db1416b7c11b144ba888ac613b00 (diff)
parent28faca63a634cd3c8de947143ee029b78a718690 (diff)
Merge pull request #11408 from t895/coil
Android: Rewrite image loading with Kotlin and Coil
Diffstat (limited to 'Source/Android/app/src/main/java')
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java184
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt175
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java88
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt89
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.java172
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.kt175
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/GridOptionDialogFragment.kt5
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java12
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java2
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoilUtils.kt95
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoverHelper.java83
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoverHelper.kt36
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GlideUtils.java248
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/TvUtil.java4
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/TvGameViewHolder.java34
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/TvGameViewHolder.kt27
16 files changed, 608 insertions, 821 deletions
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java
deleted file mode 100644
index fb9fd356da..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java
+++ /dev/null
@@ -1,184 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.adapters;
-
-import android.app.Activity;
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-
-import androidx.annotation.NonNull;
-import androidx.fragment.app.FragmentActivity;
-import androidx.recyclerview.widget.RecyclerView;
-
-import org.dolphinemu.dolphinemu.R;
-import org.dolphinemu.dolphinemu.activities.EmulationActivity;
-import org.dolphinemu.dolphinemu.databinding.CardGameBinding;
-import org.dolphinemu.dolphinemu.dialogs.GamePropertiesDialog;
-import org.dolphinemu.dolphinemu.model.GameFile;
-import org.dolphinemu.dolphinemu.services.GameFileCacheManager;
-import org.dolphinemu.dolphinemu.utils.GlideUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public final class GameAdapter extends RecyclerView.Adapter<GameAdapter.GameViewHolder> implements
- View.OnClickListener,
- View.OnLongClickListener
-{
- private List<GameFile> mGameFiles;
- private Activity mActivity;
-
- /**
- * Initializes the adapter's observer, which watches for changes to the dataset. The adapter will
- * display no data until swapDataSet is called.
- */
- public GameAdapter(Activity activity)
- {
- mGameFiles = new ArrayList<>();
- mActivity = activity;
- }
-
- /**
- * Called by the LayoutManager when it is necessary to create a new view.
- *
- * @param parent The RecyclerView (I think?) the created view will be thrown into.
- * @param viewType Not used here, but useful when more than one type of child will be used in the RecyclerView.
- * @return The created ViewHolder with references to all the child view's members.
- */
- @NonNull
- @Override
- public GameViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
- {
- CardGameBinding binding = CardGameBinding.inflate(LayoutInflater.from(parent.getContext()));
-
- binding.getRoot().setOnClickListener(this);
- binding.getRoot().setOnLongClickListener(this);
-
- // Use that view to create a ViewHolder.
- return new GameViewHolder(binding);
- }
-
- /**
- * Called by the LayoutManager when a new view is not necessary because we can recycle
- * an existing one (for example, if a view just scrolled onto the screen from the bottom, we
- * can use the view that just scrolled off the top instead of inflating a new one.)
- *
- * @param holder A ViewHolder representing the view we're recycling.
- * @param position The position of the 'new' view in the dataset.
- */
- @Override
- public void onBindViewHolder(GameViewHolder holder, int position)
- {
- Context context = holder.itemView.getContext();
- GameFile gameFile = mGameFiles.get(position);
- GlideUtils.loadGameCover(holder, holder.binding.imageGameScreen, gameFile, mActivity);
-
- if (GameFileCacheManager.findSecondDisc(gameFile) != null)
- {
- holder.binding.textGameCaption
- .setText(context.getString(R.string.disc_number, gameFile.getDiscNumber() + 1));
- }
- else
- {
- holder.binding.textGameCaption.setText(gameFile.getCompany());
- }
-
- holder.gameFile = gameFile;
-
- Animation animateIn = AnimationUtils.loadAnimation(context, R.anim.anim_card_game_in);
- animateIn.setFillAfter(true);
- Animation animateOut = AnimationUtils.loadAnimation(context, R.anim.anim_card_game_out);
- animateOut.setFillAfter(true);
- holder.binding.getRoot().setOnFocusChangeListener((v, hasFocus) ->
- holder.binding.cardGameArt.startAnimation(hasFocus ? animateIn : animateOut));
- }
-
- public static class GameViewHolder extends RecyclerView.ViewHolder
- {
- public GameFile gameFile;
- public CardGameBinding binding;
-
- public GameViewHolder(@NonNull CardGameBinding binding)
- {
- super(binding.getRoot());
- binding.getRoot().setTag(this);
- this.binding = binding;
- }
- }
-
- /**
- * Called by the LayoutManager to find out how much data we have.
- *
- * @return Size of the dataset.
- */
- @Override
- public int getItemCount()
- {
- return mGameFiles.size();
- }
-
- /**
- * Tell Android whether or not each item in the dataset has a stable identifier.
- *
- * @param hasStableIds ignored.
- */
- @Override
- public void setHasStableIds(boolean hasStableIds)
- {
- super.setHasStableIds(false);
- }
-
- /**
- * When a load is finished, call this to replace the existing data
- * with the newly-loaded data.
- */
- public void swapDataSet(List<GameFile> gameFiles)
- {
- mGameFiles = gameFiles;
- notifyDataSetChanged();
- }
-
- /**
- * Re-fetches game metadata from the game file cache.
- */
- public void refetchMetadata()
- {
- notifyItemRangeChanged(0, getItemCount());
- }
-
- /**
- * Launches the game that was clicked on.
- *
- * @param view The card representing the game the user wants to play.
- */
- @Override
- public void onClick(View view)
- {
- GameViewHolder holder = (GameViewHolder) view.getTag();
-
- String[] paths = GameFileCacheManager.findSecondDiscAndGetPaths(holder.gameFile);
- EmulationActivity.launch((FragmentActivity) view.getContext(), paths, false);
- }
-
- /**
- * Launches the details activity for this Game, using an ID stored in the
- * details button's Tag.
- *
- * @param view The Card button that was long-clicked.
- */
- @Override
- public boolean onLongClick(View view)
- {
- GameViewHolder holder = (GameViewHolder) view.getTag();
-
- GamePropertiesDialog fragment = GamePropertiesDialog.newInstance(holder.gameFile);
- ((FragmentActivity) view.getContext()).getSupportFragmentManager().beginTransaction()
- .add(fragment, GamePropertiesDialog.TAG).commit();
-
- return true;
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt
new file mode 100644
index 0000000000..919ded6fef
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.adapters
+
+import android.annotation.SuppressLint
+import androidx.recyclerview.widget.RecyclerView
+import org.dolphinemu.dolphinemu.adapters.GameAdapter.GameViewHolder
+import android.view.View.OnLongClickListener
+import org.dolphinemu.dolphinemu.model.GameFile
+import android.view.ViewGroup
+import android.view.LayoutInflater
+import android.view.View
+import org.dolphinemu.dolphinemu.services.GameFileCacheManager
+import org.dolphinemu.dolphinemu.R
+import android.view.animation.AnimationUtils
+import org.dolphinemu.dolphinemu.activities.EmulationActivity
+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.dialogs.GamePropertiesDialog
+import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
+import org.dolphinemu.dolphinemu.utils.CoilUtils
+import java.util.ArrayList
+
+class GameAdapter(private val mActivity: FragmentActivity) : RecyclerView.Adapter<GameViewHolder>(),
+ View.OnClickListener, OnLongClickListener {
+ private var mGameFiles: List<GameFile> = ArrayList()
+
+ /**
+ * Called by the LayoutManager when it is necessary to create a new view.
+ *
+ * @param parent The RecyclerView (I think?) the created view will be thrown into.
+ * @param viewType Not used here, but useful when more than one type of child will be used in the RecyclerView.
+ * @return The created ViewHolder with references to all the child view's members.
+ */
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder {
+ val binding = CardGameBinding.inflate(LayoutInflater.from(parent.context))
+ binding.root.apply {
+ setOnClickListener(this@GameAdapter)
+ setOnLongClickListener(this@GameAdapter)
+ }
+
+ // Use that view to create a ViewHolder.
+ return GameViewHolder(binding)
+ }
+
+ /**
+ * Called by the LayoutManager when a new view is not necessary because we can recycle
+ * an existing one (for example, if a view just scrolled onto the screen from the bottom, we
+ * can use the view that just scrolled off the top instead of inflating a new one.)
+ *
+ * @param holder A ViewHolder representing the view we're recycling.
+ * @param position The position of the 'new' view in the dataset.
+ */
+ override fun onBindViewHolder(holder: GameViewHolder, position: Int) {
+ val context = holder.itemView.context
+ val gameFile = mGameFiles[position]
+
+ 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)
+ animateIn.fillAfter = true
+ val animateOut = AnimationUtils.loadAnimation(context, R.anim.anim_card_game_out)
+ animateOut.fillAfter = true
+ holder.apply {
+ if (GameFileCacheManager.findSecondDisc(gameFile) != null) {
+ binding.textGameCaption.text =
+ context.getString(R.string.disc_number, gameFile.discNumber + 1)
+ } else {
+ binding.textGameCaption.text = gameFile.company
+ }
+ holder.gameFile = gameFile
+ binding.root.onFocusChangeListener =
+ View.OnFocusChangeListener { _: View?, hasFocus: Boolean ->
+ binding.cardGameArt.startAnimation(if (hasFocus) animateIn else animateOut)
+ }
+ }
+ }
+
+ class GameViewHolder(var binding: CardGameBinding) : RecyclerView.ViewHolder(binding.root) {
+ var gameFile: GameFile? = null
+
+ init {
+ binding.root.tag = this
+ }
+ }
+
+ /**
+ * Called by the LayoutManager to find out how much data we have.
+ *
+ * @return Size of the dataset.
+ */
+ override fun getItemCount(): Int {
+ return mGameFiles.size
+ }
+
+ /**
+ * Tell Android whether or not each item in the dataset has a stable identifier.
+ *
+ * @param hasStableIds ignored.
+ */
+ override fun setHasStableIds(hasStableIds: Boolean) {
+ super.setHasStableIds(false)
+ }
+
+ /**
+ * When a load is finished, call this to replace the existing data
+ * with the newly-loaded data.
+ */
+ @SuppressLint("NotifyDataSetChanged")
+ fun swapDataSet(gameFiles: List<GameFile>) {
+ mGameFiles = gameFiles
+ notifyDataSetChanged()
+ }
+
+ /**
+ * Re-fetches game metadata from the game file cache.
+ */
+ fun refetchMetadata() {
+ notifyItemRangeChanged(0, itemCount)
+ }
+
+ /**
+ * Launches the game that was clicked on.
+ *
+ * @param view The card representing the game the user wants to play.
+ */
+ override fun onClick(view: View) {
+ val holder = view.tag as GameViewHolder
+ val paths = GameFileCacheManager.findSecondDiscAndGetPaths(holder.gameFile)
+ EmulationActivity.launch(view.context as FragmentActivity, paths, false)
+ }
+
+ /**
+ * Launches the details activity for this Game, using an ID stored in the
+ * details button's Tag.
+ *
+ * @param view The Card button that was long-clicked.
+ */
+ override fun onLongClick(view: View): Boolean {
+ val holder = view.tag as GameViewHolder
+ val fragment = GamePropertiesDialog.newInstance(holder.gameFile)
+ (view.context as FragmentActivity).supportFragmentManager.beginTransaction()
+ .add(fragment, GamePropertiesDialog.TAG).commit()
+ return true
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java
deleted file mode 100644
index 53b0e56861..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java
+++ /dev/null
@@ -1,88 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.adapters;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-
-import androidx.core.content.ContextCompat;
-import androidx.fragment.app.FragmentActivity;
-import androidx.leanback.widget.ImageCardView;
-import androidx.leanback.widget.Presenter;
-
-import org.dolphinemu.dolphinemu.R;
-import org.dolphinemu.dolphinemu.dialogs.GamePropertiesDialog;
-import org.dolphinemu.dolphinemu.model.GameFile;
-import org.dolphinemu.dolphinemu.services.GameFileCacheManager;
-import org.dolphinemu.dolphinemu.utils.GlideUtils;
-import org.dolphinemu.dolphinemu.viewholders.TvGameViewHolder;
-
-/**
- * The Leanback library / docs call this a Presenter, but it works very
- * similarly to a RecyclerView.Adapter.
- */
-public final class GameRowPresenter extends Presenter
-{
- @Override
- public ViewHolder onCreateViewHolder(ViewGroup parent)
- {
- // Create a new view.
- ImageCardView gameCard = new ImageCardView(parent.getContext());
-
- gameCard.setMainImageAdjustViewBounds(true);
- gameCard.setMainImageDimensions(240, 336);
- gameCard.setMainImageScaleType(ImageView.ScaleType.CENTER_CROP);
-
- gameCard.setFocusable(true);
- gameCard.setFocusableInTouchMode(true);
-
- // Use that view to create a ViewHolder.
- return new TvGameViewHolder(gameCard);
- }
-
- @Override
- public void onBindViewHolder(ViewHolder viewHolder, Object item)
- {
- TvGameViewHolder holder = (TvGameViewHolder) viewHolder;
- Context context = holder.cardParent.getContext();
- GameFile gameFile = (GameFile) item;
-
- holder.imageScreenshot.setImageDrawable(null);
- GlideUtils.loadGameCover(null, holder.imageScreenshot, gameFile, null);
-
- holder.cardParent.setTitleText(gameFile.getTitle());
-
- if (GameFileCacheManager.findSecondDisc(gameFile) != null)
- {
- holder.cardParent.setContentText(
- context.getString(R.string.disc_number, gameFile.getDiscNumber() + 1));
- }
- else
- {
- holder.cardParent.setContentText(gameFile.getCompany());
- }
-
- holder.gameFile = gameFile;
-
- // Set the background color of the card
- Drawable background = ContextCompat.getDrawable(context, R.drawable.tv_card_background);
- holder.cardParent.setInfoAreaBackground(background);
- holder.cardParent.setOnLongClickListener((view) ->
- {
- FragmentActivity activity = (FragmentActivity) view.getContext();
- GamePropertiesDialog fragment = GamePropertiesDialog.newInstance(holder.gameFile);
- activity.getSupportFragmentManager().beginTransaction()
- .add(fragment, GamePropertiesDialog.TAG).commit();
-
- return true;
- });
- }
-
- @Override
- public void onUnbindViewHolder(ViewHolder viewHolder)
- {
- // no op
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt
new file mode 100644
index 0000000000..4f062538f9
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.adapters
+
+import androidx.leanback.widget.Presenter
+import android.view.ViewGroup
+import androidx.leanback.widget.ImageCardView
+import org.dolphinemu.dolphinemu.viewholders.TvGameViewHolder
+import org.dolphinemu.dolphinemu.model.GameFile
+import org.dolphinemu.dolphinemu.services.GameFileCacheManager
+import org.dolphinemu.dolphinemu.R
+import android.view.View
+import androidx.core.content.ContextCompat
+import android.widget.ImageView
+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.utils.CoilUtils
+
+/**
+ * The Leanback library / docs call this a Presenter, but it works very
+ * similarly to a RecyclerView.Adapter.
+ */
+class GameRowPresenter(private val mActivity: FragmentActivity) : Presenter() {
+
+ override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
+ // Create a new view.
+ val gameCard = ImageCardView(parent.context)
+ gameCard.apply {
+ setMainImageAdjustViewBounds(true)
+ setMainImageDimensions(240, 336)
+ setMainImageScaleType(ImageView.ScaleType.CENTER_CROP)
+ isFocusable = true
+ isFocusableInTouchMode = true
+ }
+
+ // Use that view to create a ViewHolder.
+ return TvGameViewHolder(gameCard)
+ }
+
+ override fun onBindViewHolder(viewHolder: ViewHolder, item: Any) {
+ val holder = viewHolder as TvGameViewHolder
+ val context = holder.cardParent.context
+ val gameFile = item as GameFile
+
+ holder.apply {
+ imageScreenshot.setImageDrawable(null)
+ cardParent.titleText = gameFile.title
+ holder.gameFile = gameFile
+
+ // Set the background color of the card
+ val background = ContextCompat.getDrawable(context, R.drawable.tv_card_background)
+ cardParent.infoAreaBackground = background
+ cardParent.setOnClickListener { view: View ->
+ val activity = view.context as FragmentActivity
+ val fragment = GamePropertiesDialog.newInstance(holder.gameFile)
+ activity.supportFragmentManager.beginTransaction()
+ .add(fragment, GamePropertiesDialog.TAG).commit()
+ }
+
+ if (GameFileCacheManager.findSecondDisc(gameFile) != null) {
+ holder.cardParent.contentText =
+ context.getString(R.string.disc_number, gameFile.discNumber + 1)
+ } else {
+ holder.cardParent.contentText = gameFile.company
+ }
+ }
+
+ 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) {
+ // no op
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.java
deleted file mode 100644
index 176f5b7610..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.java
+++ /dev/null
@@ -1,172 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.dialogs;
-
-import android.app.Dialog;
-import android.os.Bundle;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.fragment.app.DialogFragment;
-
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-
-import org.dolphinemu.dolphinemu.NativeLibrary;
-import org.dolphinemu.dolphinemu.R;
-import org.dolphinemu.dolphinemu.databinding.DialogGameDetailsBinding;
-import org.dolphinemu.dolphinemu.databinding.DialogGameDetailsTvBinding;
-import org.dolphinemu.dolphinemu.model.GameFile;
-import org.dolphinemu.dolphinemu.services.GameFileCacheManager;
-import org.dolphinemu.dolphinemu.utils.GlideUtils;
-
-public final class GameDetailsDialog extends DialogFragment
-{
- private static final String ARG_GAME_PATH = "game_path";
-
- public static GameDetailsDialog newInstance(String gamePath)
- {
- GameDetailsDialog fragment = new GameDetailsDialog();
-
- Bundle arguments = new Bundle();
- arguments.putString(ARG_GAME_PATH, gamePath);
- fragment.setArguments(arguments);
-
- return fragment;
- }
-
- @NonNull
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState)
- {
- GameFile gameFile = GameFileCacheManager.addOrGet(getArguments().getString(ARG_GAME_PATH));
-
- String country = getResources().getStringArray(R.array.countryNames)[gameFile.getCountry()];
- String description = gameFile.getDescription();
- String fileSize = NativeLibrary.FormatSize(gameFile.getFileSize(), 2);
-
- // TODO: Remove dialog_game_details_tv if we switch to an AppCompatActivity for leanback
- DialogGameDetailsBinding binding;
- DialogGameDetailsTvBinding tvBinding;
- MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext());
- if (requireActivity() instanceof AppCompatActivity)
- {
- binding = DialogGameDetailsBinding.inflate(getLayoutInflater());
-
- binding.textGameTitle.setText(gameFile.getTitle());
- binding.textDescription.setText(gameFile.getDescription());
- if (description.isEmpty())
- {
- binding.textDescription.setVisibility(View.GONE);
- }
-
- binding.textCountry.setText(country);
- binding.textCompany.setText(gameFile.getCompany());
- binding.textGameId.setText(gameFile.getGameId());
- binding.textRevision.setText(String.valueOf(gameFile.getRevision()));
-
- if (!gameFile.shouldShowFileFormatDetails())
- {
- binding.labelFileFormat.setText(R.string.game_details_file_size);
- binding.textFileFormat.setText(fileSize);
-
- binding.labelCompression.setVisibility(View.GONE);
- binding.textCompression.setVisibility(View.GONE);
- binding.labelBlockSize.setVisibility(View.GONE);
- binding.textBlockSize.setVisibility(View.GONE);
- }
- else
- {
- long blockSize = gameFile.getBlockSize();
- String compression = gameFile.getCompressionMethod();
-
- binding.textFileFormat.setText(
- getResources().getString(R.string.game_details_size_and_format,
- gameFile.getFileFormatName(), fileSize));
-
- if (compression.isEmpty())
- {
- binding.textCompression.setText(R.string.game_details_no_compression);
- }
- else
- {
- binding.textCompression.setText(gameFile.getCompressionMethod());
- }
-
- if (blockSize > 0)
- {
- binding.textBlockSize.setText(NativeLibrary.FormatSize(blockSize, 0));
- }
- else
- {
- binding.labelBlockSize.setVisibility(View.GONE);
- binding.textBlockSize.setVisibility(View.GONE);
- }
- }
-
- GlideUtils.loadGameBanner(binding.banner, gameFile);
-
- builder.setView(binding.getRoot());
- }
- else
- {
- tvBinding = DialogGameDetailsTvBinding.inflate(getLayoutInflater());
-
- tvBinding.textGameTitle.setText(gameFile.getTitle());
- tvBinding.textDescription.setText(gameFile.getDescription());
- if (description.isEmpty())
- {
- tvBinding.textDescription.setVisibility(View.GONE);
- }
-
- tvBinding.textCountry.setText(country);
- tvBinding.textCompany.setText(gameFile.getCompany());
- tvBinding.textGameId.setText(gameFile.getGameId());
- tvBinding.textRevision.setText(String.valueOf(gameFile.getRevision()));
-
- if (!gameFile.shouldShowFileFormatDetails())
- {
- tvBinding.labelFileFormat.setText(R.string.game_details_file_size);
- tvBinding.textFileFormat.setText(fileSize);
-
- tvBinding.labelCompression.setVisibility(View.GONE);
- tvBinding.textCompression.setVisibility(View.GONE);
- tvBinding.labelBlockSize.setVisibility(View.GONE);
- tvBinding.textBlockSize.setVisibility(View.GONE);
- }
- else
- {
- long blockSize = gameFile.getBlockSize();
- String compression = gameFile.getCompressionMethod();
-
- tvBinding.textFileFormat.setText(
- getResources().getString(R.string.game_details_size_and_format,
- gameFile.getFileFormatName(), fileSize));
-
- if (compression.isEmpty())
- {
- tvBinding.textCompression.setText(R.string.game_details_no_compression);
- }
- else
- {
- tvBinding.textCompression.setText(gameFile.getCompressionMethod());
- }
-
- if (blockSize > 0)
- {
- tvBinding.textBlockSize.setText(NativeLibrary.FormatSize(blockSize, 0));
- }
- else
- {
- tvBinding.labelBlockSize.setVisibility(View.GONE);
- tvBinding.textBlockSize.setVisibility(View.GONE);
- }
- }
-
- GlideUtils.loadGameBanner(tvBinding.banner, gameFile);
-
- builder.setView(tvBinding.getRoot());
- }
- return builder.create();
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.kt
new file mode 100644
index 0000000000..83d69ebbb4
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.kt
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.dialogs
+
+import android.app.Dialog
+import android.graphics.Bitmap
+import android.os.Bundle
+import android.view.View
+import android.widget.ImageView
+import org.dolphinemu.dolphinemu.services.GameFileCacheManager
+import org.dolphinemu.dolphinemu.R
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import androidx.appcompat.app.AppCompatActivity
+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.databinding.DialogGameDetailsBinding
+import org.dolphinemu.dolphinemu.databinding.DialogGameDetailsTvBinding
+import org.dolphinemu.dolphinemu.model.GameFile
+
+class GameDetailsDialog : DialogFragment() {
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val gameFile = GameFileCacheManager.addOrGet(requireArguments().getString(ARG_GAME_PATH))
+
+ val country = resources.getStringArray(R.array.countryNames)[gameFile.country]
+ val fileSize = NativeLibrary.FormatSize(gameFile.fileSize, 2)
+
+ // TODO: Remove dialog_game_details_tv if we switch to an AppCompatActivity for leanback
+ val binding: DialogGameDetailsBinding
+ val tvBinding: DialogGameDetailsTvBinding
+ val builder = MaterialAlertDialogBuilder(requireContext())
+ if (requireActivity() is AppCompatActivity) {
+ binding = DialogGameDetailsBinding.inflate(layoutInflater)
+ binding.apply {
+ textGameTitle.text = gameFile.title
+ textDescription.text = gameFile.description
+ if (gameFile.description.isEmpty()) {
+ textDescription.visibility = View.GONE
+ }
+
+ textCountry.text = country
+ textCompany.text = gameFile.company
+ textGameId.text = gameFile.gameId
+ textRevision.text = gameFile.revision.toString()
+
+ if (!gameFile.shouldShowFileFormatDetails()) {
+ labelFileFormat.setText(R.string.game_details_file_size)
+ textFileFormat.text = fileSize
+
+ labelCompression.visibility = View.GONE
+ textCompression.visibility = View.GONE
+ labelBlockSize.visibility = View.GONE
+ textBlockSize.visibility = View.GONE
+ } else {
+ val blockSize = gameFile.blockSize
+ val compression = gameFile.compressionMethod
+
+ textFileFormat.text = resources.getString(
+ R.string.game_details_size_and_format,
+ gameFile.fileFormatName,
+ fileSize
+ )
+
+ if (compression.isEmpty()) {
+ textCompression.setText(R.string.game_details_no_compression)
+ } else {
+ textCompression.text = gameFile.compressionMethod
+ }
+
+ if (blockSize > 0) {
+ textBlockSize.text = NativeLibrary.FormatSize(blockSize, 0)
+ } else {
+ labelBlockSize.visibility = View.GONE
+ textBlockSize.visibility = View.GONE
+ }
+ }
+ }
+
+ this.lifecycleScope.launch {
+ loadGameBanner(binding.banner, gameFile)
+ }
+
+ builder.setView(binding.root)
+ } else {
+ tvBinding = DialogGameDetailsTvBinding.inflate(layoutInflater)
+ tvBinding.apply {
+ textGameTitle.text = gameFile.title
+ textDescription.text = gameFile.description
+ if (gameFile.description.isEmpty()) {
+ tvBinding.textDescription.visibility = View.GONE
+ }
+
+ textCountry.text = country
+ textCompany.text = gameFile.company
+ textGameId.text = gameFile.gameId
+ textRevision.text = gameFile.revision.toString()
+
+ if (!gameFile.shouldShowFileFormatDetails()) {
+ labelFileFormat.setText(R.string.game_details_file_size)
+ textFileFormat.text = fileSize
+
+ labelCompression.visibility = View.GONE
+ textCompression.visibility = View.GONE
+ labelBlockSize.visibility = View.GONE
+ textBlockSize.visibility = View.GONE
+ } else {
+ val blockSize = gameFile.blockSize
+ val compression = gameFile.compressionMethod
+
+ textFileFormat.text = resources.getString(
+ R.string.game_details_size_and_format,
+ gameFile.fileFormatName,
+ fileSize
+ )
+
+ if (compression.isEmpty()) {
+ textCompression.setText(R.string.game_details_no_compression)
+ } else {
+ textCompression.text = gameFile.compressionMethod
+ }
+
+ if (blockSize > 0) {
+ textBlockSize.text = NativeLibrary.FormatSize(blockSize, 0)
+ } else {
+ labelBlockSize.visibility = View.GONE
+ textBlockSize.visibility = View.GONE
+ }
+ }
+ }
+
+ this.lifecycleScope.launch {
+ loadGameBanner(tvBinding.banner, gameFile)
+ }
+
+ builder.setView(tvBinding.root)
+ }
+ 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 {
+ private const val ARG_GAME_PATH = "game_path"
+
+ @JvmStatic
+ fun newInstance(gamePath: String?): GameDetailsDialog {
+ val fragment = GameDetailsDialog()
+ val arguments = Bundle()
+ arguments.putString(ARG_GAME_PATH, gamePath)
+ fragment.arguments = arguments
+ return fragment
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/GridOptionDialogFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/GridOptionDialogFragment.kt
index 5743f6ebc9..186e8f88d0 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/GridOptionDialogFragment.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/GridOptionDialogFragment.kt
@@ -1,5 +1,6 @@
package org.dolphinemu.dolphinemu.fragments
+import android.app.Activity
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import android.view.LayoutInflater
import android.view.ViewGroup
@@ -74,7 +75,7 @@ class GridOptionDialogFragment : BottomSheetDialogFragment() {
NativeConfig.LAYER_BASE,
mBindingMobile.switchDownloadCovers.isChecked
)
- mView.reloadGrid()
+ (mView as Activity).recreate()
}
}
@@ -104,7 +105,7 @@ class GridOptionDialogFragment : BottomSheetDialogFragment() {
NativeConfig.LAYER_BASE,
mBindingTv.switchDownloadCovers.isChecked
)
- mView.reloadGrid()
+ (mView as Activity).recreate()
}
}
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java
index b8de619e31..26f4c169ce 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java
@@ -2,12 +2,15 @@
package org.dolphinemu.dolphinemu.model;
-import android.content.Context;
-
import androidx.annotation.Keep;
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
private long mPointer;
@@ -68,11 +71,6 @@ public class GameFile
public native int getBannerHeight();
- public String getCoverPath(Context context)
- {
- return context.getExternalCacheDir().getPath() + "/GameCovers/" + getGameTdbId() + ".png";
- }
-
public String getCustomCoverPath()
{
return getPath().substring(0, getPath().lastIndexOf(".")) + ".cover.png";
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java
index 0e1f55154b..2da1b8591d 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java
@@ -349,7 +349,7 @@ public final class TvMainActivity extends FragmentActivity
}
// Create an adapter for this row.
- ArrayObjectAdapter row = new ArrayObjectAdapter(new GameRowPresenter());
+ ArrayObjectAdapter row = new ArrayObjectAdapter(new GameRowPresenter(this));
row.addAll(0, gameFiles);
// Keep a reference to the row in case we need to refresh it.
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoilUtils.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoilUtils.kt
new file mode 100644
index 0000000000..233f620d23
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoilUtils.kt
@@ -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
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoverHelper.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoverHelper.java
deleted file mode 100644
index 84ed1be6c5..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoverHelper.java
+++ /dev/null
@@ -1,83 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.utils;
-
-import android.graphics.Bitmap;
-
-import org.dolphinemu.dolphinemu.model.GameFile;
-
-import java.io.FileOutputStream;
-
-public final class CoverHelper
-{
- public static String buildGameTDBUrl(GameFile game, String region)
- {
- String baseUrl = "https://art.gametdb.com/wii/cover/%s/%s.png";
- return String.format(baseUrl, region, game.getGameTdbId());
- }
-
- public static String getRegion(GameFile game)
- {
- String region;
- switch (game.getRegion())
- {
- case 0: // NTSC_J
- region = "JA";
- break;
- case 1: // NTSC_U
- region = "US";
- break;
- case 4: // NTSC_K
- region = "KO";
- break;
- case 2: // PAL
- switch (game.getCountry())
- {
- case 3: // Australia
- region = "AU";
- break;
- case 4: // France
- region = "FR";
- break;
- case 5: // Germany
- region = "DE";
- break;
- case 6: // Italy
- region = "IT";
- break;
- case 8: // Netherlands
- region = "NL";
- break;
- case 9: // Russia
- region = "RU";
- break;
- case 10: // Spain
- region = "ES";
- break;
- case 0: // Europe
- default:
- region = "EN";
- break;
- }
- break;
- case 3: // Unknown
- default:
- region = "EN";
- break;
- }
- return region;
- }
-
- public static void saveCover(Bitmap cover, String path)
- {
- try
- {
- FileOutputStream out = new FileOutputStream(path);
- cover.compress(Bitmap.CompressFormat.PNG, 100, out);
- out.close();
- }
- catch (Exception ignored)
- {
- }
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoverHelper.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoverHelper.kt
new file mode 100644
index 0000000000..9359d38eb3
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoverHelper.kt
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.utils
+
+import org.dolphinemu.dolphinemu.model.GameFile
+
+object CoverHelper {
+ @JvmStatic
+ fun buildGameTDBUrl(game: GameFile, region: String?): String {
+ val baseUrl = "https://art.gametdb.com/wii/cover/%s/%s.png"
+ return String.format(baseUrl, region, game.gameTdbId)
+ }
+
+ @JvmStatic
+ fun getRegion(game: GameFile): String {
+ val region: String = when (game.region) {
+ GameFile.REGION_NTSC_J -> "JA"
+ GameFile.REGION_NTSC_U -> "US"
+ GameFile.REGION_NTSC_K -> "KO"
+ GameFile.REGION_PAL -> when (game.country) {
+ 3 -> "AU" // Australia
+ 4 -> "FR" // France
+ 5 -> "DE" // Germany
+ 6 -> "IT" // Italy
+ 8 -> "NL" // Netherlands
+ 9 -> "RU" // Russia
+ 10 -> "ES" // Spain
+ 0 -> "EN" // Europe
+ else -> "EN"
+ }
+ 3 -> "EN" // Unknown
+ else -> "EN"
+ }
+ return region
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GlideUtils.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GlideUtils.java
deleted file mode 100644
index 766d9684dc..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GlideUtils.java
+++ /dev/null
@@ -1,248 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.utils;
-
-import android.app.Activity;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.view.View;
-import android.widget.ImageView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.bumptech.glide.Glide;
-import com.bumptech.glide.load.DataSource;
-import com.bumptech.glide.load.engine.DiskCacheStrategy;
-import com.bumptech.glide.load.engine.GlideException;
-import com.bumptech.glide.request.RequestListener;
-import com.bumptech.glide.request.target.CustomTarget;
-import com.bumptech.glide.request.target.Target;
-import com.bumptech.glide.request.transition.Transition;
-
-import org.dolphinemu.dolphinemu.R;
-import org.dolphinemu.dolphinemu.adapters.GameAdapter;
-import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting;
-import org.dolphinemu.dolphinemu.model.GameFile;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-public class GlideUtils
-{
- private static final ExecutorService saveCoverExecutor = Executors.newSingleThreadExecutor();
- private static final ExecutorService unmangleExecutor = Executors.newSingleThreadExecutor();
- private static final Handler unmangleHandler = new Handler(Looper.getMainLooper());
-
- public static void loadGameBanner(ImageView imageView, GameFile gameFile)
- {
- Context context = imageView.getContext();
- int[] vector = gameFile.getBanner();
- int width = gameFile.getBannerWidth();
- int height = gameFile.getBannerHeight();
- if (width > 0 && height > 0)
- {
- Bitmap 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);
- }
- }
-
- public static void loadGameCover(GameAdapter.GameViewHolder gameViewHolder, ImageView imageView,
- GameFile gameFile, Activity activity)
- {
- if (BooleanSetting.MAIN_SHOW_GAME_TITLES.getBooleanGlobal() && gameViewHolder != null)
- {
- gameViewHolder.binding.textGameTitle.setText(gameFile.getTitle());
- gameViewHolder.binding.textGameTitle.setVisibility(View.VISIBLE);
- gameViewHolder.binding.textGameTitleInner.setVisibility(View.GONE);
- gameViewHolder.binding.textGameCaption.setVisibility(View.VISIBLE);
- }
- else if (gameViewHolder != null)
- {
- gameViewHolder.binding.textGameTitleInner.setText(gameFile.getTitle());
- gameViewHolder.binding.textGameTitle.setVisibility(View.GONE);
- gameViewHolder.binding.textGameCaption.setVisibility(View.GONE);
- }
-
- unmangleExecutor.execute(() ->
- {
- String customCoverPath = gameFile.getCustomCoverPath();
- Uri customCoverUri = null;
- boolean customCoverExists = false;
- if (ContentHandler.isContentUri(customCoverPath))
- {
- try
- {
- customCoverUri = ContentHandler.unmangle(customCoverPath);
- customCoverExists = true;
- }
- catch (FileNotFoundException | SecurityException ignored)
- {
- // Let customCoverExists remain false
- }
- }
- else
- {
- customCoverUri = Uri.parse(customCoverPath);
- customCoverExists = new File(customCoverPath).exists();
- }
-
- Context context = imageView.getContext();
- boolean finalCustomCoverExists = customCoverExists;
- Uri finalCustomCoverUri = customCoverUri;
-
- File cover = new File(gameFile.getCoverPath(context));
- boolean 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;
- }
- }
-
- if (finalCustomCoverExists)
- {
- Glide.with(imageView)
- .load(finalCustomCoverUri)
- .diskCacheStrategy(DiskCacheStrategy.NONE)
- .centerCrop()
- .error(R.drawable.no_banner)
- .listener(new RequestListener<Drawable>()
- {
- @Override public boolean onLoadFailed(@Nullable GlideException e, Object model,
- Target<Drawable> target, boolean isFirstResource)
- {
- GlideUtils.enableInnerTitle(gameViewHolder);
- return false;
- }
-
- @Override public boolean onResourceReady(Drawable resource, Object model,
- Target<Drawable> target, DataSource dataSource, boolean isFirstResource)
- {
- GlideUtils.disableInnerTitle(gameViewHolder);
- return false;
- }
- })
- .into(imageView);
- }
- else if (cachedCoverExists)
- {
- Glide.with(imageView)
- .load(cover)
- .diskCacheStrategy(DiskCacheStrategy.NONE)
- .centerCrop()
- .error(R.drawable.no_banner)
- .listener(new RequestListener<Drawable>()
- {
- @Override
- public boolean onLoadFailed(@Nullable GlideException e, Object model,
- Target<Drawable> target, boolean isFirstResource)
- {
- GlideUtils.enableInnerTitle(gameViewHolder);
- return false;
- }
-
- @Override
- public boolean onResourceReady(Drawable resource, Object model,
- Target<Drawable> target, DataSource dataSource, boolean isFirstResource)
- {
- GlideUtils.disableInnerTitle(gameViewHolder);
- return false;
- }
- })
- .into(imageView);
- }
- else if (BooleanSetting.MAIN_USE_GAME_COVERS.getBooleanGlobal())
- {
- Glide.with(context)
- .load(CoverHelper.buildGameTDBUrl(gameFile, CoverHelper.getRegion(gameFile)))
- .diskCacheStrategy(DiskCacheStrategy.NONE)
- .centerCrop()
- .error(R.drawable.no_banner)
- .listener(new RequestListener<Drawable>()
- {
- @Override
- public boolean onLoadFailed(@Nullable GlideException e, Object model,
- Target<Drawable> target, boolean isFirstResource)
- {
- GlideUtils.enableInnerTitle(gameViewHolder);
- return false;
- }
-
- @Override
- public boolean onResourceReady(Drawable resource, Object model,
- Target<Drawable> target, DataSource dataSource, boolean isFirstResource)
- {
- GlideUtils.disableInnerTitle(gameViewHolder);
- return false;
- }
- })
- .into(new CustomTarget<Drawable>()
- {
- @Override
- public void onResourceReady(@NonNull Drawable resource,
- @Nullable Transition<? super Drawable> transition)
- {
- Bitmap cover = ((BitmapDrawable) resource).getBitmap();
- saveCoverExecutor.execute(
- () -> CoverHelper.saveCover(cover, gameFile.getCoverPath(context)));
- imageView.setImageBitmap(cover);
- }
-
- @Override
- public void onLoadCleared(@Nullable Drawable placeholder)
- {
- }
- });
- }
- else
- {
- Glide.with(imageView.getContext())
- .load(R.drawable.no_banner)
- .into(imageView);
- enableInnerTitle(gameViewHolder);
- }
- });
- });
- }
-
- private static void enableInnerTitle(GameAdapter.GameViewHolder gameViewHolder)
- {
- if (gameViewHolder != null && !BooleanSetting.MAIN_SHOW_GAME_TITLES.getBooleanGlobal())
- {
- gameViewHolder.binding.textGameTitleInner.setVisibility(View.VISIBLE);
- }
- }
-
- private static void disableInnerTitle(GameAdapter.GameViewHolder gameViewHolder)
- {
- if (gameViewHolder != null && !BooleanSetting.MAIN_SHOW_GAME_TITLES.getBooleanGlobal())
- {
- gameViewHolder.binding.textGameTitleInner.setVisibility(View.GONE);
- }
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/TvUtil.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/TvUtil.java
index 940542ef49..57c567715c 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/TvUtil.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/TvUtil.java
@@ -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);
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/TvGameViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/TvGameViewHolder.java
deleted file mode 100644
index b51caa7707..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/TvGameViewHolder.java
+++ /dev/null
@@ -1,34 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.viewholders;
-
-import android.view.View;
-import android.widget.ImageView;
-
-import androidx.leanback.widget.ImageCardView;
-import androidx.leanback.widget.Presenter;
-
-import org.dolphinemu.dolphinemu.model.GameFile;
-
-/**
- * A simple class that stores references to views so that the GameAdapter doesn't need to
- * keep calling findViewById(), which is expensive.
- */
-public final class TvGameViewHolder extends Presenter.ViewHolder
-{
- public ImageCardView cardParent;
-
- public ImageView imageScreenshot;
-
- public GameFile gameFile;
-
- public TvGameViewHolder(View itemView)
- {
- super(itemView);
-
- itemView.setTag(this);
-
- cardParent = (ImageCardView) itemView;
- imageScreenshot = cardParent.getMainImageView();
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/TvGameViewHolder.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/TvGameViewHolder.kt
new file mode 100644
index 0000000000..6fc5adbfa7
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/TvGameViewHolder.kt
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.viewholders
+
+import android.view.View
+import android.widget.ImageView
+import androidx.leanback.widget.Presenter
+import androidx.leanback.widget.ImageCardView
+import org.dolphinemu.dolphinemu.model.GameFile
+
+/**
+ * A simple class that stores references to views so that the GameAdapter doesn't need to
+ * keep calling findViewById(), which is expensive.
+ */
+class TvGameViewHolder(itemView: View) : Presenter.ViewHolder(itemView) {
+ var cardParent: ImageCardView
+ var imageScreenshot: ImageView
+
+ @JvmField
+ var gameFile: GameFile? = null
+
+ init {
+ itemView.tag = this
+ cardParent = itemView as ImageCardView
+ imageScreenshot = cardParent.mainImageView
+ }
+}