diff --git a/app/build.gradle b/app/build.gradle
index 0ee925b..400d9ef 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,14 +1,14 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 29
+ compileSdkVersion 30
buildToolsVersion '29.0.3'
defaultConfig {
applicationId "com.m2049r.xmrwallet"
minSdkVersion 21
- targetSdkVersion 29
- versionCode 801
- versionName "1.18.1 'ChAdOx1'"
+ targetSdkVersion 30
+ versionCode 900
+ versionName "1.19.0.2 'XXX'"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5f9acf4..a18294f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -8,7 +8,6 @@
-
diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java
index be6dd76..4e8895e 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java
@@ -23,10 +23,8 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
-import android.media.MediaScannerConnection;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.KeyEvent;
@@ -39,6 +37,7 @@ import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.fragment.app.Fragment;
@@ -62,10 +61,13 @@ import com.m2049r.xmrwallet.service.WalletService;
import com.m2049r.xmrwallet.util.DayNightMode;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.KeyStoreHelper;
+import com.m2049r.xmrwallet.util.LegacyStorageHelper;
import com.m2049r.xmrwallet.util.LocaleHelper;
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
import com.m2049r.xmrwallet.util.NightmodeHelper;
import com.m2049r.xmrwallet.util.ThemeHelper;
+import com.m2049r.xmrwallet.util.ZipBackup;
+import com.m2049r.xmrwallet.util.ZipRestore;
import com.m2049r.xmrwallet.widget.Toolbar;
import java.io.File;
@@ -290,9 +292,6 @@ public class LoginActivity extends BaseActivity
protected void onCreate(Bundle savedInstanceState) {
Timber.d("onCreate()");
super.onCreate(savedInstanceState);
- if (savedInstanceState != null) {
- // we don't store anything ourselves
- }
setContentView(R.layout.activity_login);
toolbar = findViewById(R.id.toolbar);
@@ -319,11 +318,9 @@ public class LoginActivity extends BaseActivity
loadFavouritesWithNetwork();
- if (Helper.getWritePermission(this)) {
- if (savedInstanceState == null) startLoginFragment();
- } else {
- Timber.i("Waiting for permissions");
- }
+ LegacyStorageHelper.migrateWallets(this);
+
+ if (savedInstanceState == null) startLoginFragment();
// try intents
Intent intent = getIntent();
@@ -396,53 +393,25 @@ public class LoginActivity extends BaseActivity
.show();
}
- private class AsyncRename extends AsyncTask {
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- showProgressDialog(R.string.rename_progress);
+ private void renameWallet(String oldName, String newName) {
+ File walletFile = Helper.getWalletFile(this, oldName);
+ boolean success = renameWallet(walletFile, newName);
+ if (success) {
+ reloadWalletList();
+ } else {
+ Toast.makeText(LoginActivity.this, getString(R.string.rename_failed), Toast.LENGTH_LONG).show();
}
+ }
- @Override
- protected Boolean doInBackground(String... params) {
- if (params.length != 2) return false;
- String oldName = params[0];
- String newName = params[1];
- File walletFile = Helper.getWalletFile(LoginActivity.this, oldName);
- boolean success = renameWallet(walletFile, newName);
+ // copy + delete seems safer than rename because we can rollback easily
+ boolean renameWallet(File walletFile, String newName) {
+ if (copyWallet(walletFile, new File(walletFile.getParentFile(), newName), false, true)) {
try {
- if (success) {
- String savedPass = KeyStoreHelper.loadWalletUserPass(LoginActivity.this, oldName);
- KeyStoreHelper.saveWalletUserPass(LoginActivity.this, newName, savedPass);
- }
+ KeyStoreHelper.copyWalletUserPass(this, walletFile.getName(), newName);
} catch (KeyStoreHelper.BrokenPasswordStoreException ex) {
Timber.w(ex);
- } finally {
- // we have either set a new password or it is broken - kill the old one either way
- KeyStoreHelper.removeWalletUserPass(LoginActivity.this, oldName);
- }
- return success;
- }
-
- @Override
- protected void onPostExecute(Boolean result) {
- super.onPostExecute(result);
- if (isDestroyed()) {
- return;
- }
- dismissProgressDialog();
- if (result) {
- reloadWalletList();
- } else {
- Toast.makeText(LoginActivity.this, getString(R.string.rename_failed), Toast.LENGTH_LONG).show();
}
- }
- }
-
- // copy + delete seems safer than rename because we call rollback easily
- boolean renameWallet(File walletFile, String newName) {
- if (copyWallet(walletFile, new File(walletFile.getParentFile(), newName), false, true)) {
- deleteWallet(walletFile);
+ deleteWallet(walletFile); // also deletes the keystore entry
return true;
} else {
return false;
@@ -468,154 +437,123 @@ public class LoginActivity extends BaseActivity
alertDialogBuilder
.setCancelable(false)
.setPositiveButton(getString(R.string.label_ok),
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int id) {
- Helper.hideKeyboardAlways(LoginActivity.this);
- String newName = etRename.getText().toString();
- new AsyncRename().execute(walletName, newName);
- }
+ (dialog, id) -> {
+ Helper.hideKeyboardAlways(LoginActivity.this);
+ String newName = etRename.getText().toString();
+ renameWallet(walletName, newName);
})
.setNegativeButton(getString(R.string.label_cancel),
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int id) {
- Helper.hideKeyboardAlways(LoginActivity.this);
- dialog.cancel();
- }
+ (dialog, id) -> {
+ Helper.hideKeyboardAlways(LoginActivity.this);
+ dialog.cancel();
});
final AlertDialog dialog = alertDialogBuilder.create();
Helper.showKeyboard(dialog);
// accept keyboard "ok"
- etRename.setOnEditorActionListener(new TextView.OnEditorActionListener() {
- public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
- || (actionId == EditorInfo.IME_ACTION_DONE)) {
- Helper.hideKeyboardAlways(LoginActivity.this);
- String newName = etRename.getText().toString();
- dialog.cancel();
- new AsyncRename().execute(walletName, newName);
- return false;
- }
+ etRename.setOnEditorActionListener((v, actionId, event) -> {
+ if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
+ || (actionId == EditorInfo.IME_ACTION_DONE)) {
+ Helper.hideKeyboardAlways(LoginActivity.this);
+ String newName = etRename.getText().toString();
+ dialog.cancel();
+ renameWallet(walletName, newName);
return false;
}
+ return false;
});
dialog.show();
}
- private class AsyncBackup extends AsyncTask {
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- showProgressDialog(R.string.backup_progress);
- }
-
- @Override
- protected Boolean doInBackground(String... params) {
- if (params.length != 1) return false;
- return backupWallet(params[0]);
- }
-
- @Override
- protected void onPostExecute(Boolean result) {
- super.onPostExecute(result);
- if (isDestroyed()) {
- return;
- }
- dismissProgressDialog();
- if (!result) {
- Toast.makeText(LoginActivity.this, getString(R.string.backup_failed), Toast.LENGTH_LONG).show();
- } else {
- Toast.makeText(LoginActivity.this, getString(R.string.backup_success), Toast.LENGTH_SHORT).show();
- }
- }
- }
-
- private boolean backupWallet(String walletName) {
- File backupFolder = new File(getStorageRoot(), "backups");
- if (!backupFolder.exists()) {
- if (!backupFolder.mkdir()) {
- Timber.e("Cannot create backup dir %s", backupFolder.getAbsolutePath());
- return false;
- }
- // make folder visible over USB/MTP
- MediaScannerConnection.scanFile(this, new String[]{backupFolder.toString()}, null, null);
- }
- File walletFile = Helper.getWalletFile(LoginActivity.this, walletName);
- File backupFile = new File(backupFolder, walletName);
- Timber.d("backup " + walletFile.getAbsolutePath() + " to " + backupFile.getAbsolutePath());
- // TODO probably better to copy to a new file and then rename
- // then if something fails we have the old backup at least
- // or just create a new backup every time and keep n old backups
- boolean success = copyWallet(walletFile, backupFile, true, true);
- Timber.d("copyWallet is %s", success);
- return success;
- }
+ private static final int CREATE_BACKUP_INTENT = 4711;
+ private static final int RESTORE_BACKUP_INTENT = 4712;
+ private ZipBackup zipBackup;
@Override
public void onWalletBackup(String walletName) {
Timber.d("backup for wallet ." + walletName + ".");
- new AsyncBackup().execute(walletName);
+ zipBackup = new ZipBackup(this, walletName);
+
+ Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setType("application/zip");
+ intent.putExtra(Intent.EXTRA_TITLE, zipBackup.getBackupName());
+ startActivityForResult(intent, CREATE_BACKUP_INTENT);
}
- private class AsyncArchive extends AsyncTask {
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- showProgressDialog(R.string.archive_progress);
- }
+ @Override
+ public void onWalletRestore() {
+ Timber.d("restore wallet");
- @Override
- protected Boolean doInBackground(String... params) {
- if (params.length != 1) return false;
- String walletName = params[0];
- if (backupWallet(walletName) && deleteWallet(Helper.getWalletFile(LoginActivity.this, walletName))) {
- KeyStoreHelper.removeWalletUserPass(LoginActivity.this, walletName);
- return true;
- } else {
- return false;
- }
- }
+ Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setType("application/zip");
+ startActivityForResult(intent, RESTORE_BACKUP_INTENT);
+ }
- @Override
- protected void onPostExecute(Boolean result) {
- super.onPostExecute(result);
- if (isDestroyed()) {
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == CREATE_BACKUP_INTENT) {
+ if (data == null) {
+ // nothing selected
+ Toast.makeText(this, getString(R.string.backup_failed), Toast.LENGTH_LONG).show();
return;
}
- dismissProgressDialog();
- if (result) {
- reloadWalletList();
- } else {
- Toast.makeText(LoginActivity.this, getString(R.string.archive_failed), Toast.LENGTH_LONG).show();
+ try {
+ zipBackup.writeTo(data.getData());
+ Toast.makeText(this, getString(R.string.backup_success), Toast.LENGTH_SHORT).show();
+ } catch (IOException ex) {
+ Timber.e(ex);
+ Toast.makeText(this, getString(R.string.backup_failed), Toast.LENGTH_LONG).show();
+ }
+ } else if (requestCode == RESTORE_BACKUP_INTENT) {
+ if (data == null) {
+ // nothing selected
+ Toast.makeText(this, getString(R.string.restore_failed), Toast.LENGTH_LONG).show();
+ return;
+ }
+ try {
+ ZipRestore zipRestore = new ZipRestore(this, data.getData());
+ Toast.makeText(this, getString(R.string.menu_restore), Toast.LENGTH_SHORT).show();
+ if (zipRestore.restore()) {
+ reloadWalletList();
+ } else {
+ Toast.makeText(this, getString(R.string.restore_failed), Toast.LENGTH_LONG).show();
+ }
+ } catch (IOException ex) {
+ Timber.e(ex);
+ Toast.makeText(this, getString(R.string.restore_failed), Toast.LENGTH_LONG).show();
}
}
}
@Override
- public void onWalletArchive(final String walletName) {
- Timber.d("archive for wallet ." + walletName + ".");
+ public void onWalletDelete(final String walletName) {
+ Timber.d("delete for wallet ." + walletName + ".");
if (checkServiceRunning()) return;
- DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- switch (which) {
- case DialogInterface.BUTTON_POSITIVE:
- new AsyncArchive().execute(walletName);
- break;
- case DialogInterface.BUTTON_NEGATIVE:
- // do nothing
- break;
- }
+ DialogInterface.OnClickListener dialogClickListener = (dialog, which) -> {
+ switch (which) {
+ case DialogInterface.BUTTON_POSITIVE:
+ if (deleteWallet(Helper.getWalletFile(LoginActivity.this, walletName))) {
+ reloadWalletList();
+ } else {
+ Toast.makeText(LoginActivity.this, getString(R.string.delete_failed), Toast.LENGTH_LONG).show();
+ }
+ break;
+ case DialogInterface.BUTTON_NEGATIVE:
+ // do nothing
+ break;
}
};
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
- builder.setMessage(getString(R.string.archive_alert_message))
+ builder.setMessage(getString(R.string.delete_alert_message))
.setTitle(walletName)
- .setPositiveButton(getString(R.string.archive_alert_yes), dialogClickListener)
- .setNegativeButton(getString(R.string.archive_alert_no), dialogClickListener)
+ .setPositiveButton(getString(R.string.delete_alert_yes), dialogClickListener)
+ .setNegativeButton(getString(R.string.delete_alert_no), dialogClickListener)
.show();
}
@@ -628,6 +566,7 @@ public class LoginActivity extends BaseActivity
loginFragment.loadList();
}
} catch (ClassCastException ex) {
+ Timber.w(ex);
}
}
@@ -774,37 +713,6 @@ public class LoginActivity extends BaseActivity
startReviewFragment(b);
}
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
- @NonNull int[] grantResults) {
- Timber.d("onRequestPermissionsResult()");
- switch (requestCode) {
- case Helper.PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE:
- // If request is cancelled, the result arrays are empty.
- if (grantResults.length > 0
- && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- startLoginFragment = true;
- } else {
- String msg = getString(R.string.message_strorage_not_permitted);
- Timber.e(msg);
- Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
- }
- break;
- default:
- }
- }
-
- private boolean startLoginFragment = false;
-
- @Override
- protected void onResumeFragments() {
- super.onResumeFragments();
- if (startLoginFragment) {
- startLoginFragment();
- startLoginFragment = false;
- }
- }
-
void startLoginFragment() {
// we set these here because we cannot be ceratin we have permissions for storage before
Helper.setMoneroHome(this);
@@ -1035,22 +943,12 @@ public class LoginActivity extends BaseActivity
});
}
- void toast(final String msg) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- Toast.makeText(LoginActivity.this, msg, Toast.LENGTH_LONG).show();
- }
- });
+ private void toast(final String msg) {
+ runOnUiThread(() -> Toast.makeText(LoginActivity.this, msg, Toast.LENGTH_LONG).show());
}
- void toast(final int msgId) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- Toast.makeText(LoginActivity.this, getString(msgId), Toast.LENGTH_LONG).show();
- }
- });
+ private void toast(final int msgId) {
+ runOnUiThread(() -> Toast.makeText(LoginActivity.this, getString(msgId), Toast.LENGTH_LONG).show());
}
@Override
@@ -1058,7 +956,7 @@ public class LoginActivity extends BaseActivity
File walletFolder = getStorageRoot();
File walletFile = new File(walletFolder, name);
Timber.d("New Wallet %s", walletFile.getAbsolutePath());
- walletFile.delete(); // when recovering wallets, the cache seems corrupt
+ walletFile.delete(); // when recovering wallets, the cache seems corrupt - so remove it
popFragmentStack(GENERATE_STACK);
Toast.makeText(LoginActivity.this,
@@ -1079,8 +977,7 @@ public class LoginActivity extends BaseActivity
}
}
- boolean copyWallet(File srcWallet, File dstWallet, boolean overwrite,
- boolean ignoreCacheError) {
+ boolean copyWallet(File srcWallet, File dstWallet, boolean overwrite, boolean ignoreCacheError) {
if (walletExists(dstWallet, true) && !overwrite) return false;
boolean success = false;
File srcDir = srcWallet.getParentFile();
@@ -1123,19 +1020,14 @@ public class LoginActivity extends BaseActivity
success = addressFile.delete() && success;
}
Timber.d("deleteWallet is %s", success);
+ KeyStoreHelper.removeWalletUserPass(this, walletFile.getName());
return success;
}
void copyFile(File src, File dst) throws IOException {
- FileChannel inChannel = new FileInputStream(src).getChannel();
- FileChannel outChannel = new FileOutputStream(dst).getChannel();
- try {
+ try (FileChannel inChannel = new FileInputStream(src).getChannel();
+ FileChannel outChannel = new FileOutputStream(dst).getChannel()) {
inChannel.transferTo(0, inChannel.size(), outChannel);
- } finally {
- if (inChannel != null)
- inChannel.close();
- if (outChannel != null)
- outChannel.close();
}
}
@@ -1220,63 +1112,64 @@ public class LoginActivity extends BaseActivity
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.action_create_help_new:
- HelpFragment.display(getSupportFragmentManager(), R.string.help_create_new);
- return true;
- case R.id.action_create_help_keys:
- HelpFragment.display(getSupportFragmentManager(), R.string.help_create_keys);
- return true;
- case R.id.action_create_help_view:
- HelpFragment.display(getSupportFragmentManager(), R.string.help_create_view);
- return true;
- case R.id.action_create_help_seed:
- HelpFragment.display(getSupportFragmentManager(), R.string.help_create_seed);
- return true;
- case R.id.action_create_help_ledger:
- HelpFragment.display(getSupportFragmentManager(), R.string.help_create_ledger);
- return true;
- case R.id.action_details_help:
- HelpFragment.display(getSupportFragmentManager(), R.string.help_details);
- return true;
- case R.id.action_details_changepw:
- onWalletChangePassword();
- return true;
- case R.id.action_license_info:
- AboutFragment.display(getSupportFragmentManager());
- return true;
- case R.id.action_help_list:
- HelpFragment.display(getSupportFragmentManager(), R.string.help_list);
- return true;
- case R.id.action_help_node:
- HelpFragment.display(getSupportFragmentManager(), R.string.help_node);
- return true;
- case R.id.action_default_nodes: {
- Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
- if ((WalletManager.getInstance().getNetworkType() == NetworkType.NetworkType_Mainnet) &&
- (f instanceof NodeFragment)) {
- ((NodeFragment) f).restoreDefaultNodes();
- }
- return true;
+ final int id = item.getItemId();
+ if (id == R.id.action_create_help_new) {
+ HelpFragment.display(getSupportFragmentManager(), R.string.help_create_new);
+ return true;
+ } else if (id == R.id.action_create_help_keys) {
+ HelpFragment.display(getSupportFragmentManager(), R.string.help_create_keys);
+ return true;
+ } else if (id == R.id.action_create_help_view) {
+ HelpFragment.display(getSupportFragmentManager(), R.string.help_create_view);
+ return true;
+ } else if (id == R.id.action_create_help_seed) {
+ HelpFragment.display(getSupportFragmentManager(), R.string.help_create_seed);
+ return true;
+ } else if (id == R.id.action_create_help_ledger) {
+ HelpFragment.display(getSupportFragmentManager(), R.string.help_create_ledger);
+ return true;
+ } else if (id == R.id.action_details_help) {
+ HelpFragment.display(getSupportFragmentManager(), R.string.help_details);
+ return true;
+ } else if (id == R.id.action_details_changepw) {
+ onWalletChangePassword();
+ return true;
+ } else if (id == R.id.action_license_info) {
+ AboutFragment.display(getSupportFragmentManager());
+ return true;
+ } else if (id == R.id.action_help_list) {
+ HelpFragment.display(getSupportFragmentManager(), R.string.help_list);
+ return true;
+ } else if (id == R.id.action_help_node) {
+ HelpFragment.display(getSupportFragmentManager(), R.string.help_node);
+ return true;
+ } else if (id == R.id.action_default_nodes) {
+ Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
+ if ((WalletManager.getInstance().getNetworkType() == NetworkType.NetworkType_Mainnet) &&
+ (f instanceof NodeFragment)) {
+ ((NodeFragment) f).restoreDefaultNodes();
}
- case R.id.action_privacy_policy:
- PrivacyFragment.display(getSupportFragmentManager());
- return true;
- case R.id.action_language:
- onChangeLocale();
- return true;
- case R.id.action_theme:
- onChangeTheme();
- return true;
- case R.id.action_ledger_seed: {
- Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
- if (f instanceof GenerateFragment) {
- ((GenerateFragment) f).convertLedgerSeed();
- }
- return true;
+ return true;
+ } else if (id == R.id.action_privacy_policy) {
+ PrivacyFragment.display(getSupportFragmentManager());
+ return true;
+ } else if (id == R.id.action_language) {
+ onChangeLocale();
+ return true;
+ } else if (id == R.id.action_theme) {
+ onChangeTheme();
+ return true;
+ } else if (id == R.id.action_restore) {
+ onWalletRestore();
+ return true;
+ } else if (id == R.id.action_ledger_seed) {
+ Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
+ if (f instanceof GenerateFragment) {
+ ((GenerateFragment) f).convertLedgerSeed();
}
- default:
- return super.onOptionsItemSelected(item);
+ return true;
+ } else {
+ return super.onOptionsItemSelected(item);
}
}
@@ -1317,7 +1210,7 @@ public class LoginActivity extends BaseActivity
if (result) {
Timber.d("selected wallet is .%s.", node.getName());
// now it's getting real, onValidateFields if wallet exists
- promptAndStart(walletName, node, streetmode);
+ promptAndStart(walletName, streetmode);
} else {
if (node.getResponseCode() == 0) { // IOException
Toast.makeText(LoginActivity.this, getString(R.string.status_wallet_node_invalid), Toast.LENGTH_LONG).show();
@@ -1333,25 +1226,21 @@ public class LoginActivity extends BaseActivity
String keyPath = new File(Helper.getWalletRoot(LoginActivity.this),
walletName + ".keys").getAbsolutePath();
// check if we need connected hardware
- Wallet.Device device =
- WalletManager.getInstance().queryWalletDevice(keyPath, password);
- switch (device) {
- case Device_Ledger:
- if (!hasLedger()) {
- toast(R.string.open_wallet_ledger_missing);
- } else {
- return true;
- }
- break;
- default:
- // device could be undefined meaning the password is wrong
- // this gets dealt with later
+ Wallet.Device device = WalletManager.getInstance().queryWalletDevice(keyPath, password);
+ if (device == Wallet.Device.Device_Ledger) {
+ if (!hasLedger()) {
+ toast(R.string.open_wallet_ledger_missing);
+ } else {
return true;
+ }
+ } else {// device could be undefined meaning the password is wrong
+ // this gets dealt with later
+ return true;
}
return false;
}
- void promptAndStart(String walletName, Node node, final boolean streetmode) {
+ void promptAndStart(String walletName, final boolean streetmode) {
File walletFile = Helper.getWalletFile(this, walletName);
if (WalletManager.getInstance().walletExists(walletFile)) {
Helper.promptPassword(LoginActivity.this, walletName, false,
@@ -1418,38 +1307,23 @@ public class LoginActivity extends BaseActivity
Ledger.connect(usbManager, usbDevice);
if (!Ledger.check()) {
Ledger.disconnect();
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- Toast.makeText(LoginActivity.this,
- getString(R.string.toast_ledger_start_app, usbDevice.getProductName()),
- Toast.LENGTH_SHORT)
- .show();
- }
- });
+ runOnUiThread(() -> Toast.makeText(LoginActivity.this,
+ getString(R.string.toast_ledger_start_app, usbDevice.getProductName()),
+ Toast.LENGTH_SHORT)
+ .show());
} else {
registerDetachReceiver();
onLedgerAction();
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- Toast.makeText(LoginActivity.this,
- getString(R.string.toast_ledger_attached, usbDevice.getProductName()),
- Toast.LENGTH_SHORT)
- .show();
- }
- });
+ runOnUiThread(() -> Toast.makeText(LoginActivity.this,
+ getString(R.string.toast_ledger_attached, usbDevice.getProductName()),
+ Toast.LENGTH_SHORT)
+ .show());
}
} catch (IOException ex) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- Toast.makeText(LoginActivity.this,
- getString(R.string.open_wallet_ledger_missing),
- Toast.LENGTH_SHORT)
- .show();
- }
- });
+ runOnUiThread(() -> Toast.makeText(LoginActivity.this,
+ getString(R.string.open_wallet_ledger_missing),
+ Toast.LENGTH_SHORT)
+ .show());
}
}
@@ -1507,15 +1381,10 @@ public class LoginActivity extends BaseActivity
final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
Timber.i("Ledger detached!");
if (device != null)
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- Toast.makeText(LoginActivity.this,
- getString(R.string.toast_ledger_detached, device.getProductName()),
- Toast.LENGTH_SHORT)
- .show();
- }
- });
+ runOnUiThread(() -> Toast.makeText(LoginActivity.this,
+ getString(R.string.toast_ledger_detached, device.getProductName()),
+ Toast.LENGTH_SHORT)
+ .show());
Ledger.disconnect();
onLedgerAction();
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java b/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java
index e6caa3f..0db596e 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java
@@ -31,7 +31,6 @@ import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
-import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -62,8 +61,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
private WalletInfoAdapter adapter;
- private List walletList = new ArrayList<>();
- private List displayedList = new ArrayList<>();
+ private final List walletList = new ArrayList<>();
private View tvGuntherSays;
private ImageView ivGunther;
@@ -86,7 +84,9 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
void onWalletBackup(String name);
- void onWalletArchive(String walletName);
+ void onWalletRestore();
+
+ void onWalletDelete(String walletName);
void onAddWallet(String type);
@@ -110,7 +110,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
}
@Override
- public void onAttach(Context context) {
+ public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof Listener) {
this.activityCallback = (Listener) context;
@@ -200,12 +200,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
// Wallet touched
@Override
public void onInteraction(final View view, final WalletManager.WalletInfo infoItem) {
- String addressPrefix = WalletManager.getInstance().addressPrefix();
- if (addressPrefix.indexOf(infoItem.address.charAt(0)) < 0) {
- Toast.makeText(getActivity(), getString(R.string.prompt_wrong_net), Toast.LENGTH_LONG).show();
- return;
- }
- openWallet(infoItem.name, false);
+ openWallet(infoItem.getName(), false);
}
private void openWallet(String name, boolean streetmode) {
@@ -214,48 +209,32 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
@Override
public boolean onContextInteraction(MenuItem item, WalletManager.WalletInfo listItem) {
- switch (item.getItemId()) {
- case R.id.action_streetmode:
- openWallet(listItem.name, true);
- break;
- case R.id.action_info:
- showInfo(listItem.name);
- break;
- case R.id.action_rename:
- activityCallback.onWalletRename(listItem.name);
- break;
- case R.id.action_backup:
- activityCallback.onWalletBackup(listItem.name);
- break;
- case R.id.action_archive:
- activityCallback.onWalletArchive(listItem.name);
- break;
- default:
- return super.onContextItemSelected(item);
+ final int id = item.getItemId();
+ if (id == R.id.action_streetmode) {
+ openWallet(listItem.getName(), true);
+ } else if (id == R.id.action_info) {
+ showInfo(listItem.getName());
+ } else if (id == R.id.action_rename) {
+ activityCallback.onWalletRename(listItem.getName());
+ } else if (id == R.id.action_backup) {
+ activityCallback.onWalletBackup(listItem.getName());
+ } else if (id == R.id.action_archive) {
+ activityCallback.onWalletDelete(listItem.getName());
+ } else {
+ return super.onContextItemSelected(item);
}
return true;
}
- private void filterList() {
- displayedList.clear();
- String addressPrefix = WalletManager.getInstance().addressPrefix();
- for (WalletManager.WalletInfo s : walletList) {
- if (addressPrefix.indexOf(s.address.charAt(0)) >= 0) displayedList.add(s);
- }
- }
-
public void loadList() {
Timber.d("loadList()");
WalletManager mgr = WalletManager.getInstance();
- List walletInfos =
- mgr.findWallets(activityCallback.getStorageRoot());
walletList.clear();
- walletList.addAll(walletInfos);
- filterList();
- adapter.setInfos(displayedList);
+ walletList.addAll(mgr.findWallets(activityCallback.getStorageRoot()));
+ adapter.setInfos(walletList);
// deal with Gunther & FAB animation
- if (displayedList.isEmpty()) {
+ if (walletList.isEmpty()) {
fab.startAnimation(fab_pulse);
if (ivGunther.getDrawable() == null) {
ivGunther.setImageResource(R.drawable.ic_emptygunther);
@@ -274,7 +253,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
.getSharedPreferences(KeyStoreHelper.SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE)
.getAll().keySet();
for (WalletManager.WalletInfo s : walletList) {
- removedWallets.remove(s.name);
+ removedWallets.remove(s.getName());
}
for (String name : removedWallets) {
KeyStoreHelper.removeWalletUserPass(getActivity(), name);
@@ -292,7 +271,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
}
@Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.list_menu, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@@ -362,37 +341,29 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
@Override
public void onClick(View v) {
- int id = v.getId();
+ final int id = v.getId();
Timber.d("onClick %d/%d", id, R.id.fabLedger);
- switch (id) {
- case R.id.fab:
- animateFAB();
- break;
- case R.id.fabNew:
- fabScreen.setVisibility(View.INVISIBLE);
- isFabOpen = false;
- activityCallback.onAddWallet(GenerateFragment.TYPE_NEW);
- break;
- case R.id.fabView:
- animateFAB();
- activityCallback.onAddWallet(GenerateFragment.TYPE_VIEWONLY);
- break;
- case R.id.fabKey:
- animateFAB();
- activityCallback.onAddWallet(GenerateFragment.TYPE_KEY);
- break;
- case R.id.fabSeed:
- animateFAB();
- activityCallback.onAddWallet(GenerateFragment.TYPE_SEED);
- break;
- case R.id.fabLedger:
- Timber.d("FAB_LEDGER");
- animateFAB();
- activityCallback.onAddWallet(GenerateFragment.TYPE_LEDGER);
- break;
- case R.id.fabScreen:
- animateFAB();
- break;
+ if (id == R.id.fab) {
+ animateFAB();
+ } else if (id == R.id.fabNew) {
+ fabScreen.setVisibility(View.INVISIBLE);
+ isFabOpen = false;
+ activityCallback.onAddWallet(GenerateFragment.TYPE_NEW);
+ } else if (id == R.id.fabView) {
+ animateFAB();
+ activityCallback.onAddWallet(GenerateFragment.TYPE_VIEWONLY);
+ } else if (id == R.id.fabKey) {
+ animateFAB();
+ activityCallback.onAddWallet(GenerateFragment.TYPE_KEY);
+ } else if (id == R.id.fabSeed) {
+ animateFAB();
+ activityCallback.onAddWallet(GenerateFragment.TYPE_SEED);
+ } else if (id == R.id.fabLedger) {
+ Timber.d("FAB_LEDGER");
+ animateFAB();
+ activityCallback.onAddWallet(GenerateFragment.TYPE_LEDGER);
+ } else if (id == R.id.fabScreen) {
+ animateFAB();
}
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/WalletInfoAdapter.java b/app/src/main/java/com/m2049r/xmrwallet/layout/WalletInfoAdapter.java
index b9ad73c..ad885e7 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/layout/WalletInfoAdapter.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/layout/WalletInfoAdapter.java
@@ -74,7 +74,7 @@ public class WalletInfoAdapter extends RecyclerView.Adapter {
if (popupOpen) return;
@@ -160,8 +158,7 @@ public class WalletInfoAdapter extends RecyclerView.Adapter findWallets(String path); // this does not work - some error in boost
public class WalletInfo implements Comparable {
- public File path;
- public String name;
- public String address;
+ @Getter
+ final private File path;
+ @Getter
+ final private String name;
+
+ public WalletInfo(File wallet) {
+ path = wallet.getParentFile();
+ name = wallet.getName();
+ }
@Override
public int compareTo(WalletInfo another) {
- int n = name.toLowerCase().compareTo(another.name.toLowerCase());
- if (n != 0) {
- return n;
- } else { // wallet names are the same
- return address.compareTo(another.address);
- }
- }
- }
-
- public WalletInfo getWalletInfo(File wallet) {
- WalletInfo info = new WalletInfo();
- info.path = wallet.getParentFile();
- info.name = wallet.getName();
- File addressFile = new File(info.path, info.name + ".address.txt");
- //Timber.d(addressFile.getAbsolutePath());
- info.address = "??????";
- BufferedReader addressReader = null;
- try {
- addressReader = new BufferedReader(new FileReader(addressFile));
- info.address = addressReader.readLine();
- } catch (IOException ex) {
- Timber.d(ex.getLocalizedMessage());
- } finally {
- if (addressReader != null) {
- try {
- addressReader.close();
- } catch (IOException ex) {
- // that's just too bad
- }
- }
+ return name.toLowerCase().compareTo(another.name.toLowerCase());
}
- return info;
}
public List findWallets(File path) {
@@ -261,7 +236,7 @@ public class WalletManager {
for (int i = 0; i < found.length; i++) {
String filename = found[i].getName();
File f = new File(found[i].getParent(), filename.substring(0, filename.length() - 5)); // 5 is length of ".keys"+1
- wallets.add(getWalletInfo(f));
+ wallets.add(new WalletInfo(f));
}
return wallets;
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java
index 2428cb3..e500e8c 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java
@@ -34,7 +34,6 @@ import android.hardware.fingerprint.FingerprintManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.CancellationSignal;
-import android.os.Environment;
import android.os.StrictMode;
import android.system.ErrnoException;
import android.system.Os;
@@ -77,16 +76,12 @@ import javax.net.ssl.HttpsURLConnection;
import timber.log.Timber;
public class Helper {
- static private final String FLAVOR_SUFFIX =
- (BuildConfig.FLAVOR.startsWith("prod") ? "" : "." + BuildConfig.FLAVOR)
- + (BuildConfig.DEBUG ? "-debug" : "");
-
static public final String NOCRAZYPASS_FLAGFILE = ".nocrazypass";
static public final String BASE_CRYPTO = "XMR";
- static private final String WALLET_DIR = "monerujo" + FLAVOR_SUFFIX;
- static private final String HOME_DIR = "monero" + FLAVOR_SUFFIX;
+ static private final String WALLET_DIR = "wallets";
+ static private final String MONERO_DIR = "monero";
static public int DISPLAY_DIGITS_INFO = 5;
@@ -95,12 +90,7 @@ public class Helper {
}
static public File getStorage(Context context, String folderName) {
- if (!isExternalStorageWritable()) {
- String msg = context.getString(R.string.message_strorage_not_writable);
- Timber.e(msg);
- throw new IllegalStateException(msg);
- }
- File dir = new File(Environment.getExternalStorageDirectory(), folderName);
+ File dir = new File(context.getFilesDir(), folderName);
if (!dir.exists()) {
Timber.i("Creating %s", dir.getAbsolutePath());
dir.mkdirs(); // try to make it
@@ -113,24 +103,6 @@ public class Helper {
return dir;
}
- static public final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1;
-
- static public boolean getWritePermission(Activity context) {
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
- if (context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
- == PackageManager.PERMISSION_DENIED) {
- Timber.w("Permission denied to WRITE_EXTERNAL_STORAGE - requesting it");
- String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
- context.requestPermissions(permissions, PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
- return false;
- } else {
- return true;
- }
- } else {
- return true;
- }
- }
-
static public final int PERMISSIONS_REQUEST_CAMERA = 7;
static public boolean getCameraPermission(Activity context) {
@@ -156,12 +128,6 @@ public class Helper {
return f;
}
- /* Checks if external storage is available for read and write */
- private static boolean isExternalStorageWritable() {
- String state = Environment.getExternalStorageState();
- return Environment.MEDIA_MOUNTED.equals(state);
- }
-
static public void showKeyboard(Activity act) {
InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE);
final View focus = act.getCurrentFocus();
@@ -339,7 +305,7 @@ public class Helper {
static public void setMoneroHome(Context context) {
try {
- String home = getStorage(context, HOME_DIR).getAbsolutePath();
+ String home = getStorage(context, MONERO_DIR).getAbsolutePath();
Os.setenv("HOME", home, true);
} catch (ErrnoException ex) {
throw new IllegalStateException(ex);
@@ -355,7 +321,7 @@ public class Helper {
// TODO make the log levels refer to the WalletManagerFactory::LogLevel enum ?
static public void initLogger(Context context, int level) {
- String home = getStorage(context, HOME_DIR).getAbsolutePath();
+ String home = getStorage(context, MONERO_DIR).getAbsolutePath();
WalletManager.initLogger(home + "/monerujo", "monerujo.log");
if (level >= WalletManager.LOGLEVEL_SILENT)
WalletManager.setLogLevel(level);
diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java b/app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java
index 7c08f45..896d2d0 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java
@@ -24,9 +24,10 @@ import android.os.Build;
import android.security.KeyPairGeneratorSpec;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
-import androidx.annotation.NonNull;
import android.util.Base64;
+import androidx.annotation.NonNull;
+
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
@@ -167,6 +168,11 @@ public class KeyStoreHelper {
.remove(wallet).apply();
}
+ public static void copyWalletUserPass(Context context, String srcWallet, String dstWallet) throws BrokenPasswordStoreException {
+ final String pass = loadWalletUserPass(context, srcWallet);
+ saveWalletUserPass(context, dstWallet, pass);
+ }
+
/**
* Creates a public and private key and stores it using the Android Key
* Store, so that only this application will be able to access the keys.
diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/LegacyStorageHelper.java b/app/src/main/java/com/m2049r/xmrwallet/util/LegacyStorageHelper.java
new file mode 100644
index 0000000..60bbf4e
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/util/LegacyStorageHelper.java
@@ -0,0 +1,170 @@
+package com.m2049r.xmrwallet.util;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Environment;
+import android.preference.PreferenceManager;
+
+import com.m2049r.xmrwallet.BuildConfig;
+import com.m2049r.xmrwallet.model.WalletManager;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import lombok.RequiredArgsConstructor;
+import timber.log.Timber;
+
+@RequiredArgsConstructor
+public class LegacyStorageHelper {
+ final private File srcDir;
+ final private File dstDir;
+
+ static public void migrateWallets(Context context) {
+ try {
+ if (isStorageMigrated(context)) return;
+ if (!hasWritePermission(context)) {
+ // nothing to migrate, so don't try again
+ setStorageMigrated(context);
+ return;
+ }
+ final File oldRoot = getWalletRoot();
+ if (!oldRoot.exists()) {
+ // nothing to migrate, so don't try again
+ setStorageMigrated(context);
+ return;
+ }
+ final File newRoot = Helper.getWalletRoot(context);
+ (new LegacyStorageHelper(oldRoot, newRoot)).migrate();
+ setStorageMigrated(context); // done it once - don't try again
+ } catch (IllegalStateException ex) {
+ Timber.d(ex);
+ // nothing we can do here
+ }
+ }
+
+ public void migrate() {
+ String addressPrefix = WalletManager.getInstance().addressPrefix();
+ File[] wallets = srcDir.listFiles((dir, filename) -> filename.endsWith(".keys"));
+ for (File wallet : wallets) {
+ final String walletName = wallet.getName().substring(0, wallet.getName().length() - ".keys".length());
+ if (addressPrefix.indexOf(getAddress(walletName).charAt(0)) < 0) {
+ Timber.d("skipping %s", walletName);
+ continue;
+ }
+ try {
+ copy(walletName);
+ } catch (IOException ex) { // something failed - try to clean up
+ deleteDst(walletName);
+ }
+ }
+ }
+
+ // return "@" by default so we don't need to deal with null stuff
+ private String getAddress(String walletName) {
+ File addressFile = new File(srcDir, walletName + ".address.txt");
+ if (!addressFile.exists()) return "@";
+ try (BufferedReader addressReader = new BufferedReader(new FileReader(addressFile))) {
+ return addressReader.readLine();
+ } catch (IOException ex) {
+ Timber.d(ex.getLocalizedMessage());
+ }
+ return "@";
+ }
+
+ private void copy(String walletName) throws IOException {
+ final String dstName = getUniqueName(dstDir, walletName);
+ copyFile(new File(srcDir, walletName), new File(dstDir, dstName));
+ copyFile(new File(srcDir, walletName + ".keys"), new File(dstDir, dstName + ".keys"));
+ }
+
+ private void deleteDst(String walletName) {
+ // do our best, but if it fails, it fails
+ (new File(dstDir, walletName)).delete();
+ (new File(dstDir, walletName + ".keys")).delete();
+ }
+
+ private void copyFile(File src, File dst) throws IOException {
+ if (!src.exists()) return;
+ Timber.d("%s => %s", src.getAbsolutePath(), dst.getAbsolutePath());
+ try (FileChannel inChannel = new FileInputStream(src).getChannel();
+ FileChannel outChannel = new FileOutputStream(dst).getChannel()) {
+ inChannel.transferTo(0, inChannel.size(), outChannel);
+ }
+ }
+
+ private static boolean isExternalStorageWritable() {
+ String state = Environment.getExternalStorageState();
+ return Environment.MEDIA_MOUNTED.equals(state);
+ }
+
+ private static File getWalletRoot() {
+ if (!isExternalStorageWritable())
+ throw new IllegalStateException();
+
+ // wallet folder for legacy (pre-Q) installations
+ final String FLAVOR_SUFFIX =
+ (BuildConfig.FLAVOR.startsWith("prod") ? "" : "." + BuildConfig.FLAVOR)
+ + (BuildConfig.DEBUG ? "-debug" : "");
+ final String WALLET_DIR = "monerujo" + FLAVOR_SUFFIX;
+
+ File dir = new File(Environment.getExternalStorageDirectory(), WALLET_DIR);
+ if (!dir.exists() || !dir.isDirectory())
+ throw new IllegalStateException();
+ return dir;
+ }
+
+ private static boolean hasWritePermission(Context context) {
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
+ return context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_DENIED;
+ } else {
+ return true;
+ }
+ }
+
+ private static final Pattern WALLET_PATTERN = Pattern.compile("^(.+) \\(([0-9]+)\\).keys$");
+
+ private static String getUniqueName(File root, String name) {
+ if (!(new File(root, name + ".keys")).exists()) // does not exist => it's ok to use
+ return name;
+
+ File[] wallets = root.listFiles(
+ (dir, filename) -> {
+ Matcher m = WALLET_PATTERN.matcher(filename);
+ if (m.find())
+ return m.group(1).equals(name);
+ else return false;
+ });
+ if (wallets.length == 0) return name + " (1)";
+ int maxIndex = 0;
+ for (File wallet : wallets) {
+ try {
+ final Matcher m = WALLET_PATTERN.matcher(wallet.getName());
+ if (!m.find())
+ throw new IllegalStateException("this must match as it did before");
+ final int index = Integer.parseInt(m.group(2));
+ if (index > maxIndex) maxIndex = index;
+ } catch (NumberFormatException ex) {
+ // this cannot happen & we can ignore it if it does
+ }
+ }
+ return name + " (" + (maxIndex + 1) + ")";
+ }
+
+ private static final String MIGRATED_KEY = "migrated_legacy_storage";
+
+ public static boolean isStorageMigrated(Context context) {
+ return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(MIGRATED_KEY, false);
+ }
+
+ public static void setStorageMigrated(Context context) {
+ PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(MIGRATED_KEY, true).apply();
+ }
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/ZipBackup.java b/app/src/main/java/com/m2049r/xmrwallet/util/ZipBackup.java
new file mode 100644
index 0000000..cfdc705
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/util/ZipBackup.java
@@ -0,0 +1,64 @@
+package com.m2049r.xmrwallet.util;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public class ZipBackup {
+ final private Context context;
+ final private String walletName;
+
+ private ZipOutputStream zip;
+
+ public void writeTo(Uri zipUri) throws IOException {
+ if (zip != null)
+ throw new IllegalStateException("zip already initialized");
+ try {
+ zip = new ZipOutputStream(context.getContentResolver().openOutputStream(zipUri));
+
+ final File walletRoot = Helper.getWalletRoot(context);
+ addFile(new File(walletRoot, walletName + ".keys"));
+ addFile(new File(walletRoot, walletName));
+
+ zip.close();
+ } finally {
+ if (zip != null) zip.close();
+ }
+ }
+
+ private void addFile(File file) throws IOException {
+ if (!file.exists()) return; // ignore missing files (e.g. the cache file might not exist)
+ ZipEntry entry = new ZipEntry(file.getName());
+ zip.putNextEntry(entry);
+ writeFile(file);
+ zip.closeEntry();
+ }
+
+ private void writeFile(File source) throws IOException {
+ try (InputStream is = new FileInputStream(source)) {
+ byte[] buffer = new byte[8192];
+ int length;
+ while ((length = is.read(buffer)) > 0) {
+ zip.write(buffer, 0, length);
+ }
+ }
+ }
+
+ private static final SimpleDateFormat DATETIME_FORMATTER =
+ new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss");
+
+ public String getBackupName() {
+ return walletName + " " + DATETIME_FORMATTER.format(new Date());
+ }
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/ZipRestore.java b/app/src/main/java/com/m2049r/xmrwallet/util/ZipRestore.java
new file mode 100644
index 0000000..1b9148b
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/util/ZipRestore.java
@@ -0,0 +1,139 @@
+package com.m2049r.xmrwallet.util;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import lombok.RequiredArgsConstructor;
+import timber.log.Timber;
+
+@RequiredArgsConstructor
+public class ZipRestore {
+ final private Context context;
+ final private Uri zipUri;
+
+ private File walletRoot;
+
+ private ZipInputStream zip;
+
+ public boolean restore() throws IOException {
+ walletRoot = Helper.getWalletRoot(context);
+ String walletName = testArchive();
+ if (walletName == null) return false;
+
+ walletName = getUniqueName(walletName);
+
+ if (zip != null)
+ throw new IllegalStateException("zip already initialized");
+ try {
+ zip = new ZipInputStream(context.getContentResolver().openInputStream(zipUri));
+ for (ZipEntry entry = zip.getNextEntry(); entry != null; zip.closeEntry(), entry = zip.getNextEntry()) {
+ File destination;
+ final String name = entry.getName();
+ if (name.endsWith(".keys")) {
+ destination = new File(walletRoot, walletName + ".keys");
+ } else if (name.endsWith(".address.txt")) {
+ destination = new File(walletRoot, walletName + ".address.txt");
+ } else {
+ destination = new File(walletRoot, walletName);
+ }
+ writeFile(destination);
+ }
+ } finally {
+ if (zip != null) zip.close();
+ }
+ return true;
+ }
+
+ private void writeFile(File destination) throws IOException {
+ try (OutputStream os = new FileOutputStream(destination)) {
+ byte[] buffer = new byte[8192];
+ int length;
+ while ((length = zip.read(buffer)) > 0) {
+ os.write(buffer, 0, length);
+ }
+ }
+ }
+
+ // test the archive to contain files we expect & return the name of the contained wallet or null
+ private String testArchive() {
+ String walletName = null;
+ boolean keys = false;
+ ZipInputStream zipStream = null;
+ try {
+ zipStream = new ZipInputStream(context.getContentResolver().openInputStream(zipUri));
+ for (ZipEntry entry = zipStream.getNextEntry(); entry != null;
+ zipStream.closeEntry(), entry = zipStream.getNextEntry()) {
+ if (entry.isDirectory())
+ return null;
+ final String name = entry.getName();
+ if ((new File(name)).getParentFile() != null)
+ return null;
+ if (walletName == null) {
+ if (name.endsWith(".keys")) {
+ walletName = name.substring(0, name.length() - ".keys".length());
+ keys = true; // we have they keys
+ } else if (name.endsWith(".address.txt")) {
+ walletName = name.substring(0, name.length() - ".address.txt".length());
+ } else {
+ walletName = name;
+ }
+ } else { // we have a wallet name
+ if (name.endsWith(".keys")) {
+ if (!name.equals(walletName + ".keys")) return null;
+ keys = true; // we have they keys
+ } else if (name.endsWith(".address.txt")) {
+ if (!name.equals(walletName + ".address.txt")) return null;
+ } else if (!name.equals(walletName)) return null;
+ }
+ }
+ } catch (IOException ex) {
+ return null;
+ } finally {
+ try {
+ if (zipStream != null) zipStream.close();
+ } catch (IOException ex) {
+ Timber.w(ex);
+ }
+ }
+ // we need the keys at least
+ if (keys) return walletName;
+ else return null;
+ }
+
+ final static Pattern WALLET_PATTERN = Pattern.compile("^(.+) \\(([0-9]+)\\).keys$");
+
+ private String getUniqueName(String name) {
+ if (!(new File(walletRoot, name + ".keys")).exists()) // does not exist => it's ok to use
+ return name;
+
+ File[] wallets = walletRoot.listFiles(
+ (dir, filename) -> {
+ Matcher m = WALLET_PATTERN.matcher(filename);
+ if (m.find())
+ return m.group(1).equals(name);
+ else return false;
+ });
+ if (wallets.length == 0) return name + " (1)";
+ int maxIndex = 0;
+ for (File wallet : wallets) {
+ try {
+ final Matcher m = WALLET_PATTERN.matcher(wallet.getName());
+ m.find();
+ final int index = Integer.parseInt(m.group(2));
+ if (index > maxIndex) maxIndex = index;
+ } catch (NumberFormatException ex) {
+ // this cannot happen & we can ignore it if it does
+ }
+ }
+ return name + " (" + (maxIndex + 1) + ")";
+ }
+}
diff --git a/app/src/main/res/layout/item_wallet.xml b/app/src/main/res/layout/item_wallet.xml
index 30e3927..0b918a1 100644
--- a/app/src/main/res/layout/item_wallet.xml
+++ b/app/src/main/res/layout/item_wallet.xml
@@ -18,19 +18,9 @@
android:layout_height="wrap_content"
android:layout_marginStart="48dp"
android:layout_marginTop="16dp"
- android:layout_toStartOf="@+id/ibOptions"
- tools:text="My Wallet" />
-
-
+ tools:text="My Wallet" />
+ android:title="@string/menu_delete" />
+
\ No newline at end of file
diff --git a/app/src/main/res/values-cat/strings.xml b/app/src/main/res/values-cat/strings.xml
index f3240ec..6c517c6 100644
--- a/app/src/main/res/values-cat/strings.xml
+++ b/app/src/main/res/values-cat/strings.xml
@@ -9,7 +9,6 @@
Ajuda
Rebre
Canvi de nom
- Arxivar
Còpia de seguretat
Canvi de contrasenya
@@ -94,7 +93,6 @@
Còpia de seguretat amb èxit
Còpia de seguretat fallida!
- Arxiu fallit!
Canvi de nom fallit!
Canvi de contrasenya fallit!
La contrasenya ha canviat
@@ -273,9 +271,8 @@
Torna\'m enrere!
Detalls
- Es farà una còpia de seguretat del portamonedes i s\'esborrarà.
- Si, fes-ho!
- No gràcies!
+ Si, fes-ho!
+ No gràcies!
Crear un nou portamonedes
Restablir portamonedes de només lectura
@@ -420,4 +417,10 @@
Select a subaddress
Long-press for details
+ The wallet will be deleted!
+ Delete
+ Delete failed!
+
+ Import wallet
+ Import failed!
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 3b90f30..851b765 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -9,7 +9,7 @@
Hilfe
Empfangen
Umbenennen
- Archivieren
+ Löschen
Backup
Passwort ändern
@@ -94,7 +94,7 @@
Backup erfolgreich
Backup fehlgeschlagen!
- Archivierung fehlgeschlagen!
+ Löschen fehlgeschlagen!
Umbenennen fehlgeschlagen!
Passwort ändern fehlgeschlagen!
Passwort geändert
@@ -266,9 +266,9 @@
Nein, doch nicht!
Details
- Das Wallet wird archiviert und dann gelöscht!
- Ja, mach das!
- Nein, danke!
+ Das Wallet wird gelöscht!
+ Ja, mach das!
+ Nein, danke!
Neues Wallet erstellen
View-Only-Wallet wiederherstellen
@@ -422,4 +422,6 @@
Select a subaddress
Long-press for details
+ Import wallet
+ Import failed!
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index ee14b0c..34c692a 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -9,7 +9,6 @@
Βοήθεια
Λήψη
Μετονομασία
- Αρχειοθέτησε
Δημιουργία αντίγραφου ασφαλείας
Συνέχισε να πληκτρολογείς …
@@ -92,7 +91,6 @@
Η δημιουργία αντίγραφου ασφαλείας ήταν επιτυχής
Η δημιουργία αντίγραφου ασφαλείας απέτυχε!
- Η αρχειοθέτηση απέτυχε!
Η μετονομασία απέτυχε!
Κόμβος(Δαίμονας)
@@ -241,9 +239,8 @@
Πήγαινε με πίσω!
Λεπτομέρειες
- Το πορτοφόλι θα δημιουργήσει αντίγραφο ασφαλείας και στη συνέχεια θα διαγραφεί!
- Ναι, κάνε αυτό!
- Όχι ευχαριστώ!
+ Ναι, κάνε αυτό!
+ Όχι ευχαριστώ!
Δημιουργία νέου πορτοφολιού
Επαναφορά πορτοφολιού προβολής-μόνο
@@ -422,4 +419,10 @@
Select a subaddress
Long-press for details
+ The wallet will be deleted!
+ Delete
+ Delete failed!
+
+ Import wallet
+ Import failed!
diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml
index df99795..6237dcf 100644
--- a/app/src/main/res/values-eo/strings.xml
+++ b/app/src/main/res/values-eo/strings.xml
@@ -9,7 +9,6 @@
Helpo
Ricevi
Renomi
- Arkivo
Sekurkopio
Ŝanĝi pasfrazon
@@ -94,7 +93,6 @@
Sekurkopio sukcesis!
Sekurkopio malsukcesis!
- Arkivado malsukcesis!
Renomado malsukcesis!
Pasvortŝanĝo malsukcesis!
Pasvortŝanĝo sukcesis!
@@ -273,9 +271,8 @@
Malantaŭen!
Detaloj
- La monujo sekurkopiĝos kaj poste forviŝiĝos!
- Jes, faru tion!
- Ne dankon!
+ Jes, faru tion!
+ Ne dankon!
Krei novan monujon
Restaŭri nurvidan monujon
@@ -422,4 +419,10 @@
Select a subaddress
Long-press for details
+ The wallet will be deleted!
+ Delete
+ Delete failed!
+
+ Import wallet
+ Import failed!
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 84f96d4..7023abe 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -9,7 +9,6 @@
Ayuda
Recibir
Renombrar
- Archivar
Copia de seguridad
Cambiar contraseña
@@ -49,7 +48,6 @@
Copia de seguridad exitosa
¡Copia de seguridad fallida!
- ¡Archivado fallido!
¡Cambio de nombre fallido!
¡Cambio de contraseña fallido!
Contraseña cambiada
@@ -209,9 +207,9 @@
¡Llévame de vuelta!
Detalles
- ¡El monedero será copiado y después borrado!
- ¡Sí, hazlo!
- ¡No, gracias!
+ ¡El monedero será borrado!
+ ¡Sí, hazlo!
+ ¡No, gracias!
Crear nuevo monedero
Restaurar monedero de sólo vista
@@ -413,4 +411,9 @@
Elige una subdirección
Presiona largo para ver detalles
+ Delete
+ Delete failed!
+
+ Import wallet
+ Import failed!
diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml
index 02e5046..7f265e5 100644
--- a/app/src/main/res/values-et/strings.xml
+++ b/app/src/main/res/values-et/strings.xml
@@ -9,7 +9,6 @@
Abi
Küsi raha
Nimeta ümber
- Arhiveeri
Tagavarakoopia
Vaheta parooli
@@ -93,7 +92,6 @@
Teen ettevalmistusi …\nSee võib aega võtta!
Tagavarakoopia ebaõnnestus!
- Arhiveerimine ebaõnnestus!
Ümber nimetamine ebaõnnestus!
Parooli vahetamine ebaõnnestus!
Parool vahetatud
@@ -268,9 +266,8 @@
Vii mind tagasi!
Lisainfo
- Rahakotist tehakse tagavarakoopia ja siis see kustutatakse!
- Täpselt nii!
- Ei, tänan!
+ Täpselt nii!
+ Ei, tänan!
Loo uus rahakott
Taasta rahakott vaatamiseks
@@ -420,4 +417,10 @@
Select a subaddress
Long-press for details
+ The wallet will be deleted!
+ Delete
+ Delete failed!
+
+ Import wallet
+ Import failed!
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 7e94192..98d688b 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -9,7 +9,6 @@
Aide
Recevoir
Renommer
- Archiver
Sauvegarder
Modifier Phrase secrète
@@ -95,7 +94,6 @@
Sauvegarde réussie
Sauvegarde Échouée !
- Archivage Échouée !
Renommage Échoué !
Modification du mot de passe Échouée !
Modification du mot de passe réussie
@@ -269,9 +267,9 @@
Non merci !
Détails
- Le portefeuille sera sauvegardé puis supprimé !
- Oui, procéder !
- Non merci !
+ Le portefeuille sera supprimé !
+ Oui, procéder !
+ Non merci !
Créer un nouveau portefeuille
Restaurer un portefeuille d\'audit
@@ -426,4 +424,9 @@
Select a subaddress
Long-press for details
+ Delete
+ Delete failed!
+
+ Import wallet
+ Import failed!
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index e9966d6..bcdbb14 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -9,7 +9,6 @@
Segítség
Fogadás
Átnevezés
- Archiválás
Biztonsági mentés
Jelszómódosítás
@@ -93,7 +92,6 @@
Műveletek befejezése…\nEz eltarthat egy ideig!
Sikertelen biztonsági mentés!
- Sikertelen archiválás!
Sikertelen átnevezés!
Sikertelen jelszómódosítás!
Jelszó megváltoztatva
@@ -266,9 +264,8 @@
Inkább ne!
Részletek
- A tárcáról biztonsági másolat készül, majd törlésre fog kerülni!
- Igen, mehet!
- Inkább ne!
+ Igen, mehet!
+ Inkább ne!
Új tárca létrehozása
Figyelőtárca visszaállítása
@@ -424,4 +421,10 @@
Select a subaddress
Long-press for details
+ The wallet will be deleted!
+ Delete
+ Delete failed!
+
+ Import wallet
+ Import failed!
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 3ff7aa6..94b8b02 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -9,7 +9,6 @@
Aiuto
Ricevi
Rinomina
- Archivia
Backup
Cambia passphrase
@@ -95,7 +94,6 @@
Backup effettuato con successo
Backup fallito!
- Archiviazione fallita!
Rinomina fallita!
Modifica password fallita!
Password cambiata
@@ -268,9 +266,9 @@
Torna indietro!
Dettagli
- Il portafoglio verrà archiviato e poi eliminato!
- Sì, procedi!
- No grazie!
+ Il portafoglio verrà eliminato!
+ Sì, procedi!
+ No grazie!
Crea un nuovo portafoglio
Recupera un portafoglio solo-visualizzazione
@@ -425,4 +423,9 @@
Select a subaddress
Long-press for details
+ Delete
+ Delete failed!
+
+ Import wallet
+ Import failed!
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index ec6b209..d5721da 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -9,7 +9,6 @@
ヘルプ
受取り
名前の変更
- アーカイブ
バックアップ
パスフレーズの変更
@@ -93,7 +92,6 @@
準備中 …\nしばらく時間がかかるかもしれません!
バックアップに失敗しました!
- アーカイブに失敗しました!
名前の変更に失敗しました!
パスワード変更に失敗しました!
パスワードが変更されました
@@ -273,9 +271,8 @@
戻ります!
詳細
- ウォレットはバックアップされてから削除されます!
- はい、お願いします!
- いいえ、結構です!
+ はい、お願いします!
+ いいえ、結構です!
新しいウォレットの作成
閲覧専用ウォレットを復元
@@ -425,4 +422,10 @@
Select a subaddress
Long-press for details
+ The wallet will be deleted!
+ Delete
+ Delete failed!
+
+ Import wallet
+ Import failed!
diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml
index 40bff1c..1b2125f 100644
--- a/app/src/main/res/values-nb/strings.xml
+++ b/app/src/main/res/values-nb/strings.xml
@@ -9,7 +9,6 @@
Hjelp
Motta
Gi nytt navn
- Arkiver
Backup
[Forandre passord]
@@ -93,7 +92,6 @@
Backup suksessfull
Backup feila!
- Arkivering feila!
Kunne ikke gi nytt navn!
[Passordforandring feila!]
[Passord forandra]
@@ -266,9 +264,8 @@
Ta meg tilbake!
Detaljer
- Lommeboka vil bli backa opp og så sletta!
- Ja, gjør det!
- Nei takk!
+ Ja, gjør det!
+ Nei takk!
Lag ny lommebok
Gjenoprett bare-se lommebok
@@ -422,4 +419,10 @@
Select a subaddress
Long-press for details
+ The wallet will be deleted!
+ Delete
+ Delete failed!
+
+ Import wallet
+ Import failed!
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index 119bfea..f99fa2a 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -9,7 +9,6 @@
Help
Ontvangen
Hernoemen
- Archiveren
Back-up
Wachtzin wijzigen
@@ -93,7 +92,6 @@
Bezig met voltooien…\nDit kan even duren.
Back-up mislukt!
- Archiveren mislukt!
Hernoemen mislukt!
Wachtwoord wijzigen mislukt!
Wachtwoord is gewijzigd
@@ -263,9 +261,9 @@
Ga terug!
Details
- Er wordt een back-up gemaakt en daarna wordt de portemonnee verwijderd!
- Ja, doe dat!
- Nee, niet doen!
+ Er wordt de portemonnee verwijderd!
+ Ja, doe dat!
+ Nee, niet doen!
Nieuwe portemonnee
Alleen-lezen portemonnee herstellen
@@ -422,4 +420,9 @@
Select a subaddress
Long-press for details
+ Delete
+ Delete failed!
+
+ Import wallet
+ Import failed!
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 4fb7b1e..d2af54f 100755
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -9,7 +9,6 @@
Ajuda
Receber
Renomear
- Arquivar
Backup
Alterar Senha
@@ -93,7 +92,6 @@
Finalizando o processo …\nPode demorar um pouco
Erro ao criar backup!
- Erro ao arquivar!
Erro ao renomear!
Erro ao alterar senha!
Senha alterada
@@ -265,9 +263,8 @@
Tire-me daqui!
Detalhes
- Farei um backup da carteira e depois vou deletá-la!
- Sim, pode fazer!
- Não, obrigado!
+ Sim, pode fazer!
+ Não, obrigado!
Criar nova carteira
Restaurar carteira \"Somente leitura\"
@@ -414,4 +411,10 @@
Select a subaddress
Long-press for details
+ The wallet will be deleted!
+ Delete
+ Delete failed!
+
+ Import wallet
+ Import failed!
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 7fc18b4..c9e7046 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -9,7 +9,6 @@
Ajuda
Receber
Renomear
- Arquivar
Cópia de segurança
Alterar palavra passe
@@ -93,7 +92,6 @@
A concluir o processamento …\nIsto pode demorar!
Cópia de segurança falhou!
- Erro ao arquivar!
Erro ao renomear!
Erro ao alterar a palavra passe!
Palavra passe alterada
@@ -265,9 +263,8 @@
Volta atrás!
Detalhes
- Vai ser feita uma cópia de segurança da carteira e depois será apagada!
- Sim, faz isso!
- Não obrigado!
+ Sim, faz isso!
+ Não obrigado!
Criar nova carteira
Restaurar carteira apenas de visualização
@@ -426,4 +423,10 @@
Select a subaddress
Long-press for details
+ The wallet will be deleted!
+ Delete
+ Delete failed!
+
+ Import wallet
+ Import failed!
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index 334aa8f..54c92c2 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -9,7 +9,6 @@
Ajutor
Primește
Redenumește
- Arhivă
Backup
Continuă …
@@ -92,7 +91,6 @@
Copia de rezervă creată cu succes
Copia de rezervă a eșuat!
- Arhivarea a eșuat!
Redenumirea a eșuat!
Nodul
@@ -241,9 +239,8 @@
Du-mă înapoi!
Detalii
- Voi face o copie de siguranță a portofelului apoi va fi șters!
- Da, fă asta!
- Nu mersi!
+ Da, fă asta!
+ Nu mersi!
Creeaza portofel nou
Restaurează portofel-vizualizare
@@ -422,4 +419,10 @@
Select a subaddress
Long-press for details
+ The wallet will be deleted!
+ Delete
+ Delete failed!
+
+ Import wallet
+ Import failed!
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 9b68e19..5d4bbff 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -9,7 +9,6 @@
Помощь
Получить
Переименовать
- Архив
Резервная копия
Изменить пароль
@@ -94,7 +93,6 @@
Для данного действия …\nМожет потребоваться некоторое время!
Резервное копирование произошло с ошибкой!
- Архивирование произошло с ошибкой!
Переименование произошло с ошибкой!
Изменение пароля произошло с ошибкой!
Изменение пароля выполено успешно
@@ -267,9 +265,9 @@
Верните меня обратно!
Подробная информация
- Этот кошелек будет скопирован, а затем удален!
- Да, сделай это!
- Нет, спасибо!
+ Этот кошелек будет удален!
+ Да, сделай это!
+ Нет, спасибо!
Создать новый кошелек
Восстановить кошелек только для просмотра
@@ -426,4 +424,9 @@
Select a subaddress
Long-press for details
+ Delete
+ Delete failed!
+
+ Import wallet
+ Import failed!
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index b34b33b..c1895c2 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -9,7 +9,6 @@
Pomoc
Prijať
Premenovať
- Archivovať
Záloha
Zmena Hesla
@@ -94,7 +93,6 @@
Balím si veci …\nMôže to chvíľu trvať!
Zálohovanie zlyhalo!
- Archivácia zlyhala!
Premenovanie zlyhalo!
Zmena hesla zlyhala!
Heslo zmenené
@@ -264,9 +262,9 @@
Naspäť!
Detaily
- Peňaženka bude zálohovaná a následne zmazaná!
- Áno, poďme na to!
- Nie, díky!
+ Peňaženka bude zmazaná!
+ Áno, poďme na to!
+ Nie, díky!
Vytvor novú peňaženku
Obnoviť prezeraciu peňaženku
@@ -423,4 +421,9 @@
Select a subaddress
Long-press for details
+ Delete
+ Delete failed!
+
+ Import wallet
+ Import failed!
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index 5abd7da..7733d62 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -9,7 +9,6 @@
Pomoć
Primi
Preimenuj
- Arhiva
Bekap
Promeni lozinku
@@ -94,7 +93,6 @@
Bekap uspešan
Bekap neuspeo!
- Arhiviranje neuspelo!
Preimenovanje neuspelo!
Promena lozinke neuspela!
Lozinka promenjena
@@ -274,9 +272,9 @@
Vrati me nazad!
Detalji
- Novčanik će biti bekapovan a potom obrisan!
- Da, uradi to!
- Ne, hvala!
+ Novčanik će biti obrisan!
+ Da, uradi to!
+ Ne, hvala!
Kreiraj novi novčanik
Obnovi novčanik za pregled
@@ -421,4 +419,9 @@
Select a subaddress
Long-press for details
+ Delete
+ Delete failed!
+
+ Import wallet
+ Import failed!
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index 7d76007..ea05956 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -9,7 +9,6 @@
Hjälp
Ta emot
Byt namn
- Arkivera
Säkerhetskopiera
Ändra lösenfras
@@ -94,7 +93,6 @@
Säkerhetskopia sparad
Det gick inte att säkerhetskopiera!
- Det gick inte att arkivera!
Det gick inte att byta namn!
Det gick inte att ändra lösenord!
Lösenordet har ändrats
@@ -254,9 +252,8 @@
Ta mig tillbaka!
Detaljer
- Plånboken kommer att säkerhetskopieras och sedan tas bort!
- Ja, gör det!
- Nej tack!
+ Ja, gör det!
+ Nej tack!
Skapa ny plånbok
Återställ granskningsplånbok
@@ -414,4 +411,10 @@
Select a subaddress
Long-press for details
+ The wallet will be deleted!
+ Delete
+ Delete failed!
+
+ Import wallet
+ Import failed!
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 8f1de6b..df166ff 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -9,7 +9,6 @@
Допомога
Отримати
Перейменувати
- Архів
Резервна копія
Змінити пароль
@@ -94,7 +93,6 @@
Для даної дії …\nМоже знадобитися деякий час!
Резервне копіювання відбулося з помилкою!
- Архівування відбулося з помилкою!
Перейменування відбулося з помилкою!
Зміна паролю відбулася з помилкою!
Зміну паролю виконано успішно
@@ -267,9 +265,9 @@
Поверніть мене назад!
Детальна інформація
- Цей гаманець буде скопійовано, а потім знищено!
- Так, зроби це!
- Ні, дякую!
+ Цей гаманець буде знищено!
+ Так, зроби це!
+ Ні, дякую!
Створити новий гаманець
Відновити гаманець тільки для перегляду
@@ -426,4 +424,9 @@
Select a subaddress
Long-press for details
+ Delete
+ Delete failed!
+
+ Import wallet
+ Import failed!
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 4bbd57a..db38944 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -7,7 +7,6 @@
帮助
收款
重命名
- 存档
备份
修改密码
强度不够哦 …
@@ -70,7 +69,6 @@
整合中…\n这将花上不少时间
备份成功
备份失败!
- 存档失败!
重命名失败!
密码修改失败!
密码已修改
@@ -213,9 +211,8 @@
安全
返回上一步
详情
- 即将开始备份钱包,完成后将删除钱包!
- 好的,开始吧!
- 不用了,谢谢!
+ 好的,开始吧!
+ 不用了,谢谢!
创建新钱包
恢复只读钱包
通过私钥恢复钱包
@@ -346,4 +343,10 @@
Select a subaddress
Long-press for details
+ The wallet will be deleted!
+ Delete
+ Delete failed!
+
+ Import wallet
+ Import failed!
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index d3b717c..6f48545 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -9,7 +9,6 @@
幫助
收款
重新命名
- 封存
備份
更改密碼
@@ -95,7 +94,6 @@
備份成功
備份失敗!
- 封存失敗!
重新命名失敗!
密碼更改失敗!
密碼更改成功
@@ -265,9 +263,8 @@
不看了!
詳細資訊
- 錢包檔案將會被備份並且於列表中刪除!
- 好的!
- 不了!
+ 好的!
+ 不了!
建立新錢包
回復唯讀錢包
@@ -421,4 +418,10 @@
Select a subaddress
Long-press for details
+ The wallet will be deleted!
+ Delete
+ Delete failed!
+
+ Import wallet
+ Import failed!
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index acc9f12..7da9eea 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -10,7 +10,7 @@
Help
Receive
Rename
- Archive
+ Delete
Backup
Change Passphrase
@@ -86,16 +86,16 @@
Destination Address
Notes
- Backup in progress
- Archive in progress
- Rename in progress
+ Backup in progress
+ Archive in progress
+ Rename in progress
Change Password in progress
Wrapping things up …\nThis can take a while!
Backup successful
Backup failed!
- Archive failed!
+ Delete failed!
Rename failed!
Change Password failed!
Password changed
@@ -132,7 +132,7 @@
Saved password is incorrect.\nPlease enter password manually.
Wallet does not exist!
Node must be set!
- Wallet does not match selected net
+ Wallet does not match selected net
(Watch Only)
@@ -278,9 +278,9 @@
Take me back!
Details
- The wallet will be backed up and then deleted!
- Yes, do that!
- No thanks!
+ The wallet will be deleted!
+ Yes, do that!
+ No thanks!
- EUR
@@ -495,4 +495,6 @@
Select a subaddress
Long-press for details
+ Import wallet
+ Import failed!