mirror of
https://git.suyu.dev/suyu/suyu
synced 2024-12-25 19:02:45 -06:00
android: Convert GameAdapter to Kotlin
This commit is contained in:
parent
87f4c3f105
commit
0d044e9f2f
2 changed files with 178 additions and 244 deletions
|
@ -1,244 +0,0 @@
|
|||
package org.yuzu.yuzu_emu.adapters;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.DataSetObserver;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.SystemClock;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.yuzu.yuzu_emu.YuzuApplication;
|
||||
import org.yuzu.yuzu_emu.R;
|
||||
import org.yuzu.yuzu_emu.activities.EmulationActivity;
|
||||
import org.yuzu.yuzu_emu.model.GameDatabase;
|
||||
import org.yuzu.yuzu_emu.ui.DividerItemDecoration;
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil;
|
||||
import org.yuzu.yuzu_emu.utils.Log;
|
||||
import org.yuzu.yuzu_emu.utils.PicassoUtils;
|
||||
import org.yuzu.yuzu_emu.viewholders.GameViewHolder;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* This adapter gets its information from a database Cursor. This fact, paired with the usage of
|
||||
* ContentProviders and Loaders, allows for efficient display of a limited view into a (possibly)
|
||||
* large dataset.
|
||||
*/
|
||||
public final class GameAdapter extends RecyclerView.Adapter<GameViewHolder> implements
|
||||
View.OnClickListener {
|
||||
private Cursor mCursor;
|
||||
private GameDataSetObserver mObserver;
|
||||
|
||||
private boolean mDatasetValid;
|
||||
private long mLastClickTime = 0;
|
||||
|
||||
/**
|
||||
* Initializes the adapter's observer, which watches for changes to the dataset. The adapter will
|
||||
* display no data until a Cursor is supplied by a CursorLoader.
|
||||
*/
|
||||
public GameAdapter() {
|
||||
mDatasetValid = false;
|
||||
mObserver = new GameDataSetObserver();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
public GameViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
// Create a new view.
|
||||
View gameCard = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.card_game, parent, false);
|
||||
|
||||
gameCard.setOnClickListener(this);
|
||||
|
||||
// Use that view to create a ViewHolder.
|
||||
return new GameViewHolder(gameCard);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull GameViewHolder holder, int position) {
|
||||
if (mDatasetValid) {
|
||||
if (mCursor.moveToPosition(position)) {
|
||||
PicassoUtils.loadGameIcon(holder.imageIcon,
|
||||
mCursor.getString(GameDatabase.GAME_COLUMN_PATH));
|
||||
|
||||
holder.textGameTitle.setText(mCursor.getString(GameDatabase.GAME_COLUMN_TITLE).replaceAll("[\\t\\n\\r]+", " "));
|
||||
holder.textGameCaption.setText(mCursor.getString(GameDatabase.GAME_COLUMN_CAPTION));
|
||||
|
||||
// TODO These shouldn't be necessary once the move to a DB-based model is complete.
|
||||
holder.gameId = mCursor.getString(GameDatabase.GAME_COLUMN_GAME_ID);
|
||||
holder.path = mCursor.getString(GameDatabase.GAME_COLUMN_PATH);
|
||||
holder.title = mCursor.getString(GameDatabase.GAME_COLUMN_TITLE);
|
||||
holder.description = mCursor.getString(GameDatabase.GAME_COLUMN_DESCRIPTION);
|
||||
holder.regions = mCursor.getString(GameDatabase.GAME_COLUMN_REGIONS);
|
||||
holder.company = mCursor.getString(GameDatabase.GAME_COLUMN_CAPTION);
|
||||
|
||||
final int backgroundColorId = isValidGame(holder.path) ? R.color.view_background : R.color.view_disabled;
|
||||
View itemView = holder.getItemView();
|
||||
itemView.setBackgroundColor(ContextCompat.getColor(itemView.getContext(), backgroundColorId));
|
||||
} else {
|
||||
Log.error("[GameAdapter] Can't bind view; Cursor is not valid.");
|
||||
}
|
||||
} else {
|
||||
Log.error("[GameAdapter] Can't bind view; dataset is not valid.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the LayoutManager to find out how much data we have.
|
||||
*
|
||||
* @return Size of the dataset.
|
||||
*/
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (mDatasetValid && mCursor != null) {
|
||||
return mCursor.getCount();
|
||||
}
|
||||
Log.error("[GameAdapter] Dataset is not valid.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the contents of the _id column for a given row.
|
||||
*
|
||||
* @param position The row for which Android wants an ID.
|
||||
* @return A valid ID from the database, or 0 if not available.
|
||||
*/
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
if (mDatasetValid && mCursor != null) {
|
||||
if (mCursor.moveToPosition(position)) {
|
||||
return mCursor.getLong(GameDatabase.COLUMN_DB_ID);
|
||||
}
|
||||
}
|
||||
|
||||
Log.error("[GameAdapter] Dataset is not valid.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell Android whether or not each item in the dataset has a stable identifier.
|
||||
* Which it does, because it's a database, so always tell Android 'true'.
|
||||
*
|
||||
* @param hasStableIds ignored.
|
||||
*/
|
||||
@Override
|
||||
public void setHasStableIds(boolean hasStableIds) {
|
||||
super.setHasStableIds(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a load is finished, call this to replace the existing data with the newly-loaded
|
||||
* data.
|
||||
*
|
||||
* @param cursor The newly-loaded Cursor.
|
||||
*/
|
||||
public void swapCursor(Cursor cursor) {
|
||||
// Sanity check.
|
||||
if (cursor == mCursor) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Before getting rid of the old cursor, disassociate it from the Observer.
|
||||
final Cursor oldCursor = mCursor;
|
||||
if (oldCursor != null && mObserver != null) {
|
||||
oldCursor.unregisterDataSetObserver(mObserver);
|
||||
}
|
||||
|
||||
mCursor = cursor;
|
||||
if (mCursor != null) {
|
||||
// Attempt to associate the new Cursor with the Observer.
|
||||
if (mObserver != null) {
|
||||
mCursor.registerDataSetObserver(mObserver);
|
||||
}
|
||||
|
||||
mDatasetValid = true;
|
||||
} else {
|
||||
mDatasetValid = false;
|
||||
}
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
// Double-click prevention, using threshold of 1000 ms
|
||||
if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) {
|
||||
return;
|
||||
}
|
||||
mLastClickTime = SystemClock.elapsedRealtime();
|
||||
|
||||
GameViewHolder holder = (GameViewHolder) view.getTag();
|
||||
|
||||
EmulationActivity.launch((FragmentActivity) view.getContext(), holder.path, holder.title);
|
||||
}
|
||||
|
||||
public static class SpacesItemDecoration extends DividerItemDecoration {
|
||||
private int space;
|
||||
|
||||
public SpacesItemDecoration(Drawable divider, int space) {
|
||||
super(divider);
|
||||
this.space = space;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, @NonNull View view, @NonNull RecyclerView parent,
|
||||
@NonNull RecyclerView.State state) {
|
||||
outRect.left = 0;
|
||||
outRect.right = 0;
|
||||
outRect.bottom = space;
|
||||
outRect.top = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidGame(String path) {
|
||||
return Stream.of(
|
||||
".rar", ".zip", ".7z", ".torrent", ".tar", ".gz").noneMatch(suffix -> path.toLowerCase().endsWith(suffix));
|
||||
}
|
||||
|
||||
private final class GameDataSetObserver extends DataSetObserver {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
super.onChanged();
|
||||
|
||||
mDatasetValid = true;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInvalidated() {
|
||||
super.onInvalidated();
|
||||
|
||||
mDatasetValid = false;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.database.Cursor
|
||||
import android.database.DataSetObserver
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.activities.EmulationActivity.Companion.launch
|
||||
import org.yuzu.yuzu_emu.model.GameDatabase
|
||||
import org.yuzu.yuzu_emu.utils.Log
|
||||
import org.yuzu.yuzu_emu.utils.PicassoUtils
|
||||
import org.yuzu.yuzu_emu.viewholders.GameViewHolder
|
||||
import java.util.*
|
||||
import java.util.stream.Stream
|
||||
|
||||
/**
|
||||
* This adapter gets its information from a database Cursor. This fact, paired with the usage of
|
||||
* ContentProviders and Loaders, allows for efficient display of a limited view into a (possibly)
|
||||
* large dataset.
|
||||
*/
|
||||
class GameAdapter : RecyclerView.Adapter<GameViewHolder>(), View.OnClickListener {
|
||||
private var cursor: Cursor? = null
|
||||
private val observer: GameDataSetObserver?
|
||||
private var isDatasetValid = false
|
||||
|
||||
/**
|
||||
* Initializes the adapter's observer, which watches for changes to the dataset. The adapter will
|
||||
* display no data until a Cursor is supplied by a CursorLoader.
|
||||
*/
|
||||
init {
|
||||
observer = GameDataSetObserver()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder {
|
||||
// Create a new view.
|
||||
val gameCard = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.card_game, parent, false)
|
||||
gameCard.setOnClickListener(this)
|
||||
|
||||
// Use that view to create a ViewHolder.
|
||||
return GameViewHolder(gameCard)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: GameViewHolder, position: Int) {
|
||||
if (isDatasetValid) {
|
||||
if (cursor!!.moveToPosition(position)) {
|
||||
PicassoUtils.loadGameIcon(
|
||||
holder.imageIcon,
|
||||
cursor!!.getString(GameDatabase.GAME_COLUMN_PATH)
|
||||
)
|
||||
holder.textGameTitle.text =
|
||||
cursor!!.getString(GameDatabase.GAME_COLUMN_TITLE)
|
||||
.replace("[\\t\\n\\r]+".toRegex(), " ")
|
||||
holder.textGameCaption.text = cursor!!.getString(GameDatabase.GAME_COLUMN_CAPTION)
|
||||
|
||||
// TODO These shouldn't be necessary once the move to a DB-based model is complete.
|
||||
holder.gameId = cursor!!.getString(GameDatabase.GAME_COLUMN_GAME_ID)
|
||||
holder.path = cursor!!.getString(GameDatabase.GAME_COLUMN_PATH)
|
||||
holder.title = cursor!!.getString(GameDatabase.GAME_COLUMN_TITLE)
|
||||
holder.description = cursor!!.getString(GameDatabase.GAME_COLUMN_DESCRIPTION)
|
||||
holder.regions = cursor!!.getString(GameDatabase.GAME_COLUMN_REGIONS)
|
||||
holder.company = cursor!!.getString(GameDatabase.GAME_COLUMN_CAPTION)
|
||||
val backgroundColorId =
|
||||
if (isValidGame(holder.path!!)) R.color.view_background else R.color.view_disabled
|
||||
val itemView = holder.itemView
|
||||
itemView.setBackgroundColor(
|
||||
ContextCompat.getColor(
|
||||
itemView.context,
|
||||
backgroundColorId
|
||||
)
|
||||
)
|
||||
} else {
|
||||
Log.error("[GameAdapter] Can't bind view; Cursor is not valid.")
|
||||
}
|
||||
} else {
|
||||
Log.error("[GameAdapter] Can't bind view; dataset is not valid.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
if (isDatasetValid && cursor != null) {
|
||||
return cursor!!.count
|
||||
}
|
||||
Log.error("[GameAdapter] Dataset is not valid.")
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the contents of the _id column for a given row.
|
||||
*
|
||||
* @param position The row for which Android wants an ID.
|
||||
* @return A valid ID from the database, or 0 if not available.
|
||||
*/
|
||||
override fun getItemId(position: Int): Long {
|
||||
if (isDatasetValid && cursor != null) {
|
||||
if (cursor!!.moveToPosition(position)) {
|
||||
return cursor!!.getLong(GameDatabase.COLUMN_DB_ID)
|
||||
}
|
||||
}
|
||||
Log.error("[GameAdapter] Dataset is not valid.")
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell Android whether or not each item in the dataset has a stable identifier.
|
||||
* Which it does, because it's a database, so always tell Android 'true'.
|
||||
*
|
||||
* @param hasStableIds ignored.
|
||||
*/
|
||||
override fun setHasStableIds(hasStableIds: Boolean) {
|
||||
super.setHasStableIds(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* When a load is finished, call this to replace the existing data with the newly-loaded
|
||||
* data.
|
||||
*
|
||||
* @param cursor The newly-loaded Cursor.
|
||||
*/
|
||||
fun swapCursor(cursor: Cursor) {
|
||||
// Sanity check.
|
||||
if (cursor === this.cursor) {
|
||||
return
|
||||
}
|
||||
|
||||
// Before getting rid of the old cursor, disassociate it from the Observer.
|
||||
val oldCursor = this.cursor
|
||||
if (oldCursor != null && observer != null) {
|
||||
oldCursor.unregisterDataSetObserver(observer)
|
||||
}
|
||||
this.cursor = cursor
|
||||
isDatasetValid = if (this.cursor != null) {
|
||||
// Attempt to associate the new Cursor with the Observer.
|
||||
if (observer != null) {
|
||||
this.cursor!!.registerDataSetObserver(observer)
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
launch((view.context as FragmentActivity), holder.path, holder.title)
|
||||
}
|
||||
|
||||
private fun isValidGame(path: String): Boolean {
|
||||
return Stream.of(".rar", ".zip", ".7z", ".torrent", ".tar", ".gz")
|
||||
.noneMatch { suffix: String? ->
|
||||
path.lowercase(Locale.getDefault()).endsWith(suffix!!)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class GameDataSetObserver : DataSetObserver() {
|
||||
override fun onChanged() {
|
||||
super.onChanged()
|
||||
isDatasetValid = true
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onInvalidated() {
|
||||
super.onInvalidated()
|
||||
isDatasetValid = false
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue