summaryrefslogtreecommitdiff
path: root/Source/Android/app/src/main/java
diff options
context:
space:
mode:
authorCharles Lombardo <clombardo169@gmail.com>2023-01-06 14:58:31 -0500
committerCharles Lombardo <clombardo169@gmail.com>2023-02-08 19:34:27 -0500
commite6583f8bec814d8f3748f1d7738457600ce0de56 (patch)
tree3cbb2d54eac1e60733db1d0e74e5589306bec154 /Source/Android/app/src/main/java
parent72b22ef0a54f841b1c52d49cf0e00d3b12c29ac7 (diff)
Android: Convert image loading code to Kotlin
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.kt160
-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.kt72
-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.kt145
-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.kt46
-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/GlideUtils.kt233
-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
12 files changed, 683 insertions, 809 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..e3fa287967
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.adapters
+
+import android.annotation.SuppressLint
+import android.app.Activity
+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.utils.GlideUtils
+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 org.dolphinemu.dolphinemu.databinding.CardGameBinding
+import org.dolphinemu.dolphinemu.dialogs.GamePropertiesDialog
+import java.util.ArrayList
+
+class GameAdapter(activity: Activity) : RecyclerView.Adapter<GameViewHolder>(),
+ View.OnClickListener, OnLongClickListener {
+ private var mGameFiles: List<GameFile>
+ private val mActivity: Activity
+
+ /**
+ * Initializes the adapter's observer, which watches for changes to the dataset. The adapter will
+ * display no data until swapDataSet is called.
+ */
+ init {
+ mGameFiles = 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.
+ */
+ 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]
+
+ GlideUtils.loadGameCover(holder, holder.binding.imageGameScreen, gameFile, mActivity)
+
+ 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(binding: CardGameBinding) : RecyclerView.ViewHolder(binding.root) {
+ var gameFile: GameFile? = null
+
+ @JvmField
+ var binding: CardGameBinding
+
+ init {
+ binding.root.tag = this
+ this.binding = binding
+ }
+ }
+
+ /**
+ * 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..66a249e50d
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt
@@ -0,0 +1,72 @@
+// 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.utils.GlideUtils
+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 org.dolphinemu.dolphinemu.dialogs.GamePropertiesDialog
+
+/**
+ * The Leanback library / docs call this a Presenter, but it works very
+ * similarly to a RecyclerView.Adapter.
+ */
+class GameRowPresenter : 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
+ }
+ }
+ GlideUtils.loadGameCover(null, holder.imageScreenshot, gameFile, null)
+ }
+
+ 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..5c7cf2ed6a
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.kt
@@ -0,0 +1,145 @@
+// 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 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 org.dolphinemu.dolphinemu.NativeLibrary
+import org.dolphinemu.dolphinemu.databinding.DialogGameDetailsBinding
+import org.dolphinemu.dolphinemu.databinding.DialogGameDetailsTvBinding
+import org.dolphinemu.dolphinemu.utils.GlideUtils
+
+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
+ }
+ }
+ }
+
+ GlideUtils.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
+ }
+ }
+ }
+
+ GlideUtils.loadGameBanner(tvBinding.banner, gameFile)
+
+ builder.setView(tvBinding.root)
+ }
+ return builder.create()
+ }
+
+ 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/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..f1deeb3730
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoverHelper.kt
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.utils
+
+import org.dolphinemu.dolphinemu.model.GameFile
+import android.graphics.Bitmap
+import java.io.FileOutputStream
+import java.lang.Exception
+
+object CoverHelper {
+ 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)
+ }
+
+ fun getRegion(game: GameFile): String {
+ val region: String = when (game.region) {
+ 0 -> "JA"
+ 1 -> "US"
+ 4 -> "KO"
+ 2 -> when (game.country) {
+ 3 -> "AU"
+ 4 -> "FR"
+ 5 -> "DE"
+ 6 -> "IT"
+ 8 -> "NL"
+ 9 -> "RU"
+ 10 -> "ES"
+ 0 -> "EN"
+ else -> "EN"
+ }
+ 3 -> "EN"
+ else -> "EN"
+ }
+ return region
+ }
+
+ fun saveCover(cover: Bitmap, path: String?) {
+ try {
+ val out = FileOutputStream(path)
+ cover.compress(Bitmap.CompressFormat.PNG, 100, out)
+ out.close()
+ } catch (ignored: Exception) {
+ }
+ }
+}
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/GlideUtils.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GlideUtils.kt
new file mode 100644
index 0000000000..ba2b9e344f
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GlideUtils.kt
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.utils
+
+import org.dolphinemu.dolphinemu.utils.CoverHelper.buildGameTDBUrl
+import org.dolphinemu.dolphinemu.utils.CoverHelper.getRegion
+import org.dolphinemu.dolphinemu.utils.CoverHelper.saveCover
+import android.os.Looper
+import org.dolphinemu.dolphinemu.model.GameFile
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.engine.DiskCacheStrategy
+import org.dolphinemu.dolphinemu.R
+import org.dolphinemu.dolphinemu.adapters.GameAdapter.GameViewHolder
+import android.app.Activity
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
+import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
+import com.bumptech.glide.request.RequestListener
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import android.os.Handler
+import android.view.View
+import android.widget.ImageView
+import com.bumptech.glide.load.DataSource
+import com.bumptech.glide.load.engine.GlideException
+import com.bumptech.glide.request.target.CustomTarget
+import com.bumptech.glide.request.target.Target
+import com.bumptech.glide.request.transition.Transition
+import java.io.File
+import java.io.FileNotFoundException
+import java.util.concurrent.Executors
+
+object GlideUtils {
+ private val saveCoverExecutor = Executors.newSingleThreadExecutor()
+ private val unmangleExecutor = Executors.newSingleThreadExecutor()
+ private val unmangleHandler = Handler(Looper.getMainLooper())
+
+ fun loadGameBanner(imageView: ImageView, gameFile: GameFile) {
+ val context = imageView.context
+ val vector = gameFile.banner
+ val width = gameFile.bannerWidth
+ val height = gameFile.bannerHeight
+ if (width > 0 && height > 0) {
+ val 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)
+ }
+ }
+
+ fun loadGameCover(
+ gameViewHolder: GameViewHolder?,
+ imageView: ImageView,
+ gameFile: GameFile,
+ activity: Activity?
+ ) {
+ gameViewHolder?.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.textGameTitle.visibility = View.GONE
+ binding.textGameCaption.visibility = View.GONE
+ }
+ }
+
+ unmangleExecutor.execute {
+ 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) {
+ // Let customCoverExists remain false
+ } catch (ignored: SecurityException) {
+ }
+ } else {
+ customCoverUri = Uri.parse(customCoverPath)
+ customCoverExists = File(customCoverPath).exists()
+ }
+
+ val context = imageView.context
+ val finalCustomCoverExists = customCoverExists
+ val finalCustomCoverUri = customCoverUri
+
+ val cover = File(gameFile.getCoverPath(context))
+ val 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@post
+ }
+ }
+
+ if (finalCustomCoverExists) {
+ Glide.with(imageView)
+ .load(finalCustomCoverUri)
+ .diskCacheStrategy(DiskCacheStrategy.NONE)
+ .centerCrop()
+ .error(R.drawable.no_banner)
+ .listener(object : RequestListener<Drawable?> {
+ override fun onLoadFailed(
+ e: GlideException?,
+ model: Any,
+ target: Target<Drawable?>,
+ isFirstResource: Boolean
+ ): Boolean {
+ enableInnerTitle(gameViewHolder)
+ return false
+ }
+
+ override fun onResourceReady(
+ resource: Drawable?,
+ model: Any,
+ target: Target<Drawable?>,
+ dataSource: DataSource,
+ isFirstResource: Boolean
+ ): Boolean {
+ disableInnerTitle(gameViewHolder)
+ return false
+ }
+ })
+ .into(imageView)
+ } else if (cachedCoverExists) {
+ Glide.with(imageView)
+ .load(cover)
+ .diskCacheStrategy(DiskCacheStrategy.NONE)
+ .centerCrop()
+ .error(R.drawable.no_banner)
+ .listener(object : RequestListener<Drawable?> {
+ override fun onLoadFailed(
+ e: GlideException?,
+ model: Any,
+ target: Target<Drawable?>,
+ isFirstResource: Boolean
+ ): Boolean {
+ enableInnerTitle(gameViewHolder)
+ return false
+ }
+
+ override fun onResourceReady(
+ resource: Drawable?,
+ model: Any,
+ target: Target<Drawable?>,
+ dataSource: DataSource,
+ isFirstResource: Boolean
+ ): Boolean {
+ disableInnerTitle(gameViewHolder)
+ return false
+ }
+ })
+ .into(imageView)
+ } else if (BooleanSetting.MAIN_USE_GAME_COVERS.booleanGlobal) {
+ Glide.with(context)
+ .load(buildGameTDBUrl(gameFile, getRegion(gameFile)))
+ .diskCacheStrategy(DiskCacheStrategy.NONE)
+ .centerCrop()
+ .error(R.drawable.no_banner)
+ .listener(object : RequestListener<Drawable?> {
+ override fun onLoadFailed(
+ e: GlideException?,
+ model: Any,
+ target: Target<Drawable?>,
+ isFirstResource: Boolean
+ ): Boolean {
+ enableInnerTitle(gameViewHolder)
+ return false
+ }
+
+ override fun onResourceReady(
+ resource: Drawable?,
+ model: Any,
+ target: Target<Drawable?>,
+ dataSource: DataSource,
+ isFirstResource: Boolean
+ ): Boolean {
+ disableInnerTitle(gameViewHolder)
+ return false
+ }
+ })
+ .into(object : CustomTarget<Drawable?>() {
+ override fun onLoadCleared(placeholder: Drawable?) {}
+ override fun onResourceReady(
+ resource: Drawable,
+ transition: Transition<in Drawable?>?
+ ) {
+ val savedCover = (resource as BitmapDrawable).bitmap
+ saveCoverExecutor.execute {
+ saveCover(
+ savedCover,
+ gameFile.getCoverPath(context)
+ )
+ }
+ imageView.setImageBitmap(savedCover)
+ }
+ })
+ } else {
+ Glide.with(imageView.context)
+ .load(R.drawable.no_banner)
+ .into(imageView)
+ 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
+ }
+ }
+}
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
+ }
+}