mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-23 06:15:38 +00:00
Android: Add memory card editor
This commit is contained in:
parent
6d4a3bb5a5
commit
02e8c7de58
|
@ -11,24 +11,28 @@
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true">
|
||||||
android:networkSecurityConfig="@xml/network_security_config">
|
<activity
|
||||||
|
android:name=".MemoryCardEditorActivity"
|
||||||
|
android:label="@string/title_activity_memory_card_editor"
|
||||||
|
android:theme="@style/AppTheme.NoActionBar"></activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".GameDirectoriesActivity"
|
android:name=".GameDirectoriesActivity"
|
||||||
android:label="@string/title_activity_game_directories"
|
android:label="@string/title_activity_game_directories"
|
||||||
android:theme="@style/AppTheme.NoActionBar"></activity>
|
android:theme="@style/AppTheme.NoActionBar" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".EmulationActivity"
|
android:name=".EmulationActivity"
|
||||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||||
|
android:exported="true"
|
||||||
android:immersive="true"
|
android:immersive="true"
|
||||||
android:label="@string/title_activity_emulation"
|
android:label="@string/title_activity_emulation"
|
||||||
android:parentActivityName=".MainActivity"
|
android:parentActivityName=".MainActivity"
|
||||||
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"
|
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar">
|
||||||
android:exported="true">
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="com.github.stenzek.duckstation.MainActivity" />
|
android:value="com.github.stenzek.duckstation.MainActivity" />
|
||||||
|
|
|
@ -5,18 +5,24 @@ package com.github.stenzek.duckstation;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.storage.StorageManager;
|
import android.os.storage.StorageManager;
|
||||||
import android.provider.DocumentsContract;
|
import android.provider.DocumentsContract;
|
||||||
|
import android.provider.MediaStore;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
@ -163,6 +169,8 @@ public final class FileUtil {
|
||||||
if (os.length() > maxSize)
|
if (os.length() > maxSize)
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stream.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -172,4 +180,96 @@ public final class FileUtil {
|
||||||
|
|
||||||
return os.toString();
|
return os.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] readBytesFromUri(final Context context, final Uri uri, int maxSize) {
|
||||||
|
InputStream stream = null;
|
||||||
|
try {
|
||||||
|
stream = context.getContentResolver().openInputStream(uri);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||||
|
try {
|
||||||
|
byte[] buffer = new byte[512 * 1024];
|
||||||
|
int len;
|
||||||
|
while ((len = stream.read(buffer)) > 0) {
|
||||||
|
os.write(buffer, 0, len);
|
||||||
|
if (maxSize > 0 && os.size() > maxSize) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (os.size() == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return os.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean writeBytesToUri(final Context context, final Uri uri, final byte[] bytes) {
|
||||||
|
OutputStream stream = null;
|
||||||
|
try {
|
||||||
|
stream = context.getContentResolver().openOutputStream(uri);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes != null && bytes.length > 0) {
|
||||||
|
try {
|
||||||
|
stream.write(bytes);
|
||||||
|
stream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean deleteFileAtUri(final Context context, final Uri uri) {
|
||||||
|
try {
|
||||||
|
if (uri.getScheme() == "file") {
|
||||||
|
final File file = new File(uri.getPath());
|
||||||
|
if (!file.isFile())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return file.delete();
|
||||||
|
}
|
||||||
|
return (context.getContentResolver().delete(uri, null, null) > 0);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the file pointed at by a SAF URI.
|
||||||
|
* @param context context to access file under
|
||||||
|
* @param uri uri to retrieve file name for
|
||||||
|
* @return the name of the file, or null
|
||||||
|
*/
|
||||||
|
public static String getDocumentNameFromUri(final Context context, final Uri uri) {
|
||||||
|
Cursor cursor = null;
|
||||||
|
try {
|
||||||
|
final String[] proj = {DocumentsContract.Document.COLUMN_DISPLAY_NAME};
|
||||||
|
cursor = context.getContentResolver().query(uri, proj, null, null, null);
|
||||||
|
final int columnIndex = cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DISPLAY_NAME);
|
||||||
|
cursor.moveToFirst();
|
||||||
|
return cursor.getString(columnIndex);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
if (cursor != null)
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -239,6 +239,10 @@ public class MainActivity extends AppCompatActivity {
|
||||||
Intent intent = new Intent(this, ControllerSettingsActivity.class);
|
Intent intent = new Intent(this, ControllerSettingsActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
|
} else if (id == R.id.action_memory_card_editor) {
|
||||||
|
Intent intent = new Intent(this, MemoryCardEditorActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
return true;
|
||||||
} else if (id == R.id.action_switch_view) {
|
} else if (id == R.id.action_switch_view) {
|
||||||
switchGameListView();
|
switchGameListView();
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -0,0 +1,530 @@
|
||||||
|
package com.github.stenzek.duckstation;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
import com.google.android.material.tabs.TabLayoutMediator;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||||
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
|
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class MemoryCardEditorActivity extends AppCompatActivity {
|
||||||
|
public static final int REQUEST_IMPORT_CARD = 1;
|
||||||
|
|
||||||
|
private final ArrayList<MemoryCardImage> cards = new ArrayList<>();
|
||||||
|
private CollectionAdapter adapter;
|
||||||
|
private ViewPager2 viewPager;
|
||||||
|
private TabLayout tabLayout;
|
||||||
|
private TabLayoutMediator tabLayoutMediator;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_memory_card_editor);
|
||||||
|
|
||||||
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
|
ActionBar actionBar = getSupportActionBar();
|
||||||
|
if (actionBar != null) {
|
||||||
|
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter = new CollectionAdapter(this);
|
||||||
|
viewPager = findViewById(R.id.view_pager);
|
||||||
|
viewPager.setAdapter(adapter);
|
||||||
|
|
||||||
|
tabLayout = findViewById(R.id.tab_layout);
|
||||||
|
tabLayoutMediator = new TabLayoutMediator(tabLayout, viewPager, adapter.getTabConfigurationStrategy());
|
||||||
|
tabLayoutMediator.attach();
|
||||||
|
|
||||||
|
findViewById(R.id.open_card).setOnClickListener((v) -> openCard());
|
||||||
|
findViewById(R.id.close_card).setOnClickListener((v) -> closeCard());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
outState.remove("android:support:fragments");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
getMenuInflater().inflate(R.menu.menu_memory_card_editor, menu);
|
||||||
|
|
||||||
|
final boolean hasCurrentCard = (getCurrentCard() != null);
|
||||||
|
menu.findItem(R.id.action_delete_card).setEnabled(hasCurrentCard);
|
||||||
|
menu.findItem(R.id.action_format_card).setEnabled(hasCurrentCard);
|
||||||
|
menu.findItem(R.id.action_import_card).setEnabled(hasCurrentCard);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case android.R.id.home: {
|
||||||
|
onBackPressed();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case R.id.action_import_card: {
|
||||||
|
importCard();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case R.id.action_delete_card: {
|
||||||
|
deleteCard();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case R.id.action_format_card: {
|
||||||
|
formatCard();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openCard() {
|
||||||
|
final Uri[] uris = MemoryCardImage.getCardUris(this);
|
||||||
|
if (uris == null) {
|
||||||
|
displayMessage(getString(R.string.memory_card_editor_no_cards_found));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String[] uriTitles = new String[uris.length];
|
||||||
|
for (int i = 0; i < uris.length; i++)
|
||||||
|
uriTitles[i] = MemoryCardImage.getTitleForUri(uris[i]);
|
||||||
|
|
||||||
|
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
builder.setTitle(R.string.memory_card_editor_select_card);
|
||||||
|
builder.setItems(uriTitles, (dialog, which) -> {
|
||||||
|
final Uri uri = uris[which];
|
||||||
|
for (int i = 0; i < cards.size(); i++) {
|
||||||
|
if (cards.get(i).getUri().equals(uri)) {
|
||||||
|
displayError(getString(R.string.memory_card_editor_card_already_open));
|
||||||
|
tabLayout.getTabAt(i).select();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final MemoryCardImage card = MemoryCardImage.open(MemoryCardEditorActivity.this, uri);
|
||||||
|
if (card == null) {
|
||||||
|
displayError(getString(R.string.memory_card_editor_failed_to_open_card));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cards.add(card);
|
||||||
|
refreshView(card);
|
||||||
|
});
|
||||||
|
builder.create().show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeCard() {
|
||||||
|
final int index = tabLayout.getSelectedTabPosition();
|
||||||
|
if (index < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
cards.remove(index);
|
||||||
|
refreshView(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayMessage(String message) {
|
||||||
|
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayError(String message) {
|
||||||
|
final AlertDialog.Builder errorBuilder = new AlertDialog.Builder(this);
|
||||||
|
errorBuilder.setTitle(R.string.memory_card_editor_error);
|
||||||
|
errorBuilder.setMessage(message);
|
||||||
|
errorBuilder.setPositiveButton(R.string.main_activity_ok, (dialog1, which1) -> dialog1.dismiss());
|
||||||
|
errorBuilder.create().show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copySave(MemoryCardImage sourceCard, MemoryCardFileInfo sourceFile) {
|
||||||
|
if (cards.size() < 2) {
|
||||||
|
displayError(getString(R.string.memory_card_editor_must_have_at_least_two_cards_to_copy));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cards.indexOf(sourceCard) < 0) {
|
||||||
|
// this shouldn't happen..
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final MemoryCardImage[] destinationCards = new MemoryCardImage[cards.size() - 1];
|
||||||
|
final String[] cardTitles = new String[cards.size() - 1];
|
||||||
|
for (int i = 0, d = 0; i < cards.size(); i++) {
|
||||||
|
if (cards.get(i) == sourceCard)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
destinationCards[d] = cards.get(i);
|
||||||
|
cardTitles[d] = cards.get(i).getTitle();
|
||||||
|
d++;
|
||||||
|
}
|
||||||
|
|
||||||
|
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
builder.setTitle(getString(R.string.memory_card_editor_copy_save_to, sourceFile.getTitle()));
|
||||||
|
builder.setItems(cardTitles, (dialog, which) -> {
|
||||||
|
dialog.dismiss();
|
||||||
|
|
||||||
|
final MemoryCardImage destinationCard = destinationCards[which];
|
||||||
|
|
||||||
|
byte[] data = null;
|
||||||
|
if (destinationCard.getFreeBlocks() < sourceFile.getNumBlocks()) {
|
||||||
|
displayError(getString(R.string.memory_card_editor_copy_insufficient_blocks, sourceFile.getNumBlocks(),
|
||||||
|
destinationCard.getFreeBlocks()));
|
||||||
|
} else if (destinationCard.hasFile(sourceFile.getFilename())) {
|
||||||
|
displayError(getString(R.string.memory_card_editor_copy_already_exists, sourceFile.getFilename()));
|
||||||
|
} else if ((data = sourceCard.readFile(sourceFile.getFilename())) == null) {
|
||||||
|
displayError(getString(R.string.memory_card_editor_copy_read_failed, sourceFile.getFilename()));
|
||||||
|
} else if (!destinationCard.writeFile(sourceFile.getFilename(), data)) {
|
||||||
|
displayMessage(getString(R.string.memory_card_editor_copy_write_failed, sourceFile.getFilename()));
|
||||||
|
} else {
|
||||||
|
displayMessage(getString(R.string.memory_card_editor_copy_success, sourceFile.getFilename(),
|
||||||
|
destinationCard.getTitle()));
|
||||||
|
refreshView(destinationCard);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.create().show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteSave(MemoryCardImage card, MemoryCardFileInfo file) {
|
||||||
|
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
builder.setMessage(getString(R.string.memory_card_editor_delete_confirm, file.getFilename()));
|
||||||
|
builder.setPositiveButton(R.string.main_activity_yes, (dialog, which) -> {
|
||||||
|
if (card.deleteFile(file.getFilename())) {
|
||||||
|
displayMessage(getString(R.string.memory_card_editor_delete_success, file.getFilename()));
|
||||||
|
refreshView(card);
|
||||||
|
} else {
|
||||||
|
displayError(getString(R.string.memory_card_editor_delete_failed, file.getFilename()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.setNegativeButton(R.string.main_activity_no, (dialog, which) -> dialog.dismiss());
|
||||||
|
builder.create().show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshView(int newSelection) {
|
||||||
|
final int oldPos = viewPager.getCurrentItem();
|
||||||
|
tabLayoutMediator.detach();
|
||||||
|
viewPager.setAdapter(null);
|
||||||
|
viewPager.setAdapter(adapter);
|
||||||
|
tabLayoutMediator.attach();
|
||||||
|
|
||||||
|
if (cards.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (newSelection < 0) {
|
||||||
|
if (oldPos < cards.size())
|
||||||
|
tabLayout.getTabAt(oldPos).select();
|
||||||
|
else
|
||||||
|
tabLayout.getTabAt(cards.size() - 1).select();
|
||||||
|
} else {
|
||||||
|
tabLayout.getTabAt(newSelection).select();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshView(MemoryCardImage newSelectedCard) {
|
||||||
|
if (newSelectedCard == null)
|
||||||
|
refreshView(-1);
|
||||||
|
else
|
||||||
|
refreshView(cards.indexOf(newSelectedCard));
|
||||||
|
|
||||||
|
invalidateOptionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MemoryCardImage getCurrentCard() {
|
||||||
|
final int index = tabLayout.getSelectedTabPosition();
|
||||||
|
if (index < 0 || index >= cards.size())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return cards.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void importCard() {
|
||||||
|
if (getCurrentCard() == null) {
|
||||||
|
displayMessage(getString(R.string.memory_card_editor_no_card_selected));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
|
intent.setType("*/*");
|
||||||
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
startActivityForResult(Intent.createChooser(intent, getString(R.string.main_activity_choose_disc_image)), REQUEST_IMPORT_CARD);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void importCard(Uri uri) {
|
||||||
|
final MemoryCardImage card = getCurrentCard();
|
||||||
|
if (card == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
final byte[] data = FileUtil.readBytesFromUri(this, uri, 16 * 1024 * 1024);
|
||||||
|
if (data == null) {
|
||||||
|
displayError(getString(R.string.memory_card_editor_import_card_read_failed, uri.toString()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String importFileName = FileUtil.getDocumentNameFromUri(this, uri);
|
||||||
|
if (importFileName == null) {
|
||||||
|
importFileName = uri.getPath();
|
||||||
|
if (importFileName == null || importFileName.isEmpty())
|
||||||
|
importFileName = uri.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
final String captureImportFileName = importFileName;
|
||||||
|
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
builder.setMessage(getString(R.string.memory_card_editor_import_card_confirm_message,
|
||||||
|
importFileName, card.getTitle()));
|
||||||
|
builder.setPositiveButton(R.string.main_activity_yes, (dialog, which) -> {
|
||||||
|
dialog.dismiss();
|
||||||
|
|
||||||
|
if (!card.importCard(captureImportFileName, data)) {
|
||||||
|
displayError(getString(R.string.memory_card_editor_import_failed));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshView(card);
|
||||||
|
});
|
||||||
|
builder.setNegativeButton(R.string.main_activity_no, (dialog, which) -> dialog.dismiss());
|
||||||
|
builder.create().show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void formatCard() {
|
||||||
|
final MemoryCardImage card = getCurrentCard();
|
||||||
|
if (card == null) {
|
||||||
|
displayMessage(getString(R.string.memory_card_editor_no_card_selected));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
builder.setMessage(getString(R.string.memory_card_editor_format_card_confirm_message, card.getTitle()));
|
||||||
|
builder.setPositiveButton(R.string.main_activity_yes, (dialog, which) -> {
|
||||||
|
dialog.dismiss();
|
||||||
|
|
||||||
|
if (!card.format()) {
|
||||||
|
displayError(getString(R.string.memory_card_editor_format_card_failed, card.getUri().toString()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
displayMessage(getString(R.string.memory_card_editor_format_card_success, card.getUri().toString()));
|
||||||
|
refreshView(card);
|
||||||
|
});
|
||||||
|
builder.setNegativeButton(R.string.main_activity_no, (dialog, which) -> dialog.dismiss());
|
||||||
|
builder.create().show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteCard() {
|
||||||
|
final MemoryCardImage card = getCurrentCard();
|
||||||
|
if (card == null) {
|
||||||
|
displayMessage(getString(R.string.memory_card_editor_no_card_selected));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
builder.setMessage(getString(R.string.memory_card_editor_delete_card_confirm_message, card.getTitle()));
|
||||||
|
builder.setPositiveButton(R.string.main_activity_yes, (dialog, which) -> {
|
||||||
|
dialog.dismiss();
|
||||||
|
|
||||||
|
if (!card.delete()) {
|
||||||
|
displayError(getString(R.string.memory_card_editor_delete_card_failed, card.getUri().toString()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
displayMessage(getString(R.string.memory_card_editor_delete_card_success, card.getUri().toString()));
|
||||||
|
cards.remove(card);
|
||||||
|
refreshView(-1);
|
||||||
|
});
|
||||||
|
builder.setNegativeButton(R.string.main_activity_no, (dialog, which) -> dialog.dismiss());
|
||||||
|
builder.create().show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
|
||||||
|
switch (requestCode) {
|
||||||
|
case REQUEST_IMPORT_CARD: {
|
||||||
|
if (resultCode != RESULT_OK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
importCard(data.getData());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SaveViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||||
|
private MemoryCardEditorActivity mParent;
|
||||||
|
private View mItemView;
|
||||||
|
private MemoryCardImage mCard;
|
||||||
|
private MemoryCardFileInfo mFile;
|
||||||
|
|
||||||
|
public SaveViewHolder(MemoryCardEditorActivity parent, @NonNull View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
mParent = parent;
|
||||||
|
mItemView = itemView;
|
||||||
|
mItemView.setOnClickListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bindToEntry(MemoryCardImage card, MemoryCardFileInfo file) {
|
||||||
|
mCard = card;
|
||||||
|
mFile = file;
|
||||||
|
|
||||||
|
((TextView) mItemView.findViewById(R.id.title)).setText(mFile.getTitle());
|
||||||
|
((TextView) mItemView.findViewById(R.id.filename)).setText(mFile.getFilename());
|
||||||
|
|
||||||
|
final String blocksText = String.format("%d Blocks", mFile.getNumBlocks());
|
||||||
|
final String sizeText = String.format("%.1f KB", (float)mFile.getSize() / 1024.0f);
|
||||||
|
((TextView) mItemView.findViewById(R.id.block_size)).setText(blocksText);
|
||||||
|
((TextView) mItemView.findViewById(R.id.file_size)).setText(sizeText);
|
||||||
|
|
||||||
|
if (mFile.getNumIconFrames() > 0) {
|
||||||
|
final Bitmap bitmap = mFile.getIconFrameBitmap(0);
|
||||||
|
if (bitmap != null) {
|
||||||
|
((ImageView) mItemView.findViewById(R.id.icon)).setImageBitmap(bitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
final AlertDialog.Builder builder = new AlertDialog.Builder(mItemView.getContext());
|
||||||
|
builder.setTitle(mFile.getFilename());
|
||||||
|
builder.setItems(R.array.memory_card_editor_save_menu, ((dialog, which) -> {
|
||||||
|
switch (which) {
|
||||||
|
// Copy Save
|
||||||
|
case 0: {
|
||||||
|
dialog.dismiss();
|
||||||
|
mParent.copySave(mCard, mFile);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Delete Save
|
||||||
|
case 1: {
|
||||||
|
dialog.dismiss();
|
||||||
|
mParent.deleteSave(mCard, mFile);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
builder.create().show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SaveViewAdapter extends RecyclerView.Adapter<SaveViewHolder> {
|
||||||
|
private MemoryCardEditorActivity parent;
|
||||||
|
private MemoryCardImage card;
|
||||||
|
private MemoryCardFileInfo[] files;
|
||||||
|
|
||||||
|
public SaveViewAdapter(MemoryCardEditorActivity parent, MemoryCardImage card) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.card = card;
|
||||||
|
this.files = card.getFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public SaveViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
final View rootView = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_memory_card_save, parent, false);
|
||||||
|
return new SaveViewHolder(this.parent, rootView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull SaveViewHolder holder, int position) {
|
||||||
|
holder.bindToEntry(card, files[position]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return (files != null) ? files.length : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
return R.layout.layout_memory_card_save;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MemoryCardFileFragment extends Fragment {
|
||||||
|
private MemoryCardEditorActivity parent;
|
||||||
|
private MemoryCardImage card;
|
||||||
|
private SaveViewAdapter adapter;
|
||||||
|
private RecyclerView recyclerView;
|
||||||
|
|
||||||
|
public MemoryCardFileFragment(MemoryCardEditorActivity parent, MemoryCardImage card) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.card = card;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
return inflater.inflate(R.layout.fragment_memory_card_file, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
adapter = new SaveViewAdapter(parent, card);
|
||||||
|
recyclerView = view.findViewById(R.id.recyclerView);
|
||||||
|
recyclerView.setAdapter(adapter);
|
||||||
|
recyclerView.setLayoutManager(new LinearLayoutManager(recyclerView.getContext()));
|
||||||
|
recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(),
|
||||||
|
DividerItemDecoration.VERTICAL));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CollectionAdapter extends FragmentStateAdapter {
|
||||||
|
private MemoryCardEditorActivity parent;
|
||||||
|
private final TabLayoutMediator.TabConfigurationStrategy tabConfigurationStrategy = (tab, position) -> {
|
||||||
|
tab.setText(parent.cards.get(position).getTitle());
|
||||||
|
};
|
||||||
|
|
||||||
|
public CollectionAdapter(MemoryCardEditorActivity parent) {
|
||||||
|
super(parent);
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TabLayoutMediator.TabConfigurationStrategy getTabConfigurationStrategy() {
|
||||||
|
return tabConfigurationStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Fragment createFragment(int position) {
|
||||||
|
return new MemoryCardFileFragment(parent, parent.cards.get(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return parent.cards.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
android/app/src/main/res/drawable/ic_baseline_close_24.xml
Normal file
10
android/app/src/main/res/drawable/ic_baseline_close_24.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M20,6h-8l-2,-2L4,4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM19,14h-3v3h-2v-3h-3v-2h3L14,9h2v3h3v2z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M15,16h4v2h-4zM15,8h7v2h-7zM15,12h6v2h-6zM3,18c0,1.1 0.9,2 2,2h6c1.1,0 2,-0.9 2,-2L13,8L3,8v10zM14,5h-3l-1,-1L6,4L5,5L2,5v2h12z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M17.5,4.5c-1.95,0 -4.05,0.4 -5.5,1.5c-1.45,-1.1 -3.55,-1.5 -5.5,-1.5S2.45,4.9 1,6v14.65c0,0.65 0.73,0.45 0.75,0.45C3.1,20.45 5.05,20 6.5,20c1.95,0 4.05,0.4 5.5,1.5c1.35,-0.85 3.8,-1.5 5.5,-1.5c1.65,0 3.35,0.3 4.75,1.05C22.66,21.26 23,20.86 23,20.6V6C21.51,4.88 19.37,4.5 17.5,4.5zM21,18.5c-1.1,-0.35 -2.3,-0.5 -3.5,-0.5c-1.7,0 -4.15,0.65 -5.5,1.5V8c1.35,-0.85 3.8,-1.5 5.5,-1.5c1.2,0 2.4,0.15 3.5,0.5V18.5z"/>
|
||||||
|
</vector>
|
10
android/app/src/main/res/drawable/ic_baseline_sd_card_24.xml
Normal file
10
android/app/src/main/res/drawable/ic_baseline_sd_card_24.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M18,2h-8L4.02,8 4,20c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,4c0,-1.1 -0.9,-2 -2,-2zM12,8h-2L10,4h2v4zM15,8h-2L13,4h2v4zM18,8h-2L16,4h2v4z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:theme="@style/AppTheme.AppBarOverlay">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="?attr/colorPrimary"
|
||||||
|
app:popupTheme="@style/AppTheme.PopupOverlay" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
|
||||||
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
android:id="@+id/tab_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:tabTextAppearance="@style/TabTextAppearance"
|
||||||
|
app:tabMinWidth="150dp"
|
||||||
|
app:tabMode="scrollable" />
|
||||||
|
|
||||||
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
|
android:id="@+id/view_pager"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
android:id="@+id/open_card"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_marginBottom="@dimen/fab_margin"
|
||||||
|
android:layout_marginRight="96dp"
|
||||||
|
app:backgroundTint="@color/fab_background"
|
||||||
|
app:srcCompat="@drawable/ic_baseline_folder_open_24" />
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
android:id="@+id/close_card"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_margin="@dimen/fab_margin"
|
||||||
|
app:backgroundTint="@color/fab_background"
|
||||||
|
app:srcCompat="@drawable/ic_baseline_close_24" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?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"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
82
android/app/src/main/res/layout/layout_memory_card_save.xml
Normal file
82
android/app/src/main/res/layout/layout_memory_card_save.xml
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
<?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="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingBottom="8dp"
|
||||||
|
android:background="?android:attr/selectableItemBackground">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:foregroundGravity="center_vertical"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:srcCompat="@drawable/ic_media_cdrom" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="10dp"
|
||||||
|
android:layout_marginRight="80dp"
|
||||||
|
android:focusable="false"
|
||||||
|
android:focusableInTouchMode="false"
|
||||||
|
android:text="Save Title"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Large"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/icon"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/filename"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="10dp"
|
||||||
|
android:layout_marginRight="80dp"
|
||||||
|
android:focusable="false"
|
||||||
|
android:focusableInTouchMode="false"
|
||||||
|
android:paddingBottom="8px"
|
||||||
|
android:text="Save Filename"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/icon"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/title" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/block_size"
|
||||||
|
android:layout_width="64dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:focusable="false"
|
||||||
|
android:focusableInTouchMode="false"
|
||||||
|
android:paddingBottom="8px"
|
||||||
|
android:text="1 Block"
|
||||||
|
android:textAlignment="viewEnd"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:srcCompat="@drawable/ic_baseline_lock_24" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/file_size"
|
||||||
|
android:layout_width="64dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:focusable="false"
|
||||||
|
android:focusableInTouchMode="false"
|
||||||
|
android:text="16KB"
|
||||||
|
android:textAlignment="viewEnd"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||||
|
app:srcCompat="@drawable/ic_star_5"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/block_size" />
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -14,14 +14,14 @@
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_settings"
|
android:id="@+id/action_settings"
|
||||||
android:icon="@drawable/ic_baseline_settings_24"
|
android:icon="@drawable/ic_baseline_settings_24"
|
||||||
android:orderInCategory="101"
|
android:orderInCategory="103"
|
||||||
android:title="@string/action_settings"
|
android:title="@string/action_settings"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_switch_view"
|
android:id="@+id/action_switch_view"
|
||||||
android:icon="@drawable/ic_baseline_settings_24"
|
android:icon="@drawable/ic_baseline_settings_24"
|
||||||
android:orderInCategory="100"
|
android:orderInCategory="100"
|
||||||
android:title="Switch View"
|
android:title="@string/action_switch_view"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_controller_settings"
|
android:id="@+id/action_controller_settings"
|
||||||
|
@ -29,6 +29,12 @@
|
||||||
android:orderInCategory="102"
|
android:orderInCategory="102"
|
||||||
android:title="@string/action_controller_mapping"
|
android:title="@string/action_controller_mapping"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_memory_card_editor"
|
||||||
|
android:icon="@drawable/ic_baseline_sd_card_24"
|
||||||
|
android:orderInCategory="101"
|
||||||
|
android:title="@string/action_memory_card_editor"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_edit_game_directories"
|
android:id="@+id/action_edit_game_directories"
|
||||||
android:title="@string/menu_main_edit_game_directories" />
|
android:title="@string/menu_main_edit_game_directories" />
|
||||||
|
|
24
android/app/src/main/res/menu/menu_memory_card_editor.xml
Normal file
24
android/app/src/main/res/menu/menu_memory_card_editor.xml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_new_card"
|
||||||
|
android:title="@string/action_memory_card_editor_new_card"
|
||||||
|
android:enabled="false"
|
||||||
|
android:icon="@drawable/ic_baseline_create_new_folder_24" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_import_card"
|
||||||
|
android:title="@string/action_memory_card_editor_import_card"
|
||||||
|
android:icon="@drawable/ic_baseline_import_contacts_24"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_format_card"
|
||||||
|
android:title="@string/action_memory_card_editor_format_card"
|
||||||
|
android:enabled="false"
|
||||||
|
android:icon="@drawable/ic_baseline_delete_sweep_24" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_delete_card"
|
||||||
|
android:title="@string/action_memory_card_editor_delete_card"
|
||||||
|
android:icon="@drawable/ic_baseline_delete_24"
|
||||||
|
app:showAsAction="ifRoom"
|
||||||
|
/>
|
||||||
|
</menu>
|
|
@ -485,4 +485,8 @@
|
||||||
<item>Port2Only</item>
|
<item>Port2Only</item>
|
||||||
<item>BothPorts</item>
|
<item>BothPorts</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
<string-array name="memory_card_editor_save_menu">
|
||||||
|
<item>Copy Save</item>
|
||||||
|
<item>Delete Save</item>
|
||||||
|
</string-array>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -276,4 +276,39 @@
|
||||||
<string name="controller_settings_category_ports">Ports</string>
|
<string name="controller_settings_category_ports">Ports</string>
|
||||||
<string name="controller_settings_main_port_format">Port %d</string>
|
<string name="controller_settings_main_port_format">Port %d</string>
|
||||||
<string name="controller_settings_sub_port_format">Port %1$d%2$c</string>
|
<string name="controller_settings_sub_port_format">Port %1$d%2$c</string>
|
||||||
|
<string name="action_switch_view">Switch View</string>
|
||||||
|
<string name="title_activity_memory_card_editor">Memory Card Editor</string>
|
||||||
|
<string name="action_memory_card_editor">Memory Card Editor</string>
|
||||||
|
<string name="action_memory_card_editor_import_card">Import Card</string>
|
||||||
|
<string name="action_memory_card_editor_open_card">Open Card</string>
|
||||||
|
<string name="action_memory_card_editor_new_card">New Card</string>
|
||||||
|
<string name="action_memory_card_editor_format_card">Format Card</string>
|
||||||
|
<string name="action_memory_card_editor_delete_card">Delete Card</string>
|
||||||
|
<string name="memory_card_editor_no_cards_found">No memory cards found.</string>
|
||||||
|
<string name="memory_card_editor_card_already_open">This card is already open.</string>
|
||||||
|
<string name="memory_card_editor_failed_to_open_card">Failed to open or read memory card.</string>
|
||||||
|
<string name="memory_card_editor_must_have_at_least_two_cards_to_copy">Must have at least two cards open to copy.</string>
|
||||||
|
<string name="memory_card_editor_copy_save_to">Copy %s to...</string>
|
||||||
|
<string name="memory_card_editor_select_card">Select Card</string>
|
||||||
|
<string name="memory_card_editor_error">Error</string>
|
||||||
|
<string name="memory_card_editor_copy_insufficient_blocks">This file requires %1$d blocks, but only %2$d blocks are free.</string>
|
||||||
|
<string name="memory_card_editor_copy_already_exists">File \'%s\' already exists on destination card.</string>
|
||||||
|
<string name="memory_card_editor_copy_read_failed">Failed to read file \'%s\' from source card.</string>
|
||||||
|
<string name="memory_card_editor_copy_write_failed">Failed to write file \'%s\' to destination card.</string>
|
||||||
|
<string name="memory_card_editor_copy_success">Copied \'%1$s\' to \'%2$s\'.</string>
|
||||||
|
<string name="memory_card_editor_delete_confirm">Are you sure you want to delete the save \'%s\'?</string>
|
||||||
|
<string name="memory_card_editor_delete_success">Deleted save \'%s\'.</string>
|
||||||
|
<string name="memory_card_editor_delete_failed">Failed to delete file \'%s\'.</string>
|
||||||
|
<string name="memory_card_editor_no_card_selected">No card is selected.</string>
|
||||||
|
<string name="memory_card_editor_import_failed">Failed to import card. It may not be a supported format.</string>
|
||||||
|
<string name="memory_card_editor_delete_card_confirm_message">Memory card \'%s\' will be deleted, and cannot be recovered. Are you sure you want to delete this card?</string>
|
||||||
|
<string name="memory_card_editor_delete_card_failed">Failed to delete \'%s\'.</string>
|
||||||
|
<string name="memory_card_editor_delete_card_success">Deleted card \'%s\'.</string>
|
||||||
|
<string name="memory_card_editor_format_card_confirm_message">Memory card \'%s\' will be formatted, clearing all saves. Are you sure you want to format this card?</string>
|
||||||
|
<string name="memory_card_editor_format_card_failed">Failed to format \'%s\'.</string>
|
||||||
|
<string name="memory_card_editor_format_card_success">Formatted card \'%s\'.</string>
|
||||||
|
<string name="memory_card_editor_import_card_confirm_message">Importing \'%1$s\' will remove all saves in \'%2$s\'. Do you want to continue?</string>
|
||||||
|
<string name="memory_card_editor_import_card_read_failed">Failed to read \'%s\'.</string>
|
||||||
|
<string name="memory_card_editor_import_card_failed">Failed to import card \'%s\'. It may not be a supported format.</string>
|
||||||
|
<string name="memory_card_editor_import_card_success">Imported card \'%s\'.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue