diff --git a/src/pandroid/app/src/main/AndroidManifest.xml b/src/pandroid/app/src/main/AndroidManifest.xml
index d1d8f56f..0f097600 100644
--- a/src/pandroid/app/src/main/AndroidManifest.xml
+++ b/src/pandroid/app/src/main/AndroidManifest.xml
@@ -45,6 +45,17 @@
+
+
+
+
+
+
diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/GamesFoldersPreferences.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/GamesFoldersPreferences.java
new file mode 100644
index 00000000..ce5c0f57
--- /dev/null
+++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/GamesFoldersPreferences.java
@@ -0,0 +1,100 @@
+package com.panda3ds.pandroid.app.preferences;
+
+import android.annotation.SuppressLint;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.activity.result.ActivityResultCallback;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.google.android.material.bottomsheet.BottomSheetDialog;
+import com.panda3ds.pandroid.R;
+import com.panda3ds.pandroid.app.base.BasePreferenceFragment;
+import com.panda3ds.pandroid.data.game.GamesFolder;
+import com.panda3ds.pandroid.utils.FileUtils;
+import com.panda3ds.pandroid.utils.GameUtils;
+
+public class GamesFoldersPreferences extends BasePreferenceFragment implements ActivityResultCallback {
+ private final ActivityResultContracts.OpenDocumentTree openFolderContract = new ActivityResultContracts.OpenDocumentTree();
+ private ActivityResultLauncher pickFolderRequest;
+
+ @Override
+ public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
+ setPreferencesFromResource(R.xml.empty_preferences, rootKey);
+ setActivityTitle(R.string.pref_games_folders);
+ refreshList();
+ pickFolderRequest = registerForActivityResult(openFolderContract, this);
+ }
+
+ @SuppressLint("RestrictedApi")
+ private void refreshList(){
+ GamesFolder[] folders = GameUtils.getFolders();
+ PreferenceScreen screen = getPreferenceScreen();
+ screen.removeAll();
+ for (GamesFolder folder: folders){
+ Preference preference = new Preference(screen.getContext());
+ preference.setOnPreferenceClickListener((item)-> {
+ showFolderInfo(folder);
+ screen.performClick();
+ return false;
+ });
+ preference.setTitle(FileUtils.getName(folder.getPath()));
+ preference.setSummary(String.format(getString(R.string.games_count_f), folder.getGames().size()));
+ preference.setIcon(R.drawable.ic_folder);
+ screen.addPreference(preference);
+ }
+
+ Preference add = new Preference(screen.getContext());
+ add.setTitle(R.string.import_folder);
+ add.setIcon(R.drawable.ic_add);
+ add.setOnPreferenceClickListener(preference -> {
+ pickFolderRequest.launch(null);
+ return false;
+ });
+ screen.addPreference(add);
+ }
+
+ private void showFolderInfo(GamesFolder folder) {
+ BottomSheetDialog dialog = new BottomSheetDialog(requireActivity());
+ View layout = LayoutInflater.from(requireActivity()).inflate(R.layout.games_folder_about, null, false);
+ dialog.setContentView(layout);
+
+ ((TextView) layout.findViewById(R.id.name)).setText(FileUtils.getName(folder.getPath()));
+ ((TextView) layout.findViewById(R.id.directory)).setText(folder.getPath());
+ ((TextView) layout.findViewById(R.id.games)).setText(String.valueOf(folder.getGames().size()));
+
+ layout.findViewById(R.id.ok).setOnClickListener(v -> dialog.dismiss());
+ layout.findViewById(R.id.remove).setOnClickListener(v -> {
+ dialog.dismiss();
+ GameUtils.removeFolder(folder);
+ refreshList();
+ });
+
+ dialog.show();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (pickFolderRequest != null){
+ pickFolderRequest.unregister();
+ pickFolderRequest = null;
+ }
+ }
+
+ @Override
+ public void onActivityResult(Uri result) {
+ if (result != null){
+ FileUtils.makeUriPermanent(result.toString(), "r");
+ GameUtils.registerFolder(result.toString());
+ refreshList();
+ }
+ }
+}
diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/GeneralPreferences.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/GeneralPreferences.java
index 7ecfbcad..7ba3e7bb 100644
--- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/GeneralPreferences.java
+++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/GeneralPreferences.java
@@ -15,6 +15,7 @@ public class GeneralPreferences extends BasePreferenceFragment {
setPreferencesFromResource(R.xml.general_preference, rootKey);
setItemClick("appearance.theme", (pref) -> new ThemeSelectorDialog(requireActivity()).show());
setItemClick("appearance.ds", (pref) -> PreferenceActivity.launch(requireActivity(), DsListPreferences.class));
+ setItemClick("games.folders", (pref) -> PreferenceActivity.launch(requireActivity(), GamesFoldersPreferences.class));
setActivityTitle(R.string.general);
}
}
diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/provider/AppDataDocumentProvider.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/provider/AppDataDocumentProvider.java
new file mode 100644
index 00000000..5b8feed2
--- /dev/null
+++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/provider/AppDataDocumentProvider.java
@@ -0,0 +1,119 @@
+package com.panda3ds.pandroid.app.provider;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
+import android.provider.DocumentsProvider;
+
+import androidx.annotation.Nullable;
+
+import com.panda3ds.pandroid.R;
+import com.panda3ds.pandroid.app.PandroidApplication;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+public class AppDataDocumentProvider extends DocumentsProvider {
+ private static final String ROOT_ID = "root";
+ private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{
+ Root.COLUMN_ROOT_ID,
+ Root.COLUMN_MIME_TYPES,
+ Root.COLUMN_FLAGS,
+ Root.COLUMN_ICON,
+ Root.COLUMN_TITLE,
+ Root.COLUMN_SUMMARY,
+ Root.COLUMN_DOCUMENT_ID,
+ Root.COLUMN_AVAILABLE_BYTES
+ };
+
+ private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[]{
+ Document.COLUMN_DOCUMENT_ID,
+ Document.COLUMN_DISPLAY_NAME,
+ Document.COLUMN_MIME_TYPE,
+ Document.COLUMN_LAST_MODIFIED,
+ Document.COLUMN_SIZE
+ };
+
+ private String obtainDocumentId(File file) {
+ String basePath = baseDirectory().getAbsolutePath();
+ String fullPath = file.getAbsolutePath();
+ return (ROOT_ID + "/" + fullPath.substring(basePath.length())).replaceAll("//", "/");
+ }
+
+ private File obtainFile(String documentId) {
+ if (documentId.startsWith(ROOT_ID)) {
+ return new File(baseDirectory(), documentId.substring(ROOT_ID.length()));
+ }
+ throw new IllegalArgumentException("Invalid document id: " + documentId);
+ }
+
+ private Context context() {
+ return PandroidApplication.getAppContext();
+ }
+
+ private File baseDirectory() {
+ return context().getFilesDir();
+ }
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+ MatrixCursor cursor = new MatrixCursor(projection == null ? DEFAULT_ROOT_PROJECTION : projection);
+ cursor.newRow()
+ .add(Root.COLUMN_ROOT_ID, ROOT_ID)
+ .add(Root.COLUMN_SUMMARY, null)
+ .add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE)
+ .add(Root.COLUMN_DOCUMENT_ID, ROOT_ID + "/")
+ .add(Root.COLUMN_AVAILABLE_BYTES, baseDirectory().getFreeSpace())
+ .add(Root.COLUMN_TITLE, context().getString(R.string.app_name))
+ .add(Root.COLUMN_MIME_TYPES, "*/*")
+ .add(Root.COLUMN_ICON, R.mipmap.ic_launcher);
+ return cursor;
+ }
+
+ @Override
+ public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
+ File file = obtainFile(documentId);
+ MatrixCursor cursor = new MatrixCursor(projection == null ? DEFAULT_DOCUMENT_PROJECTION : projection);
+ includeFile(cursor, file);
+ return cursor;
+ }
+
+ private void includeFile(MatrixCursor cursor, File file) {
+ cursor.newRow()
+ .add(Document.COLUMN_DOCUMENT_ID, obtainDocumentId(file))
+ .add(Document.COLUMN_MIME_TYPE, file.isDirectory() ? Document.MIME_TYPE_DIR : "application/octect-stream")
+ .add(Document.COLUMN_FLAGS, 0)
+ .add(Document.COLUMN_LAST_MODIFIED, file.lastModified())
+ .add(Document.COLUMN_DISPLAY_NAME, file.getName())
+ .add(Document.COLUMN_SIZE, file.length());
+
+ }
+
+ @Override
+ public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException {
+ File file = obtainFile(parentDocumentId);
+ MatrixCursor cursor = new MatrixCursor(projection == null ? DEFAULT_DOCUMENT_PROJECTION : projection);
+ File[] children = file.listFiles();
+ if (children != null) {
+ for (File child : children) {
+ includeFile(cursor, child);
+ }
+ }
+
+ return cursor;
+ }
+
+ @Override
+ public ParcelFileDescriptor openDocument(String documentId, String mode, @Nullable CancellationSignal signal) throws FileNotFoundException {
+ return ParcelFileDescriptor.open(obtainFile(documentId), ParcelFileDescriptor.parseMode(mode));
+ }
+}
\ No newline at end of file
diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/game/GameMetadata.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/game/GameMetadata.java
index 5acf2593..2c033171 100644
--- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/game/GameMetadata.java
+++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/game/GameMetadata.java
@@ -15,9 +15,9 @@ import java.util.UUID;
public class GameMetadata {
private final String id;
private final String romPath;
- private final String title;
- private final String publisher;
- private final GameRegion[] regions;
+ private String title;
+ private String publisher;
+ private GameRegion[] regions;
private transient Bitmap icon;
private GameMetadata(String id, String romPath, String title, String publisher, Bitmap icon, GameRegion[] regions) {
@@ -60,7 +60,7 @@ public class GameMetadata {
}
public Bitmap getIcon() {
- if (icon == null) {
+ if (icon == null || icon.isRecycled()) {
icon = GameUtils.loadGameIcon(id);
}
return icon;
@@ -78,10 +78,15 @@ public class GameMetadata {
return false;
}
- public static GameMetadata applySMDH(GameMetadata meta, SMDH smdh) {
+ public void applySMDH(SMDH smdh) {
Bitmap icon = smdh.getBitmapIcon();
- GameMetadata newMeta = new GameMetadata(meta.getId(), meta.getRomPath(), smdh.getTitle(), smdh.getPublisher(), icon, new GameRegion[]{smdh.getRegion()});
- icon.recycle();
- return newMeta;
+ this.title = smdh.getTitle();
+ this.publisher = smdh.getPublisher();
+ this.icon = icon;
+ if (icon != null){
+ GameUtils.setGameIcon(id, icon);
+ }
+ this.regions = new GameRegion[]{smdh.getRegion()};
+ GameUtils.writeChanges();
}
}
diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/game/GamesFolder.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/game/GamesFolder.java
new file mode 100644
index 00000000..78d454a1
--- /dev/null
+++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/game/GamesFolder.java
@@ -0,0 +1,61 @@
+package com.panda3ds.pandroid.data.game;
+
+import android.net.Uri;
+import android.util.Log;
+
+import com.panda3ds.pandroid.R;
+import com.panda3ds.pandroid.app.PandroidApplication;
+import com.panda3ds.pandroid.utils.FileUtils;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.UUID;
+
+public class GamesFolder {
+
+ private final String id = UUID.randomUUID().toString();
+ private final String path;
+ private final HashMap games = new HashMap<>();
+
+ public GamesFolder(String path) {
+ this.path = path;
+ }
+
+ public boolean isValid(){
+ return FileUtils.exists(path);
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public Collection getGames() {
+ return games.values();
+ }
+
+ public void refresh() {
+ String[] gamesId = games.keySet().toArray(new String[0]);
+ for (String file: gamesId){
+ if (!FileUtils.exists(path+"/"+file)){
+ games.remove(file);
+ }
+ }
+ String unknown = PandroidApplication.getAppContext().getString(R.string.unknown);
+
+ for (String file: FileUtils.listFiles(path)){
+ String path = FileUtils.getChild(this.path, file);
+ if (FileUtils.isDirectory(path) || games.containsKey(file)){
+ continue;
+ }
+ String ext = FileUtils.extension(path);
+ if (ext.equals("3ds") || ext.equals("3dsx")){
+ String name = FileUtils.getName(path).trim().split("\\.")[0];
+ games.put(file, new GameMetadata(new Uri.Builder().path(file).authority(id).scheme("folder").build().toString(),name, unknown));
+ }
+ }
+ }
+}
diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java
index 2920c7c6..bec8bae3 100644
--- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java
+++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java
@@ -2,8 +2,10 @@ package com.panda3ds.pandroid.utils;
import android.content.Context;
import android.content.Intent;
+import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract;
import android.system.Os;
import android.util.Log;
@@ -21,6 +23,7 @@ import java.util.Objects;
public class FileUtils {
public static final String MODE_READ = "r";
+ private static final String TREE_URI = "tree";
public static final int CANONICAL_SEARCH_DEEP = 8;
private static DocumentFile parseFile(String path) {
@@ -28,7 +31,14 @@ public class FileUtils {
return DocumentFile.fromFile(new File(path));
}
Uri uri = Uri.parse(path);
- return DocumentFile.fromSingleUri(getContext(), uri);
+ DocumentFile singleFile = DocumentFile.fromSingleUri(getContext(), uri);
+ if (singleFile.length() > 0 && singleFile.length() != 4096){
+ return singleFile;
+ }
+ if (uri.getScheme().equals("content") && uri.getPath().startsWith("/"+TREE_URI)){
+ return DocumentFile.fromTreeUri(getContext(), uri);
+ }
+ return singleFile;
}
private static Context getContext() {
@@ -310,4 +320,12 @@ public class FileUtils {
}
return true;
}
+
+ public static String getChild(String path, String name){
+ return parseFile(path).findFile(name).getUri().toString();
+ }
+
+ public static boolean isDirectory(String path) {
+ return parseFile(path).isDirectory();
+ }
}
diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/GameUtils.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/GameUtils.java
index f050af0a..41e26537 100644
--- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/GameUtils.java
+++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/GameUtils.java
@@ -10,16 +10,18 @@ import android.util.Log;
import com.panda3ds.pandroid.app.GameActivity;
import com.panda3ds.pandroid.data.GsonConfigParser;
import com.panda3ds.pandroid.data.game.GameMetadata;
+import com.panda3ds.pandroid.data.game.GamesFolder;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Objects;
public class GameUtils {
private static final Bitmap DEFAULT_ICON = Bitmap.createBitmap(48, 48, Bitmap.Config.ARGB_8888);
- public static GsonConfigParser parser = new GsonConfigParser(Constants.PREF_GAME_UTILS);
+ private static GsonConfigParser parser = new GsonConfigParser(Constants.PREF_GAME_UTILS);
private static DataModel data;
@@ -27,10 +29,12 @@ public class GameUtils {
public static void initialize() {
data = parser.load(DataModel.class);
+ refreshFolders();
}
public static GameMetadata findByRomPath(String romPath) {
- for (GameMetadata game : data.games) {
+ ArrayList games = getGames();
+ for (GameMetadata game : games) {
if (Objects.equals(romPath, game.getRealPath())) {
return game;
}
@@ -42,9 +46,7 @@ public class GameUtils {
currentGame = game;
String path = game.getRealPath();
if (path.contains("://")) {
- String[] parts = Uri.decode(game.getRomPath()).split("/");
- String name = parts[parts.length - 1];
- path = "game://internal/" + name;
+ path = "game://internal/" + FileUtils.getName(game.getRealPath());
}
context.startActivity(new Intent(context, GameActivity.class).putExtra(Constants.ACTIVITY_PARAMETER_PATH, path));
@@ -72,19 +74,39 @@ public class GameUtils {
Uri uri = Uri.parse(path);
switch (uri.getScheme().toLowerCase()) {
+ case "folder": {
+ return FileUtils.getChild(data.folders.get(uri.getAuthority()).getPath(), uri.getPathSegments().get(0));
+ }
case "elf": {
- return FileUtils.getResourcePath(Constants.RESOURCE_FOLDER_ELF)+"/"+uri.getAuthority();
+ return FileUtils.getResourcePath(Constants.RESOURCE_FOLDER_ELF) + "/" + uri.getAuthority();
}
}
-
return path;
}
- public static ArrayList getGames() {
- return new ArrayList<>(data.games);
+ public static void refreshFolders() {
+ String[] keys = data.folders.keySet().toArray(new String[0]);
+ for (String key : keys) {
+ GamesFolder folder = data.folders.get(key);
+ if (!folder.isValid()){
+ data.folders.remove(key);
+ } else {
+ folder.refresh();
+ }
+ }
+ writeChanges();
}
- private static void writeChanges() {
+ public static ArrayList getGames() {
+ ArrayList games = new ArrayList<>();
+ games.addAll(data.games);
+ for (GamesFolder folder: data.folders.values()){
+ games.addAll(folder.getGames());
+ }
+ return games;
+ }
+
+ public static void writeChanges() {
parser.save(data);
}
@@ -117,7 +139,26 @@ public class GameUtils {
return DEFAULT_ICON;
}
+ public static GamesFolder[] getFolders() {
+ return data.folders.values().toArray(new GamesFolder[0]);
+ }
+
+ public static void registerFolder(String path) {
+ if (!data.folders.containsKey(path)){
+ GamesFolder folder = new GamesFolder(path);
+ data.folders.put(folder.getId(),folder);
+ folder.refresh();
+ writeChanges();
+ }
+ }
+
+ public static void removeFolder(GamesFolder folder) {
+ data.folders.remove(folder.getId());
+ writeChanges();
+ }
+
private static class DataModel {
public final List games = new ArrayList<>();
+ public final HashMap folders = new HashMap<>();
}
}
diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java
index b4c1c4b9..ac8b4922 100644
--- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java
+++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java
@@ -122,9 +122,7 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer
SMDH smdh = new SMDH(smdhData);
Log.i(Constants.LOG_TAG, "Loaded rom SDMH");
Log.i(Constants.LOG_TAG, String.format("You are playing '%s' published by '%s'", smdh.getTitle(), smdh.getPublisher()));
- GameMetadata game = GameUtils.getCurrentGame();
- GameUtils.removeGame(game);
- GameUtils.addGame(GameMetadata.applySMDH(game, smdh));
+ GameUtils.getCurrentGame().applySMDH(smdh);
}
PerformanceMonitor.initialize(getBackendName());
diff --git a/src/pandroid/app/src/main/res/drawable/ic_folder.xml b/src/pandroid/app/src/main/res/drawable/ic_folder.xml
new file mode 100644
index 00000000..011a26ef
--- /dev/null
+++ b/src/pandroid/app/src/main/res/drawable/ic_folder.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/src/pandroid/app/src/main/res/layout/games_folder_about.xml b/src/pandroid/app/src/main/res/layout/games_folder_about.xml
new file mode 100644
index 00000000..5fff79e5
--- /dev/null
+++ b/src/pandroid/app/src/main/res/layout/games_folder_about.xml
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml b/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml
index a48c6999..2d2093e6 100644
--- a/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml
+++ b/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml
@@ -69,4 +69,10 @@
Manter porporção
Disposições para as duas telas.
Altere as disposições disponiveis para as telas do console
+ Clique para mudar
+ Mudar telas
+ Pastas usadas para importar os jogos
+ Pastas de jogos
+ Adicionar pasta
+ %d Jogos
diff --git a/src/pandroid/app/src/main/res/values/strings.xml b/src/pandroid/app/src/main/res/values/strings.xml
index 747b567e..aa5b742e 100644
--- a/src/pandroid/app/src/main/res/values/strings.xml
+++ b/src/pandroid/app/src/main/res/values/strings.xml
@@ -75,4 +75,10 @@
Change layout of console screens.
Click to change
Swap screen
+ Folders for auto import games
+ Games folders
+ Import folder
+ %d Games
+ Directory
+ Remove
diff --git a/src/pandroid/app/src/main/res/xml/general_preference.xml b/src/pandroid/app/src/main/res/xml/general_preference.xml
index 1d20c566..3a81768c 100644
--- a/src/pandroid/app/src/main/res/xml/general_preference.xml
+++ b/src/pandroid/app/src/main/res/xml/general_preference.xml
@@ -16,7 +16,12 @@
app:iconSpaceReserved="false"/>
+
\ No newline at end of file