1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-08 20:40:51 +02:00

Compare commits

...

18 Commits

Author SHA1 Message Date
m2049r
b978396a38 fix versionCode 2020-03-08 20:46:23 +01:00
m2049r
c6aa04e986 update heights & fix date parsing bug (#654) 2020-03-08 20:33:34 +01:00
m2049r
6c17b8bd87 update gradle 2020-02-29 10:43:13 +01:00
m2049r
cac32f660c v1.12.11 (monero v0.15.0.1) 2019-11-25 23:30:28 +01:00
m2049r
8e70004bf2 v1.12.10 2019-11-24 17:17:54 +01:00
m2049r
c3a466c392 remove payment id everywhere (#646) 2019-11-24 17:17:25 +01:00
m2049r
e076c19e3e v1.12.9 2019-11-21 10:42:45 +01:00
m2049r
35b717756d Fix amount bugs (#645)
* fix rounding error on send

* fix check funds bug
2019-11-21 10:42:16 +01:00
m2049r
c14486306e v1.12.8 2019-11-20 07:34:25 +01:00
m2049r
c2ef25c377 change error message to 16 for payment ids (#643) 2019-11-20 07:33:29 +01:00
m2049r
b7164ef200 fix XMR missing in open wallet (#642) 2019-11-20 07:33:07 +01:00
m2049r
f94a366d51 v1.12.7 2019-11-19 22:57:43 +01:00
m2049r
286a04b5ef add XMR to receive spinners (again) (#640) 2019-11-19 22:56:28 +01:00
m2049r
1209295a8c v1.12.6 2019-11-18 12:01:49 +01:00
m2049r
037b019d4d xmrto payment through subaddress (#639)
* use subaddress for xmrto only

* fix exchange rate
2019-11-18 12:00:39 +01:00
m2049r
7a1d788f2a UI tweaks (#638)
* fix amount entry field

* password entry on own line

* remove errors when typing; field spacing
2019-11-17 10:31:19 +01:00
m2049r
87d9a8cd95 use kraken for EURXMR exchange rate (#637)
combine with ECB rates for other fiat conversions
2019-11-17 00:42:57 +01:00
m2049r
f637d7f617 update block heights (#636) 2019-11-10 23:54:40 +01:00
64 changed files with 1382 additions and 1020 deletions

View File

@@ -7,8 +7,8 @@ android {
applicationId "com.m2049r.xmrwallet" applicationId "com.m2049r.xmrwallet"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 28 targetSdkVersion 28
versionCode 193 versionCode 202
versionName "1.12.3 'Caerbannog'" versionName "1.12.12 'Caerbannog'"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild { externalNativeBuild {

View File

@@ -79,6 +79,23 @@ public class GenerateFragment extends Fragment {
private String type = null; private String type = null;
private void clearErrorOnTextEntry(final TextInputLayout textInputLayout) {
textInputLayout.getEditText().addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable editable) {
textInputLayout.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) {
}
});
}
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
@@ -110,6 +127,23 @@ public class GenerateFragment extends Fragment {
} }
} }
}); });
clearErrorOnTextEntry(etWalletName);
etWalletPassword.getEditText().addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable editable) {
checkPassword();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
etWalletMnemonic.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() { etWalletMnemonic.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override @Override
public void onFocusChange(View v, boolean hasFocus) { public void onFocusChange(View v, boolean hasFocus) {
@@ -118,6 +152,8 @@ public class GenerateFragment extends Fragment {
} }
} }
}); });
clearErrorOnTextEntry(etWalletMnemonic);
etWalletAddress.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() { etWalletAddress.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override @Override
public void onFocusChange(View v, boolean hasFocus) { public void onFocusChange(View v, boolean hasFocus) {
@@ -126,6 +162,8 @@ public class GenerateFragment extends Fragment {
} }
} }
}); });
clearErrorOnTextEntry(etWalletAddress);
etWalletViewKey.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() { etWalletViewKey.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override @Override
public void onFocusChange(View v, boolean hasFocus) { public void onFocusChange(View v, boolean hasFocus) {
@@ -134,6 +172,8 @@ public class GenerateFragment extends Fragment {
} }
} }
}); });
clearErrorOnTextEntry(etWalletViewKey);
etWalletSpendKey.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() { etWalletSpendKey.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override @Override
public void onFocusChange(View v, boolean hasFocus) { public void onFocusChange(View v, boolean hasFocus) {
@@ -142,6 +182,7 @@ public class GenerateFragment extends Fragment {
} }
} }
}); });
clearErrorOnTextEntry(etWalletSpendKey);
Helper.showKeyboard(getActivity()); Helper.showKeyboard(getActivity());
@@ -310,21 +351,6 @@ public class GenerateFragment extends Fragment {
} }
}); });
etWalletPassword.getEditText().addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable editable) {
checkPassword();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
etWalletName.requestFocus(); etWalletName.requestFocus();
initZxcvbn(); initZxcvbn();
@@ -412,7 +438,7 @@ public class GenerateFragment extends Fragment {
height = RestoreHeight.getInstance().getHeight(parser.parse(restoreHeight)); height = RestoreHeight.getInstance().getHeight(parser.parse(restoreHeight));
} catch (ParseException ex) { } catch (ParseException ex) {
} }
if (height <= 0) if ((height <= 0) && (restoreHeight.length() == 8))
try { try {
// is it a date without dashes? // is it a date without dashes?
SimpleDateFormat parser = new SimpleDateFormat("yyyyMMdd"); SimpleDateFormat parser = new SimpleDateFormat("yyyyMMdd");

View File

@@ -50,9 +50,8 @@ import com.m2049r.xmrwallet.widget.Toolbar;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import timber.log.Timber; import timber.log.Timber;
@@ -114,9 +113,12 @@ public class WalletFragment extends Fragment
ivSynced = view.findViewById(R.id.ivSynced); ivSynced = view.findViewById(R.id.ivSynced);
sCurrency = view.findViewById(R.id.sCurrency); sCurrency = view.findViewById(R.id.sCurrency);
ArrayAdapter currencyAdapter = ArrayAdapter.createFromResource(getContext(), R.array.currency, R.layout.item_spinner_balance); List<String> currencies = new ArrayList<>();
currencyAdapter.setDropDownViewResource(R.layout.item_spinner_dropdown_item); currencies.add(Helper.BASE_CRYPTO);
sCurrency.setAdapter(currencyAdapter); currencies.addAll(Arrays.asList(getResources().getStringArray(R.array.currency)));
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(getContext(), R.layout.item_spinner_balance, currencies);
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
sCurrency.setAdapter(spinnerAdapter);
bSend = view.findViewById(R.id.bSend); bSend = view.findViewById(R.id.bSend);
bReceive = view.findViewById(R.id.bReceive); bReceive = view.findViewById(R.id.bReceive);
@@ -220,7 +222,7 @@ public class WalletFragment extends Fragment
// at this point selection is XMR in case of error // at this point selection is XMR in case of error
String displayB; String displayB;
double amountA = Helper.getDecimalAmount(unlockedBalance).doubleValue(); double amountA = Helper.getDecimalAmount(unlockedBalance).doubleValue();
if (!Helper.CRYPTO.equals(balanceCurrency)) { // not XMR if (!Helper.BASE_CRYPTO.equals(balanceCurrency)) { // not XMR
double amountB = amountA * balanceRate; double amountB = amountA * balanceRate;
displayB = Helper.getFormattedAmount(amountB, false); displayB = Helper.getFormattedAmount(amountB, false);
} else { // XMR } else { // XMR
@@ -229,7 +231,7 @@ public class WalletFragment extends Fragment
showBalance(displayB); showBalance(displayB);
} }
String balanceCurrency = Helper.CRYPTO; String balanceCurrency = Helper.BASE_CRYPTO;
double balanceRate = 1.0; double balanceRate = 1.0;
private final ExchangeApi exchangeApi = Helper.getExchangeApi(); private final ExchangeApi exchangeApi = Helper.getExchangeApi();
@@ -245,7 +247,7 @@ public class WalletFragment extends Fragment
Timber.d(currency); Timber.d(currency);
if (!currency.equals(balanceCurrency) || (balanceRate <= 0)) { if (!currency.equals(balanceCurrency) || (balanceRate <= 0)) {
showExchanging(); showExchanging();
exchangeApi.queryExchangeRate(Helper.CRYPTO, currency, exchangeApi.queryExchangeRate(Helper.BASE_CRYPTO, currency,
new ExchangeCallback() { new ExchangeCallback() {
@Override @Override
public void onSuccess(final ExchangeRate exchangeRate) { public void onSuccess(final ExchangeRate exchangeRate) {
@@ -301,10 +303,10 @@ public class WalletFragment extends Fragment
public void exchange(final ExchangeRate exchangeRate) { public void exchange(final ExchangeRate exchangeRate) {
hideExchanging(); hideExchanging();
if (!Helper.CRYPTO.equals(exchangeRate.getBaseCurrency())) { if (!Helper.BASE_CRYPTO.equals(exchangeRate.getBaseCurrency())) {
Timber.e("Not XMR"); Timber.e("Not XMR");
sCurrency.setSelection(0, true); sCurrency.setSelection(0, true);
balanceCurrency = Helper.CRYPTO; balanceCurrency = Helper.BASE_CRYPTO;
balanceRate = 1.0; balanceRate = 1.0;
} else { } else {
int spinnerPosition = ((ArrayAdapter) sCurrency.getAdapter()).getPosition(exchangeRate.getQuoteCurrency()); int spinnerPosition = ((ArrayAdapter) sCurrency.getAdapter()).getPosition(exchangeRate.getQuoteCurrency());

View File

@@ -59,42 +59,36 @@ public class BarcodeData {
final public Asset asset; final public Asset asset;
final public String address; final public String address;
final public String addressName; final public String addressName;
final public String paymentId;
final public String amount; final public String amount;
final public String description; final public String description;
final public Security security; final public Security security;
final public String bip70; final public String bip70;
public BarcodeData(Asset asset, String address) { public BarcodeData(Asset asset, String address) {
this(asset, address, null, null, null, null, Security.NORMAL); this(asset, address, null, null, null, Security.NORMAL);
} }
public BarcodeData(Asset asset, String address, String amount) { public BarcodeData(Asset asset, String address, String amount) {
this(asset, address, null, null, null, amount, Security.NORMAL); this(asset, address, null, null, amount, Security.NORMAL);
} }
public BarcodeData(Asset asset, String address, String amount, String description, Security security) { public BarcodeData(Asset asset, String address, String amount, String description, Security security) {
this(asset, address, null, null, description, amount, security); this(asset, address, null, description, amount, security);
}
public BarcodeData(Asset asset, String address, String paymentId, String amount) {
this(asset, address, null, paymentId, null, amount, Security.NORMAL);
} }
public BarcodeData(Asset asset, String address, String paymentId, String description, String amount) { public BarcodeData(Asset asset, String address, String paymentId, String description, String amount) {
this(asset, address, null, paymentId, description, amount, Security.NORMAL); this(asset, address, null, description, amount, Security.NORMAL);
} }
public BarcodeData(Asset asset, String address, String addressName, String paymentId, String description, String amount, Security security) { public BarcodeData(Asset asset, String address, String addressName, String description, String amount, Security security) {
this(asset, address, addressName, null, paymentId, description, amount, security); this(asset, address, addressName, null, description, amount, security);
} }
public BarcodeData(Asset asset, String address, String addressName, String bip70, String paymentId, String description, String amount, Security security) { public BarcodeData(Asset asset, String address, String addressName, String bip70, String description, String amount, Security security) {
this.asset = asset; this.asset = asset;
this.address = address; this.address = address;
this.bip70 = bip70; this.bip70 = bip70;
this.addressName = addressName; this.addressName = addressName;
this.paymentId = paymentId;
this.description = description; this.description = description;
this.amount = amount; this.amount = amount;
this.security = security; this.security = security;
@@ -110,11 +104,6 @@ public class BarcodeData {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(BarcodeData.XMR_SCHEME).append(address); sb.append(BarcodeData.XMR_SCHEME).append(address);
boolean first = true; boolean first = true;
if ((paymentId != null) && !paymentId.isEmpty()) {
sb.append("?");
first = false;
sb.append(BarcodeData.XMR_PAYMENTID).append('=').append(paymentId);
}
if ((description != null) && !description.isEmpty()) { if ((description != null) && !description.isEmpty()) {
sb.append(first ? "?" : "&"); sb.append(first ? "?" : "&");
first = false; first = false;
@@ -185,8 +174,11 @@ public class BarcodeData {
String address = monero.getPath(); String address = monero.getPath();
String paymentId = parms.get(XMR_PAYMENTID); String paymentId = parms.get(XMR_PAYMENTID);
// deal with empty payment_id created by non-spec-conforming apps // no support for payment ids!
if ((paymentId != null) && paymentId.isEmpty()) paymentId = null; if (paymentId != null) {
Timber.e("no support for payment ids!");
return null;
}
String description = parms.get(XMR_DESCRIPTION); String description = parms.get(XMR_DESCRIPTION);
String amount = parms.get(XMR_AMOUNT); String amount = parms.get(XMR_AMOUNT);
@@ -198,10 +190,6 @@ public class BarcodeData {
return null; // we have an amount but its not a number! return null; // we have an amount but its not a number!
} }
} }
if ((paymentId != null) && !Wallet.isPaymentIdValid(paymentId)) {
Timber.d("paymentId invalid");
return null;
}
if (!Wallet.isAddressValid(address)) { if (!Wallet.isAddressValid(address)) {
Timber.d("address invalid"); Timber.d("address invalid");
@@ -267,7 +255,7 @@ public class BarcodeData {
Timber.d("[%s] is not http url", bip70); Timber.d("[%s] is not http url", bip70);
return null; return null;
} }
return new BarcodeData(BarcodeData.Asset.BTC, null, null, bip70, null, description, null, Security.NORMAL); return new BarcodeData(BarcodeData.Asset.BTC, null, null, bip70, description, null, Security.NORMAL);
} }
if (!BitcoinAddressValidator.validate(address)) { if (!BitcoinAddressValidator.validate(address)) {
Timber.d("BTC address (%s) invalid", address); Timber.d("BTC address (%s) invalid", address);

View File

@@ -29,19 +29,16 @@ public class TxData implements Parcelable {
public TxData(TxData txData) { public TxData(TxData txData) {
this.dstAddr = txData.dstAddr; this.dstAddr = txData.dstAddr;
this.paymentId = txData.paymentId;
this.amount = txData.amount; this.amount = txData.amount;
this.mixin = txData.mixin; this.mixin = txData.mixin;
this.priority = txData.priority; this.priority = txData.priority;
} }
public TxData(String dstAddr, public TxData(String dstAddr,
String paymentId,
long amount, long amount,
int mixin, int mixin,
PendingTransaction.Priority priority) { PendingTransaction.Priority priority) {
this.dstAddr = dstAddr; this.dstAddr = dstAddr;
this.paymentId = paymentId;
this.amount = amount; this.amount = amount;
this.mixin = mixin; this.mixin = mixin;
this.priority = priority; this.priority = priority;
@@ -51,10 +48,6 @@ public class TxData implements Parcelable {
return dstAddr; return dstAddr;
} }
public String getPaymentId() {
return paymentId;
}
public long getAmount() { public long getAmount() {
return amount; return amount;
} }
@@ -71,10 +64,6 @@ public class TxData implements Parcelable {
this.dstAddr = dstAddr; this.dstAddr = dstAddr;
} }
public void setPaymentId(String paymentId) {
this.paymentId = paymentId;
}
public void setAmount(long amount) { public void setAmount(long amount) {
this.amount = amount; this.amount = amount;
} }
@@ -96,7 +85,6 @@ public class TxData implements Parcelable {
} }
private String dstAddr; private String dstAddr;
private String paymentId;
private long amount; private long amount;
private int mixin; private int mixin;
private PendingTransaction.Priority priority; private PendingTransaction.Priority priority;
@@ -106,7 +94,6 @@ public class TxData implements Parcelable {
@Override @Override
public void writeToParcel(Parcel out, int flags) { public void writeToParcel(Parcel out, int flags) {
out.writeString(dstAddr); out.writeString(dstAddr);
out.writeString(paymentId);
out.writeLong(amount); out.writeLong(amount);
out.writeInt(mixin); out.writeInt(mixin);
out.writeInt(priority.getValue()); out.writeInt(priority.getValue());
@@ -125,7 +112,6 @@ public class TxData implements Parcelable {
protected TxData(Parcel in) { protected TxData(Parcel in) {
dstAddr = in.readString(); dstAddr = in.readString();
paymentId = in.readString();
amount = in.readLong(); amount = in.readLong();
mixin = in.readInt(); mixin = in.readInt();
priority = PendingTransaction.Priority.fromInteger(in.readInt()); priority = PendingTransaction.Priority.fromInteger(in.readInt());
@@ -142,14 +128,12 @@ public class TxData implements Parcelable {
StringBuffer sb = new StringBuffer(); StringBuffer sb = new StringBuffer();
sb.append("dstAddr:"); sb.append("dstAddr:");
sb.append(dstAddr); sb.append(dstAddr);
sb.append(",paymentId:");
sb.append(paymentId);
sb.append(",amount:"); sb.append(",amount:");
sb.append(amount); sb.append(amount);
sb.append(",mixin:"); sb.append(",mixin:");
sb.append(mixin); sb.append(mixin);
sb.append(",priority:"); sb.append(",priority:");
sb.append(String.valueOf(priority)); sb.append(priority);
return sb.toString(); return sb.toString();
} }
} }

View File

@@ -31,7 +31,6 @@ 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.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.TextView; import android.widget.TextView;
@@ -86,12 +85,9 @@ public class SendAddressWizardFragment extends SendWizardFragment {
private EditText etDummy; private EditText etDummy;
private TextInputLayout etAddress; private TextInputLayout etAddress;
private TextInputLayout etPaymentId;
private TextInputLayout etNotes; private TextInputLayout etNotes;
private Button bPaymentId;
private CardView cvScan; private CardView cvScan;
private View tvPaymentIdIntegrated; private View tvPaymentIdIntegrated;
private View llPaymentId;
private TextView tvXmrTo; private TextView tvXmrTo;
private View llXmrTo; private View llXmrTo;
private ImageButton bPasteAddress; private ImageButton bPasteAddress;
@@ -114,7 +110,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
View view = inflater.inflate(R.layout.fragment_send_address, container, false); View view = inflater.inflate(R.layout.fragment_send_address, container, false);
tvPaymentIdIntegrated = view.findViewById(R.id.tvPaymentIdIntegrated); tvPaymentIdIntegrated = view.findViewById(R.id.tvPaymentIdIntegrated);
llPaymentId = view.findViewById(R.id.llPaymentId);
llXmrTo = view.findViewById(R.id.llXmrTo); llXmrTo = view.findViewById(R.id.llXmrTo);
tvXmrTo = view.findViewById(R.id.tvXmrTo); tvXmrTo = view.findViewById(R.id.tvXmrTo);
tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto))); tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto)));
@@ -158,8 +153,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
etAddress.setError(null); etAddress.setError(null);
if (isIntegratedAddress()) { if (isIntegratedAddress()) {
Timber.d("isIntegratedAddress"); Timber.d("isIntegratedAddress");
etPaymentId.getEditText().getText().clear();
llPaymentId.setVisibility(View.INVISIBLE);
etAddress.setError(getString(R.string.info_paymentid_integrated)); etAddress.setError(getString(R.string.info_paymentid_integrated));
tvPaymentIdIntegrated.setVisibility(View.VISIBLE); tvPaymentIdIntegrated.setVisibility(View.VISIBLE);
llXmrTo.setVisibility(View.INVISIBLE); llXmrTo.setVisibility(View.INVISIBLE);
@@ -169,7 +162,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
setBtcMode(); setBtcMode();
} else { } else {
Timber.d("isStandardAddress or other"); Timber.d("isStandardAddress or other");
llPaymentId.setVisibility(View.VISIBLE);
tvPaymentIdIntegrated.setVisibility(View.INVISIBLE); tvPaymentIdIntegrated.setVisibility(View.INVISIBLE);
llXmrTo.setVisibility(View.INVISIBLE); llXmrTo.setVisibility(View.INVISIBLE);
sendListener.setMode(SendFragment.Mode.XMR); sendListener.setMode(SendFragment.Mode.XMR);
@@ -211,46 +203,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
} }
}); });
etPaymentId = view.findViewById(R.id.etPaymentId);
etPaymentId.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etPaymentId.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
if (checkPaymentId()) {
etNotes.requestFocus();
}
return true;
}
return false;
}
});
etPaymentId.getEditText().addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable editable) {
etPaymentId.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) {
}
});
bPaymentId = view.findViewById(R.id.bPaymentId);
bPaymentId.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final EditText et = etPaymentId.getEditText();
et.setText((Wallet.generatePaymentId()));
et.setSelection(et.getText().length());
etPaymentId.requestFocus();
}
});
etNotes = view.findViewById(R.id.etNotes); etNotes = view.findViewById(R.id.etNotes);
etNotes.getEditText().setRawInputType(InputType.TYPE_CLASS_TEXT); etNotes.getEditText().setRawInputType(InputType.TYPE_CLASS_TEXT);
etNotes.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { etNotes.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
@@ -286,8 +238,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
private void setBtcMode() { private void setBtcMode() {
Timber.d("setBtcMode"); Timber.d("setBtcMode");
etPaymentId.getEditText().getText().clear();
llPaymentId.setVisibility(View.INVISIBLE);
tvPaymentIdIntegrated.setVisibility(View.INVISIBLE); tvPaymentIdIntegrated.setVisibility(View.INVISIBLE);
llXmrTo.setVisibility(View.VISIBLE); llXmrTo.setVisibility(View.VISIBLE);
sendListener.setMode(SendFragment.Mode.BTC); sendListener.setMode(SendFragment.Mode.BTC);
@@ -342,7 +292,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
final BarcodeData barcodeData = final BarcodeData barcodeData =
new BarcodeData(BarcodeData.Asset.BTC, address, null, new BarcodeData(BarcodeData.Asset.BTC, address, null,
resolvedBip70, null, null, String.valueOf(amount), resolvedBip70, null, String.valueOf(amount),
BarcodeData.Security.BIP70); BarcodeData.Security.BIP70);
etNotes.post(new Runnable() { etNotes.post(new Runnable() {
@Override @Override
@@ -403,22 +353,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
return BitcoinAddressValidator.validate(address); return BitcoinAddressValidator.validate(address);
} }
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 {
if (!paymentId.isEmpty() && isIntegratedAddress()) {
ok = false;
etPaymentId.setError(getString(R.string.receive_integrated_paymentid_invalid));
} else {
etPaymentId.setError(null);
}
}
return ok;
}
private void shakeAddress() { private void shakeAddress() {
etAddress.startAnimation(Helper.getShakeAnimation(getContext())); etAddress.startAnimation(Helper.getShakeAnimation(getContext()));
} }
@@ -441,11 +375,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
return false; return false;
} }
if (!checkPaymentId()) {
etPaymentId.startAnimation(Helper.getShakeAnimation(getContext()));
return false;
}
if (sendListener != null) { if (sendListener != null) {
TxData txData = sendListener.getTxData(); TxData txData = sendListener.getTxData();
if (txData instanceof TxDataBtc) { if (txData instanceof TxDataBtc) {
@@ -459,10 +388,8 @@ public class SendAddressWizardFragment extends SendWizardFragment {
((TxDataBtc) txData).setBip70(null); ((TxDataBtc) txData).setBip70(null);
} }
txData.setDestinationAddress(null); txData.setDestinationAddress(null);
txData.setPaymentId("");
} else { } else {
txData.setDestinationAddress(etAddress.getEditText().getText().toString()); txData.setDestinationAddress(etAddress.getEditText().getText().toString());
txData.setPaymentId(etPaymentId.getEditText().getText().toString());
} }
txData.setUserNotes(new UserNotes(etNotes.getEditText().getText().toString())); txData.setUserNotes(new UserNotes(etNotes.getEditText().getText().toString()));
txData.setPriority(PendingTransaction.Priority.Priority_Default); txData.setPriority(PendingTransaction.Priority.Priority_Default);
@@ -525,14 +452,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
etAddress.setError(null); etAddress.setError(null);
} }
String scannedPaymentId = barcodeData.paymentId;
if (scannedPaymentId != null) {
etPaymentId.getEditText().setText(scannedPaymentId);
checkPaymentId();
} else {
etPaymentId.getEditText().getText().clear();
etPaymentId.setError(null);
}
String scannedNotes = barcodeData.description; String scannedNotes = barcodeData.description;
if (scannedNotes != null) { if (scannedNotes != null) {
etNotes.getEditText().setText(scannedNotes); etNotes.getEditText().setText(scannedNotes);

View File

@@ -116,12 +116,12 @@ public class SendAmountWizardFragment extends SendWizardFragment {
sendListener.getTxData().setAmount(Wallet.SWEEP_ALL); sendListener.getTxData().setAmount(Wallet.SWEEP_ALL);
} }
} else { } else {
if (!etAmount.validate(maxFunds)) { if (!etAmount.validate(maxFunds, 0)) {
return false; return false;
} }
if (sendListener != null) { if (sendListener != null) {
String xmr = etAmount.getAmount(); String xmr = etAmount.getNativeAmount();
if (xmr != null) { if (xmr != null) {
sendListener.getTxData().setAmount(Wallet.getAmountFromString(xmr)); sendListener.getTxData().setAmount(Wallet.getAmountFromString(xmr));
} else { } else {
@@ -148,8 +148,8 @@ public class SendAmountWizardFragment extends SendWizardFragment {
tvFunds.setText(getString(R.string.send_available, tvFunds.setText(getString(R.string.send_available,
getString(R.string.unknown_amount))); getString(R.string.unknown_amount)));
} }
// getAmount is null if exchange is in progress // getNativeAmount is null if exchange is in progress
if ((etAmount.getAmount() != null) && etAmount.getAmount().isEmpty()) { if ((etAmount.getNativeAmount() != null) && etAmount.getNativeAmount().isEmpty()) {
final BarcodeData data = sendListener.popBarcodeData(); final BarcodeData data = sendListener.popBarcodeData();
if ((data != null) && (data.amount != null)) { if ((data != null) && (data.amount != null)) {
etAmount.setAmount(data.amount); etAmount.setAmount(data.amount);

View File

@@ -31,7 +31,8 @@ import com.m2049r.xmrwallet.data.TxDataBtc;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.OkHttpHelper; import com.m2049r.xmrwallet.util.OkHttpHelper;
import com.m2049r.xmrwallet.widget.ExchangeBtcEditText; import com.m2049r.xmrwallet.widget.ExchangeEditText;
import com.m2049r.xmrwallet.widget.ExchangeOtherEditText;
import com.m2049r.xmrwallet.widget.SendProgressView; import com.m2049r.xmrwallet.widget.SendProgressView;
import com.m2049r.xmrwallet.xmrto.XmrToError; import com.m2049r.xmrwallet.xmrto.XmrToError;
import com.m2049r.xmrwallet.xmrto.XmrToException; import com.m2049r.xmrwallet.xmrto.XmrToException;
@@ -61,7 +62,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
} }
private TextView tvFunds; private TextView tvFunds;
private ExchangeBtcEditText etAmount; private ExchangeOtherEditText etAmount;
private TextView tvXmrToParms; private TextView tvXmrToParms;
private SendProgressView evParams; private SendProgressView evParams;
@@ -97,7 +98,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
} }
if (sendListener != null) { if (sendListener != null) {
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData(); TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
String btcString = etAmount.getAmount(); String btcString = etAmount.getNativeAmount();
if (btcString != null) { if (btcString != null) {
try { try {
double btc = Double.parseDouble(btcString); double btc = Double.parseDouble(btcString);
@@ -166,7 +167,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
getView().post(new Runnable() { getView().post(new Runnable() {
@Override @Override
public void run() { public void run() {
etAmount.setRate(1.0d / orderParameters.getPrice()); etAmount.setExchangeRate(1.0d / orderParameters.getPrice());
NumberFormat df = NumberFormat.getInstance(Locale.US); NumberFormat df = NumberFormat.getInstance(Locale.US);
df.setMaximumFractionDigits(6); df.setMaximumFractionDigits(6);
String min = df.format(orderParameters.getLowerLimit()); String min = df.format(orderParameters.getLowerLimit());
@@ -206,7 +207,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
} }
private void processOrderParmsError(final Exception ex) { private void processOrderParmsError(final Exception ex) {
etAmount.setRate(0); etAmount.setExchangeRate(0);
orderParameters = null; orderParameters = null;
maxBtc = 0; maxBtc = 0;
minBtc = 0; minBtc = 0;

View File

@@ -457,8 +457,7 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
} }
showProgress(3, getString(R.string.label_send_progress_create_tx)); showProgress(3, getString(R.string.label_send_progress_create_tx));
TxData txData = sendListener.getTxData(); TxData txData = sendListener.getTxData();
txData.setDestinationAddress(xmrtoStatus.getXmrReceivingAddress()); txData.setDestinationAddress(xmrtoStatus.getXmrReceivingSubaddress());
txData.setPaymentId(xmrtoStatus.getXmrRequiredPaymentIdShort());
txData.setAmount(Wallet.getAmountFromDouble(xmrtoStatus.getXmrAmountTotal())); txData.setAmount(Wallet.getAmountFromDouble(xmrtoStatus.getXmrAmountTotal()));
getActivityCallback().onPrepareSend(xmrtoStatus.getUuid(), txData); getActivityCallback().onPrepareSend(xmrtoStatus.getUuid(), txData);
} }

View File

@@ -138,12 +138,6 @@ public class SendBtcSuccessWizardFragment extends SendWizardFragment {
btcData = (TxDataBtc) sendListener.getTxData(); btcData = (TxDataBtc) sendListener.getTxData();
tvTxAddress.setText(btcData.getDestinationAddress()); tvTxAddress.setText(btcData.getDestinationAddress());
String paymentId = btcData.getPaymentId();
if ((paymentId != null) && (!paymentId.isEmpty())) {
tvTxPaymentId.setText(btcData.getPaymentId());
} else {
tvTxPaymentId.setText("-");
}
final PendingTx committedTx = sendListener.getCommittedTx(); final PendingTx committedTx = sendListener.getCommittedTx();
if (committedTx != null) { if (committedTx != null) {

View File

@@ -70,7 +70,6 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
} }
private TextView tvTxAddress; private TextView tvTxAddress;
private TextView tvTxPaymentId;
private TextView tvTxNotes; private TextView tvTxNotes;
private TextView tvTxAmount; private TextView tvTxAmount;
private TextView tvTxFee; private TextView tvTxFee;
@@ -90,7 +89,6 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
R.layout.fragment_send_confirm, container, false); R.layout.fragment_send_confirm, container, false);
tvTxAddress = view.findViewById(R.id.tvTxAddress); tvTxAddress = view.findViewById(R.id.tvTxAddress);
tvTxPaymentId = view.findViewById(R.id.tvTxPaymentId);
tvTxNotes = view.findViewById(R.id.tvTxNotes); tvTxNotes = view.findViewById(R.id.tvTxNotes);
tvTxAmount = view.findViewById(R.id.tvTxAmount); tvTxAmount = view.findViewById(R.id.tvTxAmount);
tvTxFee = view.findViewById(R.id.tvTxFee); tvTxFee = view.findViewById(R.id.tvTxFee);
@@ -192,12 +190,6 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
final TxData txData = sendListener.getTxData(); final TxData txData = sendListener.getTxData();
tvTxAddress.setText(txData.getDestinationAddress()); tvTxAddress.setText(txData.getDestinationAddress());
String paymentId = txData.getPaymentId();
if ((paymentId != null) && (!paymentId.isEmpty())) {
tvTxPaymentId.setText(txData.getPaymentId());
} else {
tvTxPaymentId.setText("-");
}
UserNotes notes = sendListener.getTxData().getUserNotes(); UserNotes notes = sendListener.getTxData().getUserNotes();
if ((notes != null) && (!notes.note.isEmpty())) { if ((notes != null) && (!notes.note.isEmpty())) {
tvTxNotes.setText(notes.note); tvTxNotes.setText(notes.note);

View File

@@ -111,12 +111,6 @@ public class SendSuccessWizardFragment extends SendWizardFragment {
final TxData txData = sendListener.getTxData(); final TxData txData = sendListener.getTxData();
tvTxAddress.setText(txData.getDestinationAddress()); tvTxAddress.setText(txData.getDestinationAddress());
String paymentId = txData.getPaymentId();
if ((paymentId != null) && (!paymentId.isEmpty())) {
tvTxPaymentId.setText(txData.getPaymentId());
} else {
tvTxPaymentId.setText("-");
}
final PendingTx committedTx = sendListener.getCommittedTx(); final PendingTx committedTx = sendListener.getCommittedTx();
if (committedTx != null) { if (committedTx != null) {

View File

@@ -298,22 +298,21 @@ public class Wallet {
public PendingTransaction createTransaction(TxData txData) { public PendingTransaction createTransaction(TxData txData) {
return createTransaction( return createTransaction(
txData.getDestinationAddress(), txData.getDestinationAddress(),
txData.getPaymentId(),
txData.getAmount(), txData.getAmount(),
txData.getMixin(), txData.getMixin(),
txData.getPriority()); txData.getPriority());
} }
public PendingTransaction createTransaction(String dst_addr, String payment_id, public PendingTransaction createTransaction(String dst_addr,
long amount, int mixin_count, long amount, int mixin_count,
PendingTransaction.Priority priority) { PendingTransaction.Priority priority) {
disposePendingTransaction(); disposePendingTransaction();
int _priority = priority.getValue(); int _priority = priority.getValue();
long txHandle = long txHandle =
(amount == SWEEP_ALL ? (amount == SWEEP_ALL ?
createSweepTransaction(dst_addr, payment_id, mixin_count, _priority, createSweepTransaction(dst_addr, "", mixin_count, _priority,
accountIndex) : accountIndex) :
createTransactionJ(dst_addr, payment_id, amount, mixin_count, _priority, createTransactionJ(dst_addr, "", amount, mixin_count, _priority,
accountIndex)); accountIndex));
pendingTransaction = new PendingTransaction(txHandle); pendingTransaction = new PendingTransaction(txHandle);
return pendingTransaction; return pendingTransaction;

View File

@@ -0,0 +1,215 @@
/*
* Copyright (c) 2019 m2049r@monerujo.io
*
* 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.
*/
// https://developer.android.com/training/basics/network-ops/xml
package com.m2049r.xmrwallet.service.exchange.ecb;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeException;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import okhttp3.Call;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
public class ExchangeApiImpl implements ExchangeApi {
@NonNull
private final OkHttpClient okHttpClient;
@NonNull
private final HttpUrl baseUrl;
//so we can inject the mockserver url
@VisibleForTesting
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient, @NonNull final HttpUrl baseUrl) {
this.okHttpClient = okHttpClient;
this.baseUrl = baseUrl;
}
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient) {
this(okHttpClient, HttpUrl.parse("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml"));
// data is daily and is refreshed around 16:00 CET every working day
}
public static boolean isSameDay(Calendar calendar, Calendar anotherCalendar) {
return (calendar.get(Calendar.YEAR) == anotherCalendar.get(Calendar.YEAR)) &&
(calendar.get(Calendar.DAY_OF_YEAR) == anotherCalendar.get(Calendar.DAY_OF_YEAR));
}
@Override
public void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency,
@NonNull final ExchangeCallback callback) {
if (!baseCurrency.equals("EUR")) {
callback.onError(new IllegalArgumentException("Only EUR supported as base"));
return;
}
if (baseCurrency.equals(quoteCurrency)) {
callback.onSuccess(new ExchangeRateImpl(quoteCurrency, 1.0, new Date()));
return;
}
if (fetchDate != null) { // we have data
boolean useCache = false;
// figure out if we can use the cached values
// data is daily and is refreshed around 16:00 CET every working day
Calendar now = Calendar.getInstance(TimeZone.getTimeZone("CET"));
int fetchWeekday = fetchDate.get(Calendar.DAY_OF_WEEK);
int fetchDay = fetchDate.get(Calendar.DAY_OF_YEAR);
int fetchHour = fetchDate.get(Calendar.HOUR_OF_DAY);
int today = now.get(Calendar.DAY_OF_YEAR);
int nowHour = now.get(Calendar.HOUR_OF_DAY);
if (
// was it fetched today before 16:00? assume no new data iff now < 16:00 as well
((today == fetchDay) && (fetchHour < 16) && (nowHour < 16))
// was it fetched after, 17:00? we can assume there is no newer data
|| ((today == fetchDay) && (fetchHour > 17))
|| ((today == fetchDay + 1) && (fetchHour > 17) && (nowHour < 16))
// is the data itself from today? there can be no newer data
|| (fxDate.get(Calendar.DAY_OF_YEAR) == today)
// was it fetched Sat/Sun? we can assume there is no newer data
|| ((fetchWeekday == Calendar.SATURDAY) || (fetchWeekday == Calendar.SUNDAY))
) { // return cached rate
try {
callback.onSuccess(getRate(quoteCurrency));
} catch (ExchangeException ex) {
callback.onError(ex);
}
return;
}
}
final Request httpRequest = createHttpRequest(baseUrl);
okHttpClient.newCall(httpRequest).enqueue(new okhttp3.Callback() {
@Override
public void onFailure(final Call call, final IOException ex) {
callback.onError(ex);
}
@Override
public void onResponse(final Call call, final Response response) throws IOException {
if (response.isSuccessful()) {
try {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(response.body().byteStream());
doc.getDocumentElement().normalize();
parse(doc);
try {
callback.onSuccess(getRate(quoteCurrency));
} catch (ExchangeException ex) {
callback.onError(ex);
}
} catch (ParserConfigurationException | SAXException ex) {
Timber.w(ex);
callback.onError(new ExchangeException(ex.getLocalizedMessage()));
}
} else {
callback.onError(new ExchangeException(response.code(), response.message()));
}
}
});
}
private Request createHttpRequest(final HttpUrl url) {
return new Request.Builder()
.url(url)
.get()
.build();
}
final private Map<String, Double> fxEntries = new HashMap<>();
private Calendar fxDate = null;
private Calendar fetchDate = null;
synchronized private ExchangeRate getRate(String currency) throws ExchangeException {
Timber.e("Getting %s", currency);
final Double rate = fxEntries.get(currency);
if (rate == null) throw new ExchangeException(404, "Currency not supported: " + currency);
return new ExchangeRateImpl(currency, rate, fxDate.getTime());
}
private final static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
{
DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
}
private void parse(final Document xmlRootDoc) {
final Map<String, Double> entries = new HashMap<>();
Calendar date = Calendar.getInstance(TimeZone.getTimeZone("CET"));
try {
NodeList cubes = xmlRootDoc.getElementsByTagName("Cube");
for (int i = 0; i < cubes.getLength(); i++) {
Node node = cubes.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element cube = (Element) node;
if (cube.hasAttribute("time")) { // a time Cube
final Date time = DATE_FORMAT.parse(cube.getAttribute("time"));
date.setTime(time);
} else if (cube.hasAttribute("currency")
&& cube.hasAttribute("rate")) { // a rate Cube
String currency = cube.getAttribute("currency");
double rate = Double.valueOf(cube.getAttribute("rate"));
entries.put(currency, rate);
} // else an empty Cube - ignore
}
}
} catch (ParseException ex) {
Timber.d(ex);
}
synchronized (this) {
if (date != null) {
fetchDate = Calendar.getInstance(TimeZone.getTimeZone("CET"));
fxDate = date;
fxEntries.clear();
fxEntries.putAll(entries);
}
// else don't change what we have
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2019 m2049r et al.
*
* 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.service.exchange.ecb;
import android.support.annotation.NonNull;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import java.util.Date;
class ExchangeRateImpl implements ExchangeRate {
private final Date date;
private final String baseCurrency = "EUR";
private final String quoteCurrency;
private final double rate;
@Override
public String getServiceName() {
return "ecb.europa.eu";
}
@Override
public String getBaseCurrency() {
return baseCurrency;
}
@Override
public String getQuoteCurrency() {
return quoteCurrency;
}
@Override
public double getRate() {
return rate;
}
ExchangeRateImpl(@NonNull final String quoteCurrency, double rate, @NonNull final Date date) {
super();
this.quoteCurrency = quoteCurrency;
this.rate = rate;
this.date = date;
}
}

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2017-2018 m2049r et al. * Copyright (c) 2017-2019 m2049r et al.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.m2049r.xmrwallet.service.exchange.coinmarketcap; package com.m2049r.xmrwallet.service.exchange.kraken;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting; import android.support.annotation.VisibleForTesting;
@@ -36,9 +36,9 @@ import okhttp3.HttpUrl;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import timber.log.Timber;
public class ExchangeApiImpl implements ExchangeApi { public class ExchangeApiImpl implements ExchangeApi {
static final String CRYPTO_ID = "328";
@NonNull @NonNull
private final OkHttpClient okHttpClient; private final OkHttpClient okHttpClient;
@@ -47,14 +47,13 @@ public class ExchangeApiImpl implements ExchangeApi {
//so we can inject the mockserver url //so we can inject the mockserver url
@VisibleForTesting @VisibleForTesting
ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient, final HttpUrl baseUrl) { public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient, final HttpUrl baseUrl) {
this.okHttpClient = okHttpClient; this.okHttpClient = okHttpClient;
this.baseUrl = baseUrl; this.baseUrl = baseUrl;
} }
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient) { public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient) {
this(okHttpClient, HttpUrl.parse("https://api.coinmarketcap.com/v2/ticker/")); this(okHttpClient, HttpUrl.parse("https://api.kraken.com/0/public/Ticker"));
} }
@Override @Override
@@ -66,29 +65,25 @@ public class ExchangeApiImpl implements ExchangeApi {
return; return;
} }
boolean inverse = false; boolean invertQuery;
String fiat = null;
if (baseCurrency.equals(Helper.CRYPTO)) {
fiat = quoteCurrency;
inverse = false;
}
if (quoteCurrency.equals(Helper.CRYPTO)) { if (Helper.BASE_CRYPTO.equals(baseCurrency)) {
fiat = baseCurrency; invertQuery = false;
inverse = true; } else if (Helper.BASE_CRYPTO.equals(quoteCurrency)) {
} invertQuery = true;
} else {
if (fiat == null) { callback.onError(new IllegalArgumentException("no crypto specified"));
callback.onError(new IllegalArgumentException("no fiat specified"));
return; return;
} }
final boolean swapAssets = inverse; Timber.d("queryExchangeRate: i %b, b %s, q %s", invertQuery, baseCurrency, quoteCurrency);
final boolean invert = invertQuery;
final String base = invert ? quoteCurrency : baseCurrency;
final String quote = invert ? baseCurrency : quoteCurrency;
final HttpUrl url = baseUrl.newBuilder() final HttpUrl url = baseUrl.newBuilder()
.addEncodedPathSegments(CRYPTO_ID + "/") .addQueryParameter("pair", base + (quote.equals("BTC") ? "XBT" : quote))
.addQueryParameter("convert", fiat)
.build(); .build();
final Request httpRequest = createHttpRequest(url); final Request httpRequest = createHttpRequest(url);
@@ -104,13 +99,13 @@ public class ExchangeApiImpl implements ExchangeApi {
if (response.isSuccessful()) { if (response.isSuccessful()) {
try { try {
final JSONObject json = new JSONObject(response.body().string()); final JSONObject json = new JSONObject(response.body().string());
final JSONObject metadata = json.getJSONObject("metadata"); final JSONArray jsonError = json.getJSONArray("error");
if (!metadata.isNull("error")) { if (jsonError.length() > 0) {
final String errorMsg = metadata.getString("error"); final String errorMsg = jsonError.getString(0);
callback.onError(new ExchangeException(response.code(), errorMsg)); callback.onError(new ExchangeException(response.code(), errorMsg));
} else { } else {
final JSONObject jsonResult = json.getJSONObject("data"); final JSONObject jsonResult = json.getJSONObject("result");
reportSuccess(jsonResult, swapAssets, callback); reportSuccess(jsonResult, invert, callback);
} }
} catch (JSONException ex) { } catch (JSONException ex) {
callback.onError(new ExchangeException(ex.getLocalizedMessage())); callback.onError(new ExchangeException(ex.getLocalizedMessage()));

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2017-2018 m2049r et al. * Copyright (c) 2017 m2049r et al.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.m2049r.xmrwallet.service.exchange.coinmarketcap; package com.m2049r.xmrwallet.service.exchange.kraken;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
@@ -25,7 +25,6 @@ import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.util.Iterator;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -38,7 +37,7 @@ class ExchangeRateImpl implements ExchangeRate {
@Override @Override
public String getServiceName() { public String getServiceName() {
return "coinmarketcap.com"; return "kraken.com";
} }
@Override @Override
@@ -65,21 +64,29 @@ class ExchangeRateImpl implements ExchangeRate {
ExchangeRateImpl(final JSONObject jsonObject, final boolean swapAssets) throws JSONException, ExchangeException { ExchangeRateImpl(final JSONObject jsonObject, final boolean swapAssets) throws JSONException, ExchangeException {
try { try {
final String baseC = jsonObject.getString("symbol"); final String key = jsonObject.keys().next(); // we expect only one
final JSONObject quotes = jsonObject.getJSONObject("quotes"); Pattern pattern = Pattern.compile("^X(.*?)Z(.*?)$");
final Iterator<String> keys = quotes.keys(); Matcher matcher = pattern.matcher(key);
String key = null; if (matcher.find()) {
// get key which is not USD unless it is the only one baseCurrency = swapAssets ? matcher.group(2) : matcher.group(1);
while (keys.hasNext()) { quoteCurrency = swapAssets ? matcher.group(1) : matcher.group(2);
key = keys.next(); } else {
if (!key.equals("USD")) break; throw new ExchangeException("no pair returned!");
}
JSONObject pair = jsonObject.getJSONObject(key);
JSONArray close = pair.getJSONArray("c");
String closePrice = close.getString(0);
if (closePrice != null) {
try {
double rate = Double.parseDouble(closePrice);
this.rate = swapAssets ? (1 / rate) : rate;
} catch (NumberFormatException ex) {
throw new ExchangeException(ex.getLocalizedMessage());
}
} else {
throw new ExchangeException("no close price returned!");
} }
final String quoteC = key;
baseCurrency = swapAssets ? quoteC : baseC;
quoteCurrency = swapAssets ? baseC : quoteC;
JSONObject quote = quotes.getJSONObject(key);
double price = quote.getDouble("price");
this.rate = swapAssets ? (1d / price) : price;
} catch (NoSuchElementException ex) { } catch (NoSuchElementException ex) {
throw new ExchangeException(ex.getLocalizedMessage()); throw new ExchangeException(ex.getLocalizedMessage());
} }

View File

@@ -0,0 +1,99 @@
/*
* Copyright (c) 2019 m2049r@monerujo.io
*
* 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.
*/
// https://developer.android.com/training/basics/network-ops/xml
package com.m2049r.xmrwallet.service.exchange.krakenEcb;
import android.support.annotation.NonNull;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import com.m2049r.xmrwallet.util.Helper;
import okhttp3.OkHttpClient;
import timber.log.Timber;
/*
Gets the XMR/EUR rate from kraken and then gets the EUR/fiat rate from the ECB
*/
public class ExchangeApiImpl implements ExchangeApi {
static public final String BASE_FIAT = "EUR";
@NonNull
private final OkHttpClient okHttpClient;
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient) {
this.okHttpClient = okHttpClient;
}
@Override
public void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency,
@NonNull final ExchangeCallback callback) {
Timber.d("B=%s Q=%s", baseCurrency, quoteCurrency);
if (baseCurrency.equals(quoteCurrency)) {
Timber.d("BASE=QUOTE=1");
callback.onSuccess(new ExchangeRateImpl(baseCurrency, quoteCurrency, 1.0));
return;
}
if (!Helper.BASE_CRYPTO.equals(baseCurrency)
&& !Helper.BASE_CRYPTO.equals(quoteCurrency)) {
callback.onError(new IllegalArgumentException("no " + Helper.BASE_CRYPTO + " specified"));
return;
}
final String quote = Helper.BASE_CRYPTO.equals(baseCurrency) ? quoteCurrency : baseCurrency;
final ExchangeApi krakenApi =
new com.m2049r.xmrwallet.service.exchange.kraken.ExchangeApiImpl(okHttpClient);
krakenApi.queryExchangeRate(Helper.BASE_CRYPTO, BASE_FIAT, new ExchangeCallback() {
@Override
public void onSuccess(final ExchangeRate krakenRate) {
Timber.d("kraken = %f", krakenRate.getRate());
final ExchangeApi ecbApi =
new com.m2049r.xmrwallet.service.exchange.ecb.ExchangeApiImpl(okHttpClient);
ecbApi.queryExchangeRate(BASE_FIAT, quote, new ExchangeCallback() {
@Override
public void onSuccess(final ExchangeRate ecbRate) {
Timber.d("ECB = %f", ecbRate.getRate());
double rate = ecbRate.getRate() * krakenRate.getRate();
Timber.d("Q=%s QC=%s", quote, quoteCurrency);
if (!quote.equals(quoteCurrency)) rate = 1.0d / rate;
Timber.d("rate = %f", rate);
final ExchangeRate exchangeRate =
new ExchangeRateImpl(baseCurrency, quoteCurrency, rate);
callback.onSuccess(exchangeRate);
}
@Override
public void onError(Exception ex) {
Timber.d(ex);
callback.onError(ex);
}
});
}
@Override
public void onError(Exception ex) {
Timber.d(ex);
callback.onError(ex);
}
});
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) 2019 m2049r et al.
*
* 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.service.exchange.krakenEcb;
import android.support.annotation.NonNull;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import java.util.Date;
class ExchangeRateImpl implements ExchangeRate {
private final String baseCurrency;
private final String quoteCurrency;
private final double rate;
@Override
public String getServiceName() {
return "kraken+ecb";
}
@Override
public String getBaseCurrency() {
return baseCurrency;
}
@Override
public String getQuoteCurrency() {
return quoteCurrency;
}
@Override
public double getRate() {
return rate;
}
ExchangeRateImpl(@NonNull final String baseCurrency, @NonNull final String quoteCurrency, double rate) {
super();
this.baseCurrency = baseCurrency;
this.quoteCurrency = quoteCurrency;
this.rate = rate;
}
}

View File

@@ -58,7 +58,6 @@ 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 com.m2049r.xmrwallet.model.NetworkType; import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi; import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
@@ -85,7 +84,7 @@ public class Helper {
static public final String NOCRAZYPASS_FLAGFILE = ".nocrazypass"; static public final String NOCRAZYPASS_FLAGFILE = ".nocrazypass";
static public final String CRYPTO = "XMR"; static public final String BASE_CRYPTO = "XMR";
static private final String WALLET_DIR = "monerujo" + FLAVOR_SUFFIX; static private final String WALLET_DIR = "monerujo" + FLAVOR_SUFFIX;
static private final String HOME_DIR = "monero" + FLAVOR_SUFFIX; static private final String HOME_DIR = "monero" + FLAVOR_SUFFIX;
@@ -207,22 +206,33 @@ public class Helper {
return d.toPlainString(); return d.toPlainString();
} }
static public String getFormattedAmount(double amount, boolean isXmr) { static public String getFormattedAmount(double amount, boolean isCrypto) {
// at this point selection is XMR in case of error // at this point selection is XMR in case of error
String displayB; String displayB;
if (isXmr) { // XMR if (isCrypto) {
long xmr = Wallet.getAmountFromDouble(amount); if ((amount >= 0) || (amount == 0)) {
if ((xmr > 0) || (amount == 0)) {
displayB = String.format(Locale.US, "%,.5f", amount); displayB = String.format(Locale.US, "%,.5f", amount);
} else { } else {
displayB = null; displayB = null;
} }
} else { // not XMR } else { // not crypto
displayB = String.format(Locale.US, "%,.2f", amount); displayB = String.format(Locale.US, "%,.2f", amount);
} }
return displayB; return displayB;
} }
// min 2 significant digits after decimal point
static public String getFormattedAmount(double amount) {
if ((amount >= 1.0d) || (amount == 0))
return String.format(Locale.US, "%,.2f", amount);
else { // amount < 1
int decimals = 1 - (int) Math.floor(Math.log10(amount));
if (decimals < 2) decimals = 2;
if (decimals > 12) decimals = 12;
return String.format(Locale.US, "%,." + decimals + "f", amount);
}
}
static public Bitmap getBitmap(Context context, int drawableId) { static public Bitmap getBitmap(Context context, int drawableId) {
Drawable drawable = ContextCompat.getDrawable(context, drawableId); Drawable drawable = ContextCompat.getDrawable(context, drawableId);
if (drawable instanceof BitmapDrawable) { if (drawable instanceof BitmapDrawable) {
@@ -624,7 +634,7 @@ public class Helper {
} }
static public ExchangeApi getExchangeApi() { static public ExchangeApi getExchangeApi() {
return new com.m2049r.xmrwallet.service.exchange.coinmarketcap.ExchangeApiImpl(OkHttpHelper.getOkHttpClient()); return new com.m2049r.xmrwallet.service.exchange.krakenEcb.ExchangeApiImpl(OkHttpHelper.getOkHttpClient());
} }
public interface Action { public interface Action {

View File

@@ -105,6 +105,14 @@ public class RestoreHeight {
blockheight.put("2019-05-01", 1824671L); blockheight.put("2019-05-01", 1824671L);
blockheight.put("2019-06-01", 1847005L); blockheight.put("2019-06-01", 1847005L);
blockheight.put("2019-07-01", 1868590L); blockheight.put("2019-07-01", 1868590L);
blockheight.put("2019-08-01", 1890878L);
blockheight.put("2019-09-01", 1913201L);
blockheight.put("2019-10-01", 1934732L);
blockheight.put("2019-11-01", 1957051L);
blockheight.put("2019-12-01", 1978433L);
blockheight.put("2020-01-01", 2001315L);
blockheight.put("2020-02-01", 2023656L);
blockheight.put("2020-03-01", 2044552L);
} }
public long getHeight(String date) { public long getHeight(String date) {

View File

@@ -1,187 +0,0 @@
/*
* 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.widget;
import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.util.Helper;
import timber.log.Timber;
public class ExchangeBtcEditText extends LinearLayout {
String btcAmount = null;
String xmrAmount = null;
private boolean validate(String amount, double max, double min) {
boolean ok = true;
if (amount != null) {
try {
double x = Double.parseDouble(amount);
if ((x < min) || (x > max)) {
ok = false;
}
} catch (NumberFormatException ex) {
Timber.e(ex.getLocalizedMessage());
ok = false;
}
} else {
ok = false;
}
return ok;
}
public boolean validate(double maxBtc, double minBtc) {
Timber.d("validate(maxBtc=%f,minBtc=%f)", maxBtc, minBtc);
boolean ok = true;
if (!validate(btcAmount, maxBtc, minBtc)) {
Timber.d("btcAmount invalid %s", btcAmount);
shakeAmountField();
return false;
}
return true;
}
void shakeAmountField() {
etAmountA.startAnimation(Helper.getShakeAnimation(getContext()));
}
void shakeExchangeField() {
tvAmountB.startAnimation(Helper.getShakeAnimation(getContext()));
}
public void setRate(double xmrBtcRate) {
this.xmrBtcRate = xmrBtcRate;
post(new Runnable() {
@Override
public void run() {
exchange();
}
});
}
public void setAmount(String btcAmount) {
this.btcAmount = btcAmount;
etAmountA.setText(btcAmount);
xmrAmount = null;
exchange();
}
public void setEditable(boolean editable) {
etAmountA.setEnabled(editable);
}
public String getAmount() {
return btcAmount;
}
EditText etAmountA;
TextView tvAmountB;
Spinner sCurrencyA;
Spinner sCurrencyB;
public ExchangeBtcEditText(Context context) {
super(context);
initializeViews(context);
}
public ExchangeBtcEditText(Context context, AttributeSet attrs) {
super(context, attrs);
initializeViews(context);
}
public ExchangeBtcEditText(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_btc_edit, this);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
etAmountA = findViewById(R.id.etAmountA);
etAmountA.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
exchange();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
tvAmountB = findViewById(R.id.tvAmountB);
sCurrencyA = findViewById(R.id.sCurrencyA);
sCurrencyB = findViewById(R.id.sCurrencyB);
ArrayAdapter<String> btcAdapter = new ArrayAdapter<String>(getContext(),
android.R.layout.simple_spinner_item,
new String[]{"BTC"});
sCurrencyA.setAdapter(btcAdapter);
sCurrencyA.setEnabled(false);
ArrayAdapter<String> xmrAdapter = new ArrayAdapter<String>(getContext(),
android.R.layout.simple_spinner_item,
new String[]{"XMR"});
sCurrencyB.setAdapter(xmrAdapter);
sCurrencyB.setEnabled(false);
etAmountA.setFocusable(true);
etAmountA.setFocusableInTouchMode(true);
}
double xmrBtcRate = 0;
public void exchange() {
btcAmount = etAmountA.getText().toString();
if (!btcAmount.isEmpty() && (xmrBtcRate > 0)) {
double xmr = xmrBtcRate * Double.parseDouble(btcAmount);
xmrAmount = Helper.getFormattedAmount(xmr, true);
} else {
xmrAmount = "";
}
tvAmountB.setText(getResources().getString(R.string.send_amount_btc_xmr, xmrAmount));
Timber.d("%s BTC =%f> %s XMR", btcAmount, xmrBtcRate, xmrAmount);
}
}

View File

@@ -0,0 +1,196 @@
/*
* Copyright (c) 2017-2019 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.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.widget.Spinner;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import com.m2049r.xmrwallet.util.Helper;
import java.util.ArrayList;
import java.util.List;
import timber.log.Timber;
public class ExchangeOtherEditText extends ExchangeEditText {
/*
all exchanges are done through XMR
baseCurrency is the native currency
*/
String baseCurrency = null; // not XMR
private double exchangeRate = 0; // baseCurrency to XMR
public void setExchangeRate(double rate) {
exchangeRate = rate;
post(new Runnable() {
@Override
public void run() {
startExchange();
}
});
}
private void setBaseCurrency(Context context, AttributeSet attrs) {
TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ExchangeEditText, 0, 0);
try {
baseCurrency = ta.getString(R.styleable.ExchangeEditText_baseSymbol);
if (baseCurrency == null)
throw new IllegalArgumentException("base currency must be set");
} finally {
ta.recycle();
}
}
public ExchangeOtherEditText(Context context, AttributeSet attrs) {
super(context, attrs);
setBaseCurrency(context, attrs);
}
public ExchangeOtherEditText(Context context,
AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
setBaseCurrency(context, attrs);
}
@Override
void setCurrencyAdapter(Spinner spinner) {
List<String> currencies = new ArrayList<>();
if (!baseCurrency.equals(Helper.BASE_CRYPTO)) currencies.add(baseCurrency);
currencies.add(Helper.BASE_CRYPTO);
setCurrencyAdapter(spinner, currencies);
}
@Override
void setInitialSpinnerSelections(Spinner baseSpinner, Spinner quoteSpinner) {
baseSpinner.setSelection(0, true);
quoteSpinner.setSelection(1, true);
}
private void localExchange(final String base, final String quote, final double rate) {
exchange(new ExchangeRate() {
@Override
public String getServiceName() {
return "Local";
}
@Override
public String getBaseCurrency() {
return base;
}
@Override
public String getQuoteCurrency() {
return quote;
}
@Override
public double getRate() {
return rate;
}
});
}
@Override
void execExchange(String currencyA, String currencyB) {
if (!currencyA.equals(baseCurrency) && !currencyB.equals(baseCurrency)) {
throw new IllegalStateException("I can only exchange " + baseCurrency);
}
showProgress();
Timber.d("execExchange(%s, %s)", currencyA, currencyB);
// first deal with XMR/baseCurrency & baseCurrency/XMR
if (currencyA.equals(Helper.BASE_CRYPTO) && (currencyB.equals(baseCurrency))) {
localExchange(currencyA, currencyB, 1.0d / exchangeRate);
return;
}
if (currencyA.equals(baseCurrency) && (currencyB.equals(Helper.BASE_CRYPTO))) {
localExchange(currencyA, currencyB, exchangeRate);
return;
}
// next, deal with XMR/baseCurrency
if (currencyA.equals(baseCurrency)) {
queryExchangeRate(Helper.BASE_CRYPTO, currencyB, exchangeRate, true);
} else {
queryExchangeRate(currencyA, Helper.BASE_CRYPTO, 1.0d / exchangeRate, false);
}
}
private void queryExchangeRate(final String base, final String quote, final double factor,
final boolean baseIsBaseCrypto) {
queryExchangeRate(base, quote,
new ExchangeCallback() {
@Override
public void onSuccess(final ExchangeRate exchangeRate) {
if (isAttachedToWindow())
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
ExchangeRate xchange = new ExchangeRate() {
@Override
public String getServiceName() {
return exchangeRate.getServiceName() + "+" + baseCurrency;
}
@Override
public String getBaseCurrency() {
return baseIsBaseCrypto ? baseCurrency : base;
}
@Override
public String getQuoteCurrency() {
return baseIsBaseCrypto ? quote : baseCurrency;
}
@Override
public double getRate() {
return exchangeRate.getRate() * factor;
}
};
exchange(xchange);
}
});
}
@Override
public void onError(final Exception e) {
Timber.e(e.getLocalizedMessage());
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
exchangeFailed();
}
});
}
});
}
}

View File

@@ -44,6 +44,9 @@ import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate; import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import timber.log.Timber; import timber.log.Timber;
@@ -147,6 +150,15 @@ public class ExchangeView extends LinearLayout {
inflater.inflate(R.layout.view_exchange, this); inflater.inflate(R.layout.view_exchange, this);
} }
void setCurrencyAdapter(Spinner spinner) {
List<String> currencies = new ArrayList<>();
currencies.add(Helper.BASE_CRYPTO);
currencies.addAll(Arrays.asList(getResources().getStringArray(R.array.currency)));
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, currencies);
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(spinnerAdapter);
}
@Override @Override
protected void onFinishInflate() { protected void onFinishInflate() {
super.onFinishInflate(); super.onFinishInflate();
@@ -161,6 +173,9 @@ public class ExchangeView extends LinearLayout {
evExchange = findViewById(R.id.evExchange); evExchange = findViewById(R.id.evExchange);
pbExchange = findViewById(R.id.pbExchange); pbExchange = findViewById(R.id.pbExchange);
setCurrencyAdapter(sCurrencyA);
setCurrencyAdapter(sCurrencyB);
// make progress circle gray // make progress circle gray
pbExchange.getIndeterminateDrawable(). pbExchange.getIndeterminateDrawable().
setColorFilter(getResources().getColor(R.color.trafficGray), setColorFilter(getResources().getColor(R.color.trafficGray),

View File

@@ -73,6 +73,8 @@ public interface QueryOrderStatus {
String getXmrReceivingAddress(); // "xmr_receiving_address": "xmr_old_style_address_user_can_send_funds_to_as_string", String getXmrReceivingAddress(); // "xmr_receiving_address": "xmr_old_style_address_user_can_send_funds_to_as_string",
String getXmrReceivingSubaddress(); // <xmr_subaddress_user_needs_to_send_funds_to_as_string>,
String getXmrReceivingIntegratedAddress(); // "xmr_receiving_integrated_address": "xmr_integrated_address_user_needs_to_send_funds_to_as_string", String getXmrReceivingIntegratedAddress(); // "xmr_receiving_integrated_address": "xmr_integrated_address_user_needs_to_send_funds_to_as_string",
int getXmrRecommendedMixin(); // "xmr_recommended_mixin": <xmr_recommended_mixin_as_integer>, int getXmrRecommendedMixin(); // "xmr_recommended_mixin": <xmr_recommended_mixin_as_integer>,

View File

@@ -52,6 +52,7 @@ class QueryOrderStatusImpl implements QueryOrderStatus {
private double xmrAmountRemaining; // "xmr_amount_remaining": <amount_in_xmr_that_the_user_must_still_send_as_float>, private double xmrAmountRemaining; // "xmr_amount_remaining": <amount_in_xmr_that_the_user_must_still_send_as_float>,
private int xmrNumConfirmationsRemaining; // "xmr_num_confirmations_remaining": <num_xmr_confirmations_remaining_before_bitcoins_will_be_sent_as_integer>, private int xmrNumConfirmationsRemaining; // "xmr_num_confirmations_remaining": <num_xmr_confirmations_remaining_before_bitcoins_will_be_sent_as_integer>,
private double xmrPriceBtc; // "xmr_price_btc": <price_of_1_btc_in_xmr_as_offered_by_service_as_float>, private double xmrPriceBtc; // "xmr_price_btc": <price_of_1_btc_in_xmr_as_offered_by_service_as_float>,
private String xmrReceivingSubaddress; // <xmr_subaddress_user_needs_to_send_funds_to_as_string>,
private String xmrReceivingAddress; // "xmr_receiving_address": "xmr_old_style_address_user_can_send_funds_to_as_string", private String xmrReceivingAddress; // "xmr_receiving_address": "xmr_old_style_address_user_can_send_funds_to_as_string",
private String xmrReceivingIntegratedAddress; // "xmr_receiving_integrated_address": "xmr_integrated_address_user_needs_to_send_funds_to_as_string", private String xmrReceivingIntegratedAddress; // "xmr_receiving_integrated_address": "xmr_integrated_address_user_needs_to_send_funds_to_as_string",
private int xmrRecommendedMixin; // "xmr_recommended_mixin": <xmr_recommended_mixin_as_integer>, private int xmrRecommendedMixin; // "xmr_recommended_mixin": <xmr_recommended_mixin_as_integer>,
@@ -115,6 +116,10 @@ class QueryOrderStatusImpl implements QueryOrderStatus {
return xmrPriceBtc; return xmrPriceBtc;
} }
public String getXmrReceivingSubaddress() {
return xmrReceivingSubaddress;
}
public String getXmrReceivingAddress() { public String getXmrReceivingAddress() {
return xmrReceivingAddress; return xmrReceivingAddress;
} }
@@ -208,6 +213,7 @@ class QueryOrderStatusImpl implements QueryOrderStatus {
xmrAmountRemaining = jsonObject.getDouble("xmr_amount_remaining"); // "xmr_amount_remaining": <amount_in_xmr_that_the_user_must_still_send_as_float>, xmrAmountRemaining = jsonObject.getDouble("xmr_amount_remaining"); // "xmr_amount_remaining": <amount_in_xmr_that_the_user_must_still_send_as_float>,
xmrNumConfirmationsRemaining = jsonObject.getInt("xmr_num_confirmations_remaining"); // "xmr_num_confirmations_remaining": <num_xmr_confirmations_remaining_before_bitcoins_will_be_sent_as_integer>, xmrNumConfirmationsRemaining = jsonObject.getInt("xmr_num_confirmations_remaining"); // "xmr_num_confirmations_remaining": <num_xmr_confirmations_remaining_before_bitcoins_will_be_sent_as_integer>,
xmrPriceBtc = jsonObject.getDouble("xmr_price_btc"); // "xmr_price_btc": <price_of_1_btc_in_xmr_as_offered_by_service_as_float>, xmrPriceBtc = jsonObject.getDouble("xmr_price_btc"); // "xmr_price_btc": <price_of_1_btc_in_xmr_as_offered_by_service_as_float>,
xmrReceivingSubaddress = jsonObject.getString("xmr_receiving_subaddress"); // <xmr_subaddress_user_needs_to_send_funds_to_as_string>,
xmrReceivingAddress = jsonObject.getString("xmr_receiving_address"); // "xmr_receiving_address": "xmr_old_style_address_user_can_send_funds_to_as_string", xmrReceivingAddress = jsonObject.getString("xmr_receiving_address"); // "xmr_receiving_address": "xmr_old_style_address_user_can_send_funds_to_as_string",
xmrReceivingIntegratedAddress = jsonObject.getString("xmr_receiving_integrated_address"); // "xmr_receiving_integrated_address": "xmr_integrated_address_user_needs_to_send_funds_to_as_string", xmrReceivingIntegratedAddress = jsonObject.getString("xmr_receiving_integrated_address"); // "xmr_receiving_integrated_address": "xmr_integrated_address_user_needs_to_send_funds_to_as_string",
xmrRecommendedMixin = jsonObject.getInt("xmr_recommended_mixin"); // "xmr_recommended_mixin": <xmr_recommended_mixin_as_integer>, xmrRecommendedMixin = jsonObject.getInt("xmr_recommended_mixin"); // "xmr_recommended_mixin": <xmr_recommended_mixin_as_integer>,

View File

@@ -10,54 +10,43 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout <android.support.design.widget.TextInputLayout
android:id="@+id/etWalletName"
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:layout_marginBottom="@dimen/header_top_first"
android:weightSum="2"> app:errorEnabled="true">
<android.support.design.widget.TextInputLayout <android.support.design.widget.TextInputEditText
android:id="@+id/etWalletName" style="@style/MoneroEdit"
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox" android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:hint="@string/generate_name_hint"
app:counterEnabled="true" android:imeOptions="actionNext"
app:counterMaxLength="20" android:inputType="text"
android:layout_marginEnd="4dp" android:maxLines="1"
app:errorEnabled="true"> android:textAlignment="textStart" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputEditText <android.support.design.widget.TextInputLayout
style="@style/MoneroEdit" android:id="@+id/etWalletPassword"
android:layout_width="match_parent" style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:layout_height="wrap_content" android:layout_width="match_parent"
android:hint="@string/generate_name_hint" android:layout_height="wrap_content"
android:imeOptions="actionNext" android:layout_marginBottom="@dimen/header_top_first"
android:inputType="text" app:errorEnabled="true">
android:maxLines="1"
android:textAlignment="textStart" />
</android.support.design.widget.TextInputLayout> <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="textVisiblePassword"
android:textAlignment="textStart" />
<android.support.design.widget.TextInputLayout </android.support.design.widget.TextInputLayout>
android:id="@+id/etWalletPassword"
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:layout_width="0dp"
android:layout_marginStart="4dp"
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="textVisiblePassword"
android:textAlignment="textStart" />
</android.support.design.widget.TextInputLayout>
</LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/llFingerprintAuth" android:id="@+id/llFingerprintAuth"
@@ -85,6 +74,7 @@
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox" style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/header_top_first"
android:visibility="gone" android:visibility="gone"
app:errorEnabled="true"> app:errorEnabled="true">
@@ -103,8 +93,8 @@
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox" style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/header_top_first"
android:visibility="gone" android:visibility="gone"
app:counterEnabled="true" app:counterEnabled="true"
app:counterMaxLength="95" app:counterMaxLength="95"
app:errorEnabled="true"> app:errorEnabled="true">
@@ -124,6 +114,7 @@
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox" style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/header_top_first"
android:visibility="gone" android:visibility="gone"
app:counterEnabled="true" app:counterEnabled="true"
app:counterMaxLength="64" app:counterMaxLength="64"
@@ -144,6 +135,7 @@
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox" style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/header_top_first"
android:visibility="gone" android:visibility="gone"
app:counterEnabled="true" app:counterEnabled="true"
app:counterMaxLength="64" app:counterMaxLength="64"
@@ -164,6 +156,7 @@
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox" style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/header_top_first"
android:visibility="gone" android:visibility="gone"
app:errorEnabled="true"> app:errorEnabled="true">

View File

@@ -108,50 +108,6 @@
android:textSize="18sp" android:textSize="18sp"
tools:text="@string/info_xmrto" /> tools:text="@string/info_xmrto" />
</LinearLayout> </LinearLayout>
<RelativeLayout
android:id="@+id/llPaymentId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="4dp"
android:orientation="horizontal"
android:visibility="visible">
<android.support.design.widget.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:id="@+id/etPaymentId"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_toStartOf="@+id/bPaymentId"
app:counterEnabled="true"
app:counterMaxLength="16"
app:errorEnabled="true">
<android.support.design.widget.TextInputEditText
style="@style/MoneroEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="10"
android:hint="@string/send_paymentid_hint"
android:imeOptions="actionNext"
android:inputType="textMultiLine"
android:textAlignment="textStart" />
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/bPaymentId"
style="@style/MoneroText.Button.Small"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_alignParentEnd="true"
android:layout_gravity="center"
android:layout_marginStart="8dp"
android:background="?android:selectableItemBackgroundBorderless"
android:drawableTop="@drawable/ic_settings_orange_24dp"
android:text="@string/send_generate_paymentid_hint" />
</RelativeLayout>
</FrameLayout> </FrameLayout>
<android.support.design.widget.TextInputLayout <android.support.design.widget.TextInputLayout

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@@ -53,11 +54,12 @@
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>
<com.m2049r.xmrwallet.widget.ExchangeBtcEditText <com.m2049r.xmrwallet.widget.ExchangeOtherEditText
android:id="@+id/etAmount" android:id="@+id/etAmount"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:orientation="vertical" /> android:layout_marginBottom="16dp"
android:orientation="vertical"
app:baseSymbol="BTC" />
</LinearLayout> </LinearLayout>

View File

@@ -107,27 +107,6 @@
android:textAlignment="textStart" android:textAlignment="textStart"
tools:text="4AdkPJoxn7JCvAby9szgnt93MSEwdnxdhaASxbTBm6x5dCwmsDep2UYN4FhStDn5i11nsJbpU7oj59ahg8gXb1Mg3viqCuk" /> tools:text="4AdkPJoxn7JCvAby9szgnt93MSEwdnxdhaASxbTBm6x5dCwmsDep2UYN4FhStDn5i11nsJbpU7oj59ahg8gXb1Mg3viqCuk" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<TextView
style="@style/MoneroText.Confirm.Label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_send_payment_id" />
<TextView
android:id="@+id/tvTxPaymentId"
style="@style/MoneroText.Confirm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
tools:text="d666a38d4a28fb38" />
</LinearLayout>
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto" <android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/cvXmrTo" android:id="@+id/cvXmrTo"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -26,29 +26,6 @@
android:textAlignment="textStart" android:textAlignment="textStart"
tools:text="4AdkPJoxn7JCvAby9szgnt93MSEwdnxdhaASxbTBm6x5dCwmsDep2UYN4FhStDn5i11nsJbpU7oj59ahg8gXb1Mg3viqCuk" /> tools:text="4AdkPJoxn7JCvAby9szgnt93MSEwdnxdhaASxbTBm6x5dCwmsDep2UYN4FhStDn5i11nsJbpU7oj59ahg8gXb1Mg3viqCuk" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:orientation="horizontal">
<TextView
style="@style/MoneroText.Confirm.Label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_send_payment_id"
android:textAlignment="textStart" />
<TextView
android:id="@+id/tvTxPaymentId"
style="@style/MoneroText.Confirm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textAlignment="textStart"
tools:text="d666a38d4a28fb38" />
</LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

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