mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-19 20:19:13 +12:00
basic file provider for access private folder from file explorer
This commit is contained in:
parent
e24dca78f1
commit
351cba9ccf
14 changed files with 509 additions and 23 deletions
|
@ -45,6 +45,17 @@
|
|||
<activity android:name=".app.preferences.InputMapActivity"
|
||||
android:configChanges="density|orientation|screenSize"/>
|
||||
|
||||
<provider
|
||||
android:authorities="com.panda3ds.pandroid.user"
|
||||
android:name="com.panda3ds.pandroid.app.provider.AppDataDocumentProvider"
|
||||
android:exported="true"
|
||||
android:grantUriPermissions="true"
|
||||
android:permission="android.permission.MANAGE_DOCUMENTS">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
||||
</intent-filter>
|
||||
</provider>
|
||||
|
||||
<service android:name=".app.services.LoggerService" android:process=":logger_service"/>
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
|
@ -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<Uri> {
|
||||
private final ActivityResultContracts.OpenDocumentTree openFolderContract = new ActivityResultContracts.OpenDocumentTree();
|
||||
private ActivityResultLauncher<Uri> 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String, GameMetadata> 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<GameMetadata> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<GameMetadata> 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<GameMetadata> 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<GameMetadata> getGames() {
|
||||
ArrayList<GameMetadata> 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<GameMetadata> games = new ArrayList<>();
|
||||
public final HashMap<String, GamesFolder> folders = new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
9
src/pandroid/app/src/main/res/drawable/ic_folder.xml
Normal file
9
src/pandroid/app/src/main/res/drawable/ic_folder.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?colorOnSurface"
|
||||
android:pathData="M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z"/>
|
||||
</vector>
|
106
src/pandroid/app/src/main/res/layout/games_folder_about.xml
Normal file
106
src/pandroid/app/src/main/res/layout/games_folder_about.xml
Normal file
|
@ -0,0 +1,106 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center|top"
|
||||
android:padding="10dp"
|
||||
android:background="@drawable/alert_dialog_background">
|
||||
|
||||
<View
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="4dp"
|
||||
android:background="?colorSurfaceVariant"
|
||||
android:layout_margin="5dp"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:src="@drawable/ic_folder"
|
||||
android:tint="?colorOnSurface"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_name"
|
||||
android:paddingVertical="5dp"
|
||||
android:textSize="20sp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:paddingVertical="20dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/directory"
|
||||
android:textAllCaps="true"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/directory"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_name"
|
||||
android:alpha="0.76"
|
||||
android:textSize="15sp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/games"
|
||||
android:textAllCaps="true"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="20dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/games"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_name"
|
||||
android:alpha="0.76"
|
||||
android:textSize="15sp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="10dp"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="end|center">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/remove"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/remove"
|
||||
app:icon="@drawable/ic_delete"
|
||||
android:textColor="#FFF"
|
||||
app:iconTint="#FFF"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
app:backgroundTint="#CC0000"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/ok"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:text="@android:string/ok"
|
||||
app:icon="@drawable/ic_done"
|
||||
android:textColor="?colorOnSurfaceVariant"
|
||||
app:iconTint="?colorOnSurfaceVariant"
|
||||
app:backgroundTint="?colorSurfaceVariant"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
|
@ -69,4 +69,10 @@
|
|||
<string name="fix_aspect">Manter porporção</string>
|
||||
<string name="dual_screen_layouts">Disposições para as duas telas.</string>
|
||||
<string name="dual_screen_layouts_summary">Altere as disposições disponiveis para as telas do console</string>
|
||||
<string name="click_to_change">Clique para mudar</string>
|
||||
<string name="swap_screen">Mudar telas</string>
|
||||
<string name="pref_games_folders_summary">Pastas usadas para importar os jogos</string>
|
||||
<string name="pref_games_folders">Pastas de jogos</string>
|
||||
<string name="import_folder">Adicionar pasta</string>
|
||||
<string name="games_count_f">%d Jogos</string>
|
||||
</resources>
|
||||
|
|
|
@ -75,4 +75,10 @@
|
|||
<string name="dual_screen_layouts_summary">Change layout of console screens.</string>
|
||||
<string name="click_to_change">Click to change</string>
|
||||
<string name="swap_screen">Swap screen</string>
|
||||
<string name="pref_games_folders_summary">Folders for auto import games</string>
|
||||
<string name="pref_games_folders">Games folders</string>
|
||||
<string name="import_folder">Import folder</string>
|
||||
<string name="games_count_f">%d Games</string>
|
||||
<string name="directory">Directory</string>
|
||||
<string name="remove">Remove</string>
|
||||
</resources>
|
||||
|
|
|
@ -16,7 +16,12 @@
|
|||
app:iconSpaceReserved="false"/>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
app:title="@string/graphics"
|
||||
app:title="@string/games"
|
||||
app:iconSpaceReserved="false">
|
||||
<Preference
|
||||
android:key="games.folders"
|
||||
app:title="@string/pref_games_folders"
|
||||
app:summary="@string/pref_games_folders_summary"
|
||||
app:iconSpaceReserved="false"/>
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
Loading…
Add table
Add a link
Reference in a new issue