mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-25 15:15:40 +00:00
Android: Implementation of basic game list
This commit is contained in:
parent
adc3a2fac1
commit
6da9e23d3b
5
android/.idea/codeStyles/codeStyleConfig.xml
Normal file
5
android/.idea/codeStyles/codeStyleConfig.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||||
|
</state>
|
||||||
|
</component>
|
|
@ -5,6 +5,7 @@
|
||||||
#include "android_audio_stream.h"
|
#include "android_audio_stream.h"
|
||||||
#include "android_gles_host_display.h"
|
#include "android_gles_host_display.h"
|
||||||
#include "core/gpu.h"
|
#include "core/gpu.h"
|
||||||
|
#include "core/game_list.h"
|
||||||
#include "core/host_display.h"
|
#include "core/host_display.h"
|
||||||
#include "core/system.h"
|
#include "core/system.h"
|
||||||
#include <android/native_window_jni.h>
|
#include <android/native_window_jni.h>
|
||||||
|
@ -35,6 +36,9 @@ static AndroidHostInterface* GetNativeClass(JNIEnv* env, jobject obj)
|
||||||
|
|
||||||
static std::string JStringToString(JNIEnv* env, jstring str)
|
static std::string JStringToString(JNIEnv* env, jstring str)
|
||||||
{
|
{
|
||||||
|
if (str == nullptr)
|
||||||
|
return {};
|
||||||
|
|
||||||
jsize length = env->GetStringUTFLength(str);
|
jsize length = env->GetStringUTFLength(str);
|
||||||
if (length == 0)
|
if (length == 0)
|
||||||
return {};
|
return {};
|
||||||
|
@ -427,3 +431,47 @@ DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_surfaceChanged, jobject obj, j
|
||||||
hi->RunOnEmulationThread(
|
hi->RunOnEmulationThread(
|
||||||
[hi, native_surface, format, width, height]() { hi->SurfaceChanged(native_surface, format, width, height); }, true);
|
[hi, native_surface, format, width, height]() { hi->SurfaceChanged(native_surface, format, width, height); }, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEFINE_JNI_ARGS_METHOD(jarray, GameList_getEntries, jobject unused, jstring j_cache_path, jstring j_redump_dat_path, jarray j_search_directories, jboolean search_recursively)
|
||||||
|
{
|
||||||
|
const std::string cache_path = JStringToString(env, j_cache_path);
|
||||||
|
const std::string redump_dat_path = JStringToString(env, j_redump_dat_path);
|
||||||
|
|
||||||
|
GameList gl;
|
||||||
|
if (!redump_dat_path.empty())
|
||||||
|
gl.ParseRedumpDatabase(redump_dat_path.c_str());
|
||||||
|
|
||||||
|
const jsize search_directories_size = env->GetArrayLength(j_search_directories);
|
||||||
|
for (jsize i = 0; i < search_directories_size; i++)
|
||||||
|
{
|
||||||
|
jobject search_dir_obj = env->GetObjectArrayElement(reinterpret_cast<jobjectArray>(j_search_directories), i);
|
||||||
|
const std::string search_dir = JStringToString(env, reinterpret_cast<jstring>(search_dir_obj));
|
||||||
|
if (!search_dir.empty())
|
||||||
|
gl.AddDirectory(search_dir.c_str(), search_recursively);
|
||||||
|
}
|
||||||
|
|
||||||
|
jclass entry_class = env->FindClass("com/github/stenzek/duckstation/GameListEntry");
|
||||||
|
Assert(entry_class != nullptr);
|
||||||
|
|
||||||
|
jmethodID entry_constructor = env->GetMethodID(entry_class, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;J)V");
|
||||||
|
Assert(entry_constructor != nullptr);
|
||||||
|
|
||||||
|
jobjectArray entry_array = env->NewObjectArray(gl.GetEntryCount(), entry_class, nullptr);
|
||||||
|
Assert(entry_array != nullptr);
|
||||||
|
|
||||||
|
u32 counter = 0;
|
||||||
|
for (const GameList::GameListEntry& entry : gl.GetEntries())
|
||||||
|
{
|
||||||
|
jstring path = env->NewStringUTF(entry.path.c_str());
|
||||||
|
jstring code = env->NewStringUTF(entry.code.c_str());
|
||||||
|
jstring title = env->NewStringUTF(entry.title.c_str());
|
||||||
|
jstring region = env->NewStringUTF(Settings::GetConsoleRegionName(entry.region));
|
||||||
|
jlong size = entry.total_size;
|
||||||
|
|
||||||
|
jobject entry_jobject = env->NewObject(entry_class, entry_constructor, path, code, title, region, size);
|
||||||
|
|
||||||
|
env->SetObjectArrayElement(entry_array, counter++, entry_jobject);
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry_array;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.github.stenzek.duckstation;
|
||||||
|
|
||||||
|
public enum ConsoleRegion {
|
||||||
|
AutoDetect,
|
||||||
|
NTSC_J,
|
||||||
|
NTSC_U,
|
||||||
|
PAL
|
||||||
|
}
|
|
@ -21,7 +21,9 @@ import androidx.core.app.NavUtils;
|
||||||
* status bar and navigation/system bar) with user interaction.
|
* status bar and navigation/system bar) with user interaction.
|
||||||
*/
|
*/
|
||||||
public class EmulationActivity extends AppCompatActivity implements SurfaceHolder.Callback {
|
public class EmulationActivity extends AppCompatActivity implements SurfaceHolder.Callback {
|
||||||
/** Interface to the native emulator core */
|
/**
|
||||||
|
* Interface to the native emulator core
|
||||||
|
*/
|
||||||
AndroidHostInterface mHostInterface;
|
AndroidHostInterface mHostInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -104,11 +106,13 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String filename = new String();
|
String bootPath = getIntent().getStringExtra("bootPath");
|
||||||
String state_filename = new String();
|
String bootSaveStatePath = getIntent().getStringExtra("bootSaveStatePath");
|
||||||
if (!mHostInterface.startEmulationThread(holder.getSurface(),filename, state_filename))
|
|
||||||
{
|
if (!mHostInterface
|
||||||
|
.startEmulationThread(holder.getSurface(), bootPath, bootSaveStatePath)) {
|
||||||
Log.e("EmulationActivity", "Failed to start emulation thread");
|
Log.e("EmulationActivity", "Failed to start emulation thread");
|
||||||
|
finishActivity(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,7 +137,7 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
||||||
}
|
}
|
||||||
|
|
||||||
mVisible = true;
|
mVisible = true;
|
||||||
mContentView = (SurfaceView)findViewById(R.id.fullscreen_content);
|
mContentView = (SurfaceView) findViewById(R.id.fullscreen_content);
|
||||||
Log.e("EmulationActivity", "adding callback");
|
Log.e("EmulationActivity", "adding callback");
|
||||||
mContentView.getHolder().addCallback(this);
|
mContentView.getHolder().addCallback(this);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
package com.github.stenzek.duckstation;
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/34927748/android-5-0-documentfile-from-tree-uri
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.storage.StorageManager;
|
||||||
|
import android.provider.DocumentsContract;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
public final class FileUtil {
|
||||||
|
static String TAG="TAG";
|
||||||
|
private static final String PRIMARY_VOLUME_NAME = "primary";
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static String getFullPathFromTreeUri(@Nullable final Uri treeUri, Context con) {
|
||||||
|
if (treeUri == null) return null;
|
||||||
|
String volumePath = getVolumePath(getVolumeIdFromTreeUri(treeUri),con);
|
||||||
|
if (volumePath == null) return File.separator;
|
||||||
|
if (volumePath.endsWith(File.separator))
|
||||||
|
volumePath = volumePath.substring(0, volumePath.length() - 1);
|
||||||
|
|
||||||
|
String documentPath = getDocumentPathFromTreeUri(treeUri);
|
||||||
|
if (documentPath.endsWith(File.separator))
|
||||||
|
documentPath = documentPath.substring(0, documentPath.length() - 1);
|
||||||
|
|
||||||
|
if (documentPath.length() > 0) {
|
||||||
|
if (documentPath.startsWith(File.separator))
|
||||||
|
return volumePath + documentPath;
|
||||||
|
else
|
||||||
|
return volumePath + File.separator + documentPath;
|
||||||
|
}
|
||||||
|
else return volumePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressLint("ObsoleteSdkInt")
|
||||||
|
private static String getVolumePath(final String volumeId, Context context) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return null;
|
||||||
|
try {
|
||||||
|
StorageManager mStorageManager =
|
||||||
|
(StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
|
||||||
|
Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
|
||||||
|
Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
|
||||||
|
Method getUuid = storageVolumeClazz.getMethod("getUuid");
|
||||||
|
Method getPath = storageVolumeClazz.getMethod("getPath");
|
||||||
|
Method isPrimary = storageVolumeClazz.getMethod("isPrimary");
|
||||||
|
Object result = getVolumeList.invoke(mStorageManager);
|
||||||
|
|
||||||
|
final int length = Array.getLength(result);
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
Object storageVolumeElement = Array.get(result, i);
|
||||||
|
String uuid = (String) getUuid.invoke(storageVolumeElement);
|
||||||
|
Boolean primary = (Boolean) isPrimary.invoke(storageVolumeElement);
|
||||||
|
|
||||||
|
// primary volume?
|
||||||
|
if (primary && PRIMARY_VOLUME_NAME.equals(volumeId))
|
||||||
|
return (String) getPath.invoke(storageVolumeElement);
|
||||||
|
|
||||||
|
// other volumes?
|
||||||
|
if (uuid != null && uuid.equals(volumeId))
|
||||||
|
return (String) getPath.invoke(storageVolumeElement);
|
||||||
|
}
|
||||||
|
// not found.
|
||||||
|
return null;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
private static String getVolumeIdFromTreeUri(final Uri treeUri) {
|
||||||
|
final String docId = DocumentsContract.getTreeDocumentId(treeUri);
|
||||||
|
final String[] split = docId.split(":");
|
||||||
|
if (split.length > 0) return split[0];
|
||||||
|
else return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
private static String getDocumentPathFromTreeUri(final Uri treeUri) {
|
||||||
|
final String docId = DocumentsContract.getTreeDocumentId(treeUri);
|
||||||
|
final String[] split = docId.split(":");
|
||||||
|
if ((split.length >= 2) && (split[1] != null)) return split[1];
|
||||||
|
else return File.separator;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
package com.github.stenzek.duckstation;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.util.ArraySet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.BaseAdapter;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class GameList {
|
||||||
|
static {
|
||||||
|
System.loadLibrary("duckstation-native");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
private String mCachePath;
|
||||||
|
private String mRedumpDatPath;
|
||||||
|
private String[] mSearchDirectories;
|
||||||
|
private boolean mSearchRecursively;
|
||||||
|
private GameListEntry[] mEntries;
|
||||||
|
|
||||||
|
static private native GameListEntry[] getEntries(String cachePath, String redumpDatPath,
|
||||||
|
String[] searchDirectories,
|
||||||
|
boolean searchRecursively);
|
||||||
|
|
||||||
|
public GameList(Context context) {
|
||||||
|
mContext = context;
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refresh() {
|
||||||
|
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
|
||||||
|
mCachePath = preferences.getString("GameList/CachePath", "");
|
||||||
|
mRedumpDatPath = preferences.getString("GameList/RedumpDatPath", "");
|
||||||
|
|
||||||
|
Set<String> searchDirectories =
|
||||||
|
preferences.getStringSet("GameList/SearchDirectories", null);
|
||||||
|
if (searchDirectories != null) {
|
||||||
|
mSearchDirectories = new String[searchDirectories.size()];
|
||||||
|
searchDirectories.toArray(mSearchDirectories);
|
||||||
|
} else {
|
||||||
|
mSearchDirectories = new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
mSearchRecursively = preferences.getBoolean("GameList/SearchRecursively", true);
|
||||||
|
|
||||||
|
// Search and get entries from native code
|
||||||
|
mEntries = getEntries(mCachePath, mRedumpDatPath, mSearchDirectories, mSearchRecursively);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEntryCount() {
|
||||||
|
return mEntries.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameListEntry getEntry(int index) {
|
||||||
|
return mEntries[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ListViewAdapter extends BaseAdapter {
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return mEntries.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getItem(int position) {
|
||||||
|
return mEntries[position];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getViewTypeCount() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
if (convertView == null) {
|
||||||
|
convertView = LayoutInflater.from(mContext)
|
||||||
|
.inflate(R.layout.game_list_view_entry, parent, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
mEntries[position].fillView(convertView);
|
||||||
|
return convertView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BaseAdapter getListViewAdapter() {
|
||||||
|
return new ListViewAdapter();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package com.github.stenzek.duckstation;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
public class GameListEntry {
|
||||||
|
private String mPath;
|
||||||
|
private String mCode;
|
||||||
|
private String mTitle;
|
||||||
|
private ConsoleRegion mRegion;
|
||||||
|
private long mSize;
|
||||||
|
|
||||||
|
public GameListEntry(String path, String code, String title, String region, long size) {
|
||||||
|
mPath = path;
|
||||||
|
mCode = code;
|
||||||
|
mTitle = title;
|
||||||
|
mSize = size;
|
||||||
|
|
||||||
|
try {
|
||||||
|
mRegion = ConsoleRegion.valueOf(region);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
mRegion = ConsoleRegion.NTSC_U;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPath() {
|
||||||
|
return mPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCode() {
|
||||||
|
return mCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return mTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleRegion getRegion() {
|
||||||
|
return mRegion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fillView(View view) {
|
||||||
|
((TextView) view.findViewById(R.id.game_list_view_entry_title)).setText(mTitle);
|
||||||
|
((TextView) view.findViewById(R.id.game_list_view_entry_path)).setText(mPath);
|
||||||
|
|
||||||
|
String sizeString = String.format("%.2f MB", (double) mSize / 1048576.0);
|
||||||
|
((TextView) view.findViewById(R.id.game_list_view_entry_size)).setText(sizeString);
|
||||||
|
|
||||||
|
int drawableId;
|
||||||
|
switch (mRegion) {
|
||||||
|
case NTSC_J:
|
||||||
|
drawableId = R.drawable.flag_jp;
|
||||||
|
break;
|
||||||
|
case NTSC_U:
|
||||||
|
default:
|
||||||
|
drawableId = R.drawable.flag_us;
|
||||||
|
break;
|
||||||
|
case PAL:
|
||||||
|
drawableId = R.drawable.flag_eu;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
((ImageView) view.findViewById(R.id.game_list_view_entry_region_icon))
|
||||||
|
.setImageDrawable(ContextCompat.getDrawable(view.getContext(), drawableId));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,9 @@
|
||||||
package com.github.stenzek.duckstation;
|
package com.github.stenzek.duckstation;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
@ -11,13 +13,32 @@ import androidx.appcompat.app.AppCompatActivity;
|
||||||
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.documentfile.provider.DocumentFile;
|
||||||
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
||||||
|
import androidx.collection.ArraySet;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Gravity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.PopupMenu;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.prefs.Preferences;
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
private static final int REQUEST_EXTERNAL_STORAGE_PERMISSIONS = 1;
|
||||||
|
private static final int REQUEST_ADD_DIRECTORY_TO_GAME_LIST = 2;
|
||||||
|
|
||||||
|
private GameList mGameList;
|
||||||
|
private ListView mGameListView;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
@ -30,9 +51,39 @@ public class MainActivity extends AppCompatActivity {
|
||||||
fab.setOnClickListener(new View.OnClickListener() {
|
fab.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
/*Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
|
Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||||
.setAction("Action", null).show();*/
|
i.addCategory(Intent.CATEGORY_DEFAULT);
|
||||||
startEmulation("nonexistant.cue");
|
i.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
|
||||||
|
startActivityForResult(Intent.createChooser(i, "Choose directory"),
|
||||||
|
REQUEST_ADD_DIRECTORY_TO_GAME_LIST);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up game list view.
|
||||||
|
mGameList = new GameList(this);
|
||||||
|
mGameListView = findViewById(R.id.game_list_view);
|
||||||
|
mGameListView.setAdapter(mGameList.getListViewAdapter());
|
||||||
|
mGameListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
startEmulation(mGameList.getEntry(position).getPath());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
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) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
menu.show();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -61,22 +112,71 @@ public class MainActivity extends AppCompatActivity {
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
|
||||||
|
switch (requestCode) {
|
||||||
|
case REQUEST_ADD_DIRECTORY_TO_GAME_LIST: {
|
||||||
|
if (resultCode != RESULT_OK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Uri treeUri = data.getData();
|
||||||
|
String path = FileUtil.getFullPathFromTreeUri(treeUri, this);
|
||||||
|
if (path.length() < 5) {
|
||||||
|
// sanity check for non-external paths.. do we need permissions or something?
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
|
Set<String> currentValues = prefs.getStringSet("GameList/SearchDirectories", null);
|
||||||
|
if (currentValues == null)
|
||||||
|
currentValues = new HashSet<String>();
|
||||||
|
|
||||||
|
currentValues.add(path);
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putStringSet("GameList/SearchDirectories", currentValues);
|
||||||
|
editor.apply();
|
||||||
|
Log.i("MainActivity", "Added path '" + path + "' to game list search directories");
|
||||||
|
mGameList.refresh();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean checkForExternalStoragePermissions() {
|
private boolean checkForExternalStoragePermissions() {
|
||||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) ==
|
||||||
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)
|
PackageManager.PERMISSION_GRANTED &&
|
||||||
{
|
ContextCompat
|
||||||
|
.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) ==
|
||||||
|
PackageManager.PERMISSION_GRANTED) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
|
ActivityCompat.requestPermissions(this,
|
||||||
|
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||||
|
REQUEST_EXTERNAL_STORAGE_PERMISSIONS);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onRequestPermissionsResult(int requestCode, String[] permissions,
|
||||||
|
int[] grantResults) {
|
||||||
|
// check that all were successful
|
||||||
|
for (int i = 0; i < grantResults.length; i++) {
|
||||||
|
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
Snackbar.make(mGameListView,
|
||||||
|
"External storage permissions are required to start emulation.",
|
||||||
|
Snackbar.LENGTH_LONG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean startEmulation(String bootPath) {
|
private boolean startEmulation(String bootPath) {
|
||||||
if (!checkForExternalStoragePermissions()) {
|
if (!checkForExternalStoragePermissions()) {
|
||||||
Snackbar.make(findViewById(R.id.fab), "External storage permissions are required to start emulation.", Snackbar.LENGTH_LONG);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent intent = new Intent(this, EmulationActivity.class);
|
Intent intent = new Intent(this, EmulationActivity.class);
|
||||||
intent.putExtra("bootPath", bootPath);
|
intent.putExtra("bootPath", bootPath);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
package com.github.stenzek.duckstation;
|
|
||||||
|
|
||||||
public class NativeLibrary {
|
|
||||||
static
|
|
||||||
{
|
|
||||||
System.loadLibrary("duckstation-native");
|
|
||||||
}
|
|
||||||
|
|
||||||
public native boolean createSystem();
|
|
||||||
public native boolean bootSystem(String filename, String stateFilename);
|
|
||||||
public native void runFrame();
|
|
||||||
}
|
|
37
android/app/src/main/res/drawable/flag_eu.xml
Normal file
37
android/app/src/main/res/drawable/flag_eu.xml
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- https://raw.githubusercontent.com/Shusshu/android-flags/master/flags/src/main/res/drawable/flag_us2.xml -->
|
||||||
|
<vector android:height="15dp" android:viewportHeight="15"
|
||||||
|
android:viewportWidth="21" android:width="21dp"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillType="evenOdd" android:pathData="M0,0h21v15h-21z"
|
||||||
|
android:strokeColor="#00000000" android:strokeWidth="1">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient android:endX="10.5" android:endY="15"
|
||||||
|
android:startX="10.5" android:startY="0" android:type="linear">
|
||||||
|
<item android:color="#FFFFFFFF" android:offset="0"/>
|
||||||
|
<item android:color="#FFF0F0F0" android:offset="1"/>
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path android:fillType="evenOdd" android:pathData="M0,0h21v15h-21z"
|
||||||
|
android:strokeColor="#00000000" android:strokeWidth="1">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient android:endX="10.5" android:endY="15"
|
||||||
|
android:startX="10.5" android:startY="0" android:type="linear">
|
||||||
|
<item android:color="#FF043CAE" android:offset="0"/>
|
||||||
|
<item android:color="#FF00339A" android:offset="1"/>
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path android:fillType="evenOdd"
|
||||||
|
android:pathData="M10.5,3L9.7929,3.2071L10,2.5L9.7929,1.7929L10.5,2L11.2071,1.7929L11,2.5L11.2071,3.2071L10.5,3ZM10.5,13L9.7929,13.2071L10,12.5L9.7929,11.7929L10.5,12L11.2071,11.7929L11,12.5L11.2071,13.2071L10.5,13ZM15.5,8L14.7929,8.2071L15,7.5L14.7929,6.7929L15.5,7L16.2071,6.7929L16,7.5L16.2071,8.2071L15.5,8ZM5.5,8L4.7929,8.2071L5,7.5L4.7929,6.7929L5.5,7L6.2071,6.7929L6,7.5L6.2071,8.2071L5.5,8ZM14.8301,5.5L14.123,5.7071L14.3301,5L14.123,4.2929L14.8301,4.5L15.5372,4.2929L15.3301,5L15.5372,5.7071L14.8301,5.5ZM6.1699,10.5L5.4628,10.7071L5.6699,10L5.4628,9.2929L6.1699,9.5L6.877,9.2929L6.6699,10L6.877,10.7071L6.1699,10.5ZM13,3.6699L12.2929,3.877L12.5,3.1699L12.2929,2.4628L13,2.6699L13.7071,2.4628L13.5,3.1699L13.7071,3.877L13,3.6699ZM8,12.3301L7.2929,12.5372L7.5,11.8301L7.2929,11.123L8,11.3301L8.7071,11.123L8.5,11.8301L8.7071,12.5372L8,12.3301ZM14.8301,10.5L14.123,10.7071L14.3301,10L14.123,9.2929L14.8301,9.5L15.5372,9.2929L15.3301,10L15.5372,10.7071L14.8301,10.5ZM6.1699,5.5L5.4628,5.7071L5.6699,5L5.4628,4.2929L6.1699,4.5L6.877,4.2929L6.6699,5L6.877,5.7071L6.1699,5.5ZM13,12.3301L12.2929,12.5372L12.5,11.8301L12.2929,11.123L13,11.3301L13.7071,11.123L13.5,11.8301L13.7071,12.5372L13,12.3301ZM8,3.6699L7.2929,3.877L7.5,3.1699L7.2929,2.4628L8,2.6699L8.7071,2.4628L8.5,3.1699L8.7071,3.877L8,3.6699Z"
|
||||||
|
android:strokeColor="#00000000" android:strokeWidth="1">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient android:endX="10.5" android:endY="13.2071"
|
||||||
|
android:startX="10.5" android:startY="1.7929" android:type="linear">
|
||||||
|
<item android:color="#FFFFD429" android:offset="0"/>
|
||||||
|
<item android:color="#FFFFCC00" android:offset="1"/>
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
</vector>
|
27
android/app/src/main/res/drawable/flag_jp.xml
Normal file
27
android/app/src/main/res/drawable/flag_jp.xml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- https://raw.githubusercontent.com/Shusshu/android-flags/master/flags/src/main/res/drawable/flag_hp.xml -->
|
||||||
|
<vector android:height="15dp" android:viewportHeight="15"
|
||||||
|
android:viewportWidth="21" android:width="21dp"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillType="evenOdd" android:pathData="M0,0h21v15h-21z"
|
||||||
|
android:strokeColor="#00000000" android:strokeWidth="1">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient android:endX="10.5" android:endY="15"
|
||||||
|
android:startX="10.5" android:startY="0" android:type="linear">
|
||||||
|
<item android:color="#FFFFFFFF" android:offset="0"/>
|
||||||
|
<item android:color="#FFF0F0F0" android:offset="1"/>
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path android:fillType="evenOdd"
|
||||||
|
android:pathData="M10.5,7.5m-4.5,0a4.5,4.5 0,1 1,9 0a4.5,4.5 0,1 1,-9 0"
|
||||||
|
android:strokeColor="#00000000" android:strokeWidth="1">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient android:endX="10.5" android:endY="12"
|
||||||
|
android:startX="10.5" android:startY="3" android:type="linear">
|
||||||
|
<item android:color="#FFD81441" android:offset="0"/>
|
||||||
|
<item android:color="#FFBB0831" android:offset="1"/>
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
</vector>
|
332
android/app/src/main/res/drawable/flag_us.xml
Normal file
332
android/app/src/main/res/drawable/flag_us.xml
Normal file
|
@ -0,0 +1,332 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- https://raw.githubusercontent.com/Shusshu/android-flags/master/flags/src/main/res/drawable/flag_us2.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="21dp"
|
||||||
|
android:height="21dp"
|
||||||
|
android:viewportWidth="10"
|
||||||
|
android:viewportHeight="13">
|
||||||
|
<path
|
||||||
|
android:fillColor="#bd3d44"
|
||||||
|
android:pathData="M0 0h13v1h-13Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M0 1h13v1h-13Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#bd3d44"
|
||||||
|
android:pathData="M0 2h13v1h-13Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M0 3h13v1h-13Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#bd3d44"
|
||||||
|
android:pathData="M0 4h13v1h-13Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M0 5h13v1h-13Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#bd3d44"
|
||||||
|
android:pathData="M0 6h13v1h-13Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M0 7h13v1h-13Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#bd3d44"
|
||||||
|
android:pathData="M0 8h13v1h-13Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M0 9h13v1h-13Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#bd3d44"
|
||||||
|
android:pathData="M0 10h13v1h-13Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M0 11h13v1h-13Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#bd3d44"
|
||||||
|
android:pathData="M0 12h13v1h-13Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#192f5d"
|
||||||
|
android:pathData="M0 0h5.2v7h-5.2Z" />
|
||||||
|
|
||||||
|
<group android:translateX="0.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="1.0" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="1.8" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="2.6" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="3.4" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="4.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
|
||||||
|
|
||||||
|
<group android:translateY="1.4">
|
||||||
|
<group android:translateX="0.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="1.0" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="1.8" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="2.6" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="3.4" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="4.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
|
||||||
|
<group android:translateY="2.9">
|
||||||
|
<group android:translateX="0.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="1.0" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="1.8" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="2.6" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="3.4" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="4.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
|
||||||
|
<group android:translateY="4.3">
|
||||||
|
<group android:translateX="0.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="1.0" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="1.8" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="2.6" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="3.4" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="4.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
|
||||||
|
<group android:translateY="5.6">
|
||||||
|
<group android:translateX="0.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="1.0" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="1.8" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="2.6" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="3.4" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="4.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<!-- Odd stars -->
|
||||||
|
|
||||||
|
<group android:translateY="0.7" android:translateX="0.4">
|
||||||
|
<group android:translateX="0.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="1.0" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="1.8" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="2.6" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="3.4" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<group android:translateY="2.1" android:translateX="0.4">
|
||||||
|
<group android:translateX="0.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="1.0" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="1.8" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="2.6" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="3.4" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<group android:translateY="3.6" android:translateX="0.4">
|
||||||
|
<group android:translateX="0.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="1.0" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="1.8" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="2.6" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="3.4" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<group android:translateY="5.0" android:translateX="0.4">
|
||||||
|
<group android:translateX="0.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="1.0" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="1.8" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="2.6" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
<group android:translateX="3.4" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
</vector>
|
|
@ -29,6 +29,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
android:layout_margin="@dimen/fab_margin"
|
android:layout_margin="@dimen/fab_margin"
|
||||||
app:srcCompat="@android:drawable/ic_dialog_email" />
|
app:backgroundTint="@android:color/background_light"
|
||||||
|
app:srcCompat="@android:drawable/ic_input_add" />
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -1,21 +1,20 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
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"
|
||||||
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"
|
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||||
tools:showIn="@layout/activity_main"
|
tools:context=".MainActivity"
|
||||||
tools:context=".MainActivity">
|
tools:showIn="@layout/activity_main">
|
||||||
|
|
||||||
<TextView
|
<ListView
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/game_list_view"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:text="Hello World!"
|
android:layout_height="match_parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
/>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
63
android/app/src/main/res/layout/game_list_view_entry.xml
Normal file
63
android/app/src/main/res/layout/game_list_view_entry.xml
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<?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:id="@+id/linearLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/game_list_view_entry_title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="Game Title"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Large"
|
||||||
|
android:focusable="false"
|
||||||
|
android:focusableInTouchMode="false"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/game_list_view_entry_path"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:paddingBottom="8px"
|
||||||
|
android:text="Game Path"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||||
|
android:focusable="false"
|
||||||
|
android:focusableInTouchMode="false"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/game_list_view_entry_title" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/game_list_view_entry_size"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:text="123.4 MB"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:focusable="false"
|
||||||
|
android:focusableInTouchMode="false"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/game_list_view_entry_region_icon"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="28dp"
|
||||||
|
android:layout_marginTop="4px"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:paddingBottom="8px"
|
||||||
|
android:focusable="false"
|
||||||
|
android:focusableInTouchMode="false"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/game_list_view_entry_size"
|
||||||
|
app:srcCompat="@drawable/flag_jp" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
22
android/app/src/main/res/menu/menu_game_list_entry.xml
Normal file
22
android/app/src/main/res/menu/menu_game_list_entry.xml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/game_list_entry_menu_info"
|
||||||
|
android:title="Show Information" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/game_list_entry_menu_fast_boot"
|
||||||
|
android:title="@string/settings_console_fast_boot" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/game_list_entry_menu_slow_boot"
|
||||||
|
android:title="Slow Boot" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/game_list_entry_menu_load_state"
|
||||||
|
android:title="Load State" >
|
||||||
|
<menu >
|
||||||
|
<item android:title="Item" />
|
||||||
|
<item android:title="Item" />
|
||||||
|
</menu>
|
||||||
|
</item>
|
||||||
|
</menu>
|
Loading…
Reference in a new issue