mirror of https://github.com/m2049r/xmrwallet.git
commit
c9bbe1db3f
|
@ -1,2 +1,2 @@
|
|||
workspace.xml
|
||||
markdown-navigator*
|
||||
markdown-*
|
||||
|
|
|
@ -21,7 +21,6 @@ You may loose all your Moneroj if you use this App. Be cautious when spending on
|
|||
- Monerujo means "Monero Wallet" according to https://www.reddit.com/r/Monero/comments/3exy7t/esperanto_corner/
|
||||
|
||||
### TODO
|
||||
- wallet backup functions
|
||||
- review visibility of methods/classes
|
||||
- more sensible error dialogs
|
||||
- check licenses of included libraries; License Dialog
|
||||
|
|
|
@ -7,8 +7,8 @@ android {
|
|||
applicationId "com.m2049r.xmrwallet"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 25
|
||||
versionCode 10
|
||||
versionName "0.5.3"
|
||||
versionCode 11
|
||||
versionName "0.6"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:icon="@drawable/ic_monero_32dp"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
|
|
|
@ -461,7 +461,6 @@ Java_com_m2049r_xmrwallet_model_WalletManager_resolveOpenAlias(JNIEnv *env, jobj
|
|||
|
||||
//TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir);
|
||||
|
||||
// actually a WalletManager function, but logically in onWalletSelected
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_m2049r_xmrwallet_model_WalletManager_closeJ(JNIEnv *env, jobject instance,
|
||||
jobject walletInstance) {
|
||||
|
|
|
@ -69,12 +69,9 @@ public class GenerateFragment extends Fragment {
|
|||
bGenerate = (Button) view.findViewById(R.id.bGenerate);
|
||||
|
||||
etWalletMnemonic.setRawInputType(InputType.TYPE_CLASS_TEXT);
|
||||
etWalletAddress.setRawInputType(InputType.TYPE_CLASS_TEXT);
|
||||
etWalletViewKey.setRawInputType(InputType.TYPE_CLASS_TEXT);
|
||||
etWalletSpendKey.setRawInputType(InputType.TYPE_CLASS_TEXT);
|
||||
|
||||
boolean testnet = WalletManager.getInstance().isTestNet();
|
||||
//etWalletMnemonic.setTextIsSelectable(testnet);
|
||||
etWalletAddress.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
etWalletViewKey.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
etWalletSpendKey.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
|
||||
Helper.showKeyboard(getActivity());
|
||||
etWalletName.addTextChangedListener(new TextWatcher() {
|
||||
|
@ -298,8 +295,12 @@ public class GenerateFragment extends Fragment {
|
|||
private void generateWallet() {
|
||||
String name = etWalletName.getText().toString();
|
||||
if (name.length() == 0) return;
|
||||
String walletPath = Helper.getWalletPath(getActivity(), name);
|
||||
if (WalletManager.getInstance().walletExists(walletPath)) {
|
||||
if (name.charAt(0)=='.') {
|
||||
Toast.makeText(getActivity(), getString(R.string.generate_wallet_dot), Toast.LENGTH_LONG).show();
|
||||
etWalletName.requestFocus();
|
||||
}
|
||||
File walletFile = Helper.getWalletFile(getActivity(), name);
|
||||
if (WalletManager.getInstance().walletExists(walletFile)) {
|
||||
Toast.makeText(getActivity(), getString(R.string.generate_wallet_exists), Toast.LENGTH_LONG).show();
|
||||
etWalletName.requestFocus();
|
||||
return;
|
||||
|
@ -348,7 +349,7 @@ public class GenerateFragment extends Fragment {
|
|||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
Log.d(TAG, "onPause()");
|
||||
Log.d(TAG, "onResume()");
|
||||
activityCallback.setTitle(getString(R.string.generate_title));
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,10 @@
|
|||
package com.m2049r.xmrwallet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
@ -28,15 +30,13 @@ import android.widget.TextView;
|
|||
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
import com.m2049r.xmrwallet.service.MoneroHandlerThread;
|
||||
|
||||
import java.io.File;
|
||||
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
|
||||
|
||||
public class GenerateReviewFragment extends Fragment {
|
||||
static final String TAG = "GenerateReviewFragment";
|
||||
static final public String VIEW_DETAILS = "details";
|
||||
static final public String VIEW_ACCEPT = "accept";
|
||||
static final public String VIEW_WALLET = "wallet";
|
||||
static final public String VIEW_TYPE_DETAILS = "details";
|
||||
static final public String VIEW_TYPE_ACCEPT = "accept";
|
||||
static final public String VIEW_TYPE_WALLET = "wallet";
|
||||
|
||||
ProgressBar pbProgress;
|
||||
TextView tvWalletName;
|
||||
|
@ -76,16 +76,12 @@ public class GenerateReviewFragment extends Fragment {
|
|||
|
||||
showProgress();
|
||||
|
||||
Bundle b = getArguments();
|
||||
String type = b.getString("type");
|
||||
if (!type.equals(VIEW_WALLET)) {
|
||||
String name = b.getString("name");
|
||||
String password = b.getString("password");
|
||||
tvWalletName.setText(new File(name).getName());
|
||||
show(name, password, type);
|
||||
} else {
|
||||
show(walletCallback.getWallet(), null, type);
|
||||
}
|
||||
Bundle args = getArguments();
|
||||
String path = args.getString("path");
|
||||
String password = args.getString("password");
|
||||
String type = args.getString("type");
|
||||
new AsyncShow().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR,
|
||||
path, password, type);
|
||||
return view;
|
||||
}
|
||||
|
||||
|
@ -96,40 +92,64 @@ public class GenerateReviewFragment extends Fragment {
|
|||
acceptCallback.onAccept(name, password);
|
||||
}
|
||||
|
||||
private void show(final String walletPath, final String password, final String type) {
|
||||
new Thread(null,
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final Wallet wallet = WalletManager.getInstance().openWallet(walletPath, password);
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
show(wallet, password, type);
|
||||
wallet.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
, "DetailsReview", MoneroHandlerThread.THREAD_STACK_SIZE).start();
|
||||
}
|
||||
private class AsyncShow extends AsyncTask<String, Void, Boolean> {
|
||||
String type;
|
||||
String password;
|
||||
|
||||
private void show(final Wallet wallet, final String password, final String type) {
|
||||
if (type.equals(GenerateReviewFragment.VIEW_ACCEPT)) {
|
||||
tvWalletPassword.setText(password);
|
||||
bAccept.setVisibility(View.VISIBLE);
|
||||
bAccept.setEnabled(true);
|
||||
String name;
|
||||
String address;
|
||||
String seed;
|
||||
String viewKey;
|
||||
boolean isWatchOnly;
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(String... params) {
|
||||
if (params.length != 3) return false;
|
||||
String walletPath = params[0];
|
||||
password = params[1];
|
||||
type = params[2];
|
||||
|
||||
Wallet wallet;
|
||||
boolean closeWallet;
|
||||
if (type.equals(GenerateReviewFragment.VIEW_TYPE_WALLET)) {
|
||||
wallet = GenerateReviewFragment.this.walletCallback.getWallet();
|
||||
closeWallet = false;
|
||||
} else {
|
||||
wallet = WalletManager.getInstance().openWallet(walletPath, password);
|
||||
closeWallet = true;
|
||||
}
|
||||
if (wallet.getStatus() != Wallet.Status.Status_Ok) return false;
|
||||
name = wallet.getName();
|
||||
address = wallet.getAddress();
|
||||
seed = wallet.getSeed();
|
||||
viewKey = wallet.getSecretViewKey();
|
||||
isWatchOnly = wallet.isWatchOnly();
|
||||
if (closeWallet) wallet.close();
|
||||
return true;
|
||||
}
|
||||
tvWalletName.setText(wallet.getName());
|
||||
tvWalletAddress.setText(wallet.getAddress());
|
||||
tvWalletMnemonic.setText(wallet.getSeed());
|
||||
tvWalletViewKey.setText(wallet.getSecretViewKey());
|
||||
String spend = wallet.isWatchOnly() ? "" : "not available - use seed for recovery";
|
||||
if (spend.length() > 0) { //TODO should be == 64, but spendkey is not in the API yet
|
||||
tvWalletSpendKey.setText(spend);
|
||||
} else {
|
||||
tvWalletSpendKey.setText(getString(R.string.generate_wallet_watchonly));
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
super.onPostExecute(result);
|
||||
if (result) {
|
||||
if (type.equals(GenerateReviewFragment.VIEW_TYPE_ACCEPT)) {
|
||||
tvWalletPassword.setText(password);
|
||||
bAccept.setVisibility(View.VISIBLE);
|
||||
bAccept.setEnabled(true);
|
||||
}
|
||||
tvWalletName.setText(name);
|
||||
tvWalletAddress.setText(address);
|
||||
tvWalletMnemonic.setText(seed);
|
||||
tvWalletViewKey.setText(viewKey);
|
||||
String spend = isWatchOnly ? "" : "not available - use seed for recovery";
|
||||
if (spend.length() > 0) { //TODO should be == 64, but spendkey is not in the API yet
|
||||
tvWalletSpendKey.setText(spend);
|
||||
} else {
|
||||
tvWalletSpendKey.setText(getString(R.string.generate_wallet_watchonly));
|
||||
}
|
||||
}
|
||||
hideProgress();
|
||||
}
|
||||
hideProgress();
|
||||
}
|
||||
|
||||
GenerateReviewFragment.Listener acceptCallback = null;
|
||||
|
@ -141,6 +161,7 @@ public class GenerateReviewFragment extends Fragment {
|
|||
|
||||
public interface ListenerWithWallet {
|
||||
Wallet getWallet();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -16,12 +16,15 @@
|
|||
|
||||
package com.m2049r.xmrwallet;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.Fragment;
|
||||
|
@ -77,16 +80,22 @@ public class LoginActivity extends AppCompatActivity
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalletSelected(final String walletName) {
|
||||
boolean checkServiceRunning() {
|
||||
if (WalletService.Running) {
|
||||
Toast.makeText(this, getString(R.string.service_busy), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalletSelected(final String walletName) {
|
||||
if (checkServiceRunning()) return;
|
||||
Log.d(TAG, "selected wallet is ." + walletName + ".");
|
||||
// now it's getting real, check if wallet exists
|
||||
String walletPath = Helper.getWalletPath(this, walletName);
|
||||
if (WalletManager.getInstance().walletExists(walletPath)) {
|
||||
File walletFile = Helper.getWalletFile(this, walletName);
|
||||
if (WalletManager.getInstance().walletExists(walletFile)) {
|
||||
promptPassword(walletName, new PasswordAction() {
|
||||
@Override
|
||||
public void action(String walletName, String password) {
|
||||
|
@ -101,12 +110,50 @@ public class LoginActivity extends AppCompatActivity
|
|||
@Override
|
||||
public void onWalletDetails(final String walletName) {
|
||||
Log.d(TAG, "details for wallet ." + walletName + ".");
|
||||
final String walletPath = Helper.getWalletPath(this, walletName);
|
||||
if (WalletManager.getInstance().walletExists(walletPath)) {
|
||||
if (checkServiceRunning()) return;
|
||||
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which) {
|
||||
case DialogInterface.BUTTON_POSITIVE:
|
||||
final File walletFile = Helper.getWalletFile(LoginActivity.this, walletName);
|
||||
if (WalletManager.getInstance().walletExists(walletFile)) {
|
||||
promptPassword(walletName, new PasswordAction() {
|
||||
@Override
|
||||
public void action(String walletName, String password) {
|
||||
startDetails(walletFile, password, GenerateReviewFragment.VIEW_TYPE_DETAILS);
|
||||
}
|
||||
});
|
||||
} else { // this cannot really happen as we prefilter choices
|
||||
Log.e(TAG, "Wallet missing: " + walletName);
|
||||
Toast.makeText(LoginActivity.this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
break;
|
||||
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
// do nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setMessage(getString(R.string.details_alert_message))
|
||||
.setPositiveButton(getString(R.string.details_alert_yes), dialogClickListener)
|
||||
.setNegativeButton(getString(R.string.details_alert_no), dialogClickListener)
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalletReceive(String walletName) {
|
||||
Log.d(TAG, "receive for wallet ." + walletName + ".");
|
||||
if (checkServiceRunning()) return;
|
||||
final File walletFile = Helper.getWalletFile(this, walletName);
|
||||
if (WalletManager.getInstance().walletExists(walletFile)) {
|
||||
promptPassword(walletName, new PasswordAction() {
|
||||
@Override
|
||||
public void action(String walletName, String password) {
|
||||
startDetails(walletPath, password, GenerateReviewFragment.VIEW_DETAILS);
|
||||
startReceive(walletFile, password);
|
||||
}
|
||||
});
|
||||
} else { // this cannot really happen as we prefilter choices
|
||||
|
@ -114,8 +161,224 @@ public class LoginActivity extends AppCompatActivity
|
|||
}
|
||||
}
|
||||
|
||||
private class AsyncRename extends AsyncTask<String, Void, Boolean> {
|
||||
ProgressDialog progressDialog = new MyProgressDialog(LoginActivity.this, R.string.rename_progress);
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
progressDialog.show();
|
||||
LoginActivity.this.asyncWaitTask = this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(String... params) {
|
||||
if (params.length != 2) return false;
|
||||
File walletFile = Helper.getWalletFile(LoginActivity.this, params[0]);
|
||||
String newName = params[1];
|
||||
return renameWallet(walletFile, newName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
super.onPostExecute(result);
|
||||
progressDialog.dismiss();
|
||||
if (result) {
|
||||
reloadWalletList();
|
||||
} else {
|
||||
Toast.makeText(LoginActivity.this, getString(R.string.rename_failed), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
LoginActivity.this.asyncWaitTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
// copy + delete seems safer than rename bevause we call rollback easily
|
||||
boolean renameWallet(File walletFile, String newName) {
|
||||
if (copyWallet(walletFile, new File(walletFile.getParentFile(), newName), false)) {
|
||||
deleteWallet(walletFile);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalletRename(final String walletName) {
|
||||
Log.d(TAG, "rename for wallet ." + walletName + ".");
|
||||
if (checkServiceRunning()) return;
|
||||
LayoutInflater li = LayoutInflater.from(this);
|
||||
View promptsView = li.inflate(R.layout.prompt_rename, null);
|
||||
|
||||
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
|
||||
alertDialogBuilder.setView(promptsView);
|
||||
|
||||
final EditText etRename = (EditText) promptsView.findViewById(R.id.etRename);
|
||||
final TextView tvRenameLabel = (TextView) promptsView.findViewById(R.id.tvRenameLabel);
|
||||
|
||||
tvRenameLabel.setText(getString(R.string.prompt_rename) + " " + walletName);
|
||||
|
||||
// set dialog message
|
||||
alertDialogBuilder
|
||||
.setCancelable(false)
|
||||
.setPositiveButton("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);
|
||||
}
|
||||
})
|
||||
.setNegativeButton("Cancel",
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int 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)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||
Helper.hideKeyboardAlways(LoginActivity.this);
|
||||
String newName = etRename.getText().toString();
|
||||
dialog.cancel();
|
||||
new AsyncRename().execute(walletName, newName);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
|
||||
private class AsyncBackup extends AsyncTask<String, Void, Boolean> {
|
||||
ProgressDialog progressDialog = new MyProgressDialog(LoginActivity.this, R.string.backup_progress);
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
progressDialog.show();
|
||||
LoginActivity.this.asyncWaitTask = this;
|
||||
}
|
||||
|
||||
@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);
|
||||
progressDialog.dismiss();
|
||||
if (!result) {
|
||||
Toast.makeText(LoginActivity.this, getString(R.string.backup_failed), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
LoginActivity.this.asyncWaitTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean backupWallet(String walletName) {
|
||||
File backupFolder = new File(getStorageRoot(), ".backups");
|
||||
if (!backupFolder.exists()) {
|
||||
if (!backupFolder.mkdir()) {
|
||||
Log.e(TAG, "Cannot create backup dir " + backupFolder.getAbsolutePath());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
File walletFile = Helper.getWalletFile(LoginActivity.this, walletName);
|
||||
File backupFile = new File(backupFolder, walletName);
|
||||
Log.d(TAG, "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
|
||||
return copyWallet(walletFile, backupFile, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalletBackup(String walletName) {
|
||||
Log.d(TAG, "backup for wallet ." + walletName + ".");
|
||||
new AsyncBackup().execute(walletName);
|
||||
}
|
||||
|
||||
private class AsyncArchive extends AsyncTask<String, Void, Boolean> {
|
||||
ProgressDialog progressDialog = new MyProgressDialog(LoginActivity.this, R.string.archive_progress);
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
progressDialog.show();
|
||||
LoginActivity.this.asyncWaitTask = this;
|
||||
}
|
||||
|
||||
@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))) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
super.onPostExecute(result);
|
||||
progressDialog.dismiss();
|
||||
if (result) {
|
||||
reloadWalletList();
|
||||
} else {
|
||||
Toast.makeText(LoginActivity.this, getString(R.string.archive_failed), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
LoginActivity.this.asyncWaitTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalletArchive(final String walletName) {
|
||||
Log.d(TAG, "archive 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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setMessage(getString(R.string.archive_alert_message))
|
||||
.setTitle(walletName)
|
||||
.setPositiveButton(getString(R.string.archive_alert_yes), dialogClickListener)
|
||||
.setNegativeButton(getString(R.string.archive_alert_no), dialogClickListener)
|
||||
.show();
|
||||
}
|
||||
|
||||
void reloadWalletList() {
|
||||
try {
|
||||
LoginFragment loginFragment = (LoginFragment)
|
||||
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
||||
loginFragment.loadList();
|
||||
} catch (ClassCastException ex) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddWallet() {
|
||||
if (checkServiceRunning()) return;
|
||||
startGenerateFragment();
|
||||
}
|
||||
|
||||
|
@ -236,10 +499,64 @@ public class LoginActivity extends AppCompatActivity
|
|||
super.onPause();
|
||||
}
|
||||
|
||||
AsyncTask asyncWaitTask = null;
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
Log.d(TAG, "onResume()");
|
||||
if (WalletService.Running && (asyncWaitTask == null)) {
|
||||
Log.d(TAG, "new process dialog");
|
||||
new AsyncWaitForService().execute();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class MyProgressDialog extends ProgressDialog {
|
||||
Activity activity;
|
||||
|
||||
public MyProgressDialog(Activity activity, int msgId) {
|
||||
super(activity);
|
||||
this.activity = activity;
|
||||
setCancelable(false);
|
||||
setMessage(activity.getString(msgId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
//activity.finish();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class AsyncWaitForService extends AsyncTask<Void, Void, Void> {
|
||||
ProgressDialog progressDialog = new MyProgressDialog(LoginActivity.this, R.string.service_progress);
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
progressDialog.show();
|
||||
LoginActivity.this.asyncWaitTask = this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
try {
|
||||
while (WalletService.Running & !isCancelled()) {
|
||||
Thread.sleep(250);
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
// oh well ...
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
super.onPostExecute(result);
|
||||
progressDialog.dismiss();
|
||||
LoginActivity.this.asyncWaitTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -251,15 +568,23 @@ public class LoginActivity extends AppCompatActivity
|
|||
startActivity(intent);
|
||||
}
|
||||
|
||||
void startDetails(final String walletPath, final String password, String type) {
|
||||
void startDetails(File walletFile, String password, String type) {
|
||||
Log.d(TAG, "startDetails()");
|
||||
Bundle b = new Bundle();
|
||||
b.putString("name", walletPath);
|
||||
b.putString("path", walletFile.getAbsolutePath());
|
||||
b.putString("password", password);
|
||||
b.putString("type", type);
|
||||
startReviewFragment(b);
|
||||
}
|
||||
|
||||
void startReceive(File walletFile, String password) {
|
||||
Log.d(TAG, "startReceive()");
|
||||
Bundle b = new Bundle();
|
||||
b.putString("path", walletFile.getAbsolutePath());
|
||||
b.putString("password", password);
|
||||
startReceiveFragment(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
|
||||
Log.d(TAG, "onRequestPermissionsResult()");
|
||||
|
@ -268,7 +593,7 @@ public class LoginActivity extends AppCompatActivity
|
|||
// If request is cancelled, the result arrays are empty.
|
||||
if (grantResults.length > 0
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
startLoginFragment();
|
||||
startLoginFragment = true;
|
||||
} else {
|
||||
String msg = getString(R.string.message_strorage_not_permitted);
|
||||
Log.e(TAG, msg);
|
||||
|
@ -280,6 +605,17 @@ public class LoginActivity extends AppCompatActivity
|
|||
}
|
||||
}
|
||||
|
||||
private boolean startLoginFragment = false;
|
||||
|
||||
@Override
|
||||
protected void onResumeFragments() {
|
||||
super.onResumeFragments();
|
||||
if (startLoginFragment) {
|
||||
startLoginFragment();
|
||||
startLoginFragment = false;
|
||||
}
|
||||
}
|
||||
|
||||
void startLoginFragment() {
|
||||
Fragment fragment = new LoginFragment();
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
|
@ -297,6 +633,11 @@ public class LoginActivity extends AppCompatActivity
|
|||
Log.d(TAG, "GenerateReviewFragment placed");
|
||||
}
|
||||
|
||||
void startReceiveFragment(Bundle extras) {
|
||||
replaceFragment(new ReceiveFragment(), null, extras);
|
||||
Log.d(TAG, "ReceiveFragment placed");
|
||||
}
|
||||
|
||||
void replaceFragment(Fragment newFragment, String stackName, Bundle extras) {
|
||||
if (extras != null) {
|
||||
newFragment.setArguments(extras);
|
||||
|
@ -316,6 +657,7 @@ public class LoginActivity extends AppCompatActivity
|
|||
//////////////////////////////////////////
|
||||
static final String MNEMONIC_LANGUAGE = "English"; // see mnemonics/electrum-words.cpp for more
|
||||
|
||||
// TODO make this an AsyncTask?
|
||||
public void createWallet(final String name, final String password, final WalletCreator walletCreator) {
|
||||
final GenerateFragment genFragment = (GenerateFragment)
|
||||
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
||||
|
@ -336,7 +678,7 @@ public class LoginActivity extends AppCompatActivity
|
|||
cacheFile.delete();
|
||||
File keysFile = new File(newWalletFolder, name + ".keys");
|
||||
keysFile.delete();
|
||||
final File addressFile = new File(newWalletFolder, name + ".address.txt");
|
||||
File addressFile = new File(newWalletFolder, name + ".address.txt");
|
||||
addressFile.delete();
|
||||
|
||||
if (cacheFile.exists() || keysFile.exists() || addressFile.exists()) {
|
||||
|
@ -345,29 +687,29 @@ public class LoginActivity extends AppCompatActivity
|
|||
return;
|
||||
}
|
||||
|
||||
String newWalletPath = new File(newWalletFolder, name).getAbsolutePath();
|
||||
boolean success = walletCreator.createWallet(newWalletPath, password);
|
||||
File newWalletFile = new File(newWalletFolder, name);
|
||||
boolean success = walletCreator.createWallet(newWalletFile, password);
|
||||
if (success) {
|
||||
startDetails(newWalletPath, password, GenerateReviewFragment.VIEW_ACCEPT);
|
||||
startDetails(newWalletFile, password, GenerateReviewFragment.VIEW_TYPE_ACCEPT);
|
||||
} else {
|
||||
Toast.makeText(LoginActivity.this,
|
||||
getString(R.string.generate_wallet_create_failed), Toast.LENGTH_LONG).show();
|
||||
Log.e(TAG, "Could not create new wallet in " + newWalletPath);
|
||||
|
||||
Log.e(TAG, "Could not create new wallet in " + newWalletFile.getAbsolutePath());
|
||||
genFragment.walletGenerateError();
|
||||
}
|
||||
}
|
||||
|
||||
interface WalletCreator {
|
||||
boolean createWallet(String path, String password);
|
||||
boolean createWallet(File aFile, String password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGenerate(String name, String password) {
|
||||
createWallet(name, password,
|
||||
new WalletCreator() {
|
||||
public boolean createWallet(String path, String password) {
|
||||
public boolean createWallet(File aFile, String password) {
|
||||
Wallet newWallet = WalletManager.getInstance()
|
||||
.createWallet(path, password, MNEMONIC_LANGUAGE);
|
||||
.createWallet(aFile, password, MNEMONIC_LANGUAGE);
|
||||
boolean success = (newWallet.getStatus() == Wallet.Status.Status_Ok);
|
||||
if (!success) Log.e(TAG, newWallet.getErrorString());
|
||||
newWallet.close();
|
||||
|
@ -380,12 +722,15 @@ public class LoginActivity extends AppCompatActivity
|
|||
public void onGenerate(String name, String password, final String seed, final long restoreHeight) {
|
||||
createWallet(name, password,
|
||||
new WalletCreator() {
|
||||
public boolean createWallet(String path, String password) {
|
||||
Wallet newWallet = WalletManager.getInstance().recoveryWallet(path, seed, restoreHeight);
|
||||
public boolean createWallet(File aFile, String password) {
|
||||
Wallet newWallet = WalletManager.getInstance().recoveryWallet(aFile, seed, restoreHeight);
|
||||
boolean success = (newWallet.getStatus() == Wallet.Status.Status_Ok);
|
||||
if (!success) Log.e(TAG, newWallet.getErrorString());
|
||||
newWallet.setPassword(password);
|
||||
success = success && newWallet.store();
|
||||
if (success) {
|
||||
newWallet.setPassword(password);
|
||||
success = success && newWallet.store();
|
||||
} else {
|
||||
Log.e(TAG, newWallet.getErrorString());
|
||||
}
|
||||
newWallet.close();
|
||||
return success;
|
||||
}
|
||||
|
@ -397,14 +742,17 @@ public class LoginActivity extends AppCompatActivity
|
|||
final String address, final String viewKey, final String spendKey, final long restoreHeight) {
|
||||
createWallet(name, password,
|
||||
new WalletCreator() {
|
||||
public boolean createWallet(String path, String password) {
|
||||
public boolean createWallet(File aFile, String password) {
|
||||
Wallet newWallet = WalletManager.getInstance()
|
||||
.createWalletFromKeys(path, MNEMONIC_LANGUAGE, restoreHeight,
|
||||
.createWalletFromKeys(aFile, MNEMONIC_LANGUAGE, restoreHeight,
|
||||
address, viewKey, spendKey);
|
||||
boolean success = (newWallet.getStatus() == Wallet.Status.Status_Ok);
|
||||
if (!success) Log.e(TAG, newWallet.getErrorString());
|
||||
newWallet.setPassword(password);
|
||||
success = success && newWallet.store();
|
||||
if (success) {
|
||||
newWallet.setPassword(password);
|
||||
success = success && newWallet.store();
|
||||
} else {
|
||||
Log.e(TAG, newWallet.getErrorString());
|
||||
}
|
||||
newWallet.close();
|
||||
return success;
|
||||
}
|
||||
|
@ -414,18 +762,18 @@ public class LoginActivity extends AppCompatActivity
|
|||
|
||||
@Override
|
||||
public void onAccept(final String name, final String password) {
|
||||
final File newWalletFolder = new File(getStorageRoot(), ".new");
|
||||
final File newWalletFile = new File(new File(getStorageRoot(), ".new"), name);
|
||||
final File walletFolder = getStorageRoot();
|
||||
final String walletPath = new File(walletFolder, name).getAbsolutePath();
|
||||
final boolean rc = copyWallet(walletFolder, newWalletFolder, name)
|
||||
final File walletFile = new File(walletFolder, name);
|
||||
final boolean rc = copyWallet(newWalletFile, walletFile, false)
|
||||
&&
|
||||
(testWallet(walletPath, password) == Wallet.Status.Status_Ok);
|
||||
(testWallet(walletFile.getAbsolutePath(), password) == Wallet.Status.Status_Ok);
|
||||
if (rc) {
|
||||
popFragmentStack(GENERATE_STACK);
|
||||
Toast.makeText(LoginActivity.this,
|
||||
getString(R.string.generate_wallet_created), Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Log.e(TAG, "Wallet store failed to " + walletPath);
|
||||
Log.e(TAG, "Wallet store failed to " + walletFile.getAbsolutePath());
|
||||
Toast.makeText(LoginActivity.this,
|
||||
getString(R.string.generate_wallet_create_failed_2), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
@ -441,30 +789,58 @@ public class LoginActivity extends AppCompatActivity
|
|||
return status;
|
||||
}
|
||||
|
||||
boolean copyWallet(File dstDir, File srcDir, String name) {
|
||||
boolean walletExists(File walletFile, boolean any) {
|
||||
File dir = walletFile.getParentFile();
|
||||
String name = walletFile.getName();
|
||||
if (any) {
|
||||
return new File(dir, name).exists()
|
||||
|| new File(dir, name + ".keys").exists()
|
||||
|| new File(dir, name + ".address.txt").exists();
|
||||
} else {
|
||||
return new File(dir, name).exists()
|
||||
&& new File(dir, name + ".keys").exists()
|
||||
&& new File(dir, name + ".address.txt").exists();
|
||||
}
|
||||
}
|
||||
|
||||
boolean copyWallet(File srcWallet, File dstWallet, boolean overwrite) {
|
||||
//Log.d(TAG, "src=" + srcWallet.exists() + " dst=" + dstWallet.exists());
|
||||
if (walletExists(dstWallet, true) && !overwrite) return false;
|
||||
if (!walletExists(srcWallet, false)) return false;
|
||||
|
||||
boolean success = false;
|
||||
File srcDir = srcWallet.getParentFile();
|
||||
String srcName = srcWallet.getName();
|
||||
File dstDir = dstWallet.getParentFile();
|
||||
String dstName = dstWallet.getName();
|
||||
try {
|
||||
// the cache is corrupt if we recover (!!)
|
||||
// the cache is ok if we immediately do a full refresh()
|
||||
// recoveryheight is ignored but not on watchonly wallet ?! - find out why
|
||||
// so we just ignore the cache file and rebuild it on first sync
|
||||
//copyFile(dstDir, srcDir, name);
|
||||
copyFile(dstDir, srcDir, name + ".keys");
|
||||
copyFile(dstDir, srcDir, name + ".address.txt");
|
||||
copyFile(new File(srcDir, srcName), new File(dstDir, dstName));
|
||||
copyFile(new File(srcDir, srcName + ".keys"), new File(dstDir, dstName + ".keys"));
|
||||
copyFile(new File(srcDir, srcName + ".address.txt"), new File(dstDir, dstName + ".address.txt"));
|
||||
success = true;
|
||||
} catch (IOException ex) {
|
||||
Log.e(TAG, "wallet copy failed: " + ex.getMessage());
|
||||
// try to rollback
|
||||
new File(dstDir, name).delete();
|
||||
new File(dstDir, name + ".keys").delete();
|
||||
new File(dstDir, name + ".address.txt").delete();
|
||||
deleteWallet(dstWallet);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
void copyFile(File dstDir, File srcDir, String name) throws IOException {
|
||||
FileChannel inChannel = new FileInputStream(new File(srcDir, name)).getChannel();
|
||||
FileChannel outChannel = new FileOutputStream(new File(dstDir, name)).getChannel();
|
||||
// do our best to delete as much as possible of the wallet files
|
||||
boolean deleteWallet(File walletFile) {
|
||||
Log.d(TAG, "deleteWallet " + walletFile.getAbsolutePath());
|
||||
if (!walletFile.isFile()) return false;
|
||||
File dir = walletFile.getParentFile();
|
||||
String name = walletFile.getName();
|
||||
boolean success = new File(dir, name).delete();
|
||||
success = new File(dir, name + ".keys").delete() && success;
|
||||
success = new File(dir, name + ".address.txt").delete() && success;
|
||||
return success;
|
||||
}
|
||||
|
||||
void copyFile(File src, File dst) throws IOException {
|
||||
FileChannel inChannel = new FileInputStream(src).getChannel();
|
||||
FileChannel outChannel = new FileOutputStream(dst).getChannel();
|
||||
try {
|
||||
inChannel.transferTo(0, inChannel.size(), outChannel);
|
||||
} finally {
|
||||
|
|
|
@ -20,6 +20,7 @@ import android.content.Context;
|
|||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.StrictMode;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.util.Log;
|
||||
|
@ -82,9 +83,17 @@ public class LoginFragment extends Fragment {
|
|||
|
||||
File getStorageRoot();
|
||||
|
||||
void onWalletSelected(final String wallet);
|
||||
void onWalletSelected(String wallet);
|
||||
|
||||
void onWalletDetails(final String wallet);
|
||||
void onWalletDetails(String wallet);
|
||||
|
||||
void onWalletReceive(String wallet);
|
||||
|
||||
void onWalletRename(String name);
|
||||
|
||||
void onWalletBackup(String name);
|
||||
|
||||
void onWalletArchive(String walletName);
|
||||
|
||||
void onAddWallet();
|
||||
|
||||
|
@ -117,14 +126,14 @@ public class LoginFragment extends Fragment {
|
|||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
Log.d(TAG, "onPause()");
|
||||
Log.d(TAG, "onResume()");
|
||||
activityCallback.setTitle(getString(R.string.login_activity_name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
||||
Log.d(TAG, "onCreateView");
|
||||
View view = inflater.inflate(R.layout.login_fragment, container, false);
|
||||
|
||||
tbMainNet = (ToggleButton) view.findViewById(R.id.tbMainNet);
|
||||
|
@ -222,31 +231,6 @@ public class LoginFragment extends Fragment {
|
|||
}
|
||||
});
|
||||
|
||||
/* listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
|
||||
@Override
|
||||
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
// Difference to opening wallet is that we don't need a daemon set
|
||||
String itemValue = (String) listView.getItemAtPosition(position);
|
||||
|
||||
if (itemValue.length() <= (WALLETNAME_PREAMBLE_LENGTH)) {
|
||||
Toast.makeText(getActivity(), getString(R.string.panic), Toast.LENGTH_LONG).show();
|
||||
return true;
|
||||
}
|
||||
|
||||
String wallet = itemValue.substring(WALLETNAME_PREAMBLE_LENGTH);
|
||||
String x = isMainNet() ? "4" : "9A";
|
||||
if (x.indexOf(itemValue.charAt(1)) < 0) {
|
||||
Toast.makeText(getActivity(), getString(R.string.prompt_wrong_net), Toast.LENGTH_LONG).show();
|
||||
return true;
|
||||
}
|
||||
|
||||
checkAndSetWalletDaemon("", !isMainNet()); // just set selected net
|
||||
|
||||
activityCallback.onWalletDetails(wallet);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
*/
|
||||
loadList();
|
||||
return view;
|
||||
}
|
||||
|
@ -260,7 +244,8 @@ public class LoginFragment extends Fragment {
|
|||
}
|
||||
}
|
||||
|
||||
private void loadList() {
|
||||
public void loadList() {
|
||||
Log.d(TAG, "loadList()");
|
||||
WalletManager mgr = WalletManager.getInstance();
|
||||
List<WalletManager.WalletInfo> walletInfos =
|
||||
mgr.findWallets(activityCallback.getStorageRoot());
|
||||
|
@ -408,32 +393,52 @@ public class LoginFragment extends Fragment {
|
|||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
|
||||
String listItem = (String) listView.getItemAtPosition(info.position);
|
||||
String name = nameFromListItem(listItem, !isMainNet());
|
||||
if (name == null) {
|
||||
Toast.makeText(getActivity(), getString(R.string.panic), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_info:
|
||||
String listItem = (String) listView.getItemAtPosition(info.position);
|
||||
return showInfo(listItem);
|
||||
showInfo(name);
|
||||
break;
|
||||
case R.id.action_receive:
|
||||
showReceive(name);
|
||||
break;
|
||||
case R.id.action_rename:
|
||||
activityCallback.onWalletRename(name);
|
||||
break;
|
||||
case R.id.action_backup:
|
||||
activityCallback.onWalletBackup(name);
|
||||
break;
|
||||
case R.id.action_archive:
|
||||
activityCallback.onWalletArchive(name);
|
||||
break;
|
||||
default:
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean showInfo(String listItem) {
|
||||
|
||||
if (listItem.length() <= (WALLETNAME_PREAMBLE_LENGTH)) {
|
||||
Toast.makeText(getActivity(), getString(R.string.panic), Toast.LENGTH_LONG).show();
|
||||
return true;
|
||||
}
|
||||
|
||||
String wallet = listItem.substring(WALLETNAME_PREAMBLE_LENGTH);
|
||||
String x = isMainNet() ? "4" : "9A";
|
||||
if (x.indexOf(listItem.charAt(1)) < 0) {
|
||||
Toast.makeText(getActivity(), getString(R.string.prompt_wrong_net), Toast.LENGTH_LONG).show();
|
||||
return true;
|
||||
}
|
||||
|
||||
checkAndSetWalletDaemon("", !isMainNet()); // just set selected net
|
||||
|
||||
activityCallback.onWalletDetails(wallet);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void showInfo(@NonNull String name) {
|
||||
checkAndSetWalletDaemon("", !isMainNet()); // just set selected net
|
||||
|
||||
activityCallback.onWalletDetails(name);
|
||||
}
|
||||
|
||||
private boolean showReceive(@NonNull String name) {
|
||||
checkAndSetWalletDaemon("", !isMainNet()); // just set selected net
|
||||
|
||||
activityCallback.onWalletReceive(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
private String nameFromListItem(String listItem, boolean testnet) {
|
||||
String wallet = listItem.substring(WALLETNAME_PREAMBLE_LENGTH);
|
||||
String x = testnet ? "9A" : "4";
|
||||
if (x.indexOf(listItem.charAt(1)) < 0) {
|
||||
return null;
|
||||
}
|
||||
return wallet;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,349 @@
|
|||
/*
|
||||
* Copyright (c) 2017 m2049r
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.m2049r.xmrwallet;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.EncodeHintType;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.qrcode.QRCodeWriter;
|
||||
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ReceiveFragment extends Fragment {
|
||||
static final String TAG = "ReceiveFragment";
|
||||
|
||||
ProgressBar pbProgress;
|
||||
TextView tvAddress;
|
||||
EditText etPaymentId;
|
||||
EditText etAmount;
|
||||
Button bPaymentId;
|
||||
Button bGenerate;
|
||||
ImageView qrCode;
|
||||
EditText etDummy;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
||||
View view = inflater.inflate(R.layout.receive_fragment, container, false);
|
||||
|
||||
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
|
||||
tvAddress = (TextView) view.findViewById(R.id.tvAddress);
|
||||
etPaymentId = (EditText) view.findViewById(R.id.etPaymentId);
|
||||
etAmount = (EditText) view.findViewById(R.id.etAmount);
|
||||
bPaymentId = (Button) view.findViewById(R.id.bPaymentId);
|
||||
qrCode = (ImageView) view.findViewById(R.id.qrCode);
|
||||
bGenerate = (Button) view.findViewById(R.id.bGenerate);
|
||||
etDummy = (EditText) view.findViewById(R.id.etDummy);
|
||||
|
||||
etPaymentId.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
|
||||
etPaymentId.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
||||
if (paymentIdOk()) {
|
||||
etAmount.requestFocus();
|
||||
} // otherwise ignore
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
etPaymentId.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
qrCode.setImageBitmap(getMoneroLogo());
|
||||
if (paymentIdOk() && amountOk()) {
|
||||
bGenerate.setEnabled(true);
|
||||
} else {
|
||||
bGenerate.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
});
|
||||
|
||||
etAmount.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||
if (paymentIdOk() && amountOk()) {
|
||||
Helper.hideKeyboard(getActivity());
|
||||
generateQr();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
etAmount.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
qrCode.setImageBitmap(getMoneroLogo());
|
||||
if (paymentIdOk() && amountOk()) {
|
||||
bGenerate.setEnabled(true);
|
||||
} else {
|
||||
bGenerate.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
});
|
||||
|
||||
bPaymentId.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
etPaymentId.setText((Wallet.generatePaymentId()));
|
||||
etPaymentId.setSelection(etPaymentId.getText().length());
|
||||
if (paymentIdOk() && amountOk()) {
|
||||
generateQr();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
bGenerate.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (paymentIdOk() && amountOk()) {
|
||||
Helper.hideKeyboard(getActivity());
|
||||
generateQr();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
showProgress();
|
||||
qrCode.setImageBitmap(getMoneroLogo());
|
||||
|
||||
Bundle b = getArguments();
|
||||
String address = b.getString("address");
|
||||
if (address == null) {
|
||||
String path = b.getString("path");
|
||||
String password = b.getString("password");
|
||||
show(path, password);
|
||||
} else {
|
||||
show(address);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
Log.d(TAG, "onResume()");
|
||||
if (paymentIdOk() && amountOk() && tvAddress.getText().length() > 0) {
|
||||
generateQr();
|
||||
}
|
||||
}
|
||||
|
||||
private void show(String address) {
|
||||
tvAddress.setText(address);
|
||||
etPaymentId.setEnabled(true);
|
||||
etAmount.setEnabled(true);
|
||||
bPaymentId.setEnabled(true);
|
||||
bGenerate.setEnabled(true);
|
||||
hideProgress();
|
||||
generateQr();
|
||||
}
|
||||
|
||||
private void show(String walletPath, String password) {
|
||||
new ReceiveFragment.AsyncShow().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR,
|
||||
walletPath, password);
|
||||
}
|
||||
|
||||
private class AsyncShow extends AsyncTask<String, Void, Boolean> {
|
||||
String password;
|
||||
|
||||
String address;
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(String... params) {
|
||||
if (params.length != 2) return false;
|
||||
String walletPath = params[0];
|
||||
password = params[1];
|
||||
Wallet wallet = WalletManager.getInstance().openWallet(walletPath, password);
|
||||
address = wallet.getAddress();
|
||||
wallet.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
super.onPostExecute(result);
|
||||
if (result) {
|
||||
show(address);
|
||||
} else {
|
||||
Toast.makeText(getActivity(), getString(R.string.receive_cannot_open), Toast.LENGTH_LONG).show();
|
||||
hideProgress();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean amountOk() {
|
||||
String amountEntry = etAmount.getText().toString();
|
||||
if (amountEntry.isEmpty()) return true;
|
||||
long amount = Wallet.getAmountFromString(amountEntry);
|
||||
return (amount > 0);
|
||||
}
|
||||
|
||||
private boolean paymentIdOk() {
|
||||
String paymentId = etPaymentId.getText().toString();
|
||||
return paymentId.isEmpty() || Wallet.isPaymentIdValid(paymentId);
|
||||
}
|
||||
|
||||
private void generateQr() {
|
||||
String address = tvAddress.getText().toString();
|
||||
String paymentId = etPaymentId.getText().toString();
|
||||
String enteredAmount = etAmount.getText().toString();
|
||||
// that's a lot of converting ...
|
||||
String amount = (enteredAmount.isEmpty() ? enteredAmount : Helper.getDisplayAmount(Wallet.getAmountFromString(enteredAmount)));
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(ScannerFragment.QR_SCHEME).append(address);
|
||||
boolean first = true;
|
||||
if (!paymentId.isEmpty()) {
|
||||
if (first) {
|
||||
sb.append("?");
|
||||
first = false;
|
||||
}
|
||||
sb.append(ScannerFragment.QR_PAYMENTID).append('=').append(paymentId);
|
||||
}
|
||||
if (!amount.isEmpty()) {
|
||||
if (first) {
|
||||
sb.append("?");
|
||||
} else {
|
||||
sb.append("&");
|
||||
}
|
||||
sb.append(ScannerFragment.QR_AMOUNT).append('=').append(amount);
|
||||
}
|
||||
String text = sb.toString();
|
||||
Bitmap qr = generate(text, 500, 500);
|
||||
if (qr != null) {
|
||||
etAmount.setText(amount);
|
||||
qrCode.setImageBitmap(qr);
|
||||
etDummy.requestFocus();
|
||||
bGenerate.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
public Bitmap generate(String text, int width, int height) {
|
||||
Map<EncodeHintType, Object> hints = new HashMap<>();
|
||||
hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
|
||||
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
|
||||
try {
|
||||
BitMatrix bitMatrix = new QRCodeWriter().encode(text, BarcodeFormat.QR_CODE, width, height, hints);
|
||||
int[] pixels = new int[width * height];
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
if (bitMatrix.get(j, i)) {
|
||||
pixels[i * width + j] = 0x00000000;
|
||||
} else {
|
||||
pixels[i * height + j] = 0xffffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
Bitmap bitmap = Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.RGB_565);
|
||||
bitmap = addLogo(bitmap);
|
||||
return bitmap;
|
||||
} catch (WriterException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO check if we can sensibly cache some of this
|
||||
private Bitmap addLogo(Bitmap qrBitmap) {
|
||||
Bitmap logo = getMoneroLogo();
|
||||
int qrWidth = qrBitmap.getWidth();
|
||||
int qrHeight = qrBitmap.getHeight();
|
||||
int logoWidth = logo.getWidth();
|
||||
int logoHeight = logo.getHeight();
|
||||
|
||||
Bitmap logoBitmap = Bitmap.createBitmap(qrWidth, qrHeight, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(logoBitmap);
|
||||
canvas.drawBitmap(qrBitmap, 0, 0, null);
|
||||
canvas.save(Canvas.ALL_SAVE_FLAG);
|
||||
// figure out how to scale the logo
|
||||
float scaleSize = 1.0f;
|
||||
while ((logoWidth / scaleSize) > (qrWidth / 5) || (logoHeight / scaleSize) > (qrHeight / 5)) {
|
||||
scaleSize *= 2;
|
||||
}
|
||||
float sx = 1.0f / scaleSize;
|
||||
canvas.scale(sx, sx, qrWidth / 2, qrHeight / 2);
|
||||
canvas.drawBitmap(logo, (qrWidth - logoWidth) / 2, (qrHeight - logoHeight) / 2, null);
|
||||
canvas.restore();
|
||||
return logoBitmap;
|
||||
}
|
||||
|
||||
private Bitmap logo = null;
|
||||
|
||||
private Bitmap getMoneroLogo() {
|
||||
if (logo == null) {
|
||||
logo = Helper.getBitmap(getContext(), R.drawable.ic_monero_qr);
|
||||
}
|
||||
return logo;
|
||||
}
|
||||
|
||||
public void showProgress() {
|
||||
pbProgress.setIndeterminate(true);
|
||||
pbProgress.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void hideProgress() {
|
||||
pbProgress.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
}
|
|
@ -100,8 +100,8 @@ public class SendFragment extends Fragment {
|
|||
|
||||
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
|
||||
|
||||
etAddress.setRawInputType(InputType.TYPE_CLASS_TEXT);
|
||||
etPaymentId.setRawInputType(InputType.TYPE_CLASS_TEXT);
|
||||
etAddress.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
etPaymentId.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
etNotes.setRawInputType(InputType.TYPE_CLASS_TEXT);
|
||||
|
||||
Helper.showKeyboard(getActivity());
|
||||
|
@ -209,7 +209,8 @@ public class SendFragment extends Fragment {
|
|||
bPaymentId.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
etPaymentId.setText((activityCallback.generatePaymentId()));
|
||||
etPaymentId.setText((Wallet.generatePaymentId()));
|
||||
etPaymentId.setSelection(etPaymentId.getText().length());
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -285,7 +286,7 @@ public class SendFragment extends Fragment {
|
|||
|
||||
private boolean paymentIdOk() {
|
||||
String paymentId = etPaymentId.getText().toString();
|
||||
return paymentId.isEmpty() || activityCallback.isPaymentIdValid(paymentId);
|
||||
return paymentId.isEmpty() || Wallet.isPaymentIdValid(paymentId);
|
||||
}
|
||||
|
||||
private void prepareSend() {
|
||||
|
@ -358,10 +359,6 @@ public class SendFragment extends Fragment {
|
|||
|
||||
void onSend(String notes);
|
||||
|
||||
String generatePaymentId();
|
||||
|
||||
boolean isPaymentIdValid(String paymentId);
|
||||
|
||||
String getWalletAddress();
|
||||
|
||||
void onDisposeRequest();
|
||||
|
|
|
@ -16,13 +16,14 @@
|
|||
|
||||
package com.m2049r.xmrwallet;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.net.UrlQuerySanitizer;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.PowerManager;
|
||||
|
@ -33,8 +34,6 @@ import android.support.v4.app.FragmentTransaction;
|
|||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
|
@ -90,20 +89,21 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
|
|||
}
|
||||
|
||||
private void startWalletService() {
|
||||
acquireWakeLock();
|
||||
Bundle extras = getIntent().getExtras();
|
||||
if (extras != null) {
|
||||
acquireWakeLock();
|
||||
String walletId = extras.getString(REQUEST_ID);
|
||||
String walletPassword = extras.getString(REQUEST_PW);
|
||||
connectWalletService(walletId, walletPassword);
|
||||
} else {
|
||||
throw new IllegalStateException("No extras passed! Panic!");
|
||||
finish();
|
||||
//throw new IllegalStateException("No extras passed! Panic!");
|
||||
}
|
||||
}
|
||||
|
||||
private void stopWalletService() {
|
||||
releaseWakeLock();
|
||||
disconnectWalletService();
|
||||
releaseWakeLock();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -120,11 +120,8 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
if (!haveWallet) return true;
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.wallet_menu, menu);
|
||||
return true;
|
||||
public boolean hasWallet() {
|
||||
return haveWallet;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -132,14 +129,16 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
|
|||
switch (item.getItemId()) {
|
||||
case R.id.action_info:
|
||||
onWalletDetails();
|
||||
break;
|
||||
return true;
|
||||
case R.id.action_receive:
|
||||
onWalletReceive();
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
Log.d(TAG, "onCreate()");
|
||||
|
@ -561,16 +560,6 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generatePaymentId() {
|
||||
return getWallet().generatePaymentId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPaymentIdValid(String paymentId) {
|
||||
return Wallet.isPaymentIdValid(paymentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWalletAddress() {
|
||||
return getWallet().getAddress();
|
||||
|
@ -595,12 +584,27 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
|
|||
}
|
||||
|
||||
private void onWalletDetails() {
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
||||
if (!(fragment instanceof GenerateReviewFragment)) {
|
||||
Bundle extras = new Bundle();
|
||||
extras.putString("type", GenerateReviewFragment.VIEW_WALLET);
|
||||
replaceFragment(new GenerateReviewFragment(), null, extras);
|
||||
}
|
||||
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which) {
|
||||
case DialogInterface.BUTTON_POSITIVE:
|
||||
Bundle extras = new Bundle();
|
||||
extras.putString("type", GenerateReviewFragment.VIEW_TYPE_WALLET);
|
||||
replaceFragment(new GenerateReviewFragment(), null, extras);
|
||||
break;
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
// do nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setMessage(getString(R.string.details_alert_message))
|
||||
.setPositiveButton(getString(R.string.details_alert_yes), dialogClickListener)
|
||||
.setNegativeButton(getString(R.string.details_alert_no), dialogClickListener)
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -608,13 +612,20 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
|
|||
getWallet().disposePendingTransaction();
|
||||
}
|
||||
|
||||
private boolean startScanFragment = false;
|
||||
|
||||
@Override
|
||||
protected void onResumeFragments() {
|
||||
super.onResumeFragments();
|
||||
if (startScanFragment) {
|
||||
startScanFragment();
|
||||
startScanFragment = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void startScanFragment() {
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
||||
if (fragment instanceof SendFragment) {
|
||||
Bundle extras = new Bundle();
|
||||
replaceFragment(new ScannerFragment(), null, extras);
|
||||
}
|
||||
Bundle extras = new Bundle();
|
||||
replaceFragment(new ScannerFragment(), null, extras);
|
||||
}
|
||||
|
||||
/// QR scanner callbacks
|
||||
|
@ -702,7 +713,7 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
|
|||
// If request is cancelled, the result arrays are empty.
|
||||
if (grantResults.length > 0
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
startScanFragment();
|
||||
startScanFragment = true;
|
||||
} else {
|
||||
String msg = getString(R.string.message_camera_not_permitted);
|
||||
Log.e(TAG, msg);
|
||||
|
@ -713,4 +724,21 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalletReceive() {
|
||||
startReceive(getWalletAddress());
|
||||
}
|
||||
|
||||
void startReceive(String address) {
|
||||
Log.d(TAG, "startReceive()");
|
||||
Bundle b = new Bundle();
|
||||
b.putString("address", address);
|
||||
startReceiveFragment(b);
|
||||
}
|
||||
|
||||
void startReceiveFragment(Bundle extras) {
|
||||
replaceFragment(new ReceiveFragment(), null, extras);
|
||||
Log.d(TAG, "ReceiveFragment placed");
|
||||
}
|
||||
|
||||
}
|
|
@ -18,12 +18,16 @@ package com.m2049r.xmrwallet;
|
|||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.constraint.ConstraintLayout;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.widget.DividerItemDecoration;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
|
@ -53,6 +57,19 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
|
|||
ProgressBar pbProgress;
|
||||
Button bSend;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
if (activityCallback.hasWallet())
|
||||
inflater.inflate(R.menu.wallet_menu, menu);
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
@ -236,6 +253,10 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
|
|||
boolean isWatchOnly();
|
||||
|
||||
String getTxKey(String txId);
|
||||
|
||||
void onWalletReceive();
|
||||
|
||||
boolean hasWallet();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -238,6 +238,7 @@ public class Wallet {
|
|||
public native void setDefaultMixin(int mixin);
|
||||
|
||||
public native boolean setUserNote(String txid, String note);
|
||||
|
||||
public native String getUserNote(String txid);
|
||||
|
||||
public native String getTxKey(String txid);
|
||||
|
|
|
@ -71,8 +71,8 @@ public class WalletManager {
|
|||
managedWallets.remove(walletId);
|
||||
}
|
||||
|
||||
public Wallet createWallet(String path, String password, String language) {
|
||||
long walletHandle = createWalletJ(path, password, language, isTestNet());
|
||||
public Wallet createWallet(File aFile, String password, String language) {
|
||||
long walletHandle = createWalletJ(aFile.getAbsolutePath(), password, language, isTestNet());
|
||||
Wallet wallet = new Wallet(walletHandle);
|
||||
manageWallet(wallet.getName(), wallet);
|
||||
return wallet;
|
||||
|
@ -89,14 +89,14 @@ public class WalletManager {
|
|||
|
||||
private native long openWalletJ(String path, String password, boolean isTestNet);
|
||||
|
||||
public Wallet recoveryWallet(String path, String mnemonic) {
|
||||
Wallet wallet = recoveryWallet(path, mnemonic, 0);
|
||||
public Wallet recoveryWallet(File aFile, String mnemonic) {
|
||||
Wallet wallet = recoveryWallet(aFile, mnemonic, 0);
|
||||
manageWallet(wallet.getName(), wallet);
|
||||
return wallet;
|
||||
}
|
||||
|
||||
public Wallet recoveryWallet(String path, String mnemonic, long restoreHeight) {
|
||||
long walletHandle = recoveryWalletJ(path, mnemonic, isTestNet(), restoreHeight);
|
||||
public Wallet recoveryWallet(File aFile, String mnemonic, long restoreHeight) {
|
||||
long walletHandle = recoveryWalletJ(aFile.getAbsolutePath(), mnemonic, isTestNet(), restoreHeight);
|
||||
Wallet wallet = new Wallet(walletHandle);
|
||||
manageWallet(wallet.getName(), wallet);
|
||||
return wallet;
|
||||
|
@ -104,9 +104,9 @@ public class WalletManager {
|
|||
|
||||
private native long recoveryWalletJ(String path, String mnemonic, boolean isTestNet, long restoreHeight);
|
||||
|
||||
public Wallet createWalletFromKeys(String path, String language, long restoreHeight,
|
||||
public Wallet createWalletFromKeys(File aFile, String language, long restoreHeight,
|
||||
String addressString, String viewKeyString, String spendKeyString) {
|
||||
long walletHandle = createWalletFromKeysJ(path, language, isTestNet(), restoreHeight,
|
||||
long walletHandle = createWalletFromKeysJ(aFile.getAbsolutePath(), language, isTestNet(), restoreHeight,
|
||||
addressString, viewKeyString, spendKeyString);
|
||||
Wallet wallet = new Wallet(walletHandle);
|
||||
manageWallet(wallet.getName(), wallet);
|
||||
|
@ -134,6 +134,10 @@ public class WalletManager {
|
|||
return closed;
|
||||
}
|
||||
|
||||
public boolean walletExists(File aFile) {
|
||||
return walletExists(aFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
public native boolean walletExists(String path);
|
||||
|
||||
public native boolean verifyWalletPassword(String keys_file_name, String password, boolean watch_only);
|
||||
|
@ -146,6 +150,31 @@ public class WalletManager {
|
|||
public String 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");
|
||||
//Log.d(TAG, addressFile.getAbsolutePath());
|
||||
info.address = "??????";
|
||||
BufferedReader addressReader = null;
|
||||
try {
|
||||
addressReader = new BufferedReader(new FileReader(addressFile));
|
||||
info.address = addressReader.readLine();
|
||||
} catch (IOException ex) {
|
||||
Log.d(TAG, ex.getLocalizedMessage());
|
||||
} finally {
|
||||
if (addressReader != null) {
|
||||
try {
|
||||
addressReader.close();
|
||||
} catch (IOException ex) {
|
||||
// that's just too bad
|
||||
}
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
public List<WalletInfo> findWallets(File path) {
|
||||
List<WalletInfo> wallets = new ArrayList<>();
|
||||
Log.d(TAG, "Scanning: " + path.getAbsolutePath());
|
||||
|
@ -155,29 +184,9 @@ public class WalletManager {
|
|||
}
|
||||
});
|
||||
for (int i = 0; i < found.length; i++) {
|
||||
WalletInfo info = new WalletInfo();
|
||||
info.path = path;
|
||||
String filename = found[i].getName();
|
||||
info.name = filename.substring(0, filename.length() - 5); // 5 is length of ".keys"+1
|
||||
File addressFile = new File(path, info.name + ".address.txt");
|
||||
//Log.d(TAG, addressFile.getAbsolutePath());
|
||||
info.address = "??????";
|
||||
BufferedReader addressReader = null;
|
||||
try {
|
||||
addressReader = new BufferedReader(new FileReader(addressFile));
|
||||
info.address = addressReader.readLine();
|
||||
} catch (IOException ex) {
|
||||
Log.d(TAG, ex.getLocalizedMessage());
|
||||
} finally {
|
||||
if (addressReader != null) {
|
||||
try {
|
||||
addressReader.close();
|
||||
} catch (IOException ex) {
|
||||
// that's just too bad
|
||||
}
|
||||
}
|
||||
}
|
||||
wallets.add(info);
|
||||
File f = new File(found[i].getParent(), filename.substring(0, filename.length() - 5)); // 5 is length of ".keys"+1
|
||||
wallets.add(getWalletInfo(f));
|
||||
}
|
||||
return wallets;
|
||||
}
|
||||
|
|
|
@ -527,7 +527,7 @@ public class WalletService extends Service {
|
|||
}
|
||||
|
||||
private Wallet openWallet(String walletName, String walletPassword) {
|
||||
String path = Helper.getWalletPath(getApplicationContext(), walletName);
|
||||
String path = Helper.getWalletFile(getApplicationContext(), walletName).getAbsolutePath();
|
||||
showProgress(20);
|
||||
Wallet wallet = null;
|
||||
WalletManager walletMgr = WalletManager.getInstance();
|
||||
|
@ -557,7 +557,7 @@ public class WalletService extends Service {
|
|||
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
|
||||
Notification notification = new Notification.Builder(this)
|
||||
.setContentTitle(getString(R.string.service_description))
|
||||
.setSmallIcon(R.drawable.ic_notification_sync_32_32)
|
||||
.setSmallIcon(R.drawable.ic_monero_32dp)
|
||||
.setContentIntent(pendingIntent)
|
||||
.build();
|
||||
startForeground(NOTIFICATION_ID, notification);
|
||||
|
|
|
@ -21,7 +21,14 @@ import android.app.Activity;
|
|||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.VectorDrawable;
|
||||
import android.os.Environment;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.util.Log;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
@ -91,12 +98,16 @@ public class Helper {
|
|||
}
|
||||
}
|
||||
|
||||
static public String getWalletPath(Context context, String aWalletName) {
|
||||
// static public String getWalletPath(Context context, String aWalletName) {
|
||||
// return getWalletFile(context, aWalletName).getAbsolutePath();
|
||||
// }
|
||||
|
||||
static public File getWalletFile(Context context, String aWalletName) {
|
||||
File walletDir = getStorageRoot(context);
|
||||
//d(TAG, "walletdir=" + walletDir.getAbsolutePath());
|
||||
File f = new File(walletDir, aWalletName);
|
||||
Log.d(TAG, "wallet = " + f.getAbsolutePath() + " size=" + f.length());
|
||||
return f.getAbsolutePath();
|
||||
return f;
|
||||
}
|
||||
|
||||
/* Checks if external storage is available for read and write */
|
||||
|
@ -136,6 +147,7 @@ public class Helper {
|
|||
return ((address.length() == 95) && ("4".indexOf(address.charAt(0)) >= 0));
|
||||
}
|
||||
}
|
||||
|
||||
static public String getDisplayAmount(long amount) {
|
||||
String s = Wallet.getDisplayAmount(amount);
|
||||
int lastZero = 0;
|
||||
|
@ -152,4 +164,25 @@ public class Helper {
|
|||
int cutoff = Math.max(lastZero, decimal + 2);
|
||||
return s.substring(0, cutoff);
|
||||
}
|
||||
|
||||
public static Bitmap getBitmap(Context context, int drawableId) {
|
||||
Drawable drawable = ContextCompat.getDrawable(context, drawableId);
|
||||
if (drawable instanceof BitmapDrawable) {
|
||||
return BitmapFactory.decodeResource(context.getResources(), drawableId);
|
||||
} else if (drawable instanceof VectorDrawable) {
|
||||
return getBitmap((VectorDrawable) drawable);
|
||||
} else {
|
||||
throw new IllegalArgumentException("unsupported drawable type");
|
||||
}
|
||||
}
|
||||
|
||||
private static Bitmap getBitmap(VectorDrawable vectorDrawable) {
|
||||
Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(),
|
||||
vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
vectorDrawable.draw(canvas);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (c) 2017 m2049r
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.m2049r.xmrwallet.util;
|
||||
|
||||
import com.m2049r.xmrwallet.service.MoneroHandlerThread;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
|
||||
public class MoneroThreadPoolExecutor {
|
||||
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
|
||||
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
|
||||
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
|
||||
private static final int KEEP_ALIVE_SECONDS = 30;
|
||||
|
||||
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
|
||||
private final AtomicInteger mCount = new AtomicInteger(1);
|
||||
|
||||
public Thread newThread(Runnable r) {
|
||||
return new Thread(null, r, "MoneroTask #" + mCount.getAndIncrement(), MoneroHandlerThread.THREAD_STACK_SIZE);
|
||||
}
|
||||
};
|
||||
|
||||
private static final BlockingQueue<Runnable> sPoolWorkQueue =
|
||||
new LinkedBlockingQueue<>(128);
|
||||
|
||||
public static final Executor MONERO_THREAD_POOL_EXECUTOR;
|
||||
|
||||
static {
|
||||
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
|
||||
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
|
||||
sPoolWorkQueue, sThreadFactory);
|
||||
threadPoolExecutor.allowCoreThreadTimeOut(true);
|
||||
MONERO_THREAD_POOL_EXECUTOR = threadPoolExecutor;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportHeight="75.0"
|
||||
android:viewportWidth="75.0">
|
||||
|
||||
<path
|
||||
android:fillColor="#ff6600"
|
||||
android:pathData="M 37.3,0.35329395 c -20.377,0 -36.903,16.524 -36.903,36.902 0,4.074 0.66,7.992 1.88,11.657 l 11.036,0 0,-31.049 23.987,23.987 23.987,-23.987 0,31.049 11.037,0 c 1.22,-3.665 1.88,-7.583 1.88,-11.657 0,-20.378 -16.526,-36.902 -36.904,-36.902" />
|
||||
<path
|
||||
android:fillColor="#4c4c4c"
|
||||
android:pathData="M 21.3164,36.895994 l 0,19.537 -15.55,0 c 6.478,10.628 18.178,17.726 31.533,17.726 13.355,0 25.056,-7.098 31.533,-17.726 l -15.549,0 0,-19.537 -15.984,15.984 z" />
|
||||
|
||||
</vector>
|
|
@ -0,0 +1,21 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportHeight="75.0"
|
||||
android:viewportWidth="75.0">
|
||||
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData=" M 37.3, 37.3
|
||||
m -36.9, 0
|
||||
a 36.9,36.9 0 1,0 73.8,0
|
||||
a 36.9,36.9 0 1,0 -73.8,0" />
|
||||
|
||||
<path
|
||||
android:fillColor="#ff6600"
|
||||
android:pathData="M 37.3,0.35329395 c -20.377,0 -36.903,16.524 -36.903,36.902 0,4.074 0.66,7.992 1.88,11.657 l 11.036,0 0,-31.049 23.987,23.987 23.987,-23.987 0,31.049 11.037,0 c 1.22,-3.665 1.88,-7.583 1.88,-11.657 0,-20.378 -16.526,-36.902 -36.904,-36.902" />
|
||||
<path
|
||||
android:fillColor="#4c4c4c"
|
||||
android:pathData="M 21.3164,36.895994 l 0,19.537 -15.55,0 c 6.478,10.628 18.178,17.726 31.533,17.726 13.355,0 25.056,-7.098 31.533,-17.726 l -15.549,0 0,-19.537 -15.984,15.984 z" />
|
||||
|
||||
</vector>
|
|
@ -0,0 +1,33 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="230.0"
|
||||
android:viewportWidth="230.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M0 0L0 230L230 230L230 0L0 0z" />
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M10 10L10 80L80 80L80 10L10 10M110 10L110 20L120 20L120 10L110 10M130 10L130 30L140 30L140 10L130 10M150 10L150 80L220 80L220 10L150 10z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M20 20L20 70L70 70L70 20L20 20M160 20L160 70L210 70L210 20L160 20z" />
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M30 30L30 60L60 60L60 30L30 30M90 30L90 40L100 40L100 30L90 30M110 30L110 40L120 40L120 30L110 30M170 30L170 60L200 60L200 30L170 30M130 40L130 50L120 50L120 60L110 60L110 50L100 50L100 70L90 70L90 90L50 90L50 120L60 120L60 130L50 130L50 140L60 140L60 130L70 130L70 140L90 140L90 180L100 180L100 170L110 170L110 180L120 180L120 190L90 190L90 220L100 220L100 210L110 210L110 220L130 220L130 210L140 210L140 220L160 220L160 210L170 210L170 220L190 220L190 210L180 210L180 200L200 200L200 220L220 220L220 210L210 210L210 200L220 200L220 190L210 190L210 180L200 180L200 190L180 190L180 170L160 170L160 160L180 160L180 140L160 140L160 130L180 130L180 120L190 120L190 110L200 110L200 120L220 120L220 100L200 100L200 90L190 90L190 110L180 110L180 100L160 100L160 90L130 90L130 100L120 100L120 70L130 70L130 80L140 80L140 70L130 70L130 60L140 60L140 40L130 40z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M100 70L100 100L90 100L90 110L80 110L80 100L70 100L70 110L60 110L60 120L70 120L70 130L90 130L90 140L100 140L100 150L110 150L110 140L120 140L120 160L110 160L110 170L120 170L120 180L130 180L130 200L140 200L140 180L150 180L150 190L170 190L170 180L150 180L150 160L160 160L160 150L150 150L150 130L160 130L160 110L150 110L150 100L130 100L130 120L140 120L140 110L150 110L150 130L130 130L130 140L120 140L120 120L110 120L110 130L100 130L100 120L90 120L90 110L100 110L100 100L110 100L110 110L120 110L120 100L110 100L110 70L100 70z" />
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M10 90L10 120L30 120L30 130L10 130L10 140L30 140L30 130L40 130L40 120L30 120L30 110L20 110L20 100L40 100L40 90L10 90M70 110L70 120L80 120L80 110L70 110M200 130L200 140L190 140L190 150L200 150L200 170L210 170L210 160L220 160L220 150L210 150L210 130L200 130M10 150L10 220L80 220L80 150L10 150M130 150L130 160L140 160L140 150L130 150z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M20 160L20 210L70 210L70 160L20 160z" />
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M30 170L30 200L60 200L60 170L30 170M130 170L130 180L140 180L140 170L130 170z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M200 190L200 200L210 200L210 190L200 190M110 200L110 210L120 210L120 200L110 200M150 200L150 210L160 210L160 200L150 200z" />
|
||||
</vector>
|
Binary file not shown.
Before Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 8.8 KiB |
|
@ -4,19 +4,13 @@
|
|||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
<ProgressBar
|
||||
android:id="@+id/pbProgress"
|
||||
style="@android:style/Widget.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8sp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pbProgress"
|
||||
style="@android:style/Widget.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="invisible" />
|
||||
</LinearLayout>
|
||||
android:visibility="invisible" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -143,7 +137,6 @@
|
|||
android:id="@+id/tvWalletSpendKey"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:selectAllOnFocus="true"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@color/colorPrimaryDark"
|
||||
android:textIsSelectable="true"
|
||||
|
|
|
@ -4,14 +4,14 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp" >
|
||||
android:padding="10dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPasswordLabel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Wallet Password"
|
||||
android:labelFor="@+id/etPassword"
|
||||
android:text="@string/prompt_password"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge" />
|
||||
|
||||
<EditText
|
||||
|
@ -19,7 +19,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="normal"
|
||||
android:inputType="textPassword" >
|
||||
android:inputType="textPassword">
|
||||
|
||||
|
||||
</EditText>
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/layout_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvRenameLabel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/prompt_rename"
|
||||
android:labelFor="@+id/etRename"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etRename"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="normal"
|
||||
android:inputType="text" />
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,122 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pbProgress"
|
||||
style="@android:style/Widget.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvAddress"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4sp"
|
||||
android:layout_marginTop="4sp"
|
||||
android:textIsSelectable="true"
|
||||
android:selectAllOnFocus="true"
|
||||
android:hint="@string/send_address_hint"
|
||||
android:textAlignment="center"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4sp"
|
||||
android:layout_marginTop="4sp"
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="10">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0sp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="8sp"
|
||||
android:layout_weight="3"
|
||||
android:text="@string/receive_paymentid_label"
|
||||
android:textAlignment="textEnd"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etPaymentId"
|
||||
android:layout_width="0sp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="5"
|
||||
android:enabled="false"
|
||||
android:hint="@string/receive_paymentid_hint"
|
||||
android:imeOptions="actionNext"
|
||||
android:inputType="textMultiLine"
|
||||
android:textAlignment="textStart"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/bPaymentId"
|
||||
android:layout_width="0sp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="2"
|
||||
android:background="@color/colorPrimary"
|
||||
android:enabled="false"
|
||||
android:minHeight="36sp"
|
||||
android:text="@string/receive_paymentid_button"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4sp"
|
||||
android:layout_marginTop="4sp"
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="10">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0sp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="8sp"
|
||||
android:layout_weight="3"
|
||||
android:text="@string/send_amount_label"
|
||||
android:textAlignment="textEnd"
|
||||
android:textSize="24sp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etAmount"
|
||||
android:layout_width="0sp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="7"
|
||||
android:enabled="false"
|
||||
android:hint="@string/receive_amount_hint"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="numberDecimal"
|
||||
android:textAlignment="textStart"
|
||||
android:textSize="24sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/bGenerate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4sp"
|
||||
android:layout_marginTop="4sp"
|
||||
android:background="@color/colorPrimary"
|
||||
android:enabled="false"
|
||||
android:minHeight="36sp"
|
||||
android:text="@string/receive_generate_hint" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/qrCode"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_margin="16sp"
|
||||
android:adjustViewBounds="true" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etDummy"
|
||||
android:layout_width="0sp"
|
||||
android:layout_height="0sp" />
|
||||
|
||||
</LinearLayout>
|
|
@ -3,6 +3,24 @@
|
|||
|
||||
<item
|
||||
android:id="@+id/action_info"
|
||||
android:icon="@drawable/ic_info_black_24dp"
|
||||
android:title="@string/menu_info" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_receive"
|
||||
android:icon="@drawable/ic_monero_qr_24dp"
|
||||
android:title="@string/menu_receive" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_rename"
|
||||
android:title="@string/menu_rename" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_backup"
|
||||
android:title="@string/menu_backup" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_archive"
|
||||
android:title="@string/menu_archive" />
|
||||
|
||||
</menu>
|
|
@ -9,4 +9,11 @@
|
|||
android:title="@string/menu_info"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_receive"
|
||||
android:icon="@drawable/ic_monero_qr_24dp"
|
||||
android:orderInCategory="100"
|
||||
android:title="@string/menu_receive"
|
||||
app:showAsAction="always" />
|
||||
|
||||
</menu>
|
|
@ -1,9 +1,26 @@
|
|||
<resources>
|
||||
<string name="app_name">Monerujo</string>
|
||||
<string name="login_activity_name">Select Wallet</string>
|
||||
<string name="login_activity_name">Monerujo</string>
|
||||
<string name="wallet_activity_name">Wallet</string>
|
||||
|
||||
<string name="menu_info">Details</string>
|
||||
<string name="menu_receive">QR Receive</string>
|
||||
<string name="menu_rename">Rename</string>
|
||||
<string name="menu_archive">Archive</string>
|
||||
<string name="menu_backup">Backup</string>
|
||||
|
||||
<string name="backup_progress">Backup in progress</string>
|
||||
<string name="archive_progress">Archive in progress</string>
|
||||
<string name="rename_progress">Rename in progress</string>
|
||||
|
||||
<string name="service_progress">Wrapping things up …\nThis can take a while!</string>
|
||||
|
||||
<string name="backup_success">Backup successful</string>
|
||||
<string name="backup_failed">Backup failed!</string>
|
||||
<string name="archive_success">Archive successful</string>
|
||||
<string name="archive_failed">Archive failed!</string>
|
||||
<string name="delete_failed">Delete failed!</string>
|
||||
<string name="rename_failed">Rename failed!</string>
|
||||
|
||||
<string name="prompt_daemon">[<user>:<pass>@]<daemon>[:<port>]</string>
|
||||
<string name="prompt_mainnet">Net Selection</string>
|
||||
|
@ -24,6 +41,8 @@
|
|||
|
||||
<string name="service_busy">I am still busy with your last wallet …</string>
|
||||
|
||||
<string name="prompt_rename">Rename</string>
|
||||
|
||||
<string name="prompt_password">Password for</string>
|
||||
<string name="bad_password">Incorrect password!</string>
|
||||
<string name="bad_wallet">Wallet does not exist!</string>
|
||||
|
@ -76,6 +95,7 @@
|
|||
<string name="generate_wallet_watchonly"><Watch Only Wallet></string>
|
||||
|
||||
<string name="generate_wallet_exists">Wallet exists! Choose another name</string>
|
||||
<string name="generate_wallet_dot">Wallet name may not begin with \'.\'</string>
|
||||
<string name="generate_wallet_created">Wallet created</string>
|
||||
<string name="generate_wallet_create_failed">Wallet create failed</string>
|
||||
<string name="generate_wallet_create_failed_1">Wallet create failed (1/2)</string>
|
||||
|
@ -151,6 +171,22 @@
|
|||
<string name="tx_pending">PENDING</string>
|
||||
<string name="tx_failed">FAILED</string>
|
||||
|
||||
<string name="receive_generate_hint">Show me the QR Code</string>
|
||||
<string name="receive_paymentid_button">Generate</string>
|
||||
<string name="receive_paymentid_label">PaymentID</string>
|
||||
<string name="receive_paymentid_hint">(optional)</string>
|
||||
<string name="receive_amount_label">Amount</string>
|
||||
<string name="receive_amount_hint">XMR (optional)</string>
|
||||
<string name="receive_cannot_open">Could not open wallet!</string>
|
||||
|
||||
<string name="details_alert_message">Sensitive data will now be shown.\nLook over your shoulder!</string>
|
||||
<string name="details_alert_yes">I\'m safe</string>
|
||||
<string name="details_alert_no">Take me back!</string>
|
||||
|
||||
<string name="archive_alert_message">The wallet will be backuped up and then deleted!</string>
|
||||
<string name="archive_alert_yes">Yes, do that!</string>
|
||||
<string name="archive_alert_no">No thanks!</string>
|
||||
|
||||
<string name="big_amount">999999.999999999999</string>
|
||||
|
||||
<string-array name="mixin">
|
||||
|
|
16
doc/FAQ.md
16
doc/FAQ.md
|
@ -17,11 +17,27 @@
|
|||
- Only 5 decimal places shown in transactions (full amount in details - click on transaction)
|
||||
- All significant figures shown in balance
|
||||
- QR Code scanning - make sure to *ALWAYS* verify the scanned code is what it is advertised to be!
|
||||
- QR Code for receiving
|
||||
- Backup wallets to ```.backups``` folder in main wallet folder (old backups are overwritten)
|
||||
- Rename wallets
|
||||
- Archive (=Backup and delete)
|
||||
|
||||
## I cannot select and copy the mnemonic seed
|
||||
Copying anything to the clipboard on Android exposes it to any other App running. So this
|
||||
is a security measure to keep your seed safe(r).
|
||||
|
||||
## My storage is getting full
|
||||
Newly generated wallets are stored in ```.new``` in the main wallet folder.
|
||||
They are never erased (for now). You can delete this whole folder from time to time.
|
||||
|
||||
Also, the backup folder ```.backups``` is never automatically cleaned up.
|
||||
You may want to do housekeeping manually with a file browser.
|
||||
|
||||
All wallet files (```testnet``` and ```mainnet```) are stored in the main ```Monerujo``` folder.
|
||||
So be careful erasing stuff. One of the future releases will split the wallets and move ```testnet```
|
||||
wallets out of there.
|
||||
|
||||
|
||||
## I sent a transaction but it's not in my received transactions list!
|
||||
Don't worry. Received transactions which are not mined yet disappear after the wallet is saved -
|
||||
I blame this on the monero code. Wait for the block to be mined.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.linked.project.id="xmrwallet" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" version="4">
|
||||
<module external.linked.project.id="xmrwallet" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="java-gradle" name="Java-Gradle">
|
||||
<configuration>
|
||||
|
|
Loading…
Reference in New Issue