Version 29/1.1.2-alpha
This commit is contained in:
m2049r 2017-10-31 22:35:23 +01:00 committed by GitHub
parent 7b1e6a89ba
commit 03c5569f91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
136 changed files with 5208 additions and 2047 deletions

View File

@ -8,8 +8,8 @@ android {
applicationId "com.m2049r.xmrwallet" applicationId "com.m2049r.xmrwallet"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 25 targetSdkVersion 25
versionCode 26 versionCode 29
versionName "1.0.3" versionName "1.1.2-alpha"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild { externalNativeBuild {
cmake { cmake {

View File

@ -19,10 +19,9 @@ package com.m2049r.xmrwallet;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.text.Editable;
import android.text.InputType; import android.text.InputType;
import android.text.TextWatcher;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -32,10 +31,7 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
@ -46,60 +42,93 @@ import java.io.File;
public class GenerateFragment extends Fragment { public class GenerateFragment extends Fragment {
static final String TAG = "GenerateFragment"; static final String TAG = "GenerateFragment";
EditText etWalletName; static final String TYPE = "type";
EditText etWalletPassword; static final String TYPE_NEW = "new";
EditText etWalletAddress; static final String TYPE_KEY = "key";
EditText etWalletMnemonic; static final String TYPE_SEED = "seed";
LinearLayout llRestoreKeys; static final String TYPE_VIEWONLY = "view";
EditText etWalletViewKey;
EditText etWalletSpendKey; TextInputLayout etWalletName;
EditText etWalletRestoreHeight; TextInputLayout etWalletPassword;
TextInputLayout etWalletAddress;
TextInputLayout etWalletMnemonic;
TextInputLayout etWalletViewKey;
TextInputLayout etWalletSpendKey;
TextInputLayout etWalletRestoreHeight;
Button bGenerate; Button bGenerate;
String type = null;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.gen_fragment, container, false); Bundle args = getArguments();
this.type = args.getString(TYPE);
etWalletName = (EditText) view.findViewById(R.id.etWalletName); View view = inflater.inflate(R.layout.fragment_generate, container, false);
etWalletPassword = (EditText) view.findViewById(R.id.etWalletPassword);
etWalletMnemonic = (EditText) view.findViewById(R.id.etWalletMnemonic); etWalletName = (TextInputLayout) view.findViewById(R.id.etWalletName);
etWalletAddress = (EditText) view.findViewById(R.id.etWalletAddress); etWalletPassword = (TextInputLayout) view.findViewById(R.id.etWalletPassword);
llRestoreKeys = (LinearLayout) view.findViewById(R.id.llRestoreKeys); etWalletMnemonic = (TextInputLayout) view.findViewById(R.id.etWalletMnemonic);
etWalletViewKey = (EditText) view.findViewById(R.id.etWalletViewKey); etWalletAddress = (TextInputLayout) view.findViewById(R.id.etWalletAddress);
etWalletSpendKey = (EditText) view.findViewById(R.id.etWalletSpendKey); etWalletViewKey = (TextInputLayout) view.findViewById(R.id.etWalletViewKey);
etWalletRestoreHeight = (EditText) view.findViewById(R.id.etWalletRestoreHeight); etWalletSpendKey = (TextInputLayout) view.findViewById(R.id.etWalletSpendKey);
etWalletRestoreHeight = (TextInputLayout) view.findViewById(R.id.etWalletRestoreHeight);
bGenerate = (Button) view.findViewById(R.id.bGenerate); bGenerate = (Button) view.findViewById(R.id.bGenerate);
etWalletMnemonic.setRawInputType(InputType.TYPE_CLASS_TEXT); etWalletMnemonic.getEditText().setRawInputType(InputType.TYPE_CLASS_TEXT);
etWalletAddress.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); etWalletAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etWalletViewKey.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); etWalletViewKey.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etWalletSpendKey.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); etWalletSpendKey.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
Helper.showKeyboard(getActivity()); etWalletName.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
etWalletName.addTextChangedListener(new TextWatcher() {
@Override @Override
public void afterTextChanged(Editable editable) { public void onFocusChange(View v, boolean hasFocus) {
if (etWalletName.length() > 0) { if (!hasFocus) {
bGenerate.setEnabled(true); checkName();
} else {
bGenerate.setEnabled(false);
} }
} }
});
etWalletMnemonic.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override @Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { public void onFocusChange(View v, boolean hasFocus) {
} if (!hasFocus) {
checkMnemonic();
@Override }
public void onTextChanged(CharSequence s, int start, int before, int count) {
} }
}); });
etWalletName.setOnEditorActionListener(new TextView.OnEditorActionListener() { etWalletAddress.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
checkAddress();
}
}
});
etWalletViewKey.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
checkViewKey();
}
}
});
etWalletSpendKey.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
checkSpendKey();
}
}
});
Helper.showKeyboard(getActivity());
//##############
etWalletName.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) {
if (etWalletName.length() > 0) { if (checkName()) {
etWalletPassword.requestFocus(); etWalletPassword.requestFocus();
} // otherwise ignore } // otherwise ignore
return true; return true;
@ -108,159 +137,112 @@ public class GenerateFragment extends Fragment {
} }
}); });
etWalletPassword.setOnEditorActionListener(new TextView.OnEditorActionListener() { if (type.equals(TYPE_NEW)) {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE);
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) { etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
if (etWalletAddress.length() > 0) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
etWalletAddress.requestFocus(); if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
} else {
etWalletMnemonic.requestFocus();
}
return true;
}
return false;
}
});
etWalletMnemonic.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 (etWalletMnemonic.length() == 0) {
etWalletAddress.requestFocus();
} else if (mnemonicOk()) {
etWalletRestoreHeight.requestFocus();
} else {
Toast.makeText(getActivity(), getString(R.string.generate_check_mnemonic), Toast.LENGTH_LONG).show();
}
return true;
}
return false;
}
});
etWalletMnemonic.addTextChangedListener(new
TextWatcher() {
@Override
public void afterTextChanged(Editable editable) {
if (etWalletMnemonic.length() > 0) {
etWalletRestoreHeight.setVisibility(View.VISIBLE);
etWalletAddress.setVisibility(View.GONE);
} else {
etWalletAddress.setVisibility(View.VISIBLE);
if (etWalletAddress.length() == 0) {
etWalletRestoreHeight.setVisibility(View.GONE);
} else {
etWalletRestoreHeight.setVisibility(View.VISIBLE);
}
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
etWalletAddress.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 (etWalletAddress.length() == 0) {
if (bGenerate.getVisibility() == View.VISIBLE) {
Helper.hideKeyboard(getActivity());
generateWallet();
}
} else if (addressOk()) {
etWalletViewKey.requestFocus();
} else {
Toast.makeText(getActivity(), getString(R.string.generate_check_address), Toast.LENGTH_LONG).show();
}
return true;
}
return false;
}
});
etWalletAddress.addTextChangedListener(new
TextWatcher() {
@Override
public void afterTextChanged(Editable editable) {
if (etWalletAddress.length() > 0) {
llRestoreKeys.setVisibility(View.VISIBLE);
etWalletMnemonic.setVisibility(View.INVISIBLE);
etWalletRestoreHeight.setVisibility(View.VISIBLE);
} else {
llRestoreKeys.setVisibility(View.GONE);
etWalletMnemonic.setVisibility(View.VISIBLE);
if (etWalletMnemonic.length() == 0) {
etWalletRestoreHeight.setVisibility(View.GONE);
} else {
etWalletRestoreHeight.setVisibility(View.VISIBLE);
}
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
etWalletViewKey.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 (viewKeyOk()) {
etWalletSpendKey.requestFocus();
} else {
Toast.makeText(getActivity(), getString(R.string.generate_check_key), Toast.LENGTH_LONG).show();
}
return true;
}
return false;
}
});
etWalletSpendKey.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 (spendKeyOk()) {
etWalletRestoreHeight.requestFocus();
} else {
Toast.makeText(getActivity(), getString(R.string.generate_check_key), Toast.LENGTH_LONG).show();
}
return true;
}
return false;
}
});
etWalletRestoreHeight.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 (bGenerate.getVisibility() == View.VISIBLE) {
Helper.hideKeyboard(getActivity()); Helper.hideKeyboard(getActivity());
generateWallet(); generateWallet();
} else { return true;
Toast.makeText(getActivity(), getString(R.string.generate_check_something), Toast.LENGTH_LONG).show();
} }
return true; return false;
} }
return false; });
} } else if (type.equals(TYPE_SEED)) {
}); etWalletPassword.getEditText().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)) {
etWalletMnemonic.requestFocus();
return true;
}
return false;
}
});
etWalletMnemonic.setVisibility(View.VISIBLE);
etWalletMnemonic.getEditText().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 (checkMnemonic()) {
etWalletRestoreHeight.requestFocus();
}
return true;
}
return false;
}
});
} else if (type.equals(TYPE_KEY) || type.equals(TYPE_VIEWONLY)) {
etWalletPassword.getEditText().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)) {
etWalletAddress.requestFocus();
return true;
}
return false;
}
});
etWalletAddress.setVisibility(View.VISIBLE);
etWalletAddress.getEditText().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 (checkAddress()) {
etWalletViewKey.requestFocus();
}
return true;
}
return false;
}
});
etWalletViewKey.setVisibility(View.VISIBLE);
etWalletViewKey.getEditText().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 (checkViewKey()) {
if (type.equals(TYPE_KEY)) {
etWalletSpendKey.requestFocus();
} else {
etWalletRestoreHeight.requestFocus();
}
}
return true;
}
return false;
}
});
}
if (type.equals(TYPE_KEY)) {
etWalletSpendKey.setVisibility(View.VISIBLE);
etWalletSpendKey.getEditText().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 (checkSpendKey()) {
etWalletRestoreHeight.requestFocus();
}
return true;
}
return false;
}
});
}
if (!type.equals(TYPE_NEW)) {
etWalletRestoreHeight.setVisibility(View.VISIBLE);
etWalletRestoreHeight.getEditText().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.hideKeyboard(getActivity());
generateWallet();
return true;
}
return false;
}
});
}
bGenerate.setOnClickListener(new View.OnClickListener() bGenerate.setOnClickListener(new View.OnClickListener()
{ {
@ -272,81 +254,112 @@ public class GenerateFragment extends Fragment {
}); });
etWalletName.requestFocus(); etWalletName.requestFocus();
return view; return view;
} }
private boolean mnemonicOk() { private boolean checkName() {
String seed = etWalletMnemonic.getText().toString(); String name = etWalletName.getEditText().getText().toString();
return (seed.split("\\s").length == 25); // 25 words boolean ok = true;
if (name.length() == 0) {
etWalletName.setError(getString(R.string.generate_wallet_name));
ok = false;
} else if (name.charAt(0) == '.') {
etWalletName.setError(getString(R.string.generate_wallet_dot));
ok = false;
} else {
File walletFile = Helper.getWalletFile(getActivity(), name);
if (WalletManager.getInstance().walletExists(walletFile)) {
etWalletName.setError(getString(R.string.generate_wallet_exists));
ok = false;
}
}
if (ok) {
etWalletName.setError(null);
}
return ok;
} }
private boolean addressOk() { private boolean checkMnemonic() {
String address = etWalletAddress.getText().toString(); String seed = etWalletMnemonic.getEditText().getText().toString();
return Wallet.isAddressValid(address, WalletManager.getInstance().isTestNet()); boolean ok = (seed.split("\\s").length == 25); // 25 words
if (!ok) {
etWalletMnemonic.setError(getString(R.string.generate_check_mnemonic));
} else {
etWalletMnemonic.setError(null);
}
return ok;
} }
private boolean viewKeyOk() { private boolean checkAddress() {
String viewKey = etWalletViewKey.getText().toString(); String address = etWalletAddress.getEditText().getText().toString();
return (viewKey.length() == 64) && (viewKey.matches("^[0-9a-fA-F]+$")); boolean ok = Wallet.isAddressValid(address, WalletManager.getInstance().isTestNet());
if (!ok) {
etWalletAddress.setError(getString(R.string.generate_check_address));
} else {
etWalletAddress.setError(null);
}
return ok;
} }
private boolean spendKeyOk() { private boolean checkViewKey() {
String spendKey = etWalletSpendKey.getText().toString(); String viewKey = etWalletViewKey.getEditText().getText().toString();
return ((spendKey.length() == 0) || ((spendKey.length() == 64) && (spendKey.matches("^[0-9a-fA-F]+$")))); boolean ok = (viewKey.length() == 64) && (viewKey.matches("^[0-9a-fA-F]+$"));
if (!ok) {
etWalletViewKey.setError(getString(R.string.generate_check_key));
} else {
etWalletViewKey.setError(null);
}
return ok;
}
private boolean checkSpendKey() {
String spendKey = etWalletSpendKey.getEditText().getText().toString();
boolean ok = ((spendKey.length() == 0) || ((spendKey.length() == 64) && (spendKey.matches("^[0-9a-fA-F]+$"))));
if (!ok) {
etWalletSpendKey.setError(getString(R.string.generate_check_key));
} else {
etWalletSpendKey.setError(null);
}
return ok;
} }
private void generateWallet() { private void generateWallet() {
String name = etWalletName.getText().toString(); if (!checkName()) return;
if (name.length() == 0) return; String name = etWalletName.getEditText().getText().toString();
if (name.charAt(0) == '.') { String password = etWalletPassword.getEditText().getText().toString();
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;
}
String password = etWalletPassword.getText().toString();
String seed = etWalletMnemonic.getText().toString();
String address = etWalletAddress.getText().toString();
long height; long height;
try { try {
height = Long.parseLong(etWalletRestoreHeight.getText().toString()); height = Long.parseLong(etWalletRestoreHeight.getEditText().getText().toString());
} catch (NumberFormatException ex) { } catch (NumberFormatException ex) {
height = 0; // Keep calm and carry on! height = 0; // Keep calm and carry on!
} }
// figure out how we want to create this wallet if (type.equals(TYPE_NEW)) {
// A. from scratch bGenerate.setEnabled(false);
if ((seed.length() == 0) && (address.length() == 0)) {
bGenerate.setVisibility(View.GONE);
activityCallback.onGenerate(name, password); activityCallback.onGenerate(name, password);
} else } else if (type.equals(TYPE_SEED)) {
// B. from seed if (!checkMnemonic()) return;
if (mnemonicOk()) { String seed = etWalletMnemonic.getEditText().getText().toString();
bGenerate.setVisibility(View.GONE); bGenerate.setEnabled(false);
activityCallback.onGenerate(name, password, seed, height); activityCallback.onGenerate(name, password, seed, height);
} else } else if (type.equals(TYPE_KEY) || type.equals(TYPE_VIEWONLY)) {
// C. from keys if (checkAddress() && checkViewKey() && checkSpendKey()) {
if (addressOk() && viewKeyOk() && (spendKeyOk())) { bGenerate.setEnabled(false);
String viewKey = etWalletViewKey.getText().toString(); String address = etWalletAddress.getEditText().getText().toString();
String spendKey = etWalletSpendKey.getText().toString(); String viewKey = etWalletViewKey.getEditText().getText().toString();
bGenerate.setVisibility(View.GONE); String spendKey = "";
activityCallback.onGenerate(name, password, address, viewKey, spendKey, height); if (type.equals(TYPE_KEY)) {
} else spendKey = etWalletSpendKey.getEditText().getText().toString();
// D. none of the above :)
{
Toast.makeText(getActivity(), getString(R.string.generate_check_something), Toast.LENGTH_LONG).show();
} }
activityCallback.onGenerate(name, password, address, viewKey, spendKey, height);
}
}
} }
public void walletGenerateError() { public void walletGenerateError() {
bGenerate.setEnabled(etWalletName.length() > 0); bGenerate.setEnabled(true);
bGenerate.setVisibility(View.VISIBLE);
} }
@Override @Override
@ -365,10 +378,7 @@ public class GenerateFragment extends Fragment {
void onGenerate(String name, String password, String address, String viewKey, String spendKey, long height); void onGenerate(String name, String password, String address, String viewKey, String spendKey, long height);
File getStorageRoot();
void setTitle(String title); void setTitle(String title);
} }
@Override @Override

View File

@ -28,12 +28,17 @@ import android.view.MenuInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.ScrollView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.m2049r.xmrwallet.layout.Toolbar;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor; import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
public class GenerateReviewFragment extends Fragment { public class GenerateReviewFragment extends Fragment {
@ -42,6 +47,8 @@ public class GenerateReviewFragment extends Fragment {
static final public String VIEW_TYPE_ACCEPT = "accept"; static final public String VIEW_TYPE_ACCEPT = "accept";
static final public String VIEW_TYPE_WALLET = "wallet"; static final public String VIEW_TYPE_WALLET = "wallet";
ScrollView scrollview;
ProgressBar pbProgress; ProgressBar pbProgress;
TextView tvWalletName; TextView tvWalletName;
TextView tvWalletPassword; TextView tvWalletPassword;
@ -49,14 +56,18 @@ public class GenerateReviewFragment extends Fragment {
TextView tvWalletMnemonic; TextView tvWalletMnemonic;
TextView tvWalletViewKey; TextView tvWalletViewKey;
TextView tvWalletSpendKey; TextView tvWalletSpendKey;
ImageButton bCopyAddress;
LinearLayout llAdvancedInfo;
Button bAdvancedInfo;
Button bAccept; Button bAccept;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.gen_review_fragment, container, false); View view = inflater.inflate(R.layout.fragment_review, container, false);
scrollview = (ScrollView) view.findViewById(R.id.scrollview);
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress); pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
tvWalletName = (TextView) view.findViewById(R.id.tvWalletName); tvWalletName = (TextView) view.findViewById(R.id.tvWalletName);
tvWalletPassword = (TextView) view.findViewById(R.id.tvWalletPassword); tvWalletPassword = (TextView) view.findViewById(R.id.tvWalletPassword);
@ -64,26 +75,15 @@ public class GenerateReviewFragment extends Fragment {
tvWalletViewKey = (TextView) view.findViewById(R.id.tvWalletViewKey); tvWalletViewKey = (TextView) view.findViewById(R.id.tvWalletViewKey);
tvWalletSpendKey = (TextView) view.findViewById(R.id.tvWalletSpendKey); tvWalletSpendKey = (TextView) view.findViewById(R.id.tvWalletSpendKey);
tvWalletMnemonic = (TextView) view.findViewById(R.id.tvWalletMnemonic); tvWalletMnemonic = (TextView) view.findViewById(R.id.tvWalletMnemonic);
bCopyAddress = (ImageButton) view.findViewById(R.id.bCopyAddress);
bAdvancedInfo = (Button) view.findViewById(R.id.bAdvancedInfo);
llAdvancedInfo = (LinearLayout) view.findViewById(R.id.llAdvancedInfo);
bAccept = (Button) view.findViewById(R.id.bAccept); bAccept = (Button) view.findViewById(R.id.bAccept);
boolean testnet = WalletManager.getInstance().isTestNet(); boolean testnet = WalletManager.getInstance().isTestNet();
tvWalletMnemonic.setTextIsSelectable(testnet); tvWalletMnemonic.setTextIsSelectable(testnet);
tvWalletSpendKey.setTextIsSelectable(testnet); tvWalletSpendKey.setTextIsSelectable(testnet);
if (!testnet) {
tvWalletMnemonic.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getActivity(), getString(R.string.message_noselect_seed), Toast.LENGTH_SHORT).show();
}
});
tvWalletSpendKey.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getActivity(), getString(R.string.message_noselect_key), Toast.LENGTH_SHORT).show();
}
});
}
bAccept.setOnClickListener(new View.OnClickListener() { bAccept.setOnClickListener(new View.OnClickListener() {
@Override @Override
@ -91,6 +91,36 @@ public class GenerateReviewFragment extends Fragment {
acceptWallet(); acceptWallet();
} }
}); });
view.findViewById(R.id.bCopyViewKey).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
copyViewKey();
}
});
bCopyAddress.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
copyAddress();
}
});
view.findViewById(R.id.bCopySeed).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
nocopy();
}
});
view.findViewById(R.id.bCopySepndKey).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
nocopy();
}
});
view.findViewById(R.id.bAdvancedInfo).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showAdvancedInfo();
}
});
showProgress(); showProgress();
@ -103,6 +133,31 @@ public class GenerateReviewFragment extends Fragment {
return view; return view;
} }
void copyViewKey() {
Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_viewkey), tvWalletViewKey.getText().toString());
Toast.makeText(getActivity(), getString(R.string.message_copy_viewkey), Toast.LENGTH_SHORT).show();
}
void copyAddress() {
Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_address), tvWalletAddress.getText().toString());
Toast.makeText(getActivity(), getString(R.string.message_copy_address), Toast.LENGTH_SHORT).show();
}
void nocopy() {
Toast.makeText(getActivity(), getString(R.string.message_nocopy), Toast.LENGTH_SHORT).show();
}
void showAdvancedInfo() {
llAdvancedInfo.setVisibility(View.VISIBLE);
bAdvancedInfo.setVisibility(View.GONE);
scrollview.post(new Runnable() {
@Override
public void run() {
scrollview.fullScroll(ScrollView.FOCUS_DOWN);
}
});
}
String type; String type;
private void acceptWallet() { private void acceptWallet() {
@ -148,7 +203,7 @@ public class GenerateReviewFragment extends Fragment {
address = wallet.getAddress(); address = wallet.getAddress();
seed = wallet.getSeed(); seed = wallet.getSeed();
viewKey = wallet.getSecretViewKey(); viewKey = wallet.getSecretViewKey();
spendKey = isWatchOnly ? getActivity().getString(R.string.watchonly_label) : wallet.getSecretSpendKey(); spendKey = isWatchOnly ? getActivity().getString(R.string.label_watchonly) : wallet.getSecretSpendKey();
isWatchOnly = wallet.isWatchOnly(); isWatchOnly = wallet.isWatchOnly();
if (closeWallet) wallet.close(); if (closeWallet) wallet.close();
return true; return true;
@ -157,6 +212,7 @@ public class GenerateReviewFragment extends Fragment {
@Override @Override
protected void onPostExecute(Boolean result) { protected void onPostExecute(Boolean result) {
super.onPostExecute(result); super.onPostExecute(result);
if (!isAdded()) return; // never mind
tvWalletName.setText(name); tvWalletName.setText(name);
if (result) { if (result) {
if (type.equals(GenerateReviewFragment.VIEW_TYPE_ACCEPT)) { if (type.equals(GenerateReviewFragment.VIEW_TYPE_ACCEPT)) {
@ -168,9 +224,14 @@ public class GenerateReviewFragment extends Fragment {
tvWalletMnemonic.setText(seed); tvWalletMnemonic.setText(seed);
tvWalletViewKey.setText(viewKey); tvWalletViewKey.setText(viewKey);
tvWalletSpendKey.setText(spendKey); tvWalletSpendKey.setText(spendKey);
bAdvancedInfo.setVisibility(View.VISIBLE);
bCopyAddress.setEnabled(true);
bCopyAddress.setImageResource(R.drawable.ic_content_copy_black_24dp);
activityCallback.setTitle(name, getString(R.string.details_title));
activityCallback.setToolbarButton(
GenerateReviewFragment.VIEW_TYPE_ACCEPT.equals(type) ? Toolbar.BUTTON_NONE : Toolbar.BUTTON_BACK);
} else { } else {
// TODO show proper error message // TODO show proper error message and/or end the fragment?
// TODO end the fragment
tvWalletAddress.setText(status.toString()); tvWalletAddress.setText(status.toString());
tvWalletMnemonic.setText(status.toString()); tvWalletMnemonic.setText(status.toString());
tvWalletViewKey.setText(status.toString()); tvWalletViewKey.setText(status.toString());
@ -180,38 +241,55 @@ public class GenerateReviewFragment extends Fragment {
} }
} }
GenerateReviewFragment.Listener acceptCallback = null; Listener activityCallback = null;
GenerateReviewFragment.ListenerWithWallet walletCallback = null; AcceptListener acceptCallback = null;
ListenerWithWallet walletCallback = null;
public interface Listener { public interface Listener {
void setTitle(String title, String subtitle);
void setToolbarButton(int type);
}
public interface AcceptListener {
void onAccept(String name, String password); void onAccept(String name, String password);
} }
public interface ListenerWithWallet { public interface ListenerWithWallet {
Wallet getWallet(); Wallet getWallet();
} }
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
super.onAttach(context); super.onAttach(context);
if (context instanceof GenerateReviewFragment.Listener) { if (context instanceof Listener) {
this.acceptCallback = (GenerateReviewFragment.Listener) context; this.activityCallback = (Listener) context;
} else if (context instanceof GenerateReviewFragment.ListenerWithWallet) { }
this.walletCallback = (GenerateReviewFragment.ListenerWithWallet) context; if (context instanceof AcceptListener) {
} else { this.acceptCallback = (AcceptListener) context;
throw new ClassCastException(context.toString() }
+ " must implement Listener"); if (context instanceof ListenerWithWallet) {
this.walletCallback = (ListenerWithWallet) context;
} }
} }
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "onResume()");
String name = tvWalletName.getText().toString();
if (name.isEmpty()) name = null;
activityCallback.setTitle(name, getString(R.string.details_title));
activityCallback.setToolbarButton(
GenerateReviewFragment.VIEW_TYPE_ACCEPT.equals(type) ? Toolbar.BUTTON_NONE : Toolbar.BUTTON_BACK);
}
public void showProgress() { public void showProgress() {
pbProgress.setIndeterminate(true);
pbProgress.setVisibility(View.VISIBLE); pbProgress.setVisibility(View.VISIBLE);
} }
public void hideProgress() { public void hideProgress() {
pbProgress.setVisibility(View.INVISIBLE); pbProgress.setVisibility(View.GONE);
} }
boolean backOk() { boolean backOk() {

View File

@ -27,30 +27,32 @@ import android.content.pm.PackageManager;
import android.media.MediaScannerConnection; import android.media.MediaScannerConnection;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.StrictMode; import android.os.Handler;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction; import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar; import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.m2049r.xmrwallet.dialog.HelpFragment; import com.m2049r.xmrwallet.dialog.HelpFragment;
import com.m2049r.xmrwallet.dialog.LicensesFragment; import com.m2049r.xmrwallet.dialog.LicensesFragment;
import com.m2049r.xmrwallet.layout.Toolbar;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.service.WalletService; import com.m2049r.xmrwallet.service.WalletService;
import com.m2049r.xmrwallet.util.AsyncExchangeRate;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor; import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
@ -60,19 +62,41 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.net.SocketAddress;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.util.Date; import java.util.Date;
public class LoginActivity extends AppCompatActivity public class LoginActivity extends AppCompatActivity
implements LoginFragment.Listener, GenerateFragment.Listener, implements LoginFragment.Listener, GenerateFragment.Listener,
GenerateReviewFragment.Listener, ReceiveFragment.Listener { GenerateReviewFragment.Listener, GenerateReviewFragment.AcceptListener, ReceiveFragment.Listener {
static final String TAG = "LoginActivity"; static final String TAG = "LoginActivity";
private static final String GENERATE_STACK = "gen"; private static final String GENERATE_STACK = "gen";
static final int DAEMON_TIMEOUT = 500; // deamon must respond in 500ms static final int DAEMON_TIMEOUT = 500; // deamon must respond in 500ms
static final int DAEMON_DNS_TIMEOUT = 5000; // how long to wait for DNS resolver
Toolbar toolbar; Toolbar toolbar;
@Override
public void setToolbarButton(int type) {
toolbar.setButton(type);
}
@Override
public void setTitle(String title) {
toolbar.setTitle(title);
}
@Override
public void setSubtitle(String subtitle) {
toolbar.setSubtitle(subtitle);
}
@Override
public void setTitle(String title, String subtitle) {
toolbar.setTitle(title, subtitle);
}
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate()"); Log.d(TAG, "onCreate()");
@ -81,9 +105,29 @@ public class LoginActivity extends AppCompatActivity
// we don't store anything ourselves // we don't store anything ourselves
} }
setContentView(R.layout.login_activity); setContentView(R.layout.activity_login);
toolbar = (Toolbar) findViewById(R.id.toolbar); toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
getSupportActionBar().setDisplayShowTitleEnabled(false);
toolbar.setOnButtonListener(new Toolbar.OnButtonListener() {
@Override
public void onButton(int type) {
switch (type) {
case Toolbar.BUTTON_BACK:
onBackPressed();
break;
case Toolbar.BUTTON_CLOSE:
finish();
break;
case Toolbar.BUTTON_DONATE:
Toast.makeText(LoginActivity.this, getString(R.string.label_donate), Toast.LENGTH_SHORT).show();
case Toolbar.BUTTON_NONE:
default:
Log.e(TAG, "Button " + type + "pressed - how can this be?");
}
}
});
if (Helper.getWritePermission(this)) { if (Helper.getWritePermission(this)) {
startLoginFragment(); startLoginFragment();
@ -102,37 +146,26 @@ public class LoginActivity extends AppCompatActivity
} }
@Override @Override
public boolean onWalletSelected(String daemon, final String walletName, boolean testnet) { public boolean onWalletSelected(String walletName, String daemon, boolean testnet) {
if (daemon.length() == 0) { if (daemon.length() == 0) {
Toast.makeText(this, getString(R.string.prompt_daemon_missing), Toast.LENGTH_SHORT).show(); Toast.makeText(this, getString(R.string.prompt_daemon_missing), Toast.LENGTH_SHORT).show();
return false; return false;
} }
if (checkServiceRunning()) return false;
if (!checkAndSetWalletDaemon(daemon, testnet)) { try {
Toast.makeText(this, getString(R.string.warn_daemon_unavailable), Toast.LENGTH_SHORT).show(); WalletNode aWalletNode = new WalletNode(walletName, daemon, testnet);
new AsyncOpenWallet().execute(aWalletNode);
} catch (IllegalArgumentException ex) {
Log.e(TAG, ex.getLocalizedMessage());
Toast.makeText(this, ex.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
return false; return false;
} }
if (checkServiceRunning()) return true;
Log.d(TAG, "selected wallet is ." + walletName + ".");
// now it's getting real, check if wallet exists
File walletFile = Helper.getWalletFile(this, walletName);
if (WalletManager.getInstance().walletExists(walletFile)) {
promptPassword(walletName, new PasswordAction() {
@Override
public void action(String walletName, String password) {
startWallet(walletName, password);
}
});
} else { // this cannot really happen as we prefilter choices
Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show();
}
return true; return true;
} }
@Override @Override
public void onWalletDetails(final String walletName, boolean testnet) { public void onWalletDetails(final String walletName, boolean testnet) {
checkAndSetWalletDaemon("", testnet); // just set selected net setNet(testnet);
Log.d(TAG, "details for wallet ." + walletName + "."); Log.d(TAG, "details for wallet ." + walletName + ".");
if (checkServiceRunning()) return; if (checkServiceRunning()) return;
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
@ -170,7 +203,7 @@ public class LoginActivity extends AppCompatActivity
@Override @Override
public void onWalletReceive(String walletName, boolean testnet) { public void onWalletReceive(String walletName, boolean testnet) {
checkAndSetWalletDaemon("", testnet); // just set selected net setNet(testnet);
Log.d(TAG, "receive for wallet ." + walletName + "."); Log.d(TAG, "receive for wallet ." + walletName + ".");
if (checkServiceRunning()) return; if (checkServiceRunning()) return;
final File walletFile = Helper.getWalletFile(this, walletName); final File walletFile = Helper.getWalletFile(this, walletName);
@ -406,10 +439,10 @@ public class LoginActivity extends AppCompatActivity
} }
@Override @Override
public void onAddWallet(boolean testnet) { public void onAddWallet(boolean testnet, String type) {
checkAndSetWalletDaemon("", testnet); setNet(testnet);
if (checkServiceRunning()) return; if (checkServiceRunning()) return;
startGenerateFragment(); startGenerateFragment(type);
} }
AlertDialog passwordDialog = null; // for preventing multiple clicks in wallet list AlertDialog passwordDialog = null; // for preventing multiple clicks in wallet list
@ -423,23 +456,33 @@ public class LoginActivity extends AppCompatActivity
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context);
alertDialogBuilder.setView(promptsView); alertDialogBuilder.setView(promptsView);
final EditText etPassword = (EditText) promptsView.findViewById(R.id.etPassword); final TextInputLayout etPassword = (TextInputLayout) promptsView.findViewById(R.id.etPassword);
final TextView tvPasswordLabel = (TextView) promptsView.findViewById(R.id.tvPasswordLabel); etPassword.setHint(LoginActivity.this.getString(R.string.prompt_password, wallet));
tvPasswordLabel.setText(LoginActivity.this.getString(R.string.prompt_password, wallet)); etPassword.getEditText().addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
if (etPassword.getError() != null) {
etPassword.setError(null);
}
}
@Override
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start,
int before, int count) {
}
});
// set dialog message // set dialog message
alertDialogBuilder alertDialogBuilder
.setCancelable(false) .setCancelable(false)
.setPositiveButton("OK", .setPositiveButton("OK", null)
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
Helper.hideKeyboardAlways(LoginActivity.this);
String pass = etPassword.getText().toString();
processPasswordEntry(wallet, pass, action);
passwordDialog = null;
}
})
.setNegativeButton("Cancel", .setNegativeButton("Cancel",
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
@ -448,20 +491,43 @@ public class LoginActivity extends AppCompatActivity
passwordDialog = null; passwordDialog = null;
} }
}); });
passwordDialog = alertDialogBuilder.create(); passwordDialog = alertDialogBuilder.create();
passwordDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String pass = etPassword.getEditText().getText().toString();
if (processPasswordEntry(wallet, pass, action)) {
passwordDialog.dismiss();
passwordDialog = null;
Helper.hideKeyboardAlways(LoginActivity.this);
} else {
etPassword.setError(getString(R.string.bad_password));
}
}
});
}
});
Helper.showKeyboard(passwordDialog); Helper.showKeyboard(passwordDialog);
// accept keyboard "ok" // accept keyboard "ok"
etPassword.setOnEditorActionListener(new TextView.OnEditorActionListener() { etPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
Helper.hideKeyboardAlways(LoginActivity.this); Helper.hideKeyboardAlways(LoginActivity.this);
String pass = etPassword.getText().toString(); String pass = etPassword.getEditText().getText().toString();
passwordDialog.cancel(); if (processPasswordEntry(wallet, pass, action)) {
processPasswordEntry(wallet, pass, action); passwordDialog.cancel();
passwordDialog = null; passwordDialog = null;
return false; } else {
etPassword.setError(getString(R.string.bad_password));
}
return true;
} }
return false; return false;
} }
@ -481,11 +547,12 @@ public class LoginActivity extends AppCompatActivity
void action(String walletName, String password); void action(String walletName, String password);
} }
private void processPasswordEntry(String walletName, String pass, PasswordAction action) { private boolean processPasswordEntry(String walletName, String pass, PasswordAction action) {
if (checkWalletPassword(walletName, pass)) { if (checkWalletPassword(walletName, pass)) {
action.action(walletName, pass); action.action(walletName, pass);
return true;
} else { } else {
Toast.makeText(this, getString(R.string.bad_password), Toast.LENGTH_SHORT).show(); return false;
} }
} }
@ -505,22 +572,14 @@ public class LoginActivity extends AppCompatActivity
//////////////////////////////////////// ////////////////////////////////////////
//////////////////////////////////////// ////////////////////////////////////////
public void setTitle(String title) {
toolbar.setTitle(title);
}
public void setSubtitle(String subtitle) {
toolbar.setSubtitle(subtitle);
}
@Override @Override
public void showNet(boolean testnet) { public void showNet(boolean testnet) {
if (testnet) { if (testnet) {
toolbar.setBackgroundResource(R.color.colorPrimaryDark); toolbar.setBackgroundResource(R.color.colorPrimaryDark);
} else { } else {
toolbar.setBackgroundResource(R.color.moneroOrange); toolbar.setBackgroundResource(R.drawable.backgound_toolbar_mainnet);
} }
setSubtitle(getString(testnet ? R.string.connect_testnet : R.string.connect_mainnet)); toolbar.setSubtitle(getString(testnet ? R.string.connect_testnet : R.string.connect_mainnet));
} }
@Override @Override
@ -532,9 +591,20 @@ public class LoginActivity extends AppCompatActivity
ProgressDialog progressDialog = null; ProgressDialog progressDialog = null;
private void showProgressDialog(int msgId) { private void showProgressDialog(int msgId) {
showProgressDialog(msgId, 0);
}
private void showProgressDialog(int msgId, long delay) {
dismissProgressDialog(); // just in case dismissProgressDialog(); // just in case
progressDialog = new MyProgressDialog(LoginActivity.this, msgId); progressDialog = new MyProgressDialog(LoginActivity.this, msgId);
progressDialog.show(); if (delay > 0) {
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
public void run() {
if (progressDialog != null) progressDialog.show();
}
}, delay);
}
} }
private void dismissProgressDialog() { private void dismissProgressDialog() {
@ -554,7 +624,6 @@ public class LoginActivity extends AppCompatActivity
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
Log.d(TAG, "onResume()"); Log.d(TAG, "onResume()");
setTitle(getString(R.string.login_activity_name));
// wait for WalletService to finish // wait for WalletService to finish
if (WalletService.Running && (progressDialog == null)) { if (WalletService.Running && (progressDialog == null)) {
// and show a progress dialog, but only if there isn't one already // and show a progress dialog, but only if there isn't one already
@ -671,8 +740,10 @@ public class LoginActivity extends AppCompatActivity
Log.d(TAG, "LoginFragment added"); Log.d(TAG, "LoginFragment added");
} }
void startGenerateFragment() { void startGenerateFragment(String type) {
replaceFragment(new GenerateFragment(), GENERATE_STACK, null); Bundle extras = new Bundle();
extras.putString(GenerateFragment.TYPE, type);
replaceFragment(new GenerateFragment(), GENERATE_STACK, extras);
Log.d(TAG, "GenerateFragment placed"); Log.d(TAG, "GenerateFragment placed");
} }
@ -705,7 +776,6 @@ public class LoginActivity extends AppCompatActivity
////////////////////////////////////////// //////////////////////////////////////////
static final String MNEMONIC_LANGUAGE = "English"; // see mnemonics/electrum-words.cpp for more static final String MNEMONIC_LANGUAGE = "English"; // see mnemonics/electrum-words.cpp for more
private class AsyncCreateWallet extends AsyncTask<Void, Void, Boolean> { private class AsyncCreateWallet extends AsyncTask<Void, Void, Boolean> {
String walletName; String walletName;
String walletPassword; String walletPassword;
@ -762,8 +832,6 @@ public class LoginActivity extends AppCompatActivity
if (result) { if (result) {
startDetails(newWalletFile, walletPassword, GenerateReviewFragment.VIEW_TYPE_ACCEPT); startDetails(newWalletFile, walletPassword, GenerateReviewFragment.VIEW_TYPE_ACCEPT);
} else { } else {
Toast.makeText(LoginActivity.this,
getString(R.string.generate_wallet_create_failed), Toast.LENGTH_LONG).show();
walletGenerateError(); walletGenerateError();
} }
} }
@ -797,7 +865,10 @@ public class LoginActivity extends AppCompatActivity
Wallet newWallet = WalletManager.getInstance() Wallet newWallet = WalletManager.getInstance()
.createWallet(aFile, password, MNEMONIC_LANGUAGE); .createWallet(aFile, password, MNEMONIC_LANGUAGE);
boolean success = (newWallet.getStatus() == Wallet.Status.Status_Ok); boolean success = (newWallet.getStatus() == Wallet.Status.Status_Ok);
if (!success) Log.e(TAG, newWallet.getErrorString()); if (!success) {
Log.e(TAG, newWallet.getErrorString());
toast(newWallet.getErrorString());
}
newWallet.close(); newWallet.close();
return success; return success;
} }
@ -816,6 +887,7 @@ public class LoginActivity extends AppCompatActivity
success = success && newWallet.store(); success = success && newWallet.store();
} else { } else {
Log.e(TAG, newWallet.getErrorString()); Log.e(TAG, newWallet.getErrorString());
toast(newWallet.getErrorString());
} }
newWallet.close(); newWallet.close();
return success; return success;
@ -838,6 +910,7 @@ public class LoginActivity extends AppCompatActivity
success = success && newWallet.store(); success = success && newWallet.store();
} else { } else {
Log.e(TAG, newWallet.getErrorString()); Log.e(TAG, newWallet.getErrorString());
toast(newWallet.getErrorString());
} }
newWallet.close(); newWallet.close();
return success; return success;
@ -845,6 +918,15 @@ public class LoginActivity extends AppCompatActivity
}); });
} }
void toast(final String msg) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(LoginActivity.this, msg, Toast.LENGTH_LONG).show();
}
});
}
@Override @Override
public void onAccept(final String name, final String password) { public void onAccept(final String name, final String password) {
File walletFolder = getStorageRoot(); File walletFolder = getStorageRoot();
@ -859,17 +941,10 @@ public class LoginActivity extends AppCompatActivity
getString(R.string.generate_wallet_created), Toast.LENGTH_SHORT).show(); getString(R.string.generate_wallet_created), Toast.LENGTH_SHORT).show();
} else { } else {
Log.e(TAG, "Wallet store failed to " + walletFile.getAbsolutePath()); Log.e(TAG, "Wallet store failed to " + walletFile.getAbsolutePath());
Toast.makeText(LoginActivity.this, Toast.makeText(LoginActivity.this, getString(R.string.generate_wallet_create_failed), Toast.LENGTH_LONG).show();
getString(R.string.generate_wallet_create_failed), Toast.LENGTH_LONG).show();
} }
} }
@Override
public void onExchange(AsyncExchangeRate.Listener listener, String currencyA, String currencyB) {
new AsyncExchangeRate(listener).execute(currencyA, currencyB);
}
Wallet.Status testWallet(String path, String password) { Wallet.Status testWallet(String path, String password) {
Log.d(TAG, "testing wallet " + path); Log.d(TAG, "testing wallet " + path);
Wallet aWallet = WalletManager.getInstance().openWallet(path, password); Wallet aWallet = WalletManager.getInstance().openWallet(path, password);
@ -896,14 +971,12 @@ public class LoginActivity extends AppCompatActivity
boolean copyWallet(File srcWallet, File dstWallet, boolean backupMode) { boolean copyWallet(File srcWallet, File dstWallet, boolean backupMode) {
if (walletExists(dstWallet, true) && !backupMode) return false; if (walletExists(dstWallet, true) && !backupMode) return false;
Log.d(TAG, "B " + backupMode);
boolean success = false; boolean success = false;
File srcDir = srcWallet.getParentFile(); File srcDir = srcWallet.getParentFile();
String srcName = srcWallet.getName(); String srcName = srcWallet.getName();
File dstDir = dstWallet.getParentFile(); File dstDir = dstWallet.getParentFile();
String dstName = dstWallet.getName(); String dstName = dstWallet.getName();
try { try {
Log.d(TAG, "C " + backupMode);
try { try {
copyFile(new File(srcDir, srcName), new File(dstDir, dstName)); copyFile(new File(srcDir, srcName), new File(dstDir, dstName));
} catch (IOException ex) { } catch (IOException ex) {
@ -962,6 +1035,12 @@ public class LoginActivity extends AppCompatActivity
if (((GenerateReviewFragment) f).backOk()) { if (((GenerateReviewFragment) f).backOk()) {
super.onBackPressed(); super.onBackPressed();
} }
} else if (f instanceof LoginFragment) {
if (((LoginFragment) f).isFabOpen()) {
((LoginFragment) f).animateFAB();
} else {
super.onBackPressed();
}
} else { } else {
super.onBackPressed(); super.onBackPressed();
} }
@ -971,10 +1050,10 @@ public class LoginActivity extends AppCompatActivity
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.action_create_help: case R.id.action_create_help:
HelpFragment.displayHelp(getSupportFragmentManager(),R.raw.help_create); HelpFragment.displayHelp(getSupportFragmentManager(), R.raw.help_create);
return true; return true;
case R.id.action_details_help: case R.id.action_details_help:
HelpFragment.displayHelp(getSupportFragmentManager(),R.raw.help_details); HelpFragment.displayHelp(getSupportFragmentManager(), R.raw.help_details);
return true; return true;
case R.id.action_lincense_info: case R.id.action_lincense_info:
LicensesFragment.displayLicensesFragment(getSupportFragmentManager()); LicensesFragment.displayLicensesFragment(getSupportFragmentManager());
@ -992,59 +1071,128 @@ public class LoginActivity extends AppCompatActivity
} }
} }
private boolean checkAndSetWalletDaemon(String daemon, boolean testnet) { private void setNet(boolean testnet) {
String daemonAddress = ""; WalletManager.getInstance().setDaemon("", testnet, "", "");
String username = ""; }
static class WalletNode {
String name = null;
String host = "";
int port = 28081;
String user = "";
String password = ""; String password = "";
if (!daemon.isEmpty()) { // no actual daemon is also fine boolean isTestnet;
WalletNode(String walletName, String daemon, boolean isTestnet) {
if ((daemon == null) || daemon.isEmpty()) return;
this.name = walletName;
String daemonAddress;
String a[] = daemon.split("@"); String a[] = daemon.split("@");
if (a.length == 1) { // no credentials if (a.length == 1) { // no credentials
daemonAddress = a[0]; daemonAddress = a[0];
} else if (a.length == 2) { // credentials } else if (a.length == 2) { // credentials
String up[] = a[0].split(":"); String userPassword[] = a[0].split(":");
if (up.length != 2) return false; if (userPassword.length != 2)
username = up[0]; throw new IllegalArgumentException("User:Password invalid");
if (!username.isEmpty()) password = up[1]; user = userPassword[0];
if (!user.isEmpty()) password = userPassword[1];
daemonAddress = a[1]; daemonAddress = a[1];
} else { } else {
return false; throw new IllegalArgumentException("Too many @");
} }
String da[] = daemonAddress.split(":"); String da[] = daemonAddress.split(":");
if ((da.length > 2) || (da.length < 1)) return false; if ((da.length > 2) || (da.length < 1))
String host = da[0]; throw new IllegalArgumentException("Too many ':' or too few");
int port; host = da[0];
if (da.length == 2) { if (da.length == 2) {
try { try {
port = Integer.parseInt(da[1]); port = Integer.parseInt(da[1]);
} catch (NumberFormatException ex) { } catch (NumberFormatException ex) {
return false; throw new IllegalArgumentException("Port not numeric");
} }
} else { } else {
port = (testnet ? 28081 : 18081); port = (isTestnet ? 28081 : 18081);
daemonAddress = daemonAddress + ":" + port;
} }
//Log.d(TAG, "DAEMON " + username + "/" + password + "/" + host + "/" + port); this.isTestnet = isTestnet;
// if (android.os.Build.VERSION.SDK_INT > 9) { }
StrictMode.ThreadPolicy prevPolicy = StrictMode.getThreadPolicy();
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder(prevPolicy).permitNetwork().build(); String getAddress() {
StrictMode.setThreadPolicy(policy); return host + ":" + port;
}
boolean isValid() {
return !host.isEmpty();
}
}
private class AsyncOpenWallet extends AsyncTask<WalletNode, Void, Boolean> {
WalletNode walletNode;
@Override
protected void onPreExecute() {
super.onPreExecute();
showProgressDialog(R.string.open_progress, DAEMON_TIMEOUT / 4);
}
@Override
protected Boolean doInBackground(WalletNode... params) {
if (params.length != 1) return false;
this.walletNode = params[0];
if (!walletNode.isValid()) return false;
Log.d(TAG, "checking " + walletNode.getAddress());
long timeDA = new Date().getTime();
SocketAddress address = new InetSocketAddress(walletNode.host, walletNode.port);
long timeDB = new Date().getTime();
Log.d(TAG, "Resolving " + walletNode.host + " took " + (timeDB - timeDA) + "ms.");
Socket socket = new Socket(); Socket socket = new Socket();
long timeA = new Date().getTime(); long timeA = new Date().getTime();
try { try {
socket.connect(new InetSocketAddress(host, port), LoginActivity.DAEMON_TIMEOUT); socket.connect(address, LoginActivity.DAEMON_TIMEOUT);
socket.close(); socket.close();
} catch (IOException ex) { } catch (IOException ex) {
Log.d(TAG, "Cannot reach daemon " + host + "/" + port + " because " + ex.getLocalizedMessage()); Log.d(TAG, "Cannot reach daemon " + walletNode.host + "/" + walletNode.port + " because " + ex.getMessage());
return false; return false;
} finally {
StrictMode.setThreadPolicy(prevPolicy);
} }
long timeB = new Date().getTime(); long timeB = new Date().getTime();
Log.d(TAG, "Daemon is " + (timeB - timeA) + "ms away."); long time = timeB - timeA;
Log.d(TAG, "Daemon " + walletNode.host + " is " + time + "ms away.");
return time < LoginActivity.DAEMON_TIMEOUT;
}
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if (isDestroyed()) {
return;
}
dismissProgressDialog();
if (result) {
Log.d(TAG, "selected wallet is ." + walletNode.name + ".");
// now it's getting real, check if wallet exists
promptAndStart(walletNode);
} else {
Toast.makeText(LoginActivity.this, getString(R.string.status_wallet_connect_timeout), Toast.LENGTH_LONG).show();
}
}
}
void promptAndStart(WalletNode walletNode) {
File walletFile = Helper.getWalletFile(this, walletNode.name);
if (WalletManager.getInstance().walletExists(walletFile)) {
WalletManager.getInstance().
setDaemon(walletNode.getAddress(), walletNode.isTestnet, walletNode.user, walletNode.password);
promptPassword(walletNode.name, new PasswordAction() {
@Override
public void action(String walletName, String password) {
startWallet(walletName, password);
}
});
} else { // this cannot really happen as we prefilter choices
Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show();
} }
WalletManager mgr = WalletManager.getInstance();
mgr.setDaemon(daemonAddress, testnet, username, password);
return true;
} }
} }

View File

@ -18,13 +18,15 @@ package com.m2049r.xmrwallet;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.drawable.AnimationDrawable;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v7.widget.RecyclerView;
import android.util.Log; import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@ -32,47 +34,42 @@ import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ListView; import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.m2049r.xmrwallet.layout.DropDownEditText; import com.m2049r.xmrwallet.layout.DropDownEditText;
import com.m2049r.xmrwallet.layout.Toolbar;
import com.m2049r.xmrwallet.layout.WalletInfoAdapter;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.NodeList; import com.m2049r.xmrwallet.util.NodeList;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.TreeSet;
public class LoginFragment extends Fragment { public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInteractionListener,
View.OnClickListener {
private static final String TAG = "LoginFragment"; private static final String TAG = "LoginFragment";
public static final String WALLETNAME_PREAMBLE = "[------] ";
public static final int WALLETNAME_PREAMBLE_LENGTH = WALLETNAME_PREAMBLE.length();
private WalletInfoAdapter adapter;
ListView listView; List<WalletManager.WalletInfo> walletList = new ArrayList<>();
Set<String> walletList = new TreeSet<>(new Comparator<String>() { List<WalletManager.WalletInfo> displayedList = new ArrayList<>();
@Override
public int compare(String o1, String o2) {
return o1.substring(WALLETNAME_PREAMBLE_LENGTH).toLowerCase()
.compareTo(o2.substring(WALLETNAME_PREAMBLE_LENGTH).toLowerCase());
}
});
List<String> displayedList = new ArrayList<>();
ImageView ivGuntherWallets;
EditText etDummy; EditText etDummy;
DropDownEditText etDaemonAddress; DropDownEditText etDaemonAddress;
ArrayAdapter<String> nodeAdapter; ArrayAdapter<String> nodeAdapter;
FloatingActionButton fabAdd;
Listener activityCallback; Listener activityCallback;
@ -82,7 +79,7 @@ public class LoginFragment extends Fragment {
File getStorageRoot(); File getStorageRoot();
boolean onWalletSelected(String daemon, String wallet, boolean testnet); boolean onWalletSelected(String wallet, String daemon, boolean testnet);
void onWalletDetails(String wallet, boolean testnet); void onWalletDetails(String wallet, boolean testnet);
@ -94,9 +91,14 @@ public class LoginFragment extends Fragment {
void onWalletArchive(String walletName); void onWalletArchive(String walletName);
void onAddWallet(boolean testnet); void onAddWallet(boolean testnet, String type);
void showNet(boolean testnet); void showNet(boolean testnet);
void setToolbarButton(int type);
void setTitle(String title);
} }
@Override @Override
@ -121,55 +123,52 @@ public class LoginFragment extends Fragment {
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
Log.d(TAG, "onResume()"); Log.d(TAG, "onResume()");
activityCallback.setTitle(null);
activityCallback.setToolbarButton(Toolbar.BUTTON_DONATE);
activityCallback.showNet(isTestnet());
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
Log.d(TAG, "onCreateView"); Log.d(TAG, "onCreateView");
View view = inflater.inflate(R.layout.login_fragment, container, false); View view = inflater.inflate(R.layout.fragment_login, container, false);
fabAdd = (FloatingActionButton) view.findViewById(R.id.fabAdd); ivGuntherWallets = (ImageView) view.findViewById(R.id.ivGuntherWallets);
fabAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
activityCallback.onAddWallet(isTestnet());
}
});
listView = (ListView) view.findViewById(R.id.list); fabScreen = (FrameLayout) view.findViewById(R.id.fabScreen);
ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(), fab = (FloatingActionButton) view.findViewById(R.id.fab);
android.R.layout.simple_list_item_1, android.R.id.text1, this.displayedList); fabNew = (FloatingActionButton) view.findViewById(R.id.fabNew);
listView.setAdapter(adapter); fabView = (FloatingActionButton) view.findViewById(R.id.fabView);
registerForContextMenu(listView); fabKey = (FloatingActionButton) view.findViewById(R.id.fabKey);
fabSeed = (FloatingActionButton) view.findViewById(R.id.fabSeed);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { fabNewL = (RelativeLayout) view.findViewById(R.id.fabNewL);
@Override fabViewL = (RelativeLayout) view.findViewById(R.id.fabViewL);
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { fabKeyL = (RelativeLayout) view.findViewById(R.id.fabKeyL);
String itemValue = (String) listView.getItemAtPosition(position); fabSeedL = (RelativeLayout) view.findViewById(R.id.fabSeedL);
if (itemValue.length() <= (WALLETNAME_PREAMBLE_LENGTH)) { fab_open_screen = AnimationUtils.loadAnimation(getContext(), R.anim.fab_open_screen);
Toast.makeText(getActivity(), getString(R.string.panic), Toast.LENGTH_LONG).show(); fab_close_screen = AnimationUtils.loadAnimation(getContext(), R.anim.fab_close_screen);
return; fab_open = AnimationUtils.loadAnimation(getContext(), R.anim.fab_open);
} fab_close = AnimationUtils.loadAnimation(getContext(), R.anim.fab_close);
rotate_forward = AnimationUtils.loadAnimation(getContext(), R.anim.rotate_forward);
rotate_backward = AnimationUtils.loadAnimation(getContext(), R.anim.rotate_backward);
fab.setOnClickListener(this);
fabNew.setOnClickListener(this);
fabView.setOnClickListener(this);
fabKey.setOnClickListener(this);
fabSeed.setOnClickListener(this);
fabScreen.setOnClickListener(this);
String x = isTestnet() ? "9A-" : "4-"; RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.list);
if (x.indexOf(itemValue.charAt(1)) < 0) { registerForContextMenu(recyclerView);
Toast.makeText(getActivity(), getString(R.string.prompt_wrong_net), Toast.LENGTH_LONG).show(); this.adapter = new WalletInfoAdapter(getActivity(), this);
return; recyclerView.setAdapter(adapter);
}
String wallet = itemValue.substring(WALLETNAME_PREAMBLE_LENGTH);
if (activityCallback.onWalletSelected(getDaemon(), wallet, isTestnet())) {
savePrefs();
}
}
});
etDummy = (EditText) view.findViewById(R.id.etDummy); etDummy = (EditText) view.findViewById(R.id.etDummy);
etDaemonAddress = (DropDownEditText) view.findViewById(R.id.etDaemonAddress); etDaemonAddress = (DropDownEditText) view.findViewById(R.id.etDaemonAddress);
nodeAdapter = new ArrayAdapter(getContext(), android.R.layout.simple_dropdown_item_1line); nodeAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_dropdown_item_1line);
etDaemonAddress.setAdapter(nodeAdapter); etDaemonAddress.setAdapter(nodeAdapter);
Helper.hideKeyboard(getActivity()); Helper.hideKeyboard(getActivity());
@ -204,72 +203,66 @@ public class LoginFragment extends Fragment {
} }
}); });
etDaemonAddress.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View arg1, int pos, long id) {
Helper.hideKeyboard(getActivity());
etDummy.requestFocus();
}
});
loadPrefs(); loadPrefs();
return view; return view;
} }
private void filterList() { void showGunther() {
displayedList.clear(); ivGuntherWallets.setImageResource(R.drawable.gunther_wallets);
String x = isTestnet() ? "9A" : "4"; final AnimationDrawable guntherWalletsAnim = (AnimationDrawable) ivGuntherWallets.getDrawable();
for (String s : walletList) { final Handler handler = new Handler();
if (x.indexOf(s.charAt(1)) >= 0) displayedList.add(s); handler.postDelayed(new Runnable() {
} @Override
} public void run() {
guntherWalletsAnim.start();
public void loadList() {
Log.d(TAG, "loadList()");
// TODO this should probably be in LoginActivity
WalletManager mgr = WalletManager.getInstance();
List<WalletManager.WalletInfo> walletInfos =
mgr.findWallets(activityCallback.getStorageRoot());
walletList.clear();
for (WalletManager.WalletInfo walletInfo : walletInfos) {
// ONCE the walletInfo.address was null - because the address.txt was empty
// this was before the wallet generation was in its own therad with huge stack
// TODO: keep an eye on Wallet.getAddress() returning empty
String displayAddress = walletInfo.address;
if ((displayAddress != null) && displayAddress.length() == 95) {
displayAddress = walletInfo.address.substring(0, 6);
walletList.add("[" + displayAddress + "] " + walletInfo.name);
} }
} }, getResources().getInteger(R.integer.gunther_wallets_delay));
filterList(); }
((BaseAdapter) listView.getAdapter()).notifyDataSetChanged();
void normalGunther() {
ivGuntherWallets.setImageResource(R.drawable.gunther_wallets_00);
}
// Callbacks from WalletInfoAdapter
@Override
public void onInteraction(final View view, final WalletManager.WalletInfo infoItem) {
String x = isTestnet() ? "9A-" : "4-";
if (x.indexOf(infoItem.address.charAt(0)) < 0) {
Toast.makeText(getActivity(), getString(R.string.prompt_wrong_net), Toast.LENGTH_LONG).show();
return;
}
if (activityCallback.onWalletSelected(infoItem.name, getDaemon(), isTestnet())) {
savePrefs();
}
} }
@Override @Override
public void onCreateContextMenu(ContextMenu menu, View v, public boolean onContextInteraction(MenuItem item, WalletManager.WalletInfo listItem) {
ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.list_context_menu, menu);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
String listItem = (String) listView.getItemAtPosition(info.position);
String name = nameFromListItem(listItem, isTestnet());
if (name == null) {
Toast.makeText(getActivity(), getString(R.string.panic), Toast.LENGTH_LONG).show();
}
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.action_info: case R.id.action_info:
showInfo(name); showInfo(listItem.name);
break; break;
case R.id.action_receive: case R.id.action_receive:
showReceive(name); showReceive(listItem.name);
break; break;
case R.id.action_rename: case R.id.action_rename:
activityCallback.onWalletRename(name); activityCallback.onWalletRename(listItem.name);
break; break;
case R.id.action_backup: case R.id.action_backup:
activityCallback.onWalletBackup(name); activityCallback.onWalletBackup(listItem.name);
break; break;
case R.id.action_archive: case R.id.action_archive:
activityCallback.onWalletArchive(name); activityCallback.onWalletArchive(listItem.name);
break; break;
default: default:
return super.onContextItemSelected(item); return super.onContextItemSelected(item);
@ -277,6 +270,31 @@ public class LoginFragment extends Fragment {
return true; return true;
} }
private void filterList() {
displayedList.clear();
String x = isTestnet() ? "9A" : "4";
for (WalletManager.WalletInfo s : walletList) {
if (x.indexOf(s.address.charAt(0)) >= 0) displayedList.add(s);
}
}
public void loadList() {
Log.d(TAG, "loadList()");
WalletManager mgr = WalletManager.getInstance();
List<WalletManager.WalletInfo> walletInfos =
mgr.findWallets(activityCallback.getStorageRoot());
walletList.clear();
walletList.addAll(walletInfos);
filterList();
adapter.setInfos(displayedList);
adapter.notifyDataSetChanged();
if (displayedList.isEmpty()) {
showGunther();
} else {
normalGunther();
}
}
private void showInfo(@NonNull String name) { private void showInfo(@NonNull String name) {
activityCallback.onWalletDetails(name, isTestnet()); activityCallback.onWalletDetails(name, isTestnet());
} }
@ -286,15 +304,6 @@ public class LoginFragment extends Fragment {
return true; 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;
}
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -392,4 +401,75 @@ public class LoginFragment extends Fragment {
etDummy.requestFocus(); etDummy.requestFocus();
Helper.hideKeyboard(getActivity()); Helper.hideKeyboard(getActivity());
} }
private boolean isFabOpen = false;
private FloatingActionButton fab, fabNew, fabView, fabKey, fabSeed;
private FrameLayout fabScreen;
private RelativeLayout fabNewL, fabViewL, fabKeyL, fabSeedL;
private Animation fab_open, fab_close, rotate_forward, rotate_backward, fab_open_screen, fab_close_screen;
public boolean isFabOpen() {
return isFabOpen;
}
public void animateFAB() {
if (isFabOpen) {
fabScreen.setVisibility(View.INVISIBLE);
fabScreen.setClickable(false);
fabScreen.startAnimation(fab_close_screen);
fab.startAnimation(rotate_backward);
fabNewL.startAnimation(fab_close);
fabNew.setClickable(false);
fabViewL.startAnimation(fab_close);
fabView.setClickable(false);
fabKeyL.startAnimation(fab_close);
fabKey.setClickable(false);
fabSeedL.startAnimation(fab_close);
fabSeed.setClickable(false);
isFabOpen = false;
} else {
fabScreen.setClickable(true);
fabScreen.startAnimation(fab_open_screen);
fab.startAnimation(rotate_forward);
fabNewL.startAnimation(fab_open);
fabNew.setClickable(true);
fabViewL.startAnimation(fab_open);
fabView.setClickable(true);
fabKeyL.startAnimation(fab_open);
fabKey.setClickable(true);
fabSeedL.startAnimation(fab_open);
fabSeed.setClickable(true);
isFabOpen = true;
}
}
@Override
public void onClick(View v) {
int id = v.getId();
switch (id) {
case R.id.fab:
animateFAB();
break;
case R.id.fabNew:
fabScreen.setVisibility(View.INVISIBLE);
isFabOpen = false;
activityCallback.onAddWallet(isTestnet(), GenerateFragment.TYPE_NEW);
break;
case R.id.fabView:
animateFAB();
activityCallback.onAddWallet(isTestnet(), GenerateFragment.TYPE_VIEWONLY);
break;
case R.id.fabKey:
animateFAB();
activityCallback.onAddWallet(isTestnet(), GenerateFragment.TYPE_KEY);
break;
case R.id.fabSeed:
animateFAB();
activityCallback.onAddWallet(isTestnet(), GenerateFragment.TYPE_SEED);
break;
case R.id.fabScreen:
animateFAB();
break;
}
}
} }

View File

@ -17,12 +17,11 @@
package com.m2049r.xmrwallet; package com.m2049r.xmrwallet;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.support.design.widget.TextInputLayout;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.text.Editable; import android.text.Editable;
import android.text.InputType; import android.text.InputType;
@ -33,12 +32,11 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.widget.AdapterView;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
@ -48,151 +46,102 @@ import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix; import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter; import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.m2049r.xmrwallet.layout.ExchangeView;
import com.m2049r.xmrwallet.layout.Toolbar;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.AsyncExchangeRate;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor; import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale;
import java.util.Map; import java.util.Map;
public class ReceiveFragment extends Fragment implements AsyncExchangeRate.Listener { public class ReceiveFragment extends Fragment {
static final String TAG = "ReceiveFragment"; static final String TAG = "ReceiveFragment";
ProgressBar pbProgress; ProgressBar pbProgress;
TextView tvAddress; TextView tvAddress;
EditText etPaymentId; TextInputLayout etPaymentId;
EditText etAmount; ExchangeView evAmount;
TextView tvAmountB;
Button bPaymentId; Button bPaymentId;
Button bGenerate; Button bGenerate;
ImageView qrCode; ImageView qrCode;
EditText etDummy; EditText etDummy;
ImageButton bCopyAddress;
Spinner sCurrencyA; //String name;
Spinner sCurrencyB;
public interface Listener { public interface Listener {
void onExchange(AsyncExchangeRate.Listener listener, String currencyA, String currencyB); void setToolbarButton(int type);
}
@Override void setTitle(String title);
public void exchange(String currencyA, String currencyB, double rate) {
// first, make sure this is what we want void setSubtitle(String subtitle);
String enteredCurrencyA = (String) sCurrencyA.getSelectedItem();
String enteredCurrencyB = (String) sCurrencyB.getSelectedItem();
if (!currencyA.equals(enteredCurrencyA) || !currencyB.equals(enteredCurrencyB)) {
// something's wrong
Log.e(TAG, "Currencies don't match!");
tvAmountB.setText("");
return;
}
String enteredAmount = etAmount.getText().toString();
String xmrAmount = "";
if (!enteredAmount.isEmpty()) {
// losing precision using double here doesn't matter
double amountA = Double.parseDouble(enteredAmount);
double amountB = amountA * rate;
if (enteredCurrencyA.equals("XMR")) {
String validatedAmountA = Helper.getDisplayAmount(Wallet.getAmountFromString(enteredAmount));
xmrAmount = validatedAmountA; // take what was entered in XMR
etAmount.setText(xmrAmount); // display what we stick into the QR code
String displayB = String.format(Locale.US, "%.2f", amountB);
tvAmountB.setText(displayB);
} else if (enteredCurrencyB.equals("XMR")) {
xmrAmount = Wallet.getDisplayAmount(Wallet.getAmountFromDouble(amountB));
// cut off at 5 decimals
xmrAmount = xmrAmount.substring(0, xmrAmount.length() - (12 - 5));
tvAmountB.setText(xmrAmount);
} else { // no XMR currency
tvAmountB.setText("");
return; // and no qr code
}
} else {
tvAmountB.setText("");
}
generateQr(xmrAmount);
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.receive_fragment, container, false); View view = inflater.inflate(R.layout.fragment_receive, container, false);
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress); pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
tvAddress = (TextView) view.findViewById(R.id.tvAddress); tvAddress = (TextView) view.findViewById(R.id.tvAddress);
etPaymentId = (EditText) view.findViewById(R.id.etPaymentId); etPaymentId = (TextInputLayout) view.findViewById(R.id.etPaymentId);
etAmount = (EditText) view.findViewById(R.id.etAmountA); evAmount = (ExchangeView) view.findViewById(R.id.evAmount);
tvAmountB = (TextView) view.findViewById(R.id.tvAmountB);
bPaymentId = (Button) view.findViewById(R.id.bPaymentId); bPaymentId = (Button) view.findViewById(R.id.bPaymentId);
qrCode = (ImageView) view.findViewById(R.id.qrCode); qrCode = (ImageView) view.findViewById(R.id.qrCode);
bGenerate = (Button) view.findViewById(R.id.bGenerate); bGenerate = (Button) view.findViewById(R.id.bGenerate);
etDummy = (EditText) view.findViewById(R.id.etDummy); etDummy = (EditText) view.findViewById(R.id.etDummy);
bCopyAddress = (ImageButton) view.findViewById(R.id.bCopyAddress);
sCurrencyA = (Spinner) view.findViewById(R.id.sCurrencyA); etPaymentId.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
sCurrencyB = (Spinner) view.findViewById(R.id.sCurrencyB);
etPaymentId.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
loadPrefs(); bCopyAddress.setOnClickListener(new View.OnClickListener() {
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 @Override
public void afterTextChanged(Editable editable) { public void onClick(View v) {
qrCode.setImageBitmap(getMoneroLogo()); copyAddress();
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() { evAmount.setOnNewAmountListener(new ExchangeView.OnNewAmountListener() {
@Override
public void onNewAmount(String xmr) {
Log.d(TAG, "new amount = " + xmr);
generateQr();
}
});
/*
evAmount.setOnAmountInvalidatedListener(new ExchangeView.OnAmountInvalidatedListener() {
@Override
public void onAmountInvalidated() {
clearQR();
}
});
evAmount.setOnFailedExchangeListener(new ExchangeView.OnFailedExchangeListener() {
@Override
public void onFailedExchange() {
Toast.makeText(getActivity(), getString(R.string.message_exchange_failed), Toast.LENGTH_LONG).show();
}
});
*/
etPaymentId.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
if (paymentIdOk() && amountOk()) { if (checkPaymentId()) { // && evAmount.checkXmrAmount(true)) {
Helper.hideKeyboard(getActivity()); generateQr();
startExchange();
} }
return true; return true;
} }
return false; return false;
} }
}); });
etAmount.addTextChangedListener(new TextWatcher() { etPaymentId.getEditText().addTextChangedListener(new TextWatcher() {
@Override @Override
public void afterTextChanged(Editable editable) { public void afterTextChanged(Editable editable) {
tvAmountB.setText(""); clearQR();
qrCode.setImageBitmap(getMoneroLogo());
if (paymentIdOk() && amountOk()) {
bGenerate.setEnabled(true);
} else {
bGenerate.setEnabled(false);
}
} }
@Override @Override
@ -207,10 +156,10 @@ public class ReceiveFragment extends Fragment implements AsyncExchangeRate.Liste
bPaymentId.setOnClickListener(new View.OnClickListener() { bPaymentId.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
etPaymentId.setText((Wallet.generatePaymentId())); etPaymentId.getEditText().setText((Wallet.generatePaymentId()));
etPaymentId.setSelection(etPaymentId.getText().length()); etPaymentId.getEditText().setSelection(etPaymentId.getEditText().getText().length());
if (paymentIdOk() && amountOk()) { if (checkPaymentId()) { //&& evAmount.checkXmrAmount(true)) {
startExchange(); generateQr();
} }
} }
}); });
@ -218,101 +167,82 @@ public class ReceiveFragment extends Fragment implements AsyncExchangeRate.Liste
bGenerate.setOnClickListener(new View.OnClickListener() { bGenerate.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (paymentIdOk() && amountOk()) { if (checkPaymentId()) {
Helper.hideKeyboard(getActivity()); evAmount.doExchange();
startExchange();
} }
} }
}); });
sCurrencyA.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
if (position != 0) {
sCurrencyB.setSelection(0, true);
}
startExchange();
}
@Override
public void onNothingSelected(AdapterView<?> parentView) {
// nothing (yet?)
}
});
sCurrencyB.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
if (position != 0) {
sCurrencyA.setSelection(0, true);
}
tvAmountB.setText("");
startExchange();
}
@Override
public void onNothingSelected(AdapterView<?> parentView) {
// nothing (yet?)
}
});
showProgress(); showProgress();
qrCode.setImageBitmap(getMoneroLogo()); clearQR();
Bundle b = getArguments(); Bundle b = getArguments();
String address = b.getString("address"); String address = b.getString("address");
String walletName = b.getString("name");
Log.d(TAG, "address=" + address + "/name=" + walletName);
if (address == null) { if (address == null) {
String path = b.getString("path"); String path = b.getString("path");
String password = b.getString("password"); String password = b.getString("password");
show(path, password); loadAndShow(path, password);
} else { } else {
show(address); show(walletName, address);
} }
return view; return view;
} }
void startExchange() { void copyAddress() {
if (paymentIdOk() && amountOk() && tvAddress.getText().length() > 0) { Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_address), tvAddress.getText().toString());
String enteredCurrencyA = (String) sCurrencyA.getSelectedItem(); Toast.makeText(getActivity(), getString(R.string.message_copy_address), Toast.LENGTH_SHORT).show();
String enteredCurrencyB = (String) sCurrencyB.getSelectedItem(); }
String enteredAmount = etAmount.getText().toString();
tvAmountB.setText(""); boolean qrValid = true;
if (!enteredAmount.isEmpty()) { // start conversion
listenerCallback.onExchange(ReceiveFragment.this, enteredCurrencyA, enteredCurrencyB); void clearQR() {
} else { if (qrValid) {
generateQr(""); qrCode.setImageBitmap(getMoneroLogo());
} qrValid = false;
} }
} }
void setQR(Bitmap qr) {
qrCode.setImageBitmap(qr);
qrValid = true;
Helper.hideKeyboard(getActivity());
etDummy.requestFocus();
}
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
Log.d(TAG, "onResume()"); Log.d(TAG, "onResume()");
if (paymentIdOk() && amountOk() && tvAddress.getText().length() > 0) { listenerCallback.setToolbarButton(Toolbar.BUTTON_BACK);
startExchange(); listenerCallback.setSubtitle(getString(R.string.receive_title));
} generateQr();
} }
private void show(String address) { private void show(String name, String address) {
Log.d(TAG, "name=" + name);
listenerCallback.setTitle(name);
tvAddress.setText(address); tvAddress.setText(address);
etPaymentId.setEnabled(true); etPaymentId.setEnabled(true);
etAmount.setEnabled(true); //etAmount.setEnabled(true);
bPaymentId.setEnabled(true); bPaymentId.setEnabled(true);
bGenerate.setEnabled(true); bGenerate.setEnabled(true);
bCopyAddress.setEnabled(true);
bCopyAddress.setImageResource(R.drawable.ic_content_copy_black_24dp);
hideProgress(); hideProgress();
startExchange(); generateQr();
} }
private void show(String walletPath, String password) { private void loadAndShow(String walletPath, String password) {
new AsyncShow().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR, new AsyncShow().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR,
walletPath, password); walletPath, password);
} }
private class AsyncShow extends AsyncTask<String, Void, Boolean> { private class AsyncShow extends AsyncTask<String, Void, Boolean> {
String password; String password;
String address; String address;
String name;
@Override @Override
protected Boolean doInBackground(String... params) { protected Boolean doInBackground(String... params) {
@ -321,6 +251,7 @@ public class ReceiveFragment extends Fragment implements AsyncExchangeRate.Liste
password = params[1]; password = params[1];
Wallet wallet = WalletManager.getInstance().openWallet(walletPath, password); Wallet wallet = WalletManager.getInstance().openWallet(walletPath, password);
address = wallet.getAddress(); address = wallet.getAddress();
name = wallet.getName();
wallet.close(); wallet.close();
return true; return true;
} }
@ -328,8 +259,9 @@ public class ReceiveFragment extends Fragment implements AsyncExchangeRate.Liste
@Override @Override
protected void onPostExecute(Boolean result) { protected void onPostExecute(Boolean result) {
super.onPostExecute(result); super.onPostExecute(result);
if (!isAdded()) return; // never mind
if (result) { if (result) {
show(address); show(name, address);
} else { } else {
Toast.makeText(getActivity(), getString(R.string.receive_cannot_open), Toast.LENGTH_LONG).show(); Toast.makeText(getActivity(), getString(R.string.receive_cannot_open), Toast.LENGTH_LONG).show();
hideProgress(); hideProgress();
@ -337,22 +269,29 @@ public class ReceiveFragment extends Fragment implements AsyncExchangeRate.Liste
} }
} }
private boolean checkPaymentId() {
String paymentId = etPaymentId.getEditText().getText().toString();
boolean ok = paymentId.isEmpty() || Wallet.isPaymentIdValid(paymentId);
private boolean amountOk() { if (!ok) {
String amountEntry = etAmount.getText().toString(); etPaymentId.setError(getString(R.string.receive_paymentid_invalid));
if (amountEntry.isEmpty()) return true; } else {
long amount = Wallet.getAmountFromString(amountEntry); etPaymentId.setError(null);
return (amount > 0); }
return ok;
} }
private boolean paymentIdOk() { private void generateQr() {
String paymentId = etPaymentId.getText().toString(); Log.d(TAG, "GENQR");
return paymentId.isEmpty() || Wallet.isPaymentIdValid(paymentId);
}
private void generateQr(String xmrAmount) {
String address = tvAddress.getText().toString(); String address = tvAddress.getText().toString();
String paymentId = etPaymentId.getText().toString(); String paymentId = etPaymentId.getEditText().getText().toString();
String xmrAmount = evAmount.getAmount();
Log.d(TAG, xmrAmount + "/" + paymentId + "/" + address);
if ((xmrAmount == null) || !Wallet.isAddressValid(address, WalletManager.getInstance().isTestNet())) {
clearQR();
Log.d(TAG, "CLEARQR");
return;
}
StringBuffer sb = new StringBuffer(); StringBuffer sb = new StringBuffer();
sb.append(ScannerFragment.QR_SCHEME).append(address); sb.append(ScannerFragment.QR_SCHEME).append(address);
boolean first = true; boolean first = true;
@ -374,9 +313,10 @@ public class ReceiveFragment extends Fragment implements AsyncExchangeRate.Liste
String text = sb.toString(); String text = sb.toString();
Bitmap qr = generate(text, 500, 500); Bitmap qr = generate(text, 500, 500);
if (qr != null) { if (qr != null) {
qrCode.setImageBitmap(qr); setQR(qr);
Log.d(TAG, "SETQR");
etDummy.requestFocus(); etDummy.requestFocus();
bGenerate.setEnabled(false); Helper.hideKeyboard(getActivity());
} }
} }
@ -439,7 +379,6 @@ public class ReceiveFragment extends Fragment implements AsyncExchangeRate.Liste
} }
public void showProgress() { public void showProgress() {
pbProgress.setIndeterminate(true);
pbProgress.setVisibility(View.VISIBLE); pbProgress.setVisibility(View.VISIBLE);
} }
@ -463,36 +402,9 @@ public class ReceiveFragment extends Fragment implements AsyncExchangeRate.Liste
static final String PREF_CURRENCY_A = "PREF_CURRENCY_A"; static final String PREF_CURRENCY_A = "PREF_CURRENCY_A";
static final String PREF_CURRENCY_B = "PREF_CURRENCY_B"; static final String PREF_CURRENCY_B = "PREF_CURRENCY_B";
void loadPrefs() {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext());
int currencyA = sharedPreferences.getInt(PREF_CURRENCY_A, 0);
int currencyB = sharedPreferences.getInt(PREF_CURRENCY_B, 0);
if (currencyA * currencyB != 0) { // make sure one of them is 0 (=XMR)
currencyA = 0;
}
// in case we change the currency lists in the future
if (currencyA >= sCurrencyA.getCount()) currencyA = 0;
if (currencyB >= sCurrencyB.getCount()) currencyB = 0;
sCurrencyA.setSelection(currencyA);
sCurrencyB.setSelection(currencyB);
}
void savePrefs() {
int currencyA = sCurrencyA.getSelectedItemPosition();
int currencyB = sCurrencyB.getSelectedItemPosition();
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext());
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putInt(PREF_CURRENCY_A, currencyA);
editor.putInt(PREF_CURRENCY_B, currencyB);
editor.apply();
}
@Override @Override
public void onPause() { public void onPause() {
Log.d(TAG, "onPause()"); Log.d(TAG, "onPause()");
savePrefs();
super.onPause(); super.onPause();
} }
} }

View File

@ -28,7 +28,6 @@ import android.widget.Toast;
import com.google.zxing.BarcodeFormat; import com.google.zxing.BarcodeFormat;
import com.google.zxing.Result; import com.google.zxing.Result;
import com.m2049r.xmrwallet.util.BarcodeData;
import me.dm7.barcodescanner.zxing.ZXingScannerView; import me.dm7.barcodescanner.zxing.ZXingScannerView;
@ -64,7 +63,6 @@ public class ScannerFragment extends Fragment implements ZXingScannerView.Result
@Override @Override
public void handleResult(Result rawResult) { public void handleResult(Result rawResult) {
//Log.d(TAG, rawResult.getBarcodeFormat().toString() + "/" + rawResult.getText());
if ((rawResult.getBarcodeFormat() == BarcodeFormat.QR_CODE) && if ((rawResult.getBarcodeFormat() == BarcodeFormat.QR_CODE) &&
(rawResult.getText().startsWith(QR_SCHEME))) { (rawResult.getText().startsWith(QR_SCHEME))) {
if (activityCallback.onAddressScanned(rawResult.getText())) { if (activityCallback.onAddressScanned(rawResult.getText())) {
@ -101,7 +99,6 @@ public class ScannerFragment extends Fragment implements ZXingScannerView.Result
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
super.onAttach(context); super.onAttach(context);
//Log.d(TAG, "attaching scan");
if (context instanceof Listener) { if (context instanceof Listener) {
this.activityCallback = (Listener) context; this.activityCallback = (Listener) context;
} else { } else {

View File

@ -20,11 +20,10 @@ import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.text.Editable;
import android.text.InputType; import android.text.InputType;
import android.text.TextWatcher;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -35,24 +34,36 @@ import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.ScrollView;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import com.m2049r.xmrwallet.layout.ExchangeView;
import com.m2049r.xmrwallet.layout.Toolbar;
import com.m2049r.xmrwallet.model.PendingTransaction; import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.AsyncExchangeRate;
import com.m2049r.xmrwallet.util.BarcodeData; import com.m2049r.xmrwallet.util.BarcodeData;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.TxData; import com.m2049r.xmrwallet.util.TxData;
public class SendFragment extends Fragment { public class SendFragment extends Fragment {
static final String TAG = "SendFragment"; static final String TAG = "SendFragment";
EditText etAddress; EditText etDummy;
EditText etPaymentId;
EditText etAmount; ScrollView scrollview;
TextInputLayout etAddress;
TextInputLayout etPaymentId;
//TextInputLayout etAmount;
ExchangeView evAmount;
TextView tvAmountB;
Spinner sCurrencyA;
Spinner sCurrencyB;
Button bScan; Button bScan;
Button bSweep;
Spinner sMixin; Spinner sMixin;
Spinner sPriority; Spinner sPriority;
Button bPrepareSend; Button bPrepareSend;
@ -61,7 +72,8 @@ public class SendFragment extends Fragment {
LinearLayout llConfirmSend; LinearLayout llConfirmSend;
TextView tvTxAmount; TextView tvTxAmount;
TextView tvTxFee; TextView tvTxFee;
TextView tvTxDust; //TextView tvTxDust;
TextView tvTxTotal;
EditText etNotes; EditText etNotes;
Button bSend; Button bSend;
Button bReallySend; Button bReallySend;
@ -78,15 +90,23 @@ public class SendFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.send_fragment, container, false); View view = inflater.inflate(R.layout.fragment_send, container, false);
etDummy = (EditText) view.findViewById(R.id.etDummy);
etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
scrollview = (ScrollView) view.findViewById(R.id.scrollview);
sMixin = (Spinner) view.findViewById(R.id.sMixin); sMixin = (Spinner) view.findViewById(R.id.sMixin);
sPriority = (Spinner) view.findViewById(R.id.sPriority); sPriority = (Spinner) view.findViewById(R.id.sPriority);
etAddress = (EditText) view.findViewById(R.id.etAddress); etAddress = (TextInputLayout) view.findViewById(R.id.etAddress);
etPaymentId = (EditText) view.findViewById(R.id.etPaymentId); etPaymentId = (TextInputLayout) view.findViewById(R.id.etPaymentId);
etAmount = (EditText) view.findViewById(R.id.etAmount); evAmount = (ExchangeView) view.findViewById(R.id.evAmount);
tvAmountB = (TextView) view.findViewById(R.id.tvAmountB);
sCurrencyA = (Spinner) view.findViewById(R.id.sCurrencyA);
sCurrencyB = (Spinner) view.findViewById(R.id.sCurrencyB);
bScan = (Button) view.findViewById(R.id.bScan); bScan = (Button) view.findViewById(R.id.bScan);
bSweep = (Button) view.findViewById(R.id.bSweep);
bPrepareSend = (Button) view.findViewById(R.id.bPrepareSend); bPrepareSend = (Button) view.findViewById(R.id.bPrepareSend);
bPaymentId = (Button) view.findViewById(R.id.bPaymentId); bPaymentId = (Button) view.findViewById(R.id.bPaymentId);
bDispose = (Button) view.findViewById(R.id.bDispose); bDispose = (Button) view.findViewById(R.id.bDispose);
@ -94,101 +114,64 @@ public class SendFragment extends Fragment {
llConfirmSend = (LinearLayout) view.findViewById(R.id.llConfirmSend); llConfirmSend = (LinearLayout) view.findViewById(R.id.llConfirmSend);
tvTxAmount = (TextView) view.findViewById(R.id.tvTxAmount); tvTxAmount = (TextView) view.findViewById(R.id.tvTxAmount);
tvTxFee = (TextView) view.findViewById(R.id.tvTxFee); tvTxFee = (TextView) view.findViewById(R.id.tvTxFee);
tvTxDust = (TextView) view.findViewById(R.id.tvTxDust); //tvTxDust = (TextView) view.findViewById(R.id.tvTxDust);
tvTxTotal = (TextView) view.findViewById(R.id.tvTxTotal);
etNotes = (EditText) view.findViewById(R.id.etNotes); etNotes = (EditText) view.findViewById(R.id.etNotes);
bSend = (Button) view.findViewById(R.id.bSend); bSend = (Button) view.findViewById(R.id.bSend);
bReallySend = (Button) view.findViewById(R.id.bReallySend); bReallySend = (Button) view.findViewById(R.id.bReallySend);
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress); pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
etAddress.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); etAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etPaymentId.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); etPaymentId.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etNotes.setRawInputType(InputType.TYPE_CLASS_TEXT); etNotes.setRawInputType(InputType.TYPE_CLASS_TEXT);
Helper.showKeyboard(getActivity()); Helper.showKeyboard(getActivity());
etAddress.requestFocus(); etAddress.getEditText().requestFocus();
etAddress.setOnEditorActionListener(new TextView.OnEditorActionListener() { etAddress.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) {
if (addressOk()) { if (checkAddress()) {
evAmount.focus();
} // otherwise ignore
return true;
}
return false;
}
});
evAmount.setOnNewAmountListener(new ExchangeView.OnNewAmountListener() {
@Override
public void onNewAmount(String xmr) {
if ((xmr != null)) {
// stupid workaround to not show error on open of screen
if ((checkAddressNoError() && checkAmountWithError()) || checkAmount()) {
etPaymentId.requestFocus(); etPaymentId.requestFocus();
} // otherwise ignore }
return true;
} }
return false;
}
});
etAddress.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable editable) {
if (addressOk() && amountOk()) {
bPrepareSend.setEnabled(true);
} else {
bPrepareSend.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) {
}
});
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;
} }
}); });
etAmount.setOnEditorActionListener(new TextView.OnEditorActionListener() { etPaymentId.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
if (amountOk()) { if (checkPaymentId()) {
etDummy.requestFocus();
Helper.hideKeyboard(getActivity()); Helper.hideKeyboard(getActivity());
disableEdit();
prepareSend();
} }
return true; return true;
} }
return false; return false;
} }
}); });
etAmount.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable editable) {
if (addressOk() && amountOk()) {
bPrepareSend.setEnabled(true);
} else {
bPrepareSend.setEnabled(false);
}
}
@Override bPrepareSend.setOnClickListener(new View.OnClickListener() {
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
setPrepareButtonState();
bPrepareSend.setOnClickListener(new View.OnClickListener()
{
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Helper.hideKeyboard(getActivity()); if (checkAddress() && checkAmountWithError() && checkPaymentId()) {
disableEdit(); Helper.hideKeyboard(getActivity());
prepareSend(); prepareSend();
}
} }
}); });
@ -210,27 +193,15 @@ public class SendFragment extends Fragment {
bPaymentId.setOnClickListener(new View.OnClickListener() { bPaymentId.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
etPaymentId.setText((Wallet.generatePaymentId())); etPaymentId.getEditText().setText((Wallet.generatePaymentId()));
etPaymentId.setSelection(etPaymentId.getText().length()); etPaymentId.getEditText().setSelection(etPaymentId.getEditText().getText().length());
}
});
bSweep.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v) {
Helper.hideKeyboard(getActivity());
prepareSweep();
} }
}); });
etNotes.setOnEditorActionListener(new TextView.OnEditorActionListener() { etNotes.setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
if (amountOk()) { Helper.hideKeyboard(getActivity());
Helper.hideKeyboard(getActivity());
}
return true; return true;
} }
return false; return false;
@ -242,8 +213,8 @@ public class SendFragment extends Fragment {
public void onClick(View v) { public void onClick(View v) {
bSend.setEnabled(false); bSend.setEnabled(false);
boolean testnet = WalletManager.getInstance().isTestNet(); boolean testnet = WalletManager.getInstance().isTestNet();
if (testnet) { if (!testnet) {
send(); //send();
} else { } else {
etNotes.setEnabled(false); etNotes.setEnabled(false);
Handler handler = new Handler(); Handler handler = new Handler();
@ -251,6 +222,13 @@ public class SendFragment extends Fragment {
@Override @Override
public void run() { public void run() {
bReallySend.setVisibility(View.VISIBLE); bReallySend.setVisibility(View.VISIBLE);
bReallySend.setEnabled(true);
scrollview.post(new Runnable() {
@Override
public void run() {
scrollview.fullScroll(ScrollView.FOCUS_DOWN);
}
});
} }
}, 1000); }, 1000);
} }
@ -264,40 +242,64 @@ public class SendFragment extends Fragment {
send(); send();
} }
}); });
etDummy.requestFocus();
Helper.hideKeyboard(getActivity());
return view; return view;
} }
private void setPrepareButtonState() { private boolean checkAddressNoError() {
if (addressOk() && amountOk() && (bSend.getVisibility() != View.VISIBLE)) { String address = etAddress.getEditText().getText().toString();
bPrepareSend.setEnabled(true);
} else {
bPrepareSend.setEnabled(false);
}
}
private boolean addressOk() {
String address = etAddress.getText().toString();
return Wallet.isAddressValid(address, WalletManager.getInstance().isTestNet()); return Wallet.isAddressValid(address, WalletManager.getInstance().isTestNet());
} }
private boolean amountOk() { private boolean checkAddress() {
long amount = Wallet.getAmountFromString(etAmount.getText().toString()); boolean ok = checkAddressNoError();
return (amount > 0); if (!ok) {
etAddress.setError(getString(R.string.send_qr_address_invalid));
} else {
etAddress.setError(null);
}
return ok;
} }
private boolean paymentIdOk() { private boolean checkAmount() {
String paymentId = etPaymentId.getText().toString(); String xmr = evAmount.getAmount();
return paymentId.isEmpty() || Wallet.isPaymentIdValid(paymentId); return (xmr != null) && (Wallet.getAmountFromString(xmr) > 0);
}
private boolean checkAmountWithError() {
boolean ok = checkAmount();
if (!ok) {
evAmount.setError(getString(R.string.receive_amount_empty));
} else {
evAmount.setError(null);
}
return ok;
}
private boolean checkPaymentId() {
String paymentId = etPaymentId.getEditText().getText().toString();
boolean ok = paymentId.isEmpty() || Wallet.isPaymentIdValid(paymentId);
if (!ok) {
etPaymentId.setError(getString(R.string.receive_paymentid_invalid));
} else {
etPaymentId.setError(null);
}
return ok;
} }
private void prepareSend() { private void prepareSend() {
String dst_addr = etAddress.getText().toString(); etDummy.requestFocus();
String paymentId = etPaymentId.getText().toString(); disableEdit();
long amount = Wallet.getAmountFromString(etAmount.getText().toString()); String dst_addr = etAddress.getEditText().getText().toString();
String paymentId = etPaymentId.getEditText().getText().toString();
long amount = Wallet.getAmountFromString(evAmount.getAmount());
int mixin = Mixins[sMixin.getSelectedItemPosition()]; int mixin = Mixins[sMixin.getSelectedItemPosition()];
int priorityIndex = sPriority.getSelectedItemPosition(); int priorityIndex = sPriority.getSelectedItemPosition();
PendingTransaction.Priority priority = Priorities[priorityIndex]; PendingTransaction.Priority priority = Priorities[priorityIndex];
//Log.d(TAG, dst_addr + "/" + paymentId + "/" + amount + "/" + mixin + "/" + priority.toString()); Log.d(TAG, dst_addr + "/" + paymentId + "/" + amount + "/" + mixin + "/" + priority.toString());
TxData txData = new TxData( TxData txData = new TxData(
dst_addr, dst_addr,
paymentId, paymentId,
@ -308,105 +310,106 @@ public class SendFragment extends Fragment {
activityCallback.onPrepareSend(txData); activityCallback.onPrepareSend(txData);
} }
private void prepareSweep() {
etAddress.setText(activityCallback.getWalletAddress());
etPaymentId.setText("");
etAmount.setText("");
disableEdit();
showProgress();
activityCallback.onPrepareSweep();
}
private void disableEdit() { private void disableEdit() {
sMixin.setEnabled(false); sMixin.setEnabled(false);
sPriority.setEnabled(false); sPriority.setEnabled(false);
etAddress.setEnabled(false); etAddress.getEditText().setEnabled(false);
etPaymentId.setEnabled(false); etPaymentId.getEditText().setEnabled(false);
etAmount.setEnabled(false); evAmount.enable(false);
bScan.setEnabled(false); bScan.setEnabled(false);
bPaymentId.setEnabled(false); bPaymentId.setEnabled(false);
bSweep.setEnabled(false);
bPrepareSend.setEnabled(false); bPrepareSend.setEnabled(false);
bPrepareSend.setVisibility(View.GONE);
} }
private void enableEdit() { private void enableEdit() {
sMixin.setEnabled(true); sMixin.setEnabled(true);
sPriority.setEnabled(true); sPriority.setEnabled(true);
etAddress.setEnabled(true); etAddress.getEditText().setEnabled(true);
etPaymentId.setEnabled(true); etPaymentId.getEditText().setEnabled(true);
etAmount.setEnabled(true); evAmount.enable(true);
bScan.setEnabled(true); bScan.setEnabled(true);
bPaymentId.setEnabled(true); bPaymentId.setEnabled(true);
bSweep.setEnabled(true);
bPrepareSend.setEnabled(true); bPrepareSend.setEnabled(true);
bPrepareSend.setVisibility(View.VISIBLE);
llConfirmSend.setVisibility(View.GONE); llConfirmSend.setVisibility(View.GONE);
bSend.setEnabled(true);
etNotes.setEnabled(true); etNotes.setEnabled(true);
bSend.setEnabled(false);
bReallySend.setVisibility(View.GONE); bReallySend.setVisibility(View.GONE);
bReallySend.setEnabled(false);
etDummy.requestFocus();
} }
private void send() { private void send() {
etNotes.setEnabled(false); etNotes.setEnabled(false);
etDummy.requestFocus();
String notes = etNotes.getText().toString(); String notes = etNotes.getText().toString();
activityCallback.onSend(notes); activityCallback.onSend(notes);
} }
SendFragment.Listener activityCallback; Listener activityCallback;
public interface Listener { public interface Listener {
void onPrepareSend(TxData data); void onPrepareSend(TxData data);
void onPrepareSweep();
void onSend(String notes); void onSend(String notes);
String getWalletAddress(); String getWalletAddress();
String getWalletName();
void onDisposeRequest(); void onDisposeRequest();
void onScanAddress(); void onScanAddress();
BarcodeData getScannedData(); BarcodeData popScannedData();
void onExchange(AsyncExchangeRate.Listener listener, String currencyA, String currencyB);
void setSubtitle(String subtitle);
void setToolbarButton(int type);
} }
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
Log.d(TAG, "onResume"); Log.d(TAG, "onResume");
BarcodeData data = activityCallback.getScannedData(); activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
activityCallback.setSubtitle(getString(R.string.send_title));
BarcodeData data = activityCallback.popScannedData();
if (data != null) { if (data != null) {
Log.d(TAG, "GOT DATA");
String scannedAddress = data.address; String scannedAddress = data.address;
if (scannedAddress != null) { if (scannedAddress != null) {
etAddress.setText(scannedAddress); etAddress.getEditText().setText(scannedAddress);
checkAddress();
} else { } else {
etAddress.getText().clear(); etAddress.getEditText().getText().clear();
etAddress.setError(null);
} }
String scannedPaymenId = data.paymentId; String scannedPaymenId = data.paymentId;
if (scannedPaymenId != null) { if (scannedPaymenId != null) {
etPaymentId.setText(scannedPaymenId); etPaymentId.getEditText().setText(scannedPaymenId);
checkPaymentId();
} else { } else {
etPaymentId.getText().clear(); etPaymentId.getEditText().getText().clear();
etPaymentId.setError(null);
} }
if (data.amount >= 0) { if (data.amount > 0) {
String scannedAmount = Helper.getDisplayAmount(data.amount); String scannedAmount = Helper.getDisplayAmount(data.amount);
etAmount.setText(scannedAmount); evAmount.setAmount(scannedAmount);
} else { } else {
etAmount.getText().clear(); evAmount.setAmount("");
}
etAmount.requestFocus();
etAmount.setSelection(etAmount.getText().length());
} else { // no scan data
// jump to first empty field
if (etAddress.getText().toString().isEmpty()) {
etAddress.requestFocus();
} else if (etPaymentId.getText().toString().isEmpty()) {
etPaymentId.requestFocus();
} else {
etAmount.requestFocus();
etAmount.setSelection(etAmount.getText().length());
} }
} }
if ((data != null) && (data.amount <= 0)) {
evAmount.focus();
} else {
etDummy.requestFocus();
Helper.hideKeyboard(getActivity());
}
} }
@Override @Override
@ -427,9 +430,18 @@ public class SendFragment extends Fragment {
return; return;
} }
llConfirmSend.setVisibility(View.VISIBLE); llConfirmSend.setVisibility(View.VISIBLE);
bSend.setEnabled(true);
tvTxAmount.setText(Wallet.getDisplayAmount(pendingTransaction.getAmount())); tvTxAmount.setText(Wallet.getDisplayAmount(pendingTransaction.getAmount()));
tvTxFee.setText(Wallet.getDisplayAmount(pendingTransaction.getFee())); tvTxFee.setText(Wallet.getDisplayAmount(pendingTransaction.getFee()));
tvTxDust.setText(Wallet.getDisplayAmount(pendingTransaction.getDust())); //tvTxDust.setText(Wallet.getDisplayAmount(pendingTransaction.getDust()));
tvTxTotal.setText(Wallet.getDisplayAmount(
pendingTransaction.getFee() + pendingTransaction.getAmount()));
scrollview.post(new Runnable() {
@Override
public void run() {
scrollview.fullScroll(ScrollView.FOCUS_DOWN);
}
});
} }
public void onCreatedTransactionFailed(String errorText) { public void onCreatedTransactionFailed(String errorText) {
@ -456,4 +468,5 @@ public class SendFragment extends Fragment {
public void hideProgress() { public void hideProgress() {
pbProgress.setVisibility(View.GONE); pbProgress.setVisibility(View.GONE);
} }
} }

View File

@ -16,19 +16,22 @@
package com.m2049r.xmrwallet; package com.m2049r.xmrwallet;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.text.InputType; import android.text.InputType;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import com.m2049r.xmrwallet.layout.Toolbar;
import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Transfer; import com.m2049r.xmrwallet.model.Transfer;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
@ -64,14 +67,13 @@ public class TxFragment extends Fragment {
TextView tvTxFee; TextView tvTxFee;
TextView tvTxTransfers; TextView tvTxTransfers;
TextView etTxNotes; TextView etTxNotes;
Button bCopy;
Button bTxNotes; Button bTxNotes;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.tx_fragment, container, false); View view = inflater.inflate(R.layout.fragment_tx_info, container, false);
tvTxTimestamp = (TextView) view.findViewById(R.id.tvTxTimestamp); tvTxTimestamp = (TextView) view.findViewById(R.id.tvTxTimestamp);
tvTxId = (TextView) view.findViewById(R.id.tvTxId); tvTxId = (TextView) view.findViewById(R.id.tvTxId);
@ -83,18 +85,10 @@ public class TxFragment extends Fragment {
tvTxFee = (TextView) view.findViewById(R.id.tvTxFee); tvTxFee = (TextView) view.findViewById(R.id.tvTxFee);
tvTxTransfers = (TextView) view.findViewById(R.id.tvTxTransfers); tvTxTransfers = (TextView) view.findViewById(R.id.tvTxTransfers);
etTxNotes = (TextView) view.findViewById(R.id.etTxNotes); etTxNotes = (TextView) view.findViewById(R.id.etTxNotes);
bCopy = (Button) view.findViewById(R.id.bCopy);
bTxNotes = (Button) view.findViewById(R.id.bTxNotes); bTxNotes = (Button) view.findViewById(R.id.bTxNotes);
etTxNotes.setRawInputType(InputType.TYPE_CLASS_TEXT); etTxNotes.setRawInputType(InputType.TYPE_CLASS_TEXT);
bCopy.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
copyToClipboard();
}
});
bTxNotes.setOnClickListener(new View.OnClickListener() { bTxNotes.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -119,28 +113,35 @@ public class TxFragment extends Fragment {
} }
} }
void copyToClipboard() { void shareTxInfo() {
if (this.info == null) return; if (this.info == null) return;
StringBuffer sb = new StringBuffer(); StringBuffer sb = new StringBuffer();
sb.append(getString(R.string.tx_address)).append(": ");
sb.append(activityCallback.getWalletAddress()).append("\n"); sb.append(getString(R.string.tx_timestamp)).append(":\n");
sb.append(getString(R.string.tx_id)).append(": "); sb.append(TS_FORMATTER.format(new Date(info.timestamp * 1000))).append("\n\n");
sb.append(info.hash).append("\n");
sb.append(getString(R.string.tx_key)).append(": "); sb.append(getString(R.string.tx_amount)).append(":\n");
sb.append(info.txKey.isEmpty() ? "-" : info.txKey).append("\n");
sb.append(getString(R.string.tx_paymentId)).append(": ");
sb.append(info.paymentId).append("\n");
sb.append(getString(R.string.tx_amount)).append(": ");
sb.append((info.direction == TransactionInfo.Direction.Direction_In ? "+" : "-")); sb.append((info.direction == TransactionInfo.Direction.Direction_In ? "+" : "-"));
sb.append(Wallet.getDisplayAmount(info.amount)).append("\n"); sb.append(Wallet.getDisplayAmount(info.amount)).append("\n");
sb.append(getString(R.string.tx_fee)).append(": "); sb.append(getString(R.string.tx_fee)).append(":\n");
sb.append(Wallet.getDisplayAmount(info.fee)).append("\n"); sb.append(Wallet.getDisplayAmount(info.fee)).append("\n\n");
sb.append(getString(R.string.tx_notes)).append(": ");
sb.append(getString(R.string.tx_notes)).append(":\n");
String oneLineNotes = info.notes.replace("\n", " ; "); String oneLineNotes = info.notes.replace("\n", " ; ");
sb.append(oneLineNotes.isEmpty() ? "-" : oneLineNotes).append("\n"); sb.append(oneLineNotes.isEmpty() ? "-" : oneLineNotes).append("\n\n");
sb.append(getString(R.string.tx_timestamp)).append(": ");
sb.append(TS_FORMATTER.format(new Date(info.timestamp * 1000))).append("\n"); sb.append(getString(R.string.tx_destination)).append(":\n");
sb.append(getString(R.string.tx_blockheight)).append(": "); sb.append(tvDestination.getText()).append("\n\n");
sb.append(getString(R.string.tx_paymentId)).append(":\n");
sb.append(info.paymentId).append("\n\n");
sb.append(getString(R.string.tx_id)).append(":\n");
sb.append(info.hash).append("\n");
sb.append(getString(R.string.tx_key)).append(":\n");
sb.append(info.txKey.isEmpty() ? "-" : info.txKey).append("\n\n");
sb.append(getString(R.string.tx_blockheight)).append(":\n");
if (info.isFailed) { if (info.isFailed) {
sb.append(getString(R.string.tx_failed)).append("\n"); sb.append(getString(R.string.tx_failed)).append("\n");
} else if (info.isPending) { } else if (info.isPending) {
@ -148,7 +149,9 @@ public class TxFragment extends Fragment {
} else { } else {
sb.append(info.blockheight).append("\n"); sb.append(info.blockheight).append("\n");
} }
sb.append(getString(R.string.tx_transfers)).append(": "); sb.append("\n");
sb.append(getString(R.string.tx_transfers)).append(":\n");
if (info.transfers != null) { if (info.transfers != null) {
boolean comma = false; boolean comma = false;
for (Transfer transfer : info.transfers) { for (Transfer transfer : info.transfers) {
@ -163,12 +166,14 @@ public class TxFragment extends Fragment {
} else { } else {
sb.append("-"); sb.append("-");
} }
sb.append("\n"); sb.append("\n\n");
ClipboardManager clipboardManager = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); //Helper.clipBoardCopy(getActivity(), getString(R.string.tx_copy_label), sb.toString());
ClipData clip = ClipData.newPlainText(getString(R.string.tx_copy_label), sb.toString()); //Toast.makeText(getActivity(), getString(R.string.tx_copy_message), Toast.LENGTH_SHORT).show();
clipboardManager.setPrimaryClip(clip); Intent sendIntent = new Intent();
Toast.makeText(getActivity(), getString(R.string.tx_copy_message), Toast.LENGTH_SHORT).show(); sendIntent.setAction(Intent.ACTION_SEND);
//Log.d(TAG, sb.toString()); sendIntent.putExtra(Intent.EXTRA_TEXT, sb.toString());
sendIntent.setType("text/plain");
startActivity(Intent.createChooser(sendIntent, null));
} }
TransactionInfo info = null; TransactionInfo info = null;
@ -176,17 +181,24 @@ public class TxFragment extends Fragment {
void loadNotes(TransactionInfo info) { void loadNotes(TransactionInfo info) {
if (info.notes == null) { if (info.notes == null) {
info.notes = activityCallback.getTxNotes(info.hash); info.notes = activityCallback.getTxNotes(info.hash);
//Log.d(TAG, "NOTES:" + info.notes + ":");
} }
etTxNotes.setText(info.notes); etTxNotes.setText(info.notes);
} }
private void setTxColour(int clr) {
tvTxAmount.setTextColor(clr);
tvTxFee.setTextColor(clr);
}
private void show(TransactionInfo info) { private void show(TransactionInfo info) {
if (info.txKey == null) { if (info.txKey == null) {
info.txKey = activityCallback.getTxKey(info.hash); info.txKey = activityCallback.getTxKey(info.hash);
//Log.d(TAG, "TXKEY:" + info.txKey + ":");
} }
loadNotes(info); loadNotes(info);
activityCallback.setSubtitle(getString(R.string.tx_title));
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
tvTxTimestamp.setText(TS_FORMATTER.format(new Date(info.timestamp * 1000))); tvTxTimestamp.setText(TS_FORMATTER.format(new Date(info.timestamp * 1000)));
tvTxId.setText(info.hash); tvTxId.setText(info.hash);
tvTxKey.setText(info.txKey.isEmpty() ? "-" : info.txKey); tvTxKey.setText(info.txKey.isEmpty() ? "-" : info.txKey);
@ -199,8 +211,30 @@ public class TxFragment extends Fragment {
tvTxBlockheight.setText("" + info.blockheight); tvTxBlockheight.setText("" + info.blockheight);
} }
String sign = (info.direction == TransactionInfo.Direction.Direction_In ? "+" : "-"); String sign = (info.direction == TransactionInfo.Direction.Direction_In ? "+" : "-");
tvTxAmount.setText(sign + Wallet.getDisplayAmount(info.amount)); tvTxAmount.setText(sign + Wallet.getDisplayAmount(info.amount));
tvTxFee.setText(Wallet.getDisplayAmount(info.fee)); if ((info.fee > 0)) {
String fee = Wallet.getDisplayAmount(info.fee);
if (info.isPending) {
tvTxFee.setText(getString(R.string.tx_list_fee_pending, fee));
} else {
tvTxFee.setText(getString(R.string.tx_list_fee, fee));
}
} else {
tvTxFee.setText(null);
tvTxFee.setVisibility(View.GONE);
}
if (info.isFailed) {
tvTxAmount.setText(getString(R.string.tx_list_amount_failed, Wallet.getDisplayAmount(info.amount)));
tvTxFee.setText(getString(R.string.tx_list_failed_text));
setTxColour(ContextCompat.getColor(getContext(), R.color.tx_failed));
} else if (info.isPending) {
setTxColour(ContextCompat.getColor(getContext(), R.color.tx_pending));
} else if (info.direction == TransactionInfo.Direction.Direction_In) {
setTxColour(ContextCompat.getColor(getContext(), R.color.tx_green));
} else {
setTxColour(ContextCompat.getColor(getContext(), R.color.tx_red));
}
Set<String> destinations = new HashSet<>(); Set<String> destinations = new HashSet<>();
StringBuffer sb = new StringBuffer(); StringBuffer sb = new StringBuffer();
StringBuffer dstSb = new StringBuffer(); StringBuffer dstSb = new StringBuffer();
@ -232,10 +266,21 @@ public class TxFragment extends Fragment {
tvTxTransfers.setText(sb.toString()); tvTxTransfers.setText(sb.toString());
tvDestination.setText(dstSb.toString()); tvDestination.setText(dstSb.toString());
this.info = info; this.info = info;
bCopy.setEnabled(true);
} }
TxFragment.Listener activityCallback; @Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.tx_info_menu, menu);
super.onCreateOptionsMenu(menu, inflater);
}
Listener activityCallback;
public interface Listener { public interface Listener {
String getWalletAddress(); String getWalletAddress();
@ -246,6 +291,10 @@ public class TxFragment extends Fragment {
void onSetNote(String txId, String notes); void onSetNote(String txId, String notes);
void setToolbarButton(int type);
void setSubtitle(String subtitle);
} }
@Override @Override

View File

@ -17,7 +17,6 @@
package com.m2049r.xmrwallet; package com.m2049r.xmrwallet;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
@ -25,7 +24,6 @@ import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.os.PowerManager; import android.os.PowerManager;
@ -34,11 +32,11 @@ import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction; import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log; import android.util.Log;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.Toast; import android.widget.Toast;
import com.m2049r.xmrwallet.layout.Toolbar;
import com.m2049r.xmrwallet.model.PendingTransaction; import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
@ -49,16 +47,13 @@ import com.m2049r.xmrwallet.util.BarcodeData;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.TxData; import com.m2049r.xmrwallet.util.TxData;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
public class WalletActivity extends AppCompatActivity implements WalletFragment.Listener, public class WalletActivity extends AppCompatActivity implements WalletFragment.Listener,
WalletService.Observer, SendFragment.Listener, TxFragment.Listener, WalletService.Observer, SendFragment.Listener, TxFragment.Listener,
GenerateReviewFragment.ListenerWithWallet, GenerateReviewFragment.ListenerWithWallet,
GenerateReviewFragment.Listener,
ScannerFragment.Listener, ReceiveFragment.Listener { ScannerFragment.Listener, ReceiveFragment.Listener {
private static final String TAG = "WalletActivity"; private static final String TAG = "WalletActivity";
@ -67,6 +62,27 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
Toolbar toolbar; Toolbar toolbar;
@Override
public void setToolbarButton(int type) {
toolbar.setButton(type);
}
@Override
public void setTitle(String title, String subtitle) {
toolbar.setTitle(title, subtitle);
}
@Override
public void setTitle(String title) {
Log.d(TAG, "setTitle:" + title + ".");
toolbar.setTitle(title);
}
@Override
public void setSubtitle(String subtitle) {
toolbar.setSubtitle(subtitle);
}
private boolean synced = false; private boolean synced = false;
@Override @Override
@ -137,8 +153,11 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
case R.id.action_info: case R.id.action_info:
onWalletDetails(); onWalletDetails();
return true; return true;
case R.id.action_receive: case R.id.action_donate:
onWalletReceive(); onWalletDetails();
return true;
case R.id.action_share:
onShareTxInfo();
return true; return true;
default: default:
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
@ -157,17 +176,35 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
return; return;
} }
setContentView(R.layout.wallet_activity); setContentView(R.layout.activity_wallet);
toolbar = (Toolbar) findViewById(R.id.toolbar); toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
getSupportActionBar().setDisplayShowTitleEnabled(false);
toolbar.setTitle(R.string.app_name); toolbar.setOnButtonListener(new Toolbar.OnButtonListener() {
@Override
public void onButton(int type) {
switch (type) {
case Toolbar.BUTTON_BACK:
onBackPressed();
break;
case Toolbar.BUTTON_CLOSE:
finish();
break;
case Toolbar.BUTTON_DONATE:
Toast.makeText(WalletActivity.this, getString(R.string.label_donate), Toast.LENGTH_SHORT).show();
case Toolbar.BUTTON_NONE:
default:
Log.e(TAG, "Button " + type + "pressed - how can this be?");
}
}
});
boolean testnet = WalletManager.getInstance().isTestNet(); boolean testnet = WalletManager.getInstance().isTestNet();
if (testnet) { if (testnet) {
toolbar.setBackgroundResource(R.color.colorPrimaryDark); toolbar.setBackgroundResource(R.color.colorPrimaryDark);
} else { } else {
toolbar.setBackgroundResource(R.color.moneroOrange); toolbar.setBackgroundResource(R.drawable.backgound_toolbar_mainnet);
} }
Fragment walletFragment = new WalletFragment(); Fragment walletFragment = new WalletFragment();
@ -195,18 +232,15 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
// service that we know is running in our own process, we can // service that we know is running in our own process, we can
// cast its IBinder to a concrete class and directly access it. // cast its IBinder to a concrete class and directly access it.
mBoundService = ((WalletService.WalletServiceBinder) service).getService(); mBoundService = ((WalletService.WalletServiceBinder) service).getService();
//Log.d(TAG, "setting observer of " + mBoundService);
mBoundService.setObserver(WalletActivity.this); mBoundService.setObserver(WalletActivity.this);
Bundle extras = getIntent().getExtras(); Bundle extras = getIntent().getExtras();
if (extras != null) { if (extras != null) {
String walletId = extras.getString(REQUEST_ID); String walletId = extras.getString(REQUEST_ID);
if (walletId != null) { if (walletId != null) {
setTitle(walletId); setTitle(walletId, getString(R.string.status_wallet_connecting));
setSubtitle("");
} }
} }
updateProgress(); updateProgress();
//TODO show current pbProgress (eg. if the service is already busy saving last wallet)
Log.d(TAG, "CONNECTED"); Log.d(TAG, "CONNECTED");
} }
@ -216,8 +250,7 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
// Because it is running in our same process, we should never // Because it is running in our same process, we should never
// see this happen. // see this happen.
mBoundService = null; mBoundService = null;
setTitle(getString(R.string.wallet_activity_name)); setTitle(getString(R.string.wallet_activity_name), getString(R.string.status_wallet_disconnected));
setSubtitle("");
Log.d(TAG, "DISCONNECTED"); Log.d(TAG, "DISCONNECTED");
} }
}; };
@ -269,7 +302,7 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
wl.acquire(); wl.acquire();
Log.d(TAG, "WakeLock acquired"); Log.d(TAG, "WakeLock acquired");
} catch (SecurityException ex) { } catch (SecurityException ex) {
Log.d(TAG, "WakeLock NOT acquired: " + ex.getLocalizedMessage()); Log.w(TAG, "WakeLock NOT acquired: " + ex.getLocalizedMessage());
wl = null; wl = null;
} }
} }
@ -292,9 +325,9 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
} }
} }
////////////////////////////////////////// //////////////////////////////////////////
// WalletFragment.Listener // WalletFragment.Listener
////////////////////////////////////////// //////////////////////////////////////////
@Override @Override
public boolean hasBoundService() { public boolean hasBoundService() {
@ -311,16 +344,6 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
return mBoundService.getDaemonHeight(); return mBoundService.getDaemonHeight();
} }
@Override
public void setTitle(String title) {
toolbar.setTitle(title);
}
@Override
public void setSubtitle(String subtitle) {
toolbar.setSubtitle(subtitle);
}
@Override @Override
public void onSendRequest() { public void onSendRequest() {
replaceFragment(new SendFragment(), null, null); replaceFragment(new SendFragment(), null, null);
@ -342,9 +365,9 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
} }
} }
/////////////////////////// ///////////////////////////
// WalletService.Observer // WalletService.Observer
/////////////////////////// ///////////////////////////
// refresh and return if successful // refresh and return if successful
@Override @Override
@ -514,9 +537,9 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
} }
} }
/////////////////////////// ///////////////////////////
// SendFragment.Listener // SendFragment.Listener
/////////////////////////// ///////////////////////////
@Override @Override
public void onSend(String notes) { public void onSend(String notes) {
@ -561,20 +584,13 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
} }
@Override @Override
public void onPrepareSweep() { public String getWalletAddress() {
if (mIsBound) { // no point in talking to unbound service return getWallet().getAddress();
Intent intent = new Intent(getApplicationContext(), WalletService.class);
intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_SWEEP);
startService(intent);
Log.d(TAG, "SWEEP TX request sent");
} else {
Log.e(TAG, "Service not bound");
}
} }
@Override @Override
public String getWalletAddress() { public String getWalletName() {
return getWallet().getAddress(); return getWallet().getName();
} }
void popFragmentStack(String name) { void popFragmentStack(String name) {
@ -619,6 +635,18 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
.show(); .show();
} }
void onShareTxInfo() {
try {
TxFragment fragment = (TxFragment)
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
fragment.shareTxInfo();
} catch (ClassCastException ex) {
// not in wallet fragment
Log.e(TAG, ex.getLocalizedMessage());
// keep calm and carry on
}
}
@Override @Override
public void onDisposeRequest() { public void onDisposeRequest() {
getWallet().disposePendingTransaction(); getWallet().disposePendingTransaction();
@ -710,7 +738,7 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
@Override @Override
public BarcodeData getScannedData() { public BarcodeData popScannedData() {
BarcodeData data = scannedData; BarcodeData data = scannedData;
scannedData = null; scannedData = null;
return data; return data;
@ -745,6 +773,7 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
Log.d(TAG, "startReceive()"); Log.d(TAG, "startReceive()");
Bundle b = new Bundle(); Bundle b = new Bundle();
b.putString("address", address); b.putString("address", address);
b.putString("name", getWalletName());
startReceiveFragment(b); startReceiveFragment(b);
} }

View File

@ -19,22 +19,25 @@ package com.m2049r.xmrwallet;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.constraint.ConstraintLayout;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button; import android.widget.Button;
import android.widget.LinearLayout; import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import com.m2049r.xmrwallet.layout.AsyncExchangeRate;
import com.m2049r.xmrwallet.layout.Toolbar;
import com.m2049r.xmrwallet.layout.TransactionInfoAdapter; import com.m2049r.xmrwallet.layout.TransactionInfoAdapter;
import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
@ -43,19 +46,24 @@ import com.m2049r.xmrwallet.util.Helper;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.List; import java.util.List;
public class WalletFragment extends Fragment implements TransactionInfoAdapter.OnInteractionListener { public class WalletFragment extends Fragment
implements TransactionInfoAdapter.OnInteractionListener,
AsyncExchangeRate.Listener {
private static final String TAG = "WalletFragment"; private static final String TAG = "WalletFragment";
private TransactionInfoAdapter adapter; private TransactionInfoAdapter adapter;
private NumberFormat formatter = NumberFormat.getInstance(); private NumberFormat formatter = NumberFormat.getInstance();
FrameLayout flExchange;
TextView tvBalance; TextView tvBalance;
TextView tvUnconfirmedAmount; TextView tvUnconfirmedAmount;
TextView tvBlockHeightProgress;
ConstraintLayout clProgress;
TextView tvProgress; TextView tvProgress;
ImageView ivSynced;
ProgressBar pbProgress; ProgressBar pbProgress;
Button bReceive;
Button bSend; Button bSend;
Spinner sCurrency;
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -72,44 +80,145 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.wallet_fragment, container, false); View view = inflater.inflate(R.layout.fragment_wallet, container, false);
flExchange = (FrameLayout) view.findViewById(R.id.flExchange);
((ProgressBar) view.findViewById(R.id.pbExchange)).getIndeterminateDrawable().
setColorFilter(getResources().getColor(R.color.trafficGray),
android.graphics.PorterDuff.Mode.MULTIPLY);
tvProgress = (TextView) view.findViewById(R.id.tvProgress); tvProgress = (TextView) view.findViewById(R.id.tvProgress);
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress); pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
clProgress = (ConstraintLayout) view.findViewById(R.id.clProgress);
tvBalance = (TextView) view.findViewById(R.id.tvBalance); tvBalance = (TextView) view.findViewById(R.id.tvBalance);
tvBalance.setText(getResources().getString(R.string.xmr_balance, Helper.getDisplayAmount(0))); tvBalance.setText(Helper.getDisplayAmount(0));
tvUnconfirmedAmount = (TextView) view.findViewById(R.id.tvUnconfirmedAmount); tvUnconfirmedAmount = (TextView) view.findViewById(R.id.tvUnconfirmedAmount);
tvUnconfirmedAmount.setText(getResources().getString(R.string.xmr_unconfirmed_amount, Helper.getDisplayAmount(0))); tvUnconfirmedAmount.setText(getResources().getString(R.string.xmr_unconfirmed_amount, Helper.getDisplayAmount(0)));
tvBlockHeightProgress = (TextView) view.findViewById(R.id.tvBlockHeightProgress); ivSynced = (ImageView) view.findViewById(R.id.ivSynced);
sCurrency = (Spinner) view.findViewById(R.id.sCurrency);
sCurrency.setAdapter(ArrayAdapter.createFromResource(getContext(), R.array.currency, R.layout.item_spinner));
bSend = (Button) view.findViewById(R.id.bSend); bSend = (Button) view.findViewById(R.id.bSend);
bReceive = (Button) view.findViewById(R.id.bReceive);
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.list); RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.list);
this.adapter = new TransactionInfoAdapter(getActivity(), this); this.adapter = new TransactionInfoAdapter(getActivity(), this);
recyclerView.setAdapter(adapter); recyclerView.setAdapter(adapter);
bSend.setOnClickListener(new View.OnClickListener() bSend.setOnClickListener(new View.OnClickListener() {
{
@Override @Override
public void onClick(View v) { public void onClick(View v) {
activityCallback.onSendRequest(); activityCallback.onSendRequest();
} }
}); });
bReceive.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
activityCallback.onWalletReceive();
}
});
sCurrency.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
refreshBalance();
}
@Override
public void onNothingSelected(AdapterView<?> parentView) {
// nothing (yet?)
}
});
if (activityCallback.isSynced()) { if (activityCallback.isSynced()) {
onSynced(); onSynced();
} }
// activityCallback.setTitle(getString(R.string.status_wallet_loading));
activityCallback.forceUpdate(); activityCallback.forceUpdate();
return view; return view;
} }
String balanceCurrency = "XMR";
double balanceRate = 1.0;
void refreshBalance() {
if (sCurrency.getSelectedItemPosition() == 0) { // XMR
double amountXmr = Double.parseDouble(Wallet.getDisplayAmount(unlockedBalance)); // assume this cannot fail!
tvBalance.setText(Helper.getFormattedAmount(amountXmr, true));
} else { // not XMR
String currency = (String) sCurrency.getSelectedItem();
if (!currency.equals(balanceCurrency) || (balanceRate <= 0)) {
showExchanging();
new AsyncExchangeRate(this).execute("XMR", currency);
} else {
exchange("XMR", balanceCurrency, balanceRate);
}
}
}
boolean isExchanging = false;
void showExchanging() {
isExchanging = true;
tvBalance.setVisibility(View.GONE);
flExchange.setVisibility(View.VISIBLE);
}
void hideExchanging() {
isExchanging = false;
tvBalance.setVisibility(View.VISIBLE);
flExchange.setVisibility(View.GONE);
}
// Callbacks from AsyncExchangeRate
// callback from AsyncExchangeRate when it can't get exchange rate
public void exchangeFailed() {
sCurrency.setSelection(0, true); // default to XMR
double amountXmr = Double.parseDouble(Wallet.getDisplayAmount(unlockedBalance)); // assume this cannot fail!
tvBalance.setText(Helper.getFormattedAmount(amountXmr, true));
hideExchanging();
}
void updateBalance() {
if (isExchanging) return; // wait for exchange to finish - it will fire this itself then.
// at this point selection is XMR in case of error
String displayB;
double amountA = Double.parseDouble(Wallet.getDisplayAmount(unlockedBalance)); // assume this cannot fail!
if (!"XMR".equals(balanceCurrency)) { // not XMR
double amountB = amountA * balanceRate;
displayB = Helper.getFormattedAmount(amountB, false);
} else { // XMR
displayB = Helper.getFormattedAmount(amountA, true);
}
tvBalance.setText(displayB);
}
// callback from AsyncExchangeRate when we have a rate
public void exchange(String currencyA, String currencyB, double rate) {
hideExchanging();
if (!"XMR".equals(currencyA)) {
Log.e(TAG, "Not XMR");
sCurrency.setSelection(0, true);
balanceCurrency = "XMR";
balanceRate = 1.0;
} else {
int spinnerPosition = ((ArrayAdapter) sCurrency.getAdapter()).getPosition(currencyB);
if (spinnerPosition < 0) { // requested currency not in list
Log.e(TAG, "Requested currency not in list " + currencyB);
sCurrency.setSelection(0, true);
} else {
sCurrency.setSelection(spinnerPosition, true);
}
balanceCurrency = currencyB;
balanceRate = rate;
}
updateBalance();
}
// Callbacks from TransactionInfoAdapter // Callbacks from TransactionInfoAdapter
@Override @Override
public void onInteraction(final View view, final TransactionInfo infoItem) { public void onInteraction(final View view, final TransactionInfo infoItem) {
@ -117,6 +226,7 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
} }
// called from activity // called from activity
public void onRefreshed(final Wallet wallet, final boolean full) { public void onRefreshed(final Wallet wallet, final boolean full) {
Log.d(TAG, "onRefreshed()"); Log.d(TAG, "onRefreshed()");
if (full) { if (full) {
@ -134,13 +244,17 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
} }
} }
public void setProgressText(final String text) {
tvProgress.setText(text);
}
public void onProgress(final String text) { public void onProgress(final String text) {
if (text != null) { if (text != null) {
tvProgress.setText(text); setProgressText(text);
showProgress(); pbProgress.setVisibility(View.VISIBLE);
} else { } else {
hideProgress(); pbProgress.setVisibility(View.INVISIBLE);
tvProgress.setText(getString(R.string.status_working)); setProgressText(getString(R.string.status_working));
onProgress(-1); onProgress(-1);
} }
} }
@ -154,48 +268,35 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
} }
} }
public void showProgress() { void setActivityTitle(Wallet wallet) {
clProgress.setVisibility(View.VISIBLE); if (wallet == null) return;
tvBlockHeightProgress.setVisibility(View.GONE); walletTitle = wallet.getName();
} String watchOnly = (wallet.isWatchOnly() ? getString(R.string.label_watchonly) : "");
walletSubtitle = wallet.getAddress().substring(0, 16) + "" + watchOnly;
public void hideProgress() { activityCallback.setTitle(walletTitle, walletSubtitle);
clProgress.setVisibility(View.GONE); Log.d(TAG, "wallet title is " + walletTitle);
tvBlockHeightProgress.setVisibility(View.VISIBLE);
}
String setActivityTitle(Wallet wallet) {
if (wallet == null) return null;
String shortName = wallet.getName();
if (shortName.length() > 16) {
shortName = shortName.substring(0, 14) + "...";
}
// TODO very very rarely this craches because getAddress returns "" or so ...
// maybe because this runs in the ui thread and not in a 5MB thread
String title = "[" + wallet.getAddress().substring(0, 6) + "] " + shortName;
activityCallback.setTitle(title);
String watchOnly = (wallet.isWatchOnly() ? " " + getString(R.string.watchonly_label) : "");
String net = (wallet.isTestNet() ? getString(R.string.connect_testnet) : getString(R.string.connect_mainnet));
activityCallback.setSubtitle(net + " " + watchOnly);
Log.d(TAG, "wallet title is " + title);
return title;
} }
private long firstBlock = 0; private long firstBlock = 0;
private String walletTitle = null; private String walletTitle = null;
private String walletSubtitle = null;
private long unlockedBalance = 0;
private void updateStatus(Wallet wallet) { private void updateStatus(Wallet wallet) {
if (!isAdded()) return; if (!isAdded()) return;
Log.d(TAG, "updateStatus()"); Log.d(TAG, "updateStatus()");
if (walletTitle == null) { if (walletTitle == null) {
walletTitle = setActivityTitle(wallet); setActivityTitle(wallet);
onProgress(100); // of loading onProgress(100); // of loading
} }
long balance = wallet.getBalance(); long balance = wallet.getBalance();
long unlockedBalance = wallet.getUnlockedBalance(); unlockedBalance = wallet.getUnlockedBalance();
tvBalance.setText(getResources().getString(R.string.xmr_balance, Helper.getDisplayAmount(unlockedBalance))); refreshBalance();
tvUnconfirmedAmount.setText(getResources().getString(R.string.xmr_unconfirmed_amount, Helper.getDisplayAmount(balance - unlockedBalance))); double amountXmr = Double.parseDouble(Helper.getDisplayAmount(balance - unlockedBalance)); // assume this cannot fail!
String unconfirmed = Helper.getFormattedAmount(amountXmr, true);
tvUnconfirmedAmount.setText(getResources().getString(R.string.xmr_unconfirmed_amount, unconfirmed));
//tvUnconfirmedAmount.setText(getResources().getString(R.string.xmr_unconfirmed_amount,
// Helper.getDisplayAmount(balance - unlockedBalance, Helper.DISPLAY_DIGITS_SHORT)));
String sync = ""; String sync = "";
if (!activityCallback.hasBoundService()) if (!activityCallback.hasBoundService())
throw new IllegalStateException("WalletService not bound."); throw new IllegalStateException("WalletService not bound.");
@ -212,13 +313,13 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
onProgress(getString(R.string.status_syncing) + " " + sync); onProgress(getString(R.string.status_syncing) + " " + sync);
if (x == 0) x = -1; if (x == 0) x = -1;
onProgress(x); onProgress(x);
ivSynced.setVisibility(View.GONE);
} else { } else {
sync = getString(R.string.status_synced) + ": " + formatter.format(wallet.getBlockChainHeight()); sync = getString(R.string.status_synced) + ": " + formatter.format(wallet.getBlockChainHeight());
ivSynced.setVisibility(View.VISIBLE);
} }
} }
tvBlockHeightProgress.setText(sync); setProgressText(sync);
//String net = (wallet.isTestNet() ? getString(R.string.connect_testnet) : getString(R.string.connect_mainnet));
//activityCallback.setSubtitle(net + " " + daemonConnected.toString().substring(17));
// TODO show connected status somewhere // TODO show connected status somewhere
} }
@ -234,10 +335,6 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
long getDaemonHeight(); //mBoundService.getDaemonHeight(); long getDaemonHeight(); //mBoundService.getDaemonHeight();
void setTitle(String title);
void setSubtitle(String subtitle);
void onSendRequest(); void onSendRequest();
void onTxDetailsRequest(TransactionInfo info); void onTxDetailsRequest(TransactionInfo info);
@ -251,6 +348,12 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
void onWalletReceive(); void onWalletReceive();
boolean hasWallet(); boolean hasWallet();
void setToolbarButton(int type);
void setTitle(String title, String subtitle);
void setSubtitle(String subtitle);
} }
@Override @Override
@ -263,4 +366,12 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
+ " must implement Listener"); + " must implement Listener");
} }
} }
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "onResume()");
activityCallback.setTitle(walletTitle, walletSubtitle);
activityCallback.setToolbarButton(Toolbar.BUTTON_CLOSE);
}
} }

View File

@ -26,6 +26,7 @@ import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction; import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.webkit.WebView; import android.webkit.WebView;
@ -43,6 +44,7 @@ import java.io.InputStreamReader;
* http://speakman.net.nz * http://speakman.net.nz
*/ */
public class HelpFragment extends DialogFragment { public class HelpFragment extends DialogFragment {
static final String TAG = "HelpFragment";
private static final String FRAGMENT_TAG = "com.m2049r.xmrwallet.dialog.HelpFragment"; private static final String FRAGMENT_TAG = "com.m2049r.xmrwallet.dialog.HelpFragment";
private static final String HELP_ID = "HELP_ID"; private static final String HELP_ID = "HELP_ID";
@ -67,7 +69,6 @@ public class HelpFragment extends DialogFragment {
if (prev != null) { if (prev != null) {
ft.remove(prev); ft.remove(prev);
} }
ft.addToBackStack(null);
// Create and show the dialog. // Create and show the dialog.
DialogFragment newFragment = HelpFragment.newInstance(helpResourceId); DialogFragment newFragment = HelpFragment.newInstance(helpResourceId);
@ -100,7 +101,7 @@ public class HelpFragment extends DialogFragment {
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
@Override @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
View content = LayoutInflater.from(getActivity()).inflate(R.layout.help_fragment, null); View content = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_help, null);
webView = (WebView) content.findViewById(R.id.helpFragmentWebView); webView = (WebView) content.findViewById(R.id.helpFragmentWebView);
progress = (ProgressBar) content.findViewById(R.id.helpFragmentProgress); progress = (ProgressBar) content.findViewById(R.id.helpFragmentProgress);
@ -136,8 +137,8 @@ public class HelpFragment extends DialogFragment {
sb.append("\n"); sb.append("\n");
} }
bufferedReader.close(); bufferedReader.close();
} catch (IOException e) { } catch (IOException ex) {
// TODO You may want to include some logging here. Log.e(TAG, ex.getLocalizedMessage());
} }
return sb.toString(); return sb.toString();

View File

@ -16,22 +16,17 @@
package com.m2049r.xmrwallet.dialog; package com.m2049r.xmrwallet.dialog;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
//TODO If you don't support Android 2.x, you should use the non-support version!
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction; import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.webkit.WebView; import android.webkit.WebView;
@ -41,11 +36,17 @@ import android.widget.TextView;
import com.m2049r.xmrwallet.BuildConfig; import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/** /**
* Created by Adam Speakman on 24/09/13. * Created by Adam Speakman on 24/09/13.
* http://speakman.net.nz * http://speakman.net.nz
*/ */
public class LicensesFragment extends DialogFragment { public class LicensesFragment extends DialogFragment {
static final String TAG = "LicensesFragment";
int versionCode = BuildConfig.VERSION_CODE; int versionCode = BuildConfig.VERSION_CODE;
String versionName = BuildConfig.VERSION_NAME; String versionName = BuildConfig.VERSION_NAME;
@ -102,7 +103,7 @@ public class LicensesFragment extends DialogFragment {
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
@Override @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
View content = LayoutInflater.from(getActivity()).inflate(R.layout.licenses_fragment, null); View content = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_licenses, null);
mWebView = (WebView) content.findViewById(R.id.licensesFragmentWebView); mWebView = (WebView) content.findViewById(R.id.licensesFragmentWebView);
mIndeterminateProgress = (ProgressBar) content.findViewById(R.id.licensesFragmentIndeterminateProgress); mIndeterminateProgress = (ProgressBar) content.findViewById(R.id.licensesFragmentIndeterminateProgress);
@ -140,8 +141,8 @@ public class LicensesFragment extends DialogFragment {
sb.append("\n"); sb.append("\n");
} }
bufferedReader.close(); bufferedReader.close();
} catch (IOException e) { } catch (IOException ex) {
// TODO You may want to include some logging here. Log.e(TAG, ex.getLocalizedMessage());
} }
return sb.toString(); return sb.toString();

View File

@ -0,0 +1,159 @@
/*
* 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.layout;
import android.os.AsyncTask;
import android.util.Log;
import com.m2049r.xmrwallet.util.Helper;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.lang.ref.WeakReference;
public class AsyncExchangeRate extends AsyncTask<String, Void, Boolean> {
static final String TAG = "AsyncExchangeRate";
static final long TIME_REFRESH_INTERVAL = 60000; // refresh exchange rate max every minute
public interface Listener {
void exchangeFailed();
// callback from AsyncExchangeRate when we have a rate
void exchange(String currencyA, String currencyB, double rate);
}
static long RateTime = 0;
static double Rate = 0;
static String Fiat = null;
private final WeakReference<Listener> exchangeViewRef;
public AsyncExchangeRate(Listener exchangeView) {
super();
exchangeViewRef = new WeakReference<>(exchangeView);
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
boolean inverse = false;
String currencyA = null;
String currencyB = null;
@Override
protected Boolean doInBackground(String... params) {
if (params.length != 2) return false;
Log.d(TAG, "Getting " + params[0]);
currencyA = params[0];
currencyB = params[1];
String fiat = null;
if (currencyA.equals("XMR")) {
fiat = currencyB;
inverse = false;
}
if (currencyB.equals("XMR")) {
fiat = currencyA;
inverse = true;
}
if (currencyA.equals(currencyB)) {
Fiat = null;
Rate = 1;
RateTime = System.currentTimeMillis();
return true;
}
if (fiat == null) {
Fiat = null;
Rate = 0;
RateTime = 0;
return false;
}
if (!fiat.equals(Fiat)) { // new currency - reset all
Fiat = fiat;
Rate = 0;
RateTime = 0;
}
if (System.currentTimeMillis() > RateTime + TIME_REFRESH_INTERVAL) {
Log.d(TAG, "Fetching " + Fiat);
String closePrice = getExchangeRate(Fiat);
if (closePrice != null) {
try {
Rate = Double.parseDouble(closePrice);
RateTime = System.currentTimeMillis();
return true;
} catch (NumberFormatException ex) {
Rate = 0;
Log.e(TAG, ex.getLocalizedMessage());
return false;
}
} else {
Rate = 0;
Log.e(TAG, "exchange url failed");
return false;
}
}
return true; // no change but still valid
}
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
Listener exchangeView = exchangeViewRef.get();
if (result) {
Log.d(TAG, "yay! = " + Rate);
if (exchangeView != null) {
exchangeView.exchange(currencyA, currencyB, inverse ? (1 / Rate) : Rate);
}
} else {
Log.d(TAG, "nay!");
if (exchangeView != null) {
exchangeView.exchangeFailed();
}
}
}
// "https://api.kraken.com/0/public/Ticker?pair=XMREUR"
String getExchangeRate(String fiat) {
String jsonResponse =
Helper.getUrl("https://api.kraken.com/0/public/Ticker?pair=XMR" + fiat);
if (jsonResponse == null) return null;
try {
JSONObject response = new JSONObject(jsonResponse);
JSONArray errors = response.getJSONArray("error");
Log.e(TAG, "errors=" + errors.toString());
if (errors.length() == 0) {
JSONObject result = response.getJSONObject("result");
JSONObject pair = result.getJSONObject("XXMRZ" + fiat);
JSONArray close = pair.getJSONArray("c");
String closePrice = close.getString(0);
Log.d(TAG, "closePrice=" + closePrice);
return closePrice;
}
} catch (JSONException ex) {
Log.e(TAG, ex.getLocalizedMessage());
}
return null;
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.
*/
// based on from https://stackoverflow.com/a/45325876 (which did not work for me)
package com.m2049r.xmrwallet.layout;
import android.content.Context;
import android.support.design.widget.TextInputLayout;
import android.util.AttributeSet;
import android.widget.EditText;
public class CTextInputLayout extends TextInputLayout {
public CTextInputLayout(Context context) {
super(context);
}
public CTextInputLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CTextInputLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public int getBaseline() {
EditText editText = getEditText();
return editText.getBaseline() - (getMeasuredHeight() - editText.getMeasuredHeight());
}
}

View File

@ -0,0 +1,399 @@
/*
* 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.
*/
// based on https://code.tutsplus.com/tutorials/creating-compound-views-on-android--cms-22889
package com.m2049r.xmrwallet.layout;
import android.content.Context;
import android.support.design.widget.TextInputLayout;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.Helper;
import java.util.Locale;
public class ExchangeView extends LinearLayout implements AsyncExchangeRate.Listener {
static final String TAG = "ExchangeView";
public boolean focus() {
return etAmount.requestFocus();
}
public void enable(boolean enable) {
etAmount.setEnabled(enable);
sCurrencyA.setEnabled(enable);
sCurrencyB.setEnabled(enable);
}
String xmrAmount = null;
String notXmrAmount = null;
void setXmr(String xmr) {
xmrAmount = xmr;
if (onNewAmountListener != null) {
onNewAmountListener.onNewAmount(xmr);
}
}
public void setAmount(String xmrAmount) {
if (xmrAmount != null) {
setCurrencyA(0);
setXmr(xmrAmount);
this.notXmrAmount = null;
doExchange();
} else {
setXmr(null);
this.notXmrAmount = null;
tvAmountB.setText("--");
}
}
public String getAmount() {
return xmrAmount;
}
public void setError(String msg) {
etAmount.setError(msg);
}
TextInputLayout etAmount;
TextView tvAmountB;
Spinner sCurrencyA;
Spinner sCurrencyB;
ImageView evExchange;
ProgressBar pbExchange;
public void setCurrencyA(int currency) {
if ((currency != 0) && (getCurrencyB() != 0)) {
setCurrencyB(0);
}
sCurrencyA.setSelection(currency, true);
doExchange();
}
public void setCurrencyB(int currency) {
if ((currency != 0) && (getCurrencyA() != 0)) {
setCurrencyA(0);
}
sCurrencyB.setSelection(currency, true);
doExchange();
}
public int getCurrencyA() {
return sCurrencyA.getSelectedItemPosition();
}
public int getCurrencyB() {
return sCurrencyB.getSelectedItemPosition();
}
public ExchangeView(Context context) {
super(context);
initializeViews(context);
}
public ExchangeView(Context context, AttributeSet attrs) {
super(context, attrs);
initializeViews(context);
}
public ExchangeView(Context context,
AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
initializeViews(context);
}
/**
* Inflates the views in the layout.
*
* @param context the current context for the view.
*/
private void initializeViews(Context context) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.view_exchange, this);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
etAmount = (TextInputLayout) findViewById(R.id.etAmount);
tvAmountB = (TextView) findViewById(R.id.tvAmountB);
sCurrencyA = (Spinner) findViewById(R.id.sCurrencyA);
sCurrencyB = (Spinner) findViewById(R.id.sCurrencyB);
evExchange = (ImageView) findViewById(R.id.evExchange);
pbExchange = (ProgressBar) findViewById(R.id.pbExchange);
// make progress circle gray
pbExchange.getIndeterminateDrawable().
setColorFilter(getResources().getColor(R.color.trafficGray),
android.graphics.PorterDuff.Mode.MULTIPLY);
sCurrencyA.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
if (position != 0) { // if not XMR, select XMR on other
sCurrencyB.setSelection(0, true);
}
doExchange();
}
@Override
public void onNothingSelected(AdapterView<?> parentView) {
// nothing (yet?)
}
});
sCurrencyB.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
if (position != 0) { // if not XMR, select XMR on other
sCurrencyA.setSelection(0, true);
}
doExchange();
}
@Override
public void onNothingSelected(AdapterView<?> parentView) {
// nothing (yet?)
}
});
etAmount.getEditText().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)) {
doExchange();
return true;
}
return false;
}
});
etAmount.getEditText().addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable editable) {
etAmount.setError(null);
if ((xmrAmount != null) || (notXmrAmount != null)) {
tvAmountB.setText("--");
setXmr(null);
notXmrAmount = null;
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
}
final static double MAX_AMOUNT_XMR = 1000;
final static double MAX_AMOUNT_NOTXMR = 100000;
public boolean checkEnteredAmount() {
boolean ok = true;
Log.d(TAG, "checkEnteredAmount");
String amountEntry = etAmount.getEditText().getText().toString();
if (!amountEntry.isEmpty()) {
try {
double a = Double.parseDouble(amountEntry);
double maxAmount = (getCurrencyA() == 0) ? MAX_AMOUNT_XMR : MAX_AMOUNT_NOTXMR;
if (a > (maxAmount)) {
etAmount.setError(getResources().
getString(R.string.receive_amount_too_big,
String.format(Locale.US, "%,.0f", maxAmount)));
ok = false;
} else if (a < 0) {
etAmount.setError(getResources().getString(R.string.receive_amount_negative));
ok = false;
}
} catch (NumberFormatException ex) {
etAmount.setError(getResources().getString(R.string.receive_amount_nan));
ok = false;
}
}
if (ok) {
etAmount.setError(null);
}
return ok;
}
int selectedNotXmrCurrency() {
return Math.max(getCurrencyA(), getCurrencyB());
}
public void doExchange() {
tvAmountB.setText("--");
// TODO cache & use cached exchange rate here
startExchange();
}
void startExchange() {
showProgress();
String currencyA = (String) sCurrencyA.getSelectedItem();
String currencyB = (String) sCurrencyB.getSelectedItem();
new AsyncExchangeRate(this).execute(currencyA, currencyB);
}
public void exchange(double rate) {
if (getCurrencyA() == 0) {
if (!xmrAmount.isEmpty() && (rate > 0)) {
double amountB = rate * Double.parseDouble(xmrAmount);
notXmrAmount = Helper.getFormattedAmount(amountB, getCurrencyB() == 0);
} else {
notXmrAmount = "";
}
tvAmountB.setText(notXmrAmount);
} else if (getCurrencyB() == 0) {
if (!notXmrAmount.isEmpty() && (rate > 0)) {
double amountB = rate * Double.parseDouble(notXmrAmount);
setXmr(Helper.getFormattedAmount(amountB, true));
} else {
setXmr("");
}
tvAmountB.setText(xmrAmount);
} else { // no XMR currency - cannot happen!
Log.e(TAG, "No XMR currency!");
setXmr(null);
notXmrAmount = null;
return;
}
}
boolean prepareExchange() {
Log.d(TAG, "prepareExchange()");
if (checkEnteredAmount()) {
String enteredAmount = etAmount.getEditText().getText().toString();
if (!enteredAmount.isEmpty()) {
String cleanAmount = "";
if (getCurrencyA() == 0) {
// sanitize the input
cleanAmount = Helper.getDisplayAmount(Wallet.getAmountFromString(enteredAmount));
setXmr(cleanAmount);
notXmrAmount = null;
Log.d(TAG, "cleanAmount = " + cleanAmount);
} else if (getCurrencyB() == 0) { // we use B & 0 here for the else below ...
// sanitize the input
double amountA = Double.parseDouble(enteredAmount);
cleanAmount = String.format(Locale.US, "%.2f", amountA);
setXmr(null);
notXmrAmount = cleanAmount;
} else { // no XMR currency - cannot happen!
Log.e(TAG, "No XMR currency!");
setXmr(null);
notXmrAmount = null;
return false;
}
Log.d(TAG, "prepareExchange() " + cleanAmount);
//etAmount.getEditText().setText(cleanAmount); // display what we use
} else {
setXmr("");
notXmrAmount = "";
}
return true;
} else {
setXmr(null);
notXmrAmount = null;
return false;
}
}
// callback from AsyncExchangeRate when it failed getting a rate
public void exchangeFailed() {
hideProgress();
exchange(0);
// TODO Toast it failed - I think this happens elsewhere already
}
// callback from AsyncExchangeRate when we have a rate
public void exchange(String currencyA, String currencyB, double rate) {
hideProgress();
// first, make sure this is what we want
String enteredCurrencyA = (String) sCurrencyA.getSelectedItem();
String enteredCurrencyB = (String) sCurrencyB.getSelectedItem();
if (!currencyA.equals(enteredCurrencyA) || !currencyB.equals(enteredCurrencyB)) {
// something's wrong
Log.e(TAG, "Currencies don't match!");
return;
}
if (prepareExchange()) {
exchange(rate);
}
}
private void showProgress() {
pbExchange.setVisibility(View.VISIBLE);
}
private void hideProgress() {
pbExchange.setVisibility(View.INVISIBLE);
}
// Hooks
public interface OnNewAmountListener {
void onNewAmount(String xmr);
}
OnNewAmountListener onNewAmountListener;
public void setOnNewAmountListener(OnNewAmountListener listener) {
onNewAmountListener = listener;
}
public interface OnAmountInvalidatedListener {
void onAmountInvalidated();
}
OnAmountInvalidatedListener onAmountInvalidatedListener;
public void setOnAmountInvalidatedListener(OnAmountInvalidatedListener listener) {
onAmountInvalidatedListener = listener;
}
public interface OnFailedExchangeListener {
void onFailedExchange();
}
OnFailedExchangeListener onFailedExchangeListener;
public void setOnFailedExchangeListener(OnFailedExchangeListener listener) {
onFailedExchangeListener = listener;
}
}

View File

@ -0,0 +1,155 @@
/*
* 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.
*/
// based on https://code.tutsplus.com/tutorials/creating-compound-views-on-android--cms-22889
package com.m2049r.xmrwallet.layout;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.m2049r.xmrwallet.R;
public class Toolbar extends android.support.v7.widget.Toolbar {
static final String TAG = "Toolbar";
public interface OnButtonListener {
void onButton(int type);
}
OnButtonListener onButtonListener;
public void setOnButtonListener(OnButtonListener listener) {
onButtonListener = listener;
}
ImageView toolbarImage;
TextView toolbarTitle;
TextView toolbarSubtitle;
Button bDonate;
public Toolbar(Context context) {
super(context);
initializeViews(context);
}
public Toolbar(Context context, AttributeSet attrs) {
super(context, attrs);
initializeViews(context);
}
public Toolbar(Context context,
AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
initializeViews(context);
}
/**
* Inflates the views in the layout.
*
* @param context the current context for the view.
*/
private void initializeViews(Context context) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.view_toolbar, this);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
toolbarImage = (ImageView) findViewById(R.id.toolbarImage);
toolbarTitle = (TextView) findViewById(R.id.toolbarTitle);
toolbarSubtitle = (TextView) findViewById(R.id.toolbarSubtitle);
bDonate = (Button) findViewById(R.id.bDonate);
bDonate.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (onButtonListener != null) {
onButtonListener.onButton(buttonType);
}
}
});
}
public void setTitle(String title, String subtitle) {
setTitle(title);
setSubtitle(subtitle);
}
public void setTitle(String title) {
toolbarTitle.setText(title);
if (title != null) {
toolbarImage.setVisibility(View.INVISIBLE);
toolbarTitle.setVisibility(View.VISIBLE);
} else {
toolbarImage.setVisibility(View.VISIBLE);
toolbarTitle.setVisibility(View.INVISIBLE);
}
}
public final static int BUTTON_NONE = 0;
public final static int BUTTON_BACK = 1;
public final static int BUTTON_CLOSE = 2;
public final static int BUTTON_DONATE = 3;
int buttonType = BUTTON_DONATE;
public void setButton(int type) {
switch (type) {
case BUTTON_BACK:
Log.d(TAG, "BUTTON_BACK");
bDonate.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_arrow_back_white_24dp, 0, 0, 0);
bDonate.setText(null);
bDonate.setVisibility(View.VISIBLE);
break;
case BUTTON_CLOSE:
Log.d(TAG, "BUTTON_CLOSE");
bDonate.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_close_white_24dp, 0, 0, 0);
bDonate.setText(R.string.label_close);
bDonate.setVisibility(View.VISIBLE);
break;
case BUTTON_DONATE:
Log.d(TAG, "BUTTON_DONATE");
bDonate.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_favorite_white_24dp, 0, 0, 0);
bDonate.setText(R.string.label_donate);
bDonate.setVisibility(View.VISIBLE);
break;
case BUTTON_NONE:
default:
Log.d(TAG, "BUTTON_NONE");
bDonate.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
bDonate.setText(null);
bDonate.setVisibility(View.INVISIBLE);
}
buttonType = type;
}
public void setSubtitle(String subtitle) {
toolbarSubtitle.setText(subtitle);
if (subtitle != null) {
toolbarSubtitle.setVisibility(View.VISIBLE);
} else {
toolbarSubtitle.setVisibility(View.INVISIBLE);
}
}
}

View File

@ -17,7 +17,6 @@
package com.m2049r.xmrwallet.layout; package com.m2049r.xmrwallet.layout;
import android.content.Context; import android.content.Context;
import android.graphics.Color;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.util.Log; import android.util.Log;
@ -28,13 +27,12 @@ import android.widget.TextView;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.util.Helper;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.TimeZone; import java.util.TimeZone;
@ -69,7 +67,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
outboundColour = ContextCompat.getColor(context, R.color.tx_red); outboundColour = ContextCompat.getColor(context, R.color.tx_red);
pendingColour = ContextCompat.getColor(context, R.color.tx_pending); pendingColour = ContextCompat.getColor(context, R.color.tx_pending);
failedColour = ContextCompat.getColor(context, R.color.tx_failed); failedColour = ContextCompat.getColor(context, R.color.tx_failed);
this.infoItems = new ArrayList<>(); infoItems = new ArrayList<>();
this.listener = listener; this.listener = listener;
Calendar cal = Calendar.getInstance(); Calendar cal = Calendar.getInstance();
TimeZone tz = cal.getTimeZone(); //get the local time zone. TimeZone tz = cal.getTimeZone(); //get the local time zone.
@ -79,7 +77,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
@Override @Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()) View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.transaction_item, parent, false); .inflate(R.layout.item_transaction, parent, false);
return new ViewHolder(view); return new ViewHolder(view);
} }
@ -99,22 +97,8 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
this.infoItems.clear(); this.infoItems.clear();
if (data != null) { if (data != null) {
Log.d(TAG, "setInfos " + data.size()); Log.d(TAG, "setInfos " + data.size());
// sort by block height infoItems.addAll(data);
Collections.sort(data, new Comparator<TransactionInfo>() { Collections.sort(infoItems);
@Override
public int compare(TransactionInfo o1, TransactionInfo o2) {
long b1 = o1.timestamp;
long b2 = o2.timestamp;
if (b1 > b2) {
return -1;
} else if (b1 < b2) {
return 1;
} else {
return o1.hash.compareTo(o2.hash);
}
}
});
this.infoItems.addAll(data);
} else { } else {
Log.d(TAG, "setInfos null"); Log.d(TAG, "setInfos null");
} }
@ -147,36 +131,41 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
void bind(int position) { void bind(int position) {
this.infoItem = infoItems.get(position); this.infoItem = infoItems.get(position);
String displayAmount = Wallet.getDisplayAmount(infoItem.amount);
// TODO fix this with i8n code but cryptonote::print_money always uses '.' for decimal point long realAmount = infoItem.amount;
String amount = displayAmount.substring(0, displayAmount.length() - (12 - 5)); if (infoItem.isPending) {
this.tvAmount.setText(amount); realAmount = realAmount - infoItem.fee;
}
String displayAmount = Helper.getDisplayAmount(realAmount, Helper.DISPLAY_DIGITS_INFO);
if (infoItem.direction == TransactionInfo.Direction.Direction_Out) {
this.tvAmount.setText(context.getString(R.string.tx_list_amount_negative, displayAmount));
} else {
this.tvAmount.setText(context.getString(R.string.tx_list_amount_positive, displayAmount));
}
if ((infoItem.fee > 0)) { if ((infoItem.fee > 0)) {
String feeAmount = Wallet.getDisplayAmount(infoItem.fee); String fee = Helper.getDisplayAmount(infoItem.fee, 5);
String fee = feeAmount.substring(0, feeAmount.length() - (12 - 5)); this.tvFee.setText(context.getString(R.string.tx_list_fee, fee));
if (infoItem.isPending) {
this.tvFee.setText(context.getString(R.string.tx_list_fee_pending, fee));
} else {
this.tvFee.setText(context.getString(R.string.tx_list_fee, fee));
}
} else { } else {
this.tvFee.setText(""); this.tvFee.setText("");
} }
if (infoItem.isFailed) { if (infoItem.isFailed) {
this.tvAmount.setText(context.getString(R.string.tx_list_amount_failed, amount)); this.tvAmount.setText(context.getString(R.string.tx_list_amount_failed, displayAmount));
this.tvFee.setText(context.getString(R.string.tx_list_failed_text));
setTxColour(failedColour); setTxColour(failedColour);
} else if (infoItem.isPending) { } else if (infoItem.isPending) {
setTxColour(pendingColour); setTxColour(pendingColour);
if (infoItem.direction == TransactionInfo.Direction.Direction_Out) {
this.tvAmount.setText(context.getString(R.string.tx_list_amount_negative, amount));
}
} else if (infoItem.direction == TransactionInfo.Direction.Direction_In) { } else if (infoItem.direction == TransactionInfo.Direction.Direction_In) {
setTxColour(inboundColour); setTxColour(inboundColour);
} else { } else {
setTxColour(outboundColour); setTxColour(outboundColour);
} }
this.tvPaymentId.setText(infoItem.paymentId.equals("0000000000000000") ? "" : infoItem.paymentId); if ((infoItem.notes == null) || (infoItem.notes.isEmpty())) {
this.tvPaymentId.setText(infoItem.paymentId.equals("0000000000000000") ? "" : infoItem.paymentId);
} else {
this.tvPaymentId.setText(infoItem.notes);
}
this.tvDateTime.setText(getDateTime(infoItem.timestamp)); this.tvDateTime.setText(getDateTime(infoItem.timestamp));
itemView.setOnClickListener(this); itemView.setOnClickListener(this);

View File

@ -0,0 +1,157 @@
/*
* 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.layout;
import android.content.Context;
import android.support.v7.widget.PopupMenu;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.model.WalletManager;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.ViewHolder> {
private static final String TAG = "WalletInfoAdapter";
private final SimpleDateFormat DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
public interface OnInteractionListener {
void onInteraction(View view, WalletManager.WalletInfo item);
boolean onContextInteraction(MenuItem item, WalletManager.WalletInfo infoItem);
}
private final List<WalletManager.WalletInfo> infoItems;
private final OnInteractionListener listener;
Context context;
public WalletInfoAdapter(Context context, OnInteractionListener listener) {
this.context = context;
this.infoItems = new ArrayList<>();
this.listener = listener;
Calendar cal = Calendar.getInstance();
TimeZone tz = cal.getTimeZone(); //get the local time zone.
DATETIME_FORMATTER.setTimeZone(tz);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_wallet, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
holder.bind(position);
}
@Override
public int getItemCount() {
return infoItems.size();
}
public WalletManager.WalletInfo getItem(int position) {
return infoItems.get(position);
}
public void setInfos(List<WalletManager.WalletInfo> data) {
// TODO do stuff with data so we can really recycle elements (i.e. add only new tx)
// as the WalletInfo items are always recreated, we cannot recycle
infoItems.clear();
if (data != null) {
Log.d(TAG, "setInfos " + data.size());
infoItems.addAll(data);
Collections.sort(infoItems);
} else {
Log.d(TAG, "setInfos null");
}
notifyDataSetChanged();
}
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
final TextView tvName;
final TextView tvAddress;
final ImageButton ibOptions;
WalletManager.WalletInfo infoItem;
ViewHolder(View itemView) {
super(itemView);
tvName = (TextView) itemView.findViewById(R.id.tvName);
tvAddress = (TextView) itemView.findViewById(R.id.tvAddress);
ibOptions = (ImageButton) itemView.findViewById(R.id.ibOptions);
ibOptions.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//creating a popup menu
PopupMenu popup = new PopupMenu(context, ibOptions);
//inflating menu from xml resource
popup.inflate(R.menu.list_context_menu);
//adding click listener
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
if (listener != null) {
return listener.onContextInteraction(item, infoItem);
}
return false;
}
});
//displaying the popup
popup.show();
}
});
itemView.setOnClickListener(this);
}
private String getDateTime(long time) {
return DATETIME_FORMATTER.format(new Date(time * 1000));
}
void bind(int position) {
infoItem = infoItems.get(position);
tvName.setText(infoItem.name);
tvAddress.setText(infoItem.address.substring(0, 16) + "...");
}
@Override
public void onClick(View view) {
if (listener != null) {
int position = getAdapterPosition(); // gets item position
if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it
listener.onInteraction(view, infoItems.get(position));
}
}
}
}
}

View File

@ -30,6 +30,12 @@ public class TransactionHistory {
this.handle = handle; this.handle = handle;
} }
public void loadNotes(Wallet wallet) {
for (TransactionInfo info : transactions) {
info.notes = wallet.getUserNote(info.hash);
}
}
public native int getCount(); public native int getCount();
//private native long getTransactionByIndexJ(int i); //private native long getTransactionByIndexJ(int i);
@ -42,6 +48,11 @@ public class TransactionHistory {
private List<TransactionInfo> transactions = new ArrayList<>(); private List<TransactionInfo> transactions = new ArrayList<>();
public void refreshWithNotes(Wallet wallet) {
refresh();
loadNotes(wallet);
}
public void refresh() { public void refresh() {
transactions = refreshJ(); transactions = refreshJ();
} }

View File

@ -25,7 +25,7 @@ import java.util.Random;
// this is not the TransactionInfo from the API as that is owned by the TransactionHistory // this is not the TransactionInfo from the API as that is owned by the TransactionHistory
// this is a POJO for the TransactionInfoAdapter // this is a POJO for the TransactionInfoAdapter
public class TransactionInfo implements Parcelable { public class TransactionInfo implements Parcelable, Comparable<TransactionInfo> {
static final String TAG = "TransactionInfo"; static final String TAG = "TransactionInfo";
public enum Direction { public enum Direction {
@ -92,7 +92,6 @@ public class TransactionInfo implements Parcelable {
this.confirmations = confirmations; this.confirmations = confirmations;
this.transfers = transfers; this.transfers = transfers;
} }
Random rnd = new Random();
public String toString() { public String toString() {
return direction + "@" + blockheight + " " + amount; return direction + "@" + blockheight + " " + amount;
@ -146,4 +145,17 @@ public class TransactionInfo implements Parcelable {
return 0; return 0;
} }
@Override
public int compareTo(TransactionInfo another) {
long b1 = this.timestamp;
long b2 = another.timestamp;
if (b1 > b2) {
return -1;
} else if (b1 < b2) {
return 1;
} else {
return this.hash.compareTo(another.hash);
}
}
} }

View File

@ -144,10 +144,20 @@ public class WalletManager {
//public native List<String> findWallets(String path); // this does not work - some error in boost //public native List<String> findWallets(String path); // this does not work - some error in boost
public class WalletInfo { public class WalletInfo implements Comparable<WalletInfo> {
public File path; public File path;
public String name; public String name;
public String address; public String address;
@Override
public int compareTo(WalletInfo another) {
int n = name.toLowerCase().compareTo(another.name.toLowerCase());
if (n != 0) {
return n;
} else { // wallet names are the same
return address.compareTo(another.address);
}
}
} }
public WalletInfo getWalletInfo(File wallet) { public WalletInfo getWalletInfo(File wallet) {

View File

@ -147,7 +147,7 @@ public class WalletService extends Service {
if (updated) { if (updated) {
if (observer != null) { if (observer != null) {
updateDaemonState(wallet, 0); updateDaemonState(wallet, 0);
wallet.getHistory().refresh(); wallet.getHistory().refreshWithNotes(wallet);
if (observer != null) { if (observer != null) {
updated = !observer.onRefreshed(wallet, true); updated = !observer.onRefreshed(wallet, true);
} }
@ -297,7 +297,7 @@ public class WalletService extends Service {
boolean rc = myWallet.store(); boolean rc = myWallet.store();
Log.d(TAG, "wallet stored: " + myWallet.getName() + " with rc=" + rc); Log.d(TAG, "wallet stored: " + myWallet.getName() + " with rc=" + rc);
if (!rc) { if (!rc) {
Log.d(TAG, "Wallet store failed: " + myWallet.getErrorString()); Log.w(TAG, "Wallet store failed: " + myWallet.getErrorString());
} }
if (observer != null) observer.onWalletStored(rc); if (observer != null) observer.onWalletStored(rc);
} else if (cmd.equals(REQUEST_CMD_TX)) { } else if (cmd.equals(REQUEST_CMD_TX)) {
@ -309,7 +309,7 @@ public class WalletService extends Service {
PendingTransaction.Status status = pendingTransaction.getStatus(); PendingTransaction.Status status = pendingTransaction.getStatus();
Log.d(TAG, "transaction status " + status); Log.d(TAG, "transaction status " + status);
if (status != PendingTransaction.Status.Status_Ok) { if (status != PendingTransaction.Status.Status_Ok) {
Log.d(TAG, "Create Transaction failed: " + pendingTransaction.getErrorString()); Log.w(TAG, "Create Transaction failed: " + pendingTransaction.getErrorString());
} }
if (observer != null) { if (observer != null) {
observer.onCreatedTransaction(pendingTransaction); observer.onCreatedTransaction(pendingTransaction);
@ -323,7 +323,7 @@ public class WalletService extends Service {
PendingTransaction.Status status = pendingTransaction.getStatus(); PendingTransaction.Status status = pendingTransaction.getStatus();
Log.d(TAG, "transaction status " + status); Log.d(TAG, "transaction status " + status);
if (status != PendingTransaction.Status.Status_Ok) { if (status != PendingTransaction.Status.Status_Ok) {
Log.d(TAG, "Create Transaction failed: " + pendingTransaction.getErrorString()); Log.w(TAG, "Create Transaction failed: " + pendingTransaction.getErrorString());
} }
if (observer != null) { if (observer != null) {
observer.onCreatedTransaction(pendingTransaction); observer.onCreatedTransaction(pendingTransaction);
@ -352,7 +352,7 @@ public class WalletService extends Service {
boolean rc = myWallet.store(); boolean rc = myWallet.store();
Log.d(TAG, "wallet stored: " + myWallet.getName() + " with rc=" + rc); Log.d(TAG, "wallet stored: " + myWallet.getName() + " with rc=" + rc);
if (!rc) { if (!rc) {
Log.d(TAG, "Wallet store failed: " + myWallet.getErrorString()); Log.w(TAG, "Wallet store failed: " + myWallet.getErrorString());
} }
if (observer != null) observer.onWalletStored(rc); if (observer != null) observer.onWalletStored(rc);
listener.updated = true; listener.updated = true;
@ -372,7 +372,7 @@ public class WalletService extends Service {
boolean rc = myWallet.store(); boolean rc = myWallet.store();
Log.d(TAG, "wallet stored: " + myWallet.getName() + " with rc=" + rc); Log.d(TAG, "wallet stored: " + myWallet.getName() + " with rc=" + rc);
if (!rc) { if (!rc) {
Log.d(TAG, "Wallet store failed: " + myWallet.getErrorString()); Log.w(TAG, "Wallet store failed: " + myWallet.getErrorString());
} }
if (observer != null) observer.onWalletStored(rc); if (observer != null) observer.onWalletStored(rc);
} }
@ -505,17 +505,12 @@ public class WalletService extends Service {
} }
private Wallet loadWallet(String walletName, String walletPassword) { private Wallet loadWallet(String walletName, String walletPassword) {
//String path = Helper.getWalletPath(getApplicationContext(), walletName);
//Log.d(TAG, "open wallet " + path);
Wallet wallet = openWallet(walletName, walletPassword); Wallet wallet = openWallet(walletName, walletPassword);
//Log.d(TAG, "wallet opened: " + wallet);
if (wallet != null) { if (wallet != null) {
//Log.d(TAG, wallet.getStatus().toString());
Log.d(TAG, "Using daemon " + WalletManager.getInstance().getDaemonAddress()); Log.d(TAG, "Using daemon " + WalletManager.getInstance().getDaemonAddress());
showProgress(55); showProgress(55);
wallet.init(0); wallet.init(0);
showProgress(90); showProgress(90);
//Log.d(TAG, wallet.getConnectionStatus().toString());
} }
return wallet; return wallet;
} }

View File

@ -19,6 +19,8 @@ package com.m2049r.xmrwallet.util;
import android.Manifest; import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Bitmap; import android.graphics.Bitmap;
@ -40,7 +42,9 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL; import java.net.URL;
import java.util.Locale;
import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.HttpsURLConnection;
@ -48,6 +52,9 @@ public class Helper {
static private final String TAG = "Helper"; static private final String TAG = "Helper";
static private final String WALLET_DIR = "monerujo"; static private final String WALLET_DIR = "monerujo";
static public int DISPLAY_DIGITS_INFO = 5;
static public int DISPLAY_DIGITS_SHORT = 5;
static public File getStorageRoot(Context context) { static public File getStorageRoot(Context context) {
if (!isExternalStorageWritable()) { if (!isExternalStorageWritable()) {
String msg = context.getString(R.string.message_strorage_not_writable); String msg = context.getString(R.string.message_strorage_not_writable);
@ -73,7 +80,7 @@ public class Helper {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
if (context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) if (context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_DENIED) { == PackageManager.PERMISSION_DENIED) {
Log.d(TAG, "Permission denied to WRITE_EXTERNAL_STORAGE - requesting it"); Log.w(TAG, "Permission denied to WRITE_EXTERNAL_STORAGE - requesting it");
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE}; String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
context.requestPermissions(permissions, PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE); context.requestPermissions(permissions, PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
return false; return false;
@ -91,7 +98,7 @@ public class Helper {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
if (context.checkSelfPermission(Manifest.permission.CAMERA) if (context.checkSelfPermission(Manifest.permission.CAMERA)
== PackageManager.PERMISSION_DENIED) { == PackageManager.PERMISSION_DENIED) {
Log.d(TAG, "Permission denied for CAMERA - requesting it"); Log.w(TAG, "Permission denied for CAMERA - requesting it");
String[] permissions = {Manifest.permission.CAMERA}; String[] permissions = {Manifest.permission.CAMERA};
context.requestPermissions(permissions, PERMISSIONS_REQUEST_CAMERA); context.requestPermissions(permissions, PERMISSIONS_REQUEST_CAMERA);
return false; return false;
@ -103,13 +110,8 @@ public class Helper {
} }
} }
// static public String getWalletPath(Context context, String aWalletName) {
// return getWalletFile(context, aWalletName).getAbsolutePath();
// }
static public File getWalletFile(Context context, String aWalletName) { static public File getWalletFile(Context context, String aWalletName) {
File walletDir = getStorageRoot(context); File walletDir = getStorageRoot(context);
//d(TAG, "walletdir=" + walletDir.getAbsolutePath());
File f = new File(walletDir, aWalletName); File f = new File(walletDir, aWalletName);
Log.d(TAG, "wallet = " + f.getAbsolutePath() + " size=" + f.length()); Log.d(TAG, "wallet = " + f.getAbsolutePath() + " size=" + f.length());
return f; return f;
@ -127,6 +129,7 @@ public class Helper {
} }
static public void hideKeyboard(Activity act) { static public void hideKeyboard(Activity act) {
if (act == null) return;
if (act.getCurrentFocus() == null) { if (act.getCurrentFocus() == null) {
act.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); act.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
} else { } else {
@ -145,19 +148,38 @@ public class Helper {
} }
static public String getDisplayAmount(long amount) { static public String getDisplayAmount(long amount) {
String s = Wallet.getDisplayAmount(amount); return getDisplayAmount(amount, 20);
}
static public String getDisplayAmount(long amount, int maxDecimals) {
return getDisplayAmount(Wallet.getDisplayAmount(amount), maxDecimals);
}
// amountString must have '.' as decimal point
static public String getDisplayAmount(String amountString, int maxDecimals) {
int lastZero = 0; int lastZero = 0;
int decimal = 0; int decimal = 0;
for (int i = s.length() - 1; i >= 0; i--) { for (int i = amountString.length() - 1; i >= 0; i--) {
if ((lastZero == 0) && (s.charAt(i) != '0')) lastZero = i + 1; if ((lastZero == 0) && (amountString.charAt(i) != '0')) lastZero = i + 1;
// TODO i18n // TODO i18n
if (s.charAt(i) == '.') { if (amountString.charAt(i) == '.') {
decimal = i; decimal = i + 1;
break; break;
} }
} }
int cutoff = Math.max(lastZero, decimal + 2); int cutoff = Math.min(Math.max(lastZero, decimal + 2), decimal + maxDecimals);
return s.substring(0, cutoff); return amountString.substring(0, cutoff);
}
static public String getFormattedAmount(double amount, boolean isXmr) {
// at this point selection is XMR in case of error
String displayB;
if (isXmr) { // not XMR
displayB = String.format(Locale.US, "%,.5f", amount);
} else { // XMR
displayB = String.format(Locale.US, "%,.2f", amount);
}
return displayB;
} }
static public Bitmap getBitmap(Context context, int drawableId) { static public Bitmap getBitmap(Context context, int drawableId) {
@ -180,11 +202,15 @@ public class Helper {
return bitmap; return bitmap;
} }
static final int HTTP_TIMEOUT = 5000;
static public String getUrl(String httpsUrl) { static public String getUrl(String httpsUrl) {
HttpsURLConnection urlConnection = null; HttpsURLConnection urlConnection = null;
try { try {
URL url = new URL(httpsUrl); URL url = new URL(httpsUrl);
urlConnection = (HttpsURLConnection) url.openConnection(); urlConnection = (HttpsURLConnection) url.openConnection();
urlConnection.setConnectTimeout(HTTP_TIMEOUT);
urlConnection.setReadTimeout(HTTP_TIMEOUT);
InputStreamReader in = new InputStreamReader(urlConnection.getInputStream()); InputStreamReader in = new InputStreamReader(urlConnection.getInputStream());
StringBuffer sb = new StringBuffer(); StringBuffer sb = new StringBuffer();
final int BUFFER_SIZE = 512; final int BUFFER_SIZE = 512;
@ -195,6 +221,8 @@ public class Helper {
length = in.read(buffer, 0, BUFFER_SIZE); length = in.read(buffer, 0, BUFFER_SIZE);
} }
return sb.toString(); return sb.toString();
} catch (SocketTimeoutException ex) {
Log.w(TAG, "C " + ex.getLocalizedMessage());
} catch (MalformedURLException ex) { } catch (MalformedURLException ex) {
Log.e(TAG, "A " + ex.getLocalizedMessage()); Log.e(TAG, "A " + ex.getLocalizedMessage());
} catch (IOException ex) { } catch (IOException ex) {
@ -206,4 +234,10 @@ public class Helper {
} }
return null; return null;
} }
static public void clipBoardCopy(Context context, String label, String text) {
ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText(label, text);
clipboardManager.setPrimaryClip(clip);
}
} }

View File

@ -16,8 +16,6 @@
package com.m2049r.xmrwallet.util; package com.m2049r.xmrwallet.util;
import android.util.Log;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">
<scale
android:duration="300"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:interpolator="@android:anim/linear_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="0.0"
android:toYScale="0.0" />
<alpha
android:duration="300"
android:fromAlpha="1.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:toAlpha="0.0" />
</set>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="false">
<alpha
android:duration="300"
android:fromAlpha="1.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:toAlpha="0.0" />
</set>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">
<scale
android:duration="300"
android:fromXScale="0.0"
android:fromYScale="0.0"
android:interpolator="@android:anim/linear_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="1.0"
android:toYScale="1.0" />
<alpha
android:duration="300"
android:fromAlpha="0.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:toAlpha="1.0" />
</set>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">
<alpha
android:duration="300"
android:fromAlpha="0.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:toAlpha="1.0" />
</set>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">
<rotate
android:duration="300"
android:fromDegrees="315"
android:interpolator="@android:anim/linear_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="0" />
</set>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">
<rotate
android:duration="300"
android:fromDegrees="0"
android:interpolator="@android:anim/linear_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="315" />
</set>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/moneroGray" android:state_enabled="false" /> <item android:color="@color/moneroText" android:state_enabled="false" />
<item android:color="@color/moneroWhite" /> <item android:color="@color/moneroWhite" />
</selector> </selector>

View File

@ -0,0 +1,352 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="1080dp"
android:height="1920dp"
android:viewportWidth="1080"
android:viewportHeight="1920">
<group>
<path
android:fillColor="#F6F5F5"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 134.4 -393.2 L 215.2 -363 L 231.2 -273.1 L 109.9 -318.4 L 134.4 -393.2 Z" />
<path
android:fillColor="#F6F5F5"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 457.7 -272.4 L 538.5 -242.2 L 408.8 -122.7 L 424.8 -32.7 L 263.1 -93.1 L 457.7 -272.4 Z" />
<path
android:fillColor="#F0F0F0"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 1023.6 -61 L 1266.1 29.6 L 1071.5 208.9 L 1023.6 -61 Z" />
<path
android:fillColor="#F6F5F5"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 1266.1 29.6 L 1589.5 150.4 L 1524.6 210.1 L 1363 149.7 L 1233.2 269.3 L 1071.5 208.9 L 1266.1 29.6 Z" />
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 215.2 -363 L 457.7 -272.4 L 263.1 -93.1 L 215.2 -363 Z" />
<path
android:fillColor="#F0F0F0"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 1363 149.7 L 1524.6 210.1 L 1394.9 329.6 L 1363 149.7 Z" />
<path
android:fillColor="#F0F0F0"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 109.9 -318.4 L 231.2 -273.1 L 263.1 -93.1 L 424.8 -32.7 L 440.7 57.3 L 36.5 -93.7 L 109.9 -318.4 Z" />
<path
android:fillColor="#F6F5F5"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 36.5 -93.7 L 440.7 57.3 L 456.7 147.2 L 52.5 -3.8 L -12.4 56 L 36.5 -93.7 Z" />
<path
android:fillColor="#F0F0F0"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 619.5 -212 L 1023.6 -61 L 1071.5 208.9 L 1233.2 269.3 L 1281.1 539.2 L 634.4 297.6 L 602.4 117.7 L 683.3 147.9 L 619.5 -212 Z" />
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 1589.5 150.4 L 1670.4 180.5 L 1572.5 480 L 1377.9 659.3 L 1362 569.4 L 1297 629.1 L 1233.2 269.3 L 1363 149.7 L 1394.9 329.6 L 1589.5 150.4 Z" />
<path
android:fillColor="#F0F0F0"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 52.5 -3.8 L 456.7 147.2 L 472.7 237.2 L 278.1 416.5 L 230.1 146.6 L 100.4 266.1 L 52.5 -3.8 Z" />
<path
android:fillColor="#F6F5F5"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 1474.7 779.4 L 1232.2 688.9 L 1167.3 748.7 L 1183.3 838.6 L 1021.6 778.1 L 891.8 897.7 L 568.5 777 L 503.6 836.8 L 180.2 716 L 634.4 297.6 L 1281.1 539.2 L 1297 629.1 L 1362 569.4 L 1377.9 659.3 L 1572.5 480 L 1474.7 779.4 Z" />
<path
android:fillColor="#F0F0F0"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 1232.2 688.9 L 1474.7 779.4 L 1345 898.9 L 1264.2 868.8 L 1232.2 688.9 Z" />
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 538.5 -242.2 L 619.5 -212 L 683.3 147.9 L 602.4 117.7 L 634.4 297.6 L 180.2 716 L 341.9 776.4 L 357.9 866.3 L 163.3 1045.6 L 51.5 415.8 L -78.3 535.4 L 2.6 565.6 L -127.2 685.1 L -208 654.9 L -12.4 56 L 52.5 -3.8 L 100.4 266.1 L 230.1 146.6 L 278.1 416.5 L 472.7 237.2 L 408.8 -122.7 Z" />
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 1232.2 688.9 L 1264.2 868.8 L 1199.2 928.5 L 1167.3 748.7 L 1232.2 688.9 Z" />
<path
android:fillColor="#F6F5F5"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 1264.2 868.8 L 1345 898.9 L 1360.9 988.9 L 1199.2 928.5 L 1264.2 868.8 Z" />
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 1474.7 779.4 L 1376.9 1078.9 L 1345 898.9 L 1474.7 779.4 Z" />
<path
android:fillColor="#F6F5F5"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 51.5 415.8 L 115.3 775.7 L -127.2 685.1 L 2.6 565.6 L -78.3 535.4 L 51.5 415.8 Z" />
<path
android:fillColor="#F0F0F0"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 341.9 776.4 L 503.6 836.8 L 438.7 896.5 L 357.9 866.3 Z" />
<path
android:fillColor="#F0F0F0"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 811 867.6 L 891.8 897.7 L 923.7 1077.6 L 842.9 1047.5 L 811 867.6 Z" />
<path
android:fillColor="#F0F0F0"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M -208 654.9 L 115.3 775.7 L 147.3 955.6 L -176.1 834.8 Z" />
<path
android:fillColor="#F6F5F5"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 357.9 866.3 L 438.7 896.5 L 373.8 956.3 L 405.8 1136.2 L 421.7 1226.1 L -225 984.6 L -354.7 1104.1 L -305.8 954.4 L -176.1 834.8 L 147.3 955.6 L 163.3 1045.6 Z" />
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 811 867.5 L 842.9 1047.4 L 778.1 1107.3 L 746.2 927.4 L 811 867.5 Z" />
<path
android:fillColor="#F6F5F5"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 842.9 1047.5 L 923.7 1077.6 L 939.7 1167.6 L 778.1 1107.3 L 842.9 1047.5 Z" />
<path
android:fillColor="#F0F0F0"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 568.5 777 L 811 867.6 L 746.1 927.4 L 778.1 1107.3 L 939.7 1167.6 L 874.8 1227.3 L 632.4 1136.8 L 568.5 777 Z" />
<path
android:fillColor="#F6F5F5"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 405.8 1136.2 L 421.7 1226.2 L 421.7 1226.1 Z" />
<path
android:fillColor="#F0F0F0"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 1021.6 778.1 L 1183.3 838.6 L 1199.2 928.5 L 1360.9 988.9 L 1376.9 1078.9 L 1254.6 1453.2 L 1214.2 1438.1 L 1134.4 988.4 L 1069.5 1048.1 L 1021.6 778.1 Z" />
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M -208 654.9 L -176.1 834.8 L -305.8 954.4 Z" />
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 476.8 1536.3 L 485.6 1586 L 485.6 1586 Z" />
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 485.7 1586 L 728.1 1676.6 L 533.5 1855.9 Z" />
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 728.1 1676.6 L 889.8 1737 L 760 1856.5 Z" />
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 729.2 1256.9 L 745.1 1346.9 L 664.3 1316.7 Z" />
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 1069.5 1048.1 L 1134.4 988.3 L 1214.2 1438.1 L 1149.2 1497.9 L 745.1 1346.9 L 810 1287.1 L 874.8 1227.3 L 939.7 1167.6 L 923.7 1077.6 L 891.8 897.7 L 1021.6 778.1 Z" />
<path
android:fillColor="#F0F0F0"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 729.2 1257 L 810 1287.2 L 745.1 1346.9 L 729.2 1257 Z" />
<path
android:fillColor="#F0F0F0"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M -144.2 1014.8 L 421.7 1226.1 L 437.6 1316.1 L 518.6 1346.3 L 453.6 1406.1 L 469.6 1496 L 65.4 1345 L 49.4 1255 L -15.4 1314.9 L -96.3 1284.7 Z" />
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 568.5 777 L 632.4 1136.8 L 437.7 1316.1 L 421.7 1226.2 L 421.7 1226.1 L 405.8 1136.2 L 373.8 956.3 L 438.7 896.5 L 503.6 836.8 Z" />
<path
android:fillColor="#F6F5F5"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 874.8 1227.3 L 810 1287.1 L 729.2 1256.9 L 664.3 1316.7 L 583.5 1286.6 L 518.6 1346.3 L 437.6 1316.1 L 632.3 1136.7 L 874.8 1227.3 " />
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M -225 984.6 L -144.2 1014.8 L -96.2 1284.7 L -355.7 1523.8 L -194.1 1584.2 L -178.1 1674.1 L -420.6 1583.5 L -452.6 1403.6 L -354.7 1104.1 Z" />
<path
android:fillColor="#F6F5F5"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M -145.2 1434.4 L -129.2 1524.3 L 65.4 1345 L 469.6 1496 L 476.8 1536.3 L 485.6 1586 L 243.1 1495.4 L 113.3 1614.9 L 48.4 1674.7 L -355.8 1523.8 L -96.3 1284.7 L -15.5 1314.8 Z" />
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 1132.3 1827.6 L 1083.4 1977.3 L 888.8 2156.6 L 872.9 2066.6 L 937.7 2006.9 Z" />
<path
android:fillColor="#F6F5F5"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M -15.5 1314.8 L -15.4 1314.9 L -145.1 1434.4 Z" />
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 49.4 1255.1 L 65.4 1345.1 L -129.2 1524.4 L -145.1 1434.4 L -15.5 1314.8 L -15.4 1314.9 Z" />
<path
android:fillColor="#F6F5F5"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 1214.2 1438.1 L 1254.6 1453.2 L 1230.1 1528.1 L 1149.2 1497.9 Z" />
<path
android:fillColor="#F6F5F5"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M -452.6 1403.6 L -420.6 1583.5 L -501.5 1553.3 L -452.6 1403.6 Z" />
<path
android:fillColor="#F0F0F0"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M -194.1 1584.2 L 48.4 1674.7 L -16.4 1734.5 L 47.5 2094.3 L -195.1 2003.7 L -243 1733.9 L -307.9 1793.6 L -509.9 1718.1 L -525.9 1628.2 L -501.5 1553.3 L -178.1 1674.1 Z" />
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 306.9 1855.2 L 436.7 1735.7 L 468.6 1915.6 L 274 2094.9 L 258 2004.9 L 128.3 2124.5 L 47.4 2094.3 L -16.4 1734.5 L 48.4 1674.7 L 113.3 1614.9 L 243.1 1495.4 Z" />
<path
android:fillColor="#F0F0F0"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 243.1 1495.4 L 485.6 1586 L 533.5 1855.9 L 695.2 1916.3 L 630.3 1976 L 468.6 1915.6 L 436.7 1735.7 L 306.9 1855.2 Z" />
<path
android:fillColor="#F0F0F0"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 695.2 1916.3 L 937.7 2006.9 L 872.9 2066.6 L 888.8 2156.6 L 1010.1 2201.9 L 936.7 2426.5 L 775 2366.1 Z" />
<path
android:fillColor="#F6F5F5"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 1083.5 1977.3 L 1010.1 2201.9 L 888.8 2156.6 L 1083.5 1977.3 Z" />
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M -243 1733.9 L -195.1 2003.7 L -437.6 1913.2 L -243 1733.9 Z" />
<path
android:fillColor="#F6F5F5"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 468.6 1915.6 L 630.3 1976 L 435.7 2155.3 L 370.8 2215.1 L 128.3 2124.5 L 258 2004.9 L 274 2094.9 Z" />
<path
android:fillColor="#F6F5F5"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M -509.9 1718.1 L -307.9 1793.6 L -437.6 1913.2 L -599.3 1852.8 L -574.8 1777.8 L -509.9 1718.1 Z" />
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M -525.9 1628.2 L -509.9 1718.1 L -574.8 1777.8 L -525.9 1628.2 Z" />
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 695.2 1916.3 L 775 2366.1 L 370.8 2215.1 L 435.7 2155.3 L 630.3 1976 Z" />
<path
android:fillColor="#F0F0F0"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 583.5 1286.6 L 1230.2 1528.1 L 1181.3 1677.8 L 615.4 1466.5 Z" />
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 583.5 1286.6 L 615.4 1466.5 L 485.7 1586 L 453.7 1406.1 Z" />
<path
android:fillColor="#F6F5F5"
android:fillAlpha="0.62"
android:strokeAlpha="0.62"
android:strokeWidth="1"
android:pathData="M 615.4 1466.5 L 1181.3 1677.8 L 1132.3 1827.6 L 937.7 2006.9 L 533.5 1855.9 L 728.1 1676.6 L 760 1856.5 L 889.8 1737 L 485.7 1586 Z" />
</group>
</vector>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/white" />
<padding
android:bottom="8dp"
android:left="8dp"
android:right="8dp"
android:top="8dp" />
</shape>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<padding
android:bottom="8dp"
android:left="8dp"
android:right="8dp"
android:top="8dp" />
<corners android:radius="3dp" />
<stroke
android:width="2dp"
android:color="@color/gradientOrange"
android:dashGap="16dp"
android:dashWidth="16dp" />
</shape>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/white" />
<padding
android:bottom="8dp"
android:left="8dp"
android:right="8dp"
android:top="8dp" />
<stroke
android:width="2dp"
android:color="#FF979797"
android:dashGap="8dp"
android:dashWidth="8dp" />
</shape>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<padding
android:bottom="8dp"
android:left="8dp"
android:right="8dp"
android:top="8dp" />
<stroke
android:width="2dp"
android:color="@color/gradientOrange" />
</shape>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/gradient_all" />
<item
android:height="?attr/actionBarSize"
android:drawable="@drawable/texture_cash" />
</layer-list>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/gradientOrange" />
</shape>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/moneroGray" />
</shape>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/button_disabled" android:state_enabled="false" />
<item android:drawable="@drawable/button_default" android:state_enabled="true" />
<item android:drawable="@drawable/button_default" android:state_pressed="false" />
</selector>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="45"
android:endColor="@color/gradientOrange"
android:startColor="@color/gradientPink"
android:type="linear" />
</shape>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<gradient
android:angle="45"
android:endColor="@color/gradientOrange"
android:startColor="@color/gradientPink"
android:type="linear" />
</shape>

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true">
<item
android:drawable="@drawable/gunther_wallets_00"
android:duration="500" />
<item
android:drawable="@drawable/gunther_wallets_01"
android:duration="200" />
<item
android:drawable="@drawable/gunther_wallets_02"
android:duration="2000" />
<item
android:drawable="@drawable/gunther_wallets_03"
android:duration="1000" />
<item
android:drawable="@drawable/gunther_wallets_04"
android:duration="100" />
<item
android:drawable="@drawable/gunther_wallets_05"
android:duration="1000" />
<item
android:drawable="@drawable/gunther_wallets_06"
android:duration="0" />
<item
android:drawable="@drawable/gunther_wallets_07"
android:duration="0" />
<item
android:drawable="@drawable/gunther_wallets_08"
android:duration="0" />
<item
android:drawable="@drawable/gunther_wallets_09"
android:duration="0" />
<item
android:drawable="@drawable/gunther_wallets_10"
android:duration="0" />
<item
android:drawable="@drawable/gunther_wallets_11"
android:duration="0" />
<item
android:drawable="@drawable/gunther_wallets_12"
android:duration="0" />
<item
android:drawable="@drawable/gunther_wallets_13"
android:duration="0" />
<item
android:drawable="@drawable/gunther_wallets_14"
android:duration="0" />
<item
android:drawable="@drawable/gunther_wallets_15"
android:duration="0" />
<item
android:drawable="@drawable/gunther_wallets_16"
android:duration="0" />
<item
android:drawable="@drawable/gunther_wallets_17"
android:duration="0" />
<item
android:drawable="@drawable/gunther_wallets_18"
android:duration="5000" />
<item
android:drawable="@drawable/gunther_wallets_19"
android:duration="0" />
<item
android:drawable="@drawable/gunther_wallets_20"
android:duration="0" />
<item
android:drawable="@drawable/gunther_wallets_21"
android:duration="100" />
<item
android:drawable="@drawable/gunther_wallets_22"
android:duration="200" />
</animation-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="20.0"
android:viewportWidth="20.0">
<path
android:fillColor="@color/moneroFab"
android:pathData="M11,5L9,5L9,9L5,9L5,11L9,11L9,15L11,15L11,11L15,11L15,9L11,9L11,5L11,5ZM10,0C4.5,0 0,4.5 0,10C0,15.5 4.5,20 10,20C15.5,20 20,15.5 20,10C20,4.5 15.5,0 10,0L10,0ZM10,18C5.6,18 2,14.4 2,10C2,5.6 5.6,2 10,2C14.4,2 18,5.6 18,10C18,14.4 14.4,18 10,18L10,18Z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFffffff"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFffffff"
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFffffff"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/moneroBlack"
android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/moneroGray"
android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
</vector>

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<group
android:translateX="0.000000"
android:translateY="+7.000000">
<path
android:fillColor="#417505"
android:pathData="M17.6,1.4L16.2,0L9.9,6.3L11.3,7.7L17.6,1.4L17.6,1.4ZM21.8,0L11.3,10.6L7.1,6.4L5.7,7.8L11.3,13.4L23.3,1.4L21.8,0L21.8,0ZM0,7.8L5.6,13.4L7,12L1.4,6.4L0,7.8L0,7.8Z" />
</group>
</vector>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="29dp"
android:height="17dp"
android:viewportHeight="17"
android:viewportWidth="29">
<group
android:translateX="-219.000000"
android:translateY="-74.000000">
<group
android:translateX="219.000000"
android:translateY="74.000000">
<path
android:fillColor="#FEFEFE"
android:pathData="M14.4474298,13.6840973 C11.5770468,13.6840973 9.24995106,11.3571559
9.24995106,8.48677287 C9.24995106,5.61623564 11.5770468,3.28929415
14.4474298,3.28929415 C17.3178128,3.28929415 19.6449085,5.61623564
19.6449085,8.48677287 C19.6449085,11.3571559 17.3178128,13.6840973
14.4474298,13.6840973 M14.4474298,-4.62765957e-05 C8.23695638,-4.62765957e-05
2.82706809,3.42395904 3.08510638e-05,8.48692713 C2.82706809,13.5494324
8.23695638,16.9734378 14.4474298,16.9734378 C20.6579032,16.9734378
26.0676372,13.5494324 28.8948287,8.48692713 C26.0676372,3.42395904
20.6579032,-4.62765957e-05 14.4474298,-4.62765957e-05"
android:strokeWidth="1" />
</group>
</group>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="14dp"
android:height="14dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFffffff"
android:pathData="M12,21.35l-1.45,-1.32C5.4,15.36 2,12.28 2,8.5 2,5.42 4.42,3 7.5,3c1.74,0 3.41,0.81 4.5,2.09C13.09,3.81 14.76,3 16.5,3 19.58,3 22,5.42 22,8.5c0,3.78 -3.4,6.86 -8.55,11.54L12,21.35z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFffffff"
android:pathData="M12,21.35l-1.45,-1.32C5.4,15.36 2,12.28 2,8.5 2,5.42 4.42,3 7.5,3c1.74,0 3.41,0.81 4.5,2.09C13.09,3.81 14.76,3 16.5,3 19.58,3 22,5.42 22,8.5c0,3.78 -3.4,6.86 -8.55,11.54L12,21.35z" />
</vector>

View File

@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="22.0"
android:viewportWidth="22.0">
<path
android:fillColor="#FFFFFF"
android:pathData="M13.985,8.648C14.245,8.798 14.478,8.873 14.684,8.873C14.817,8.873 14.925,8.842 15.008,8.777C15.093,8.713 15.135,8.63 15.135,8.527C15.135,8.418 15.089,8.333 14.998,8.272C14.907,8.212 14.778,8.181 14.611,8.181C14.43,8.181 14.24,8.219 14.04,8.295L14.293,6.397L15.913,6.397L15.913,7.177L14.985,7.177L14.915,7.502C14.976,7.498 15.025,7.496 15.063,7.496C15.33,7.496 15.556,7.592 15.74,7.782C15.923,7.971 16.014,8.207 16.014,8.49C16.014,8.822 15.896,9.099 15.66,9.321C15.426,9.543 15.132,9.654 14.776,9.654C14.472,9.654 14.175,9.572 13.882,9.407L13.985,8.648ZM15,11C16.657,11 18,9.657 18,8C18,6.343 16.657,5 15,5C13.343,5 12,6.343 12,8C12,9.657 13.343,11 15,11L15,11Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
<path
android:fillColor="#FFFFFF"
android:pathData="M21.344,12.691C20.503,12.122 19.588,12.527 19.168,12.769L14.417,16.615L10.807,16.616L10.035,15.847L12.871,15.847C13.724,15.847 14.679,15.293 14.679,14.308C14.679,13.262 13.724,12.769 12.871,12.769L11.808,12.769C11.019,12.769 10.096,12.839 9.446,12.492C8.828,12.179 8.116,12.001 7.356,12.001C6.081,12.001 4.939,12.507 4.157,13.307L-0,17.385L4.631,22L6.175,19.692L13.993,19.692C14.771,19.692 15.522,19.408 16.103,18.893L21.397,13.745C21.72,13.459 21.702,12.933 21.344,12.691"
android:strokeColor="#00000000"
android:strokeWidth="1" />
<path
android:fillColor="#FFFFFF"
android:pathData="M10.115,1.099L11.102,1.099L11.102,3.926L10.397,3.926L10.397,1.776L10.115,1.776L10.115,1.099ZM10.692,5C11.967,5 13,3.881 13,2.5C13,1.119 11.967,-0 10.692,-0C9.418,-0 8.384,1.119 8.384,2.5C8.384,3.881 9.418,5 10.692,5L10.692,5Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>

View File

@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24.0" android:viewportHeight="24.0"
android:viewportHeight="24.0"> android:viewportWidth="24.0">
<path <path
android:fillColor="#FF000000" android:fillColor="#FFffffff"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"/> android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z" />
</vector> </vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/moneroFab"
android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
</vector>

View File

@ -4,6 +4,6 @@
android:viewportHeight="24.0" android:viewportHeight="24.0"
android:viewportWidth="24.0"> android:viewportWidth="24.0">
<path <path
android:fillColor="#FF000000" android:fillColor="#FFffffff"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z" /> android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z" />
</vector> </vector>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="29dp"
android:height="13dp"
android:viewportHeight="13"
android:viewportWidth="29">
<group
android:translateX="-219.000000"
android:translateY="-136.000000">
<group
android:translateX="219.000000"
android:translateY="136.000000">
<path
android:fillColor="#FEFEFE"
android:pathData="M6.29687258,9.00290326 C4.95784407,9.00290326 3.87228646,7.88216454
3.87228646,6.49992511 C3.87228646,5.11783546 4.95784407,3.99709674
6.29687258,3.99709674 C7.63604617,3.99709674 8.72160379,5.11783546
8.72160379,6.49992511 C8.72160379,7.88216454 7.63604617,9.00290326
6.29687258,9.00290326 L6.29687258,9.00290326 Z M19.5790661,4.89647346
L12.4001961,4.89647346 C11.7082656,2.08212076 9.23841162,0 6.29687258,0
C2.81922883,0 0,2.9099068 0,6.49992511 C0,10.0899434 2.81922883,13 6.29687258,13
C9.23841162,13 11.7082656,10.9178792 12.4001961,8.10352654
L19.5790661,8.10352654 L19.5790661,13 L21.3018556,13 L21.3018556,9.33689328
L23.3905602,9.33689328 L23.3905602,13 L25.3240193,13 L25.3240193,10.81259
L27.3216078,10.81259 L27.3216078,13 L29,13 L29,8.10352654 L29,4.89647346
L19.5790661,4.89647346 Z"
android:strokeWidth="1" />
</group>
</group>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M8.59,16.34l4.58,-4.59 -4.58,-4.59L10,5.75l6,6 -6,6z"/>
</vector>

View File

@ -0,0 +1,36 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="135dp"
android:height="48dp"
android:viewportHeight="108.0"
android:viewportWidth="304.0">
<path
android:fillColor="#FFffffff"
android:pathData="M74.4,65.14q-4.73,-4.88 -4.73,-12.84t4.95,-12.87A16.88,16.88 0,0 1,87 34.52a16.88,16.88 0,0 1,12.4 4.92q4.95,4.92 4.95,12.84A16.91,16.91 0,0 1,99.2 65.11,17.62 17.62,0 0,1 86.58,70 16.2,16.2 0,0 1,74.4 65.14ZM86.58,65.33a11.53,11.53 0,0 0,8.36 -3.48q3.54,-3.48 3.54,-9.61T95.1,42.66a11.11,11.11 0,0 0,-8.24 -3.44,10.7 10.7,0 0,0 -8.11,3.44q-3.26,3.45 -3.26,9.61t3.13,9.61A10.27,10.27 0,0 0,86.58 65.33Z" />
<path
android:fillColor="#FFffffff"
android:pathData="M117.21,35.08v5.57q3.51,-6.14 11.21,-6.14a13,13 0,0 1,9.52 3.79q3.82,3.79 3.82,10.8V69.46h-5.64V50.05q0,-5.2 -2.5,-7.89a9,9 0,0 0,-6.92 -2.69,9.09 9.09,0 0,0 -7,2.69q-2.54,2.69 -2.54,7.89V69.46h-5.7V35.08Z" />
<path
android:fillColor="#FFffffff"
android:pathData="M165.24,70a15.82,15.82 0,0 1,-12 -4.88q-4.63,-4.88 -4.63,-12.9t4.7,-12.87a16.2,16.2 0,0 1,12.18 -4.85q7.48,0 12,4.6A16,16 0,0 1,182 50.87a21.51,21.51 0,0 1,-0.25 3.38L154.4,54.25a11.81,11.81 0,0 0,3.22 8.11,10.3 10.3,0 0,0 7.61,3 10.63,10.63 0,0 0,5.92 -1.57,9.3 9.3,0 0,0 3.54,-4.13h6.14a15.79,15.79 0,0 1,-5.61 7.45A16.33,16.33 0,0 1,165.24 70ZM172.94,42.16a11.23,11.23 0,0 0,-15.16 -0.06,11.65 11.65,0 0,0 -3.32,8h21.61A10.73,10.73 0,0 0,172.94 42.19Z" />
<path
android:fillColor="#FFffffff"
android:pathData="M194.8,35.08v5.82q3.07,-6.39 10.46,-6.39v5.95h-1.5q-4.38,0 -6.67,2.29t-2.29,7.92V69.46h-5.7V35.08Z" />
<path
android:fillColor="#FFffffff"
android:pathData="M236.38,35.08h5.7V69.46h-5.7V64Q232.93,70 225.17,70a13,13 0,0 1,-9.52 -3.79q-3.82,-3.79 -3.82,-10.74V35.08h5.64V54.56q0,5.14 2.54,7.86a10.15,10.15 0,0 0,13.84 0q2.54,-2.72 2.54,-7.86Z" />
<path
android:fillColor="#FFffffff"
android:pathData="M254.35,29.57a3.9,3.9 0,0 0,2.76 -6.64,3.85 3.85,0 0,0 -6.58,2.76 3.8,3.8 0,0 0,1.1 2.75A3.64,3.64 0,0 0,254.35 29.57Z" />
<path
android:fillColor="#FFffffff"
android:pathData="M251.43,76.85a4.48,4.48 0,0 1,-1 3.24,4.61 4.61,0 0,1 -3.33,1h-2.43v4.67h3.56c5.93,0 8.85,-3 8.85,-8.88V35.07h-5.69Z" />
<path
android:fillColor="#FFffffff"
android:pathData="M59.82,29.57a3.9,3.9 0,0 0,2.76 -6.64A3.85,3.85 0,0 0,56 25.69a3.8,3.8 0,0 0,1.1 2.75A3.64,3.64 0,0 0,59.82 29.57Z" />
<path
android:fillColor="#FFffffff"
android:pathData="M59,38.31a13.12,13.12 0,0 0,-9.64 -3.79,13.81 13.81,0 0,0 -7.35,2A14,14 0,0 0,36.92 42a12,12 0,0 0,-4.82 -5.54A13.65,13.65 0,0 0,25 34.52q-7.7,0 -11.21,6.14V35.07H8V69.46h5.75V50.05q0,-5.2 2.54,-7.89a9.09,9.09 0,0 1,7 -2.69,9 9,0 0,1 6.91,2.69q2.5,2.69 2.5,7.89v19.4h5.64V50.05q0,-5.2 2.54,-7.89a9.06,9.06 0,0 1,6.92 -2.69,9 9,0 0,1 6.91,2.69c1.69,1.8 2.52,4.43 2.52,7.89V69.46l0,7.39a4.48,4.48 0,0 1,-0.95 3.24,4.59 4.59,0 0,1 -3.33,1H50.41v4.67H54c5.93,0 8.82,-3 8.82,-8.88V49.11C62.82,44.44 61.56,40.84 59,38.31Z" />
<path
android:fillColor="#FFffffff"
android:pathData="M269,65.14q-4.73,-4.88 -4.73,-12.84t4.95,-12.87a18.09,18.09 0,0 1,24.8 0Q299,44.35 299,52.28a16.91,16.91 0,0 1,-5.17 12.84A17.62,17.62 0,0 1,281.21 70,16.2 16.2,0 0,1 269,65.14ZM281.18,65.33a11.53,11.53 0,0 0,8.36 -3.48q3.54,-3.48 3.54,-9.61t-3.38,-9.58a11.11,11.11 0,0 0,-8.24 -3.44,10.7 10.7,0 0,0 -8.11,3.44q-3.26,3.45 -3.26,9.61t3.13,9.61A10.27,10.27 0,0 0,281.21 65.33Z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
</vector>

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="29dp"
android:height="29dp"
android:viewportHeight="29"
android:viewportWidth="29">
<group
android:translateX="-219.000000"
android:translateY="-7.000000">
<group
android:translateX="219.000000"
android:translateY="7.000000">
<path
android:fillColor="#FEFEFE"
android:pathData="M11.0476403,8.86547314 C10.1626979,9.41441547 9.41459398,10.1624996
8.86563718,11.047619 L3.4524022,5.63432651 L5.63440531,3.45238095
L11.0476403,8.86547314 Z M14.8450352,7.41694052 C14.2508096,7.41694052
13.6733955,7.47975215 13.1190689,7.5952381 L13.1190689,0 L16.5714498,0
L16.5714498,7.5952381 C16.0168991,7.47995092 15.4394849,7.41694052
14.8450352,7.41694052 Z M7.92772894,14.8624137 C7.92772894,15.4344301
7.99486447,15.9902634 8.11830037,16.5240878 L0,16.5240878 L0,13.2007396
L8.11830037,13.2007396 C7.99486447,13.734564 7.92772894,14.2903973
7.92772894,14.8624137 Z M20.1343478,11.047619 C19.5854054,10.1627
18.8375216,9.41461581 17.9524022,8.86547314 L23.3654944,3.45238095
L25.5476403,5.63432651 L20.1343478,11.047619 Z M21.4047832,13.1190476
L29.0000212,13.1190476 L29.0000212,16.5714286 L21.4047832,16.5714286
C21.5202661,16.0168778 21.5830761,15.4394636 21.5830761,14.8452381
C21.5830761,14.2510125 21.5202661,13.6735984 21.4047832,13.1190476 Z
M8.86557962,17.952381 C9.4145509,18.8373234 10.1626745,19.5854273
11.0476403,20.1345844 L5.63446287,25.547619 L3.4524022,23.3656159
L8.86557962,17.952381 Z M17.9524022,20.1345269 C18.8373679,19.5855845
19.5854916,18.8375004 20.1344629,17.952381 L25.5476403,23.3654731
L23.36578,25.547619 L17.9524022,20.1345269 Z M14.8450352,21.5830595
C15.4394849,21.5830595 16.0168991,21.5200491 16.5714498,21.4047619
L16.5714498,29 L13.1190689,29 L13.1190689,21.4047619 C13.6733955,21.5200491
14.2508096,21.5830595 14.8450352,21.5830595 Z"
android:strokeWidth="1" />
</group>
</group>
</vector>

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="24dp"
android:height="24dp"
android:viewportHeight="30.0"
android:viewportWidth="30.0">
<path
android:fillColor="#FF640C"
android:fillType="evenOdd"
android:pathData="M26.667,27.996L27.991,26.667L27.991,20L30,20L30,26.667C30,28.5 28.5,30 26.667,30L20,30L20,27.996L26.667,27.996ZM3.333,27.996L10,27.996L10,30L3.333,30C1.5,30 0,28.5 0,26.667L0,20L2.009,20L2.009,26.667L3.333,27.996ZM26.667,2.004L20,2.004L20,0L26.667,0C28.5,0 30,1.5 30,3.333L30,10L27.991,10L27.991,3.333L26.667,2.004ZM3.333,2.004L2.009,3.333L2.009,10L0,10L0,3.333C0,1.5 1.5,0 3.333,0L10,0L10,2.004L3.333,2.004Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>

View File

@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="6.35"
android:viewportHeight="6.3500004">
<path
android:pathData="m4.518,3.147c0.341,-0.341 0.45,-0.827 0.327,-1.26 -0.433,-0.123 -0.919,-0.014 -1.26,0.327 -0.341,0.341 -0.45,0.827 -0.327,1.26 0.433,0.123 0.919,0.014 1.26,-0.328"
android:fillColor="#ffffff"/>
<path
android:pathData="m2.731,2.214c-0.341,-0.341 -0.826,-0.45 -1.26,-0.327 -0.123,0.433 -0.014,0.919 0.327,1.26 0.341,0.341 0.827,0.45 1.26,0.328 0.123,-0.433 0.014,-0.919 -0.327,-1.26"
android:fillColor="#ffffff"/>
<path
android:pathData="M3.064,2.185l0.206,0l0,3.126l-0.206,0z"
android:fillColor="#ffffff"/>
<path
android:pathData="m4.18,5.357c0,-0.549 -0.445,-0.994 -0.994,-0.994 -0.549,0 -0.994,0.445 -0.994,0.994 0,0.549 0.445,0.994 0.994,0.994 0.549,0 0.994,-0.445 0.994,-0.994"
android:fillColor="#ffffff"/>
<path
android:pathData="m3.834,1.123c0,-0.482 -0.266,-0.903 -0.659,-1.123 -0.393,0.22 -0.659,0.64 -0.659,1.123 -0,0.483 0.266,0.903 0.659,1.123 0.393,-0.22 0.659,-0.64 0.659,-1.123"
android:fillColor="#ffffff"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/gradientOrange"
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="@color/moneroWhite"
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="16.0"
android:viewportWidth="13.0">
<path
android:fillColor="@color/trafficGray"
android:fillType="evenOdd"
android:pathData="M8.401,0.431L8.401,1.741C8.401,2.064 8.163,2.326 7.87,2.326L0.569,2.326C0.255,2.326 0,2.605 0,2.951L0,5.031C0,5.376 0.255,5.657 0.569,5.657L7.87,5.657C8.163,5.657 8.401,5.918 8.401,6.24L8.401,7.569C8.401,7.934 8.788,8.133 9.042,7.899L12.809,4.447C13.064,4.215 13.064,3.785 12.809,3.553L9.042,0.101C8.788,-0.133 8.401,0.066 8.401,0.431L8.401,0.431L8.401,0.431ZM4.599,8.431L4.599,9.742C4.599,10.065 4.837,10.326 5.131,10.326L12.431,10.326C12.746,10.326 13,10.606 13,10.952L13,13.03C13,13.376 12.746,13.656 12.431,13.656L5.131,13.656C4.837,13.656 4.599,13.917 4.599,14.24L4.599,15.569C4.599,15.934 4.212,16.133 3.957,15.9L0.19,12.447C-0.063,12.215 -0.063,11.786 0.19,11.553L3.957,8.101C4.212,7.867 4.599,8.066 4.599,8.431L4.599,8.431L4.599,8.431Z" />
</vector>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<solid android:color="@color/colorAccent" />
</shape>
</item>
<item android:state_pressed="false">
<shape>
<solid android:color="@android:color/transparent" />
</shape>
</item>
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -2,11 +2,17 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@drawable/backgound_all"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:orientation="vertical"> android:orientation="vertical">
<include <com.m2049r.xmrwallet.layout.Toolbar
layout="@layout/toolbar" /> android:id="@+id/toolbar"
style="@style/ToolBarStyle.Event"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/backgound_toolbar_mainnet"
android:minHeight="?attr/actionBarSize" />
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container" android:id="@+id/fragment_container"

View File

@ -2,12 +2,17 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@drawable/backgound_all"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:background="@color/moneroBlack"
android:orientation="vertical"> android:orientation="vertical">
<include <com.m2049r.xmrwallet.layout.Toolbar
layout="@layout/toolbar" /> android:id="@+id/toolbar"
style="@style/ToolBarStyle.Event"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/backgound_toolbar_mainnet"
android:minHeight="?attr/actionBarSize" />
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container" android:id="@+id/fragment_container"

View File

@ -0,0 +1,160 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8sp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="2">
<android.support.design.widget.TextInputLayout
android:id="@+id/etWalletName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
app:counterEnabled="true"
app:counterMaxLength="20"
app:errorEnabled="true">
<android.support.design.widget.TextInputEditText
style="@style/MoneroEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/generate_name_hint"
android:imeOptions="actionNext"
android:inputType="text"
android:maxLines="1"
android:textAlignment="textStart" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/etWalletPassword"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<android.support.design.widget.TextInputEditText
style="@style/MoneroEdit"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/generate_password_hint"
android:imeOptions="actionNext"
android:inputType="text"
android:textAlignment="textStart" />
</android.support.design.widget.TextInputLayout>
</LinearLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/etWalletMnemonic"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:errorEnabled="true">
<android.support.design.widget.TextInputEditText
style="@style/MoneroEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/generate_mnemonic_hint"
android:imeOptions="actionNext"
android:inputType="textMultiLine"
android:textAlignment="center" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/etWalletAddress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:counterEnabled="true"
app:counterMaxLength="95"
app:errorEnabled="true">
<android.support.design.widget.TextInputEditText
style="@style/MoneroEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/generate_address_hint"
android:imeOptions="actionNext"
android:inputType="textMultiLine"
android:textAlignment="center" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/etWalletViewKey"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:counterEnabled="true"
app:counterMaxLength="64"
app:errorEnabled="true">
<android.support.design.widget.TextInputEditText
android:id="@+id/textInputEditText"
style="@style/MoneroEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/generate_viewkey_hint"
android:imeOptions="actionNext"
android:inputType="textMultiLine"
android:textAlignment="center" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/etWalletSpendKey"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:counterEnabled="true"
app:counterMaxLength="64"
app:errorEnabled="true">
<android.support.design.widget.TextInputEditText
style="@style/MoneroEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/generate_spendkey_hint"
android:imeOptions="actionNext"
android:inputType="textMultiLine"
android:textAlignment="center" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/etWalletRestoreHeight"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:errorEnabled="true">
<android.support.design.widget.TextInputEditText
style="@style/MoneroEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/generate_restoreheight_hint"
android:imeOptions="actionDone"
android:inputType="number"
android:textAlignment="center" />
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/bGenerate"
style="@style/MoneroButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/generate_buttonGenerate" />
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8sp">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/etDummy"
android:layout_width="0dp"
android:layout_height="0dp" />
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:hint="@string/label_daemon">
<com.m2049r.xmrwallet.layout.DropDownEditText
android:id="@+id/etDaemonAddress"
style="@style/MoneroEdit.Small"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:hint="@string/prompt_daemon"
android:imeOptions="actionDone"
android:inputType="textWebEmailAddress|textNoSuggestions"
android:maxLines="1"
android:textIsSelectable="true" />
</android.support.design.widget.TextInputLayout>
<TextView
style="@style/MoneroLabel.Heading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="8sp"
android:text="@string/label_login_wallets" />
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/ivGuntherWallets"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/gunther_wallets_00" />
<android.support.v7.widget.RecyclerView xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="72dp"
app:layoutManager="LinearLayoutManager"
tools:listitem="@layout/item_wallet" />
</FrameLayout>
</LinearLayout>
<include layout="@layout/layout_fabmenu" />
</FrameLayout>

Some files were not shown because too many files have changed in this diff Show More