Android: Add game grid view
This commit is contained in:
parent
2106197418
commit
5bd39bc2c7
|
@ -0,0 +1,159 @@
|
||||||
|
package com.github.stenzek.duckstation;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.widget.PopupMenu;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
public class GameGridFragment extends Fragment implements GameList.OnRefreshListener {
|
||||||
|
private static final int SPACING_DIPS = 25;
|
||||||
|
private static final int WIDTH_DIPS = 160;
|
||||||
|
|
||||||
|
private MainActivity mParent;
|
||||||
|
private RecyclerView mRecyclerView;
|
||||||
|
private ViewAdapter mAdapter;
|
||||||
|
private GridAutofitLayoutManager mLayoutManager;
|
||||||
|
|
||||||
|
public GameGridFragment(MainActivity parent) {
|
||||||
|
super(R.layout.fragment_game_grid);
|
||||||
|
this.mParent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GameList getGameList() {
|
||||||
|
return mParent.getGameList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
|
mAdapter = new ViewAdapter(mParent, getGameList());
|
||||||
|
getGameList().addRefreshListener(this);
|
||||||
|
|
||||||
|
mRecyclerView = view.findViewById(R.id.game_list_view);
|
||||||
|
mRecyclerView.setAdapter(mAdapter);
|
||||||
|
|
||||||
|
final int columnWidth = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
|
||||||
|
WIDTH_DIPS + SPACING_DIPS, getResources().getDisplayMetrics()));
|
||||||
|
mLayoutManager = new GridAutofitLayoutManager(getContext(), columnWidth);
|
||||||
|
mRecyclerView.setLayoutManager(mLayoutManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
|
||||||
|
getGameList().removeRefreshListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onGameListRefresh() {
|
||||||
|
mAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
|
||||||
|
private MainActivity mParent;
|
||||||
|
private ImageView mImageView;
|
||||||
|
private GameListEntry mEntry;
|
||||||
|
|
||||||
|
public ViewHolder(@NonNull MainActivity parent, @NonNull View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
mParent = parent;
|
||||||
|
mImageView = itemView.findViewById(R.id.imageView);
|
||||||
|
mImageView.setOnClickListener(this);
|
||||||
|
mImageView.setOnLongClickListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bindToEntry(GameListEntry entry) {
|
||||||
|
mEntry = entry;
|
||||||
|
|
||||||
|
final String coverPath = entry.getCoverPath();
|
||||||
|
if (coverPath == null) {
|
||||||
|
mImageView.setImageDrawable(ContextCompat.getDrawable(mParent, R.drawable.ic_media_cdrom));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new ImageLoadTask(mImageView).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, coverPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
mParent.startEmulation(mEntry.getPath(), mParent.shouldResumeStateByDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onLongClick(View v) {
|
||||||
|
PopupMenu menu = new PopupMenu(mParent, v, Gravity.RIGHT | Gravity.TOP);
|
||||||
|
menu.getMenuInflater().inflate(R.menu.menu_game_list_entry, menu.getMenu());
|
||||||
|
menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onMenuItemClick(MenuItem item) {
|
||||||
|
int id = item.getItemId();
|
||||||
|
if (id == R.id.game_list_entry_menu_start_game) {
|
||||||
|
mParent.startEmulation(mEntry.getPath(), false);
|
||||||
|
return true;
|
||||||
|
} else if (id == R.id.game_list_entry_menu_resume_game) {
|
||||||
|
mParent.startEmulation(mEntry.getPath(), true);
|
||||||
|
return true;
|
||||||
|
} else if (id == R.id.game_list_entry_menu_properties) {
|
||||||
|
mParent.openGameProperties(mEntry.getPath());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
menu.show();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ViewAdapter extends RecyclerView.Adapter<ViewHolder> {
|
||||||
|
private MainActivity mParent;
|
||||||
|
private LayoutInflater mInflater;
|
||||||
|
private GameList mGameList;
|
||||||
|
|
||||||
|
public ViewAdapter(@NonNull MainActivity parent, @NonNull GameList gameList) {
|
||||||
|
mParent = parent;
|
||||||
|
mInflater = LayoutInflater.from(parent);
|
||||||
|
mGameList = gameList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
return new ViewHolder(mParent, mInflater.inflate(R.layout.layout_game_grid_entry, parent, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||||
|
GameListEntry entry = mGameList.getEntry(position);
|
||||||
|
holder.bindToEntry(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return mGameList.getEntryCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
return R.layout.layout_game_grid_entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,13 +8,19 @@ import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.BaseAdapter;
|
import android.widget.BaseAdapter;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
|
||||||
public class GameList {
|
public class GameList {
|
||||||
|
public interface OnRefreshListener {
|
||||||
|
void onGameListRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
private Activity mContext;
|
private Activity mContext;
|
||||||
private GameListEntry[] mEntries;
|
private GameListEntry[] mEntries;
|
||||||
private ListViewAdapter mAdapter;
|
private ListViewAdapter mAdapter;
|
||||||
|
private ArrayList<OnRefreshListener> mRefreshListeners = new ArrayList<>();
|
||||||
|
|
||||||
public GameList(Activity context) {
|
public GameList(Activity context) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
|
@ -22,6 +28,13 @@ public class GameList {
|
||||||
mEntries = new GameListEntry[0];
|
mEntries = new GameListEntry[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addRefreshListener(OnRefreshListener listener) {
|
||||||
|
mRefreshListeners.add(listener);
|
||||||
|
}
|
||||||
|
public void removeRefreshListener(OnRefreshListener listener) {
|
||||||
|
mRefreshListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
private class GameListEntryComparator implements Comparator<GameListEntry> {
|
private class GameListEntryComparator implements Comparator<GameListEntry> {
|
||||||
@Override
|
@Override
|
||||||
public int compare(GameListEntry left, GameListEntry right) {
|
public int compare(GameListEntry left, GameListEntry right) {
|
||||||
|
@ -29,7 +42,6 @@ public class GameList {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void refresh(boolean invalidateCache, boolean invalidateDatabase, Activity parentActivity) {
|
public void refresh(boolean invalidateCache, boolean invalidateDatabase, Activity parentActivity) {
|
||||||
// Search and get entries from native code
|
// Search and get entries from native code
|
||||||
AndroidProgressCallback progressCallback = new AndroidProgressCallback(mContext);
|
AndroidProgressCallback progressCallback = new AndroidProgressCallback(mContext);
|
||||||
|
@ -47,6 +59,8 @@ public class GameList {
|
||||||
}
|
}
|
||||||
mEntries = newEntries;
|
mEntries = newEntries;
|
||||||
mAdapter.notifyDataSetChanged();
|
mAdapter.notifyDataSetChanged();
|
||||||
|
for (OnRefreshListener listener : mRefreshListeners)
|
||||||
|
listener.onGameListRefresh();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,6 +96,8 @@ public class GameListEntry {
|
||||||
return mCompatibilityRating;
|
return mCompatibilityRating;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCoverPath() { return mCoverPath; }
|
||||||
|
|
||||||
public static String getFileNameForPath(String path) {
|
public static String getFileNameForPath(String path) {
|
||||||
int lastSlash = path.lastIndexOf('/');
|
int lastSlash = path.lastIndexOf('/');
|
||||||
if (lastSlash > 0 && lastSlash < path.length() - 1)
|
if (lastSlash > 0 && lastSlash < path.length() - 1)
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
package com.github.stenzek.duckstation;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.PopupMenu;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
public class GameListFragment extends Fragment {
|
||||||
|
private MainActivity mParent;
|
||||||
|
private ListView mGameListView;
|
||||||
|
|
||||||
|
public GameListFragment(MainActivity parent) {
|
||||||
|
super(R.layout.fragment_game_list);
|
||||||
|
this.mParent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GameList getGameList() {
|
||||||
|
return mParent.getGameList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
|
mGameListView = view.findViewById(R.id.game_list_view);
|
||||||
|
mGameListView.setAdapter(getGameList().getListViewAdapter());
|
||||||
|
mGameListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
mParent.startEmulation(getGameList().getEntry(position).getPath(), mParent.shouldResumeStateByDefault());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mGameListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
|
||||||
|
long id) {
|
||||||
|
PopupMenu menu = new PopupMenu(getContext(), view, Gravity.RIGHT | Gravity.TOP);
|
||||||
|
menu.getMenuInflater().inflate(R.menu.menu_game_list_entry, menu.getMenu());
|
||||||
|
menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onMenuItemClick(MenuItem item) {
|
||||||
|
int id = item.getItemId();
|
||||||
|
if (id == R.id.game_list_entry_menu_start_game) {
|
||||||
|
mParent.startEmulation(getGameList().getEntry(position).getPath(), false);
|
||||||
|
return true;
|
||||||
|
} else if (id == R.id.game_list_entry_menu_resume_game) {
|
||||||
|
mParent.startEmulation(getGameList().getEntry(position).getPath(), true);
|
||||||
|
return true;
|
||||||
|
} else if (id == R.id.game_list_entry_menu_properties) {
|
||||||
|
mParent.openGameProperties(getGameList().getEntry(position).getPath());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
menu.show();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package com.github.stenzek.duckstation;
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/26666143/recyclerview-gridlayoutmanager-how-to-auto-detect-span-count
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
public class GridAutofitLayoutManager extends GridLayoutManager
|
||||||
|
{
|
||||||
|
private int columnWidth;
|
||||||
|
private boolean isColumnWidthChanged = true;
|
||||||
|
private int lastWidth;
|
||||||
|
private int lastHeight;
|
||||||
|
|
||||||
|
public GridAutofitLayoutManager(@NonNull final Context context, final int columnWidth) {
|
||||||
|
/* Initially set spanCount to 1, will be changed automatically later. */
|
||||||
|
super(context, 1);
|
||||||
|
setColumnWidth(checkedColumnWidth(context, columnWidth));
|
||||||
|
}
|
||||||
|
|
||||||
|
public GridAutofitLayoutManager(
|
||||||
|
@NonNull final Context context,
|
||||||
|
final int columnWidth,
|
||||||
|
final int orientation,
|
||||||
|
final boolean reverseLayout) {
|
||||||
|
|
||||||
|
/* Initially set spanCount to 1, will be changed automatically later. */
|
||||||
|
super(context, 1, orientation, reverseLayout);
|
||||||
|
setColumnWidth(checkedColumnWidth(context, columnWidth));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int checkedColumnWidth(@NonNull final Context context, int columnWidth) {
|
||||||
|
if (columnWidth <= 0) {
|
||||||
|
/* Set default columnWidth value (48dp here). It is better to move this constant
|
||||||
|
to static constant on top, but we need context to convert it to dp, so can't really
|
||||||
|
do so. */
|
||||||
|
columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
|
||||||
|
context.getResources().getDisplayMetrics());
|
||||||
|
}
|
||||||
|
return columnWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColumnWidth(final int newColumnWidth) {
|
||||||
|
if (newColumnWidth > 0 && newColumnWidth != columnWidth) {
|
||||||
|
columnWidth = newColumnWidth;
|
||||||
|
isColumnWidthChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLayoutChildren(@NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) {
|
||||||
|
final int width = getWidth();
|
||||||
|
final int height = getHeight();
|
||||||
|
if (columnWidth > 0 && width > 0 && height > 0 && (isColumnWidthChanged || lastWidth != width || lastHeight != height)) {
|
||||||
|
final int totalSpace;
|
||||||
|
if (getOrientation() == VERTICAL) {
|
||||||
|
totalSpace = width - getPaddingRight() - getPaddingLeft();
|
||||||
|
} else {
|
||||||
|
totalSpace = height - getPaddingTop() - getPaddingBottom();
|
||||||
|
}
|
||||||
|
final int spanCount = Math.max(1, totalSpace / columnWidth);
|
||||||
|
setSpanCount(spanCount);
|
||||||
|
isColumnWidthChanged = false;
|
||||||
|
}
|
||||||
|
lastWidth = width;
|
||||||
|
lastHeight = height;
|
||||||
|
super.onLayoutChildren(recycler, state);
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,30 +12,28 @@ import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Gravity;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.PopupMenu;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.app.AppCompatDelegate;
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.core.app.ActivityCompat;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.fragment.app.FragmentFactory;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
private static final int REQUEST_EXTERNAL_STORAGE_PERMISSIONS = 1;
|
private static final int REQUEST_EXTERNAL_STORAGE_PERMISSIONS = 1;
|
||||||
|
@ -47,7 +45,32 @@ public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private GameList mGameList;
|
private GameList mGameList;
|
||||||
private ListView mGameListView;
|
private ListView mGameListView;
|
||||||
|
private GameListFragment mGameListFragment;
|
||||||
|
private GameGridFragment mGameGridFragment;
|
||||||
private boolean mHasExternalStoragePermissions = false;
|
private boolean mHasExternalStoragePermissions = false;
|
||||||
|
private boolean mIsShowingGameGrid = false;
|
||||||
|
|
||||||
|
public MainActivity() {
|
||||||
|
getSupportFragmentManager().setFragmentFactory(createFragmentFactory());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getTitleString() {
|
||||||
|
String scmVersion = AndroidHostInterface.getScmVersion();
|
||||||
|
final int gitHashPos = scmVersion.indexOf("-g");
|
||||||
|
if (gitHashPos > 0)
|
||||||
|
scmVersion = scmVersion.substring(0, gitHashPos);
|
||||||
|
|
||||||
|
return String.format("DuckStation %s", scmVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameList getGameList() {
|
||||||
|
return mGameList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean shouldResumeStateByDefault() {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
|
return prefs.getBoolean("Main/SaveStateOnExit", true);
|
||||||
|
}
|
||||||
|
|
||||||
private void setLanguage() {
|
private void setLanguage() {
|
||||||
String language = PreferenceManager.getDefaultSharedPreferences(this).getString("Main/Language", "none");
|
String language = PreferenceManager.getDefaultSharedPreferences(this).getString("Main/Language", "none");
|
||||||
|
@ -86,20 +109,42 @@ public class MainActivity extends AppCompatActivity {
|
||||||
private void loadSettings() {
|
private void loadSettings() {
|
||||||
setLanguage();
|
setLanguage();
|
||||||
setTheme();
|
setTheme();
|
||||||
|
|
||||||
|
mIsShowingGameGrid = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("Main/GameGridView", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldResumeStateByDefault() {
|
private FragmentFactory createFragmentFactory() {
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
return new FragmentFactory() {
|
||||||
return prefs.getBoolean("Main/SaveStateOnExit", true);
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
|
||||||
|
if (className == GameListFragment.class.getName())
|
||||||
|
return new GameListFragment(MainActivity.this);
|
||||||
|
else if (className == GameGridFragment.class.getName())
|
||||||
|
return new GameGridFragment(MainActivity.this);
|
||||||
|
|
||||||
|
return super.instantiate(classLoader, className);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getTitleString() {
|
private void switchGameListView() {
|
||||||
String scmVersion = AndroidHostInterface.getScmVersion();
|
mIsShowingGameGrid = !mIsShowingGameGrid;
|
||||||
final int gitHashPos = scmVersion.indexOf("-g");
|
PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
if (gitHashPos > 0)
|
.edit()
|
||||||
scmVersion = scmVersion.substring(0, gitHashPos);
|
.putBoolean("Main/GameGridView", mIsShowingGameGrid)
|
||||||
|
.commit();
|
||||||
|
|
||||||
return String.format("DuckStation %s", scmVersion);
|
updateGameListFragment();
|
||||||
|
invalidateOptionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateGameListFragment() {
|
||||||
|
getSupportFragmentManager()
|
||||||
|
.beginTransaction()
|
||||||
|
.setReorderingAllowed(true).
|
||||||
|
replace(R.id.content_fragment, mIsShowingGameGrid ? mGameGridFragment : mGameListFragment)
|
||||||
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -127,42 +172,9 @@ public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
// Set up game list view.
|
// Set up game list view.
|
||||||
mGameList = new GameList(this);
|
mGameList = new GameList(this);
|
||||||
mGameListView = findViewById(R.id.game_list_view);
|
mGameListFragment = new GameListFragment(this);
|
||||||
mGameListView.setAdapter(mGameList.getListViewAdapter());
|
mGameGridFragment = new GameGridFragment(this);
|
||||||
mGameListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
updateGameListFragment();
|
||||||
@Override
|
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
|
||||||
startEmulation(mGameList.getEntry(position).getPath(), shouldResumeStateByDefault());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
mGameListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
|
|
||||||
long id) {
|
|
||||||
PopupMenu menu = new PopupMenu(MainActivity.this, view,
|
|
||||||
Gravity.RIGHT | Gravity.TOP);
|
|
||||||
menu.getMenuInflater().inflate(R.menu.menu_game_list_entry, menu.getMenu());
|
|
||||||
menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onMenuItemClick(MenuItem item) {
|
|
||||||
int id = item.getItemId();
|
|
||||||
if (id == R.id.game_list_entry_menu_start_game) {
|
|
||||||
startEmulation(mGameList.getEntry(position).getPath(), false);
|
|
||||||
return true;
|
|
||||||
} else if (id == R.id.game_list_entry_menu_resume_game) {
|
|
||||||
startEmulation(mGameList.getEntry(position).getPath(), true);
|
|
||||||
return true;
|
|
||||||
} else if (id == R.id.game_list_entry_menu_properties) {
|
|
||||||
openGameProperties(mGameList.getEntry(position).getPath());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
menu.show();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mHasExternalStoragePermissions = checkForExternalStoragePermissions();
|
mHasExternalStoragePermissions = checkForExternalStoragePermissions();
|
||||||
if (mHasExternalStoragePermissions)
|
if (mHasExternalStoragePermissions)
|
||||||
|
@ -194,6 +206,13 @@ public class MainActivity extends AppCompatActivity {
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
// Inflate the menu; this adds items to the action bar if it is present.
|
// Inflate the menu; this adds items to the action bar if it is present.
|
||||||
getMenuInflater().inflate(R.menu.menu_main, menu);
|
getMenuInflater().inflate(R.menu.menu_main, menu);
|
||||||
|
|
||||||
|
final MenuItem switchViewItem = menu.findItem(R.id.action_switch_view);
|
||||||
|
if (switchViewItem != null) {
|
||||||
|
switchViewItem.setTitle(mIsShowingGameGrid ? R.string.action_show_game_list : R.string.action_show_game_grid);
|
||||||
|
switchViewItem.setIcon(mIsShowingGameGrid ? R.drawable.ic_baseline_view_list_24 : R.drawable.ic_baseline_grid_view_24);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,6 +246,9 @@ public class MainActivity extends AppCompatActivity {
|
||||||
Intent intent = new Intent(this, ControllerMappingActivity.class);
|
Intent intent = new Intent(this, ControllerMappingActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
|
} else if (id == R.id.action_switch_view) {
|
||||||
|
switchGameListView();
|
||||||
|
return true;
|
||||||
} else if (id == R.id.action_show_version) {
|
} else if (id == R.id.action_show_version) {
|
||||||
showVersion();
|
showVersion();
|
||||||
return true;
|
return true;
|
||||||
|
@ -325,14 +347,14 @@ public class MainActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean openGameProperties(String path) {
|
public boolean openGameProperties(String path) {
|
||||||
Intent intent = new Intent(this, GamePropertiesActivity.class);
|
Intent intent = new Intent(this, GamePropertiesActivity.class);
|
||||||
intent.putExtra("path", path);
|
intent.putExtra("path", path);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean startEmulation(String bootPath, boolean resumeState) {
|
public boolean startEmulation(String bootPath, boolean resumeState) {
|
||||||
if (!doBIOSCheck())
|
if (!doBIOSCheck())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M3,3v8h8L11,3L3,3zM9,9L5,9L5,5h4v4zM3,13v8h8v-8L3,13zM9,19L5,19v-4h4v4zM13,3v8h8L21,3h-8zM19,9h-4L15,5h4v4zM13,13v8h8v-8h-8zM19,19h-4v-4h4v4z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M4,14h4v-4L4,10v4zM4,19h4v-4L4,15v4zM4,9h4L8,5L4,5v4zM9,14h12v-4L9,10v4zM9,19h12v-4L9,15v4zM9,5v4h12L21,5L9,5z"/>
|
||||||
|
</vector>
|
|
@ -33,7 +33,12 @@
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<include layout="@layout/content_main" />
|
<FrameLayout
|
||||||
|
android:id="@+id/content_fragment"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/fab_resume"
|
android:id="@+id/fab_resume"
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/game_list_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -3,10 +3,7 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
|
||||||
tools:context=".MainActivity"
|
|
||||||
tools:showIn="@layout/activity_main">
|
|
||||||
|
|
||||||
<ListView
|
<ListView
|
||||||
android:id="@+id/game_list_view"
|
android:id="@+id/game_list_view"
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="160dp"
|
||||||
|
android:layout_height="160dp"
|
||||||
|
android:layout_margin="25dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:foreground="?android:attr/selectableItemBackground"
|
||||||
|
app:srcCompat="@drawable/ic_media_cdrom"
|
||||||
|
tools:layout_editor_absoluteX="1dp"
|
||||||
|
tools:layout_editor_absoluteY="1dp" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -14,6 +14,12 @@
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_settings"
|
android:id="@+id/action_settings"
|
||||||
android:icon="@drawable/ic_baseline_settings_24"
|
android:icon="@drawable/ic_baseline_settings_24"
|
||||||
|
android:orderInCategory="101"
|
||||||
|
android:title="@string/action_settings"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_switch_view"
|
||||||
|
android:icon="@drawable/ic_baseline_settings_24"
|
||||||
android:orderInCategory="100"
|
android:orderInCategory="100"
|
||||||
android:title="@string/action_settings"
|
android:title="@string/action_settings"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
|
|
|
@ -227,4 +227,6 @@
|
||||||
<string name="dialog_touchscreen_controller_buttons">Touchscreen Controller Buttons</string>
|
<string name="dialog_touchscreen_controller_buttons">Touchscreen Controller Buttons</string>
|
||||||
<string name="dialog_touchscreen_controller_settings">Touchscreen Controller Settings</string>
|
<string name="dialog_touchscreen_controller_settings">Touchscreen Controller Settings</string>
|
||||||
<string name="settings_summary_console_tty_output">Logs debug messages printed by games.</string>
|
<string name="settings_summary_console_tty_output">Logs debug messages printed by games.</string>
|
||||||
|
<string name="action_show_game_list">List View</string>
|
||||||
|
<string name="action_show_game_grid">Grid View</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in New Issue