diff --git a/Source/Android/app/build.gradle b/Source/Android/app/build.gradle index 3b1f8c3ca4..11349147e8 100644 --- a/Source/Android/app/build.gradle +++ b/Source/Android/app/build.gradle @@ -155,7 +155,7 @@ dependencies { implementation 'com.android.volley:volley:1.2.1' // For loading game covers from disk and GameTDB - implementation 'com.github.bumptech.glide:glide:4.13.2' + implementation 'io.coil-kt:coil:2.2.2' implementation 'com.nononsenseapps:filepicker:4.2.1' } 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 implements - View.OnClickListener, - View.OnLongClickListener -{ - private List 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 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(), + View.OnClickListener, OnLongClickListener { + private var mGameFiles: List = 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) { + 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() - { - @Override public boolean onLoadFailed(@Nullable GlideException e, Object model, - Target target, boolean isFirstResource) - { - GlideUtils.enableInnerTitle(gameViewHolder); - return false; - } - - @Override public boolean onResourceReady(Drawable resource, Object model, - Target 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() - { - @Override - public boolean onLoadFailed(@Nullable GlideException e, Object model, - Target target, boolean isFirstResource) - { - GlideUtils.enableInnerTitle(gameViewHolder); - return false; - } - - @Override - public boolean onResourceReady(Drawable resource, Object model, - Target 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() - { - @Override - public boolean onLoadFailed(@Nullable GlideException e, Object model, - Target target, boolean isFirstResource) - { - GlideUtils.enableInnerTitle(gameViewHolder); - return false; - } - - @Override - public boolean onResourceReady(Drawable resource, Object model, - Target target, DataSource dataSource, boolean isFirstResource) - { - GlideUtils.disableInnerTitle(gameViewHolder); - return false; - } - }) - .into(new CustomTarget() - { - @Override - public void onResourceReady(@NonNull Drawable resource, - @Nullable Transition 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 + } +}