mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-04 00:53:36 +02:00
Compare commits
45 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f00da6ecda | ||
![]() |
1cecd0b718 | ||
![]() |
a0d6117bbb | ||
![]() |
0b0648a172 | ||
![]() |
775dcf01ae | ||
![]() |
aed4051d44 | ||
![]() |
a586c0781a | ||
![]() |
616d93cb18 | ||
![]() |
73d9cb6d58 | ||
![]() |
9846e8b5cf | ||
![]() |
aa66a12dac | ||
![]() |
65ce9b0889 | ||
![]() |
291e311b8a | ||
![]() |
41290f51fd | ||
![]() |
a11c898e2c | ||
![]() |
9c921183ab | ||
![]() |
b978396a38 | ||
![]() |
c6aa04e986 | ||
![]() |
6c17b8bd87 | ||
![]() |
835a35c6a8 | ||
![]() |
cac32f660c | ||
![]() |
8e70004bf2 | ||
![]() |
c3a466c392 | ||
![]() |
e076c19e3e | ||
![]() |
35b717756d | ||
![]() |
c14486306e | ||
![]() |
c2ef25c377 | ||
![]() |
b7164ef200 | ||
![]() |
f94a366d51 | ||
![]() |
286a04b5ef | ||
![]() |
1209295a8c | ||
![]() |
037b019d4d | ||
![]() |
7a1d788f2a | ||
![]() |
87d9a8cd95 | ||
![]() |
f637d7f617 | ||
![]() |
a4b9a7c6fb | ||
![]() |
9f01155cb7 | ||
![]() |
08e8a48138 | ||
![]() |
551c3b9fb6 | ||
![]() |
2258cb7096 | ||
![]() |
6d61841cf3 | ||
![]() |
c65508d288 | ||
![]() |
2c3f582672 | ||
![]() |
f46ba75771 | ||
![]() |
0ce5f2b6ca |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,3 +14,4 @@
|
||||
/app/prodMainnet
|
||||
/app/alphaStagenet
|
||||
/app/prodStagenet
|
||||
/app/.cxx
|
||||
|
@@ -155,6 +155,18 @@ add_library(net STATIC IMPORTED)
|
||||
set_target_properties(net PROPERTIES IMPORTED_LOCATION
|
||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libnet.a)
|
||||
|
||||
add_library(hardforks STATIC IMPORTED)
|
||||
set_target_properties(hardforks PROPERTIES IMPORTED_LOCATION
|
||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libhardforks.a)
|
||||
|
||||
add_library(randomx STATIC IMPORTED)
|
||||
set_target_properties(randomx PROPERTIES IMPORTED_LOCATION
|
||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/librandomx.a)
|
||||
|
||||
add_library(rpc_base STATIC IMPORTED)
|
||||
set_target_properties(rpc_base PROPERTIES IMPORTED_LOCATION
|
||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/librpc_base.a)
|
||||
|
||||
#############
|
||||
# System
|
||||
#############
|
||||
@@ -188,6 +200,9 @@ target_link_libraries( monerujo
|
||||
device_trezor
|
||||
multisig
|
||||
version
|
||||
randomx
|
||||
hardforks
|
||||
rpc_base
|
||||
|
||||
boost_chrono
|
||||
boost_date_time
|
||||
|
@@ -2,13 +2,13 @@ apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
buildToolsVersion '28.0.3'
|
||||
buildToolsVersion '29.0.2'
|
||||
defaultConfig {
|
||||
applicationId "com.m2049r.xmrwallet"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 28
|
||||
versionCode 183
|
||||
versionName "1.11.13 'Chernushka'"
|
||||
versionCode 302
|
||||
versionName "1.13.2 'ReStart'"
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
externalNativeBuild {
|
||||
|
@@ -785,7 +785,7 @@ Java_com_m2049r_xmrwallet_model_Wallet_getDaemonBlockChainTargetHeight(JNIEnv *e
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_m2049r_xmrwallet_model_Wallet_isSynchronized(JNIEnv *env, jobject instance) {
|
||||
Java_com_m2049r_xmrwallet_model_Wallet_isSynchronizedJ(JNIEnv *env, jobject instance) {
|
||||
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
|
||||
return static_cast<jboolean>(wallet->synchronized());
|
||||
}
|
||||
@@ -909,6 +909,16 @@ Java_com_m2049r_xmrwallet_model_Wallet_refreshAsync(JNIEnv *env, jobject instanc
|
||||
wallet->refreshAsync();
|
||||
}
|
||||
|
||||
//TODO virtual bool rescanBlockchain() = 0;
|
||||
|
||||
//virtual void rescanBlockchainAsync() = 0;
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_m2049r_xmrwallet_model_Wallet_rescanBlockchainAsync(JNIEnv *env, jobject instance) {
|
||||
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
|
||||
wallet->rescanBlockchainAsync();
|
||||
}
|
||||
|
||||
|
||||
//TODO virtual void setAutoRefreshInterval(int millis) = 0;
|
||||
//TODO virtual int autoRefreshInterval() const = 0;
|
||||
|
||||
|
@@ -79,6 +79,23 @@ public class GenerateFragment extends Fragment {
|
||||
|
||||
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
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
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() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
@@ -118,6 +152,8 @@ public class GenerateFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
});
|
||||
clearErrorOnTextEntry(etWalletMnemonic);
|
||||
|
||||
etWalletAddress.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
@@ -126,6 +162,8 @@ public class GenerateFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
});
|
||||
clearErrorOnTextEntry(etWalletAddress);
|
||||
|
||||
etWalletViewKey.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
@@ -134,6 +172,8 @@ public class GenerateFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
});
|
||||
clearErrorOnTextEntry(etWalletViewKey);
|
||||
|
||||
etWalletSpendKey.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
@@ -142,6 +182,7 @@ public class GenerateFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
});
|
||||
clearErrorOnTextEntry(etWalletSpendKey);
|
||||
|
||||
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();
|
||||
initZxcvbn();
|
||||
|
||||
@@ -412,7 +438,7 @@ public class GenerateFragment extends Fragment {
|
||||
height = RestoreHeight.getInstance().getHeight(parser.parse(restoreHeight));
|
||||
} catch (ParseException ex) {
|
||||
}
|
||||
if (height <= 0)
|
||||
if ((height <= 0) && (restoreHeight.length() == 8))
|
||||
try {
|
||||
// is it a date without dashes?
|
||||
SimpleDateFormat parser = new SimpleDateFormat("yyyyMMdd");
|
||||
|
@@ -266,6 +266,7 @@ public class NodeFragment extends Fragment
|
||||
seedList.add(new NodeInfo(new InetSocketAddress("198.74.231.92", 18080)));
|
||||
seedList.add(new NodeInfo(new InetSocketAddress("195.154.123.123", 18080)));
|
||||
seedList.add(new NodeInfo(new InetSocketAddress("212.83.172.165", 18080)));
|
||||
seedList.add(new NodeInfo(new InetSocketAddress("192.110.160.146", 18080)));
|
||||
d.seedPeers(seedList);
|
||||
d.awaitTermination(NODES_TO_FIND);
|
||||
}
|
||||
|
@@ -217,6 +217,20 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
releaseWakeLock();
|
||||
}
|
||||
|
||||
private void onWalletRescan() {
|
||||
try {
|
||||
final WalletFragment walletFragment = (WalletFragment)
|
||||
getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName());
|
||||
getWallet().rescanBlockchainAsync();
|
||||
synced = false;
|
||||
walletFragment.unsync();
|
||||
invalidateOptionsMenu();
|
||||
} catch (ClassCastException ex) {
|
||||
Timber.d(ex.getLocalizedMessage());
|
||||
// keep calm and carry on
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
Timber.d("onStop()");
|
||||
@@ -243,7 +257,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
MenuItem renameItem = menu.findItem(R.id.action_rename);
|
||||
if (renameItem != null)
|
||||
renameItem.setVisible(hasWallet() && getWallet().isSynchronized());
|
||||
renameItem.setEnabled(hasWallet() && getWallet().isSynchronized());
|
||||
MenuItem streetmodeItem = menu.findItem(R.id.action_streetmode);
|
||||
if (streetmodeItem != null)
|
||||
if (isStreetMode()) {
|
||||
@@ -251,12 +265,18 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
} else {
|
||||
streetmodeItem.setIcon(R.drawable.gunther_24dp);
|
||||
}
|
||||
final MenuItem rescanItem = menu.findItem(R.id.action_rescan);
|
||||
if (rescanItem != null)
|
||||
rescanItem.setEnabled(isSynced());
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_rescan:
|
||||
onWalletRescan();
|
||||
return true;
|
||||
case R.id.action_info:
|
||||
onWalletDetails();
|
||||
return true;
|
||||
|
@@ -50,9 +50,8 @@ import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
@@ -114,9 +113,12 @@ public class WalletFragment extends Fragment
|
||||
ivSynced = view.findViewById(R.id.ivSynced);
|
||||
|
||||
sCurrency = view.findViewById(R.id.sCurrency);
|
||||
ArrayAdapter currencyAdapter = ArrayAdapter.createFromResource(getContext(), R.array.currency, R.layout.item_spinner_balance);
|
||||
currencyAdapter.setDropDownViewResource(R.layout.item_spinner_dropdown_item);
|
||||
sCurrency.setAdapter(currencyAdapter);
|
||||
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(), R.layout.item_spinner_balance, currencies);
|
||||
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
sCurrency.setAdapter(spinnerAdapter);
|
||||
|
||||
bSend = view.findViewById(R.id.bSend);
|
||||
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
|
||||
String displayB;
|
||||
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;
|
||||
displayB = Helper.getFormattedAmount(amountB, false);
|
||||
} else { // XMR
|
||||
@@ -229,7 +231,7 @@ public class WalletFragment extends Fragment
|
||||
showBalance(displayB);
|
||||
}
|
||||
|
||||
String balanceCurrency = Helper.CRYPTO;
|
||||
String balanceCurrency = Helper.BASE_CRYPTO;
|
||||
double balanceRate = 1.0;
|
||||
|
||||
private final ExchangeApi exchangeApi = Helper.getExchangeApi();
|
||||
@@ -245,7 +247,7 @@ public class WalletFragment extends Fragment
|
||||
Timber.d(currency);
|
||||
if (!currency.equals(balanceCurrency) || (balanceRate <= 0)) {
|
||||
showExchanging();
|
||||
exchangeApi.queryExchangeRate(Helper.CRYPTO, currency,
|
||||
exchangeApi.queryExchangeRate(Helper.BASE_CRYPTO, currency,
|
||||
new ExchangeCallback() {
|
||||
@Override
|
||||
public void onSuccess(final ExchangeRate exchangeRate) {
|
||||
@@ -301,10 +303,10 @@ public class WalletFragment extends Fragment
|
||||
|
||||
public void exchange(final ExchangeRate exchangeRate) {
|
||||
hideExchanging();
|
||||
if (!Helper.CRYPTO.equals(exchangeRate.getBaseCurrency())) {
|
||||
if (!Helper.BASE_CRYPTO.equals(exchangeRate.getBaseCurrency())) {
|
||||
Timber.e("Not XMR");
|
||||
sCurrency.setSelection(0, true);
|
||||
balanceCurrency = Helper.CRYPTO;
|
||||
balanceCurrency = Helper.BASE_CRYPTO;
|
||||
balanceRate = 1.0;
|
||||
} else {
|
||||
int spinnerPosition = ((ArrayAdapter) sCurrency.getAdapter()).getPosition(exchangeRate.getQuoteCurrency());
|
||||
@@ -354,6 +356,15 @@ public class WalletFragment extends Fragment
|
||||
if (isVisible()) enableAccountsList(true); //otherwise it is enabled in onResume()
|
||||
}
|
||||
|
||||
public void unsync() {
|
||||
if (!activityCallback.isWatchOnly()) {
|
||||
bSend.setVisibility(View.INVISIBLE);
|
||||
bSend.setEnabled(false);
|
||||
}
|
||||
if (isVisible()) enableAccountsList(false); //otherwise it is enabled in onResume()
|
||||
firstBlock = 0;
|
||||
}
|
||||
|
||||
boolean walletLoaded = false;
|
||||
|
||||
public void onLoaded() {
|
||||
|
@@ -59,42 +59,36 @@ public class BarcodeData {
|
||||
final public Asset asset;
|
||||
final public String address;
|
||||
final public String addressName;
|
||||
final public String paymentId;
|
||||
final public String amount;
|
||||
final public String description;
|
||||
final public Security security;
|
||||
final public String bip70;
|
||||
|
||||
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) {
|
||||
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) {
|
||||
this(asset, address, null, null, description, amount, security);
|
||||
}
|
||||
|
||||
public BarcodeData(Asset asset, String address, String paymentId, String amount) {
|
||||
this(asset, address, null, paymentId, null, amount, Security.NORMAL);
|
||||
this(asset, address, null, description, amount, security);
|
||||
}
|
||||
|
||||
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) {
|
||||
this(asset, address, addressName, null, paymentId, description, amount, security);
|
||||
public BarcodeData(Asset asset, String address, String addressName, String description, String amount, Security 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.address = address;
|
||||
this.bip70 = bip70;
|
||||
this.addressName = addressName;
|
||||
this.paymentId = paymentId;
|
||||
this.description = description;
|
||||
this.amount = amount;
|
||||
this.security = security;
|
||||
@@ -110,11 +104,6 @@ public class BarcodeData {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(BarcodeData.XMR_SCHEME).append(address);
|
||||
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()) {
|
||||
sb.append(first ? "?" : "&");
|
||||
first = false;
|
||||
@@ -185,8 +174,11 @@ public class BarcodeData {
|
||||
String address = monero.getPath();
|
||||
|
||||
String paymentId = parms.get(XMR_PAYMENTID);
|
||||
// deal with empty payment_id created by non-spec-conforming apps
|
||||
if ((paymentId != null) && paymentId.isEmpty()) paymentId = null;
|
||||
// no support for payment ids!
|
||||
if (paymentId != null) {
|
||||
Timber.e("no support for payment ids!");
|
||||
return null;
|
||||
}
|
||||
|
||||
String description = parms.get(XMR_DESCRIPTION);
|
||||
String amount = parms.get(XMR_AMOUNT);
|
||||
@@ -198,10 +190,6 @@ public class BarcodeData {
|
||||
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)) {
|
||||
Timber.d("address invalid");
|
||||
@@ -267,7 +255,7 @@ public class BarcodeData {
|
||||
Timber.d("[%s] is not http url", bip70);
|
||||
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)) {
|
||||
Timber.d("BTC address (%s) invalid", address);
|
||||
|
@@ -235,8 +235,10 @@ public class NodeInfo extends Node {
|
||||
String rpcVersion = json.getString("jsonrpc");
|
||||
if (!RPC_VERSION.equals(rpcVersion))
|
||||
return false;
|
||||
final JSONObject header = json.getJSONObject(
|
||||
"result").getJSONObject("block_header");
|
||||
final JSONObject result = json.getJSONObject("result");
|
||||
if (!result.has("credits")) // introduced in monero v0.15.0
|
||||
return false;
|
||||
final JSONObject header = result.getJSONObject("block_header");
|
||||
height = header.getLong("height");
|
||||
timestamp = header.getLong("timestamp");
|
||||
majorVersion = header.getInt("major_version");
|
||||
|
@@ -29,19 +29,16 @@ public class TxData implements Parcelable {
|
||||
|
||||
public TxData(TxData txData) {
|
||||
this.dstAddr = txData.dstAddr;
|
||||
this.paymentId = txData.paymentId;
|
||||
this.amount = txData.amount;
|
||||
this.mixin = txData.mixin;
|
||||
this.priority = txData.priority;
|
||||
}
|
||||
|
||||
public TxData(String dstAddr,
|
||||
String paymentId,
|
||||
long amount,
|
||||
int mixin,
|
||||
PendingTransaction.Priority priority) {
|
||||
this.dstAddr = dstAddr;
|
||||
this.paymentId = paymentId;
|
||||
this.amount = amount;
|
||||
this.mixin = mixin;
|
||||
this.priority = priority;
|
||||
@@ -51,10 +48,6 @@ public class TxData implements Parcelable {
|
||||
return dstAddr;
|
||||
}
|
||||
|
||||
public String getPaymentId() {
|
||||
return paymentId;
|
||||
}
|
||||
|
||||
public long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
@@ -71,10 +64,6 @@ public class TxData implements Parcelable {
|
||||
this.dstAddr = dstAddr;
|
||||
}
|
||||
|
||||
public void setPaymentId(String paymentId) {
|
||||
this.paymentId = paymentId;
|
||||
}
|
||||
|
||||
public void setAmount(long amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
@@ -96,7 +85,6 @@ public class TxData implements Parcelable {
|
||||
}
|
||||
|
||||
private String dstAddr;
|
||||
private String paymentId;
|
||||
private long amount;
|
||||
private int mixin;
|
||||
private PendingTransaction.Priority priority;
|
||||
@@ -106,7 +94,6 @@ public class TxData implements Parcelable {
|
||||
@Override
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
out.writeString(dstAddr);
|
||||
out.writeString(paymentId);
|
||||
out.writeLong(amount);
|
||||
out.writeInt(mixin);
|
||||
out.writeInt(priority.getValue());
|
||||
@@ -125,7 +112,6 @@ public class TxData implements Parcelable {
|
||||
|
||||
protected TxData(Parcel in) {
|
||||
dstAddr = in.readString();
|
||||
paymentId = in.readString();
|
||||
amount = in.readLong();
|
||||
mixin = in.readInt();
|
||||
priority = PendingTransaction.Priority.fromInteger(in.readInt());
|
||||
@@ -142,14 +128,12 @@ public class TxData implements Parcelable {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append("dstAddr:");
|
||||
sb.append(dstAddr);
|
||||
sb.append(",paymentId:");
|
||||
sb.append(paymentId);
|
||||
sb.append(",amount:");
|
||||
sb.append(amount);
|
||||
sb.append(",mixin:");
|
||||
sb.append(mixin);
|
||||
sb.append(",priority:");
|
||||
sb.append(String.valueOf(priority));
|
||||
sb.append(priority);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
@@ -31,7 +31,6 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
@@ -86,12 +85,9 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
|
||||
private EditText etDummy;
|
||||
private TextInputLayout etAddress;
|
||||
private TextInputLayout etPaymentId;
|
||||
private TextInputLayout etNotes;
|
||||
private Button bPaymentId;
|
||||
private CardView cvScan;
|
||||
private View tvPaymentIdIntegrated;
|
||||
private View llPaymentId;
|
||||
private TextView tvXmrTo;
|
||||
private View llXmrTo;
|
||||
private ImageButton bPasteAddress;
|
||||
@@ -114,7 +110,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
View view = inflater.inflate(R.layout.fragment_send_address, container, false);
|
||||
|
||||
tvPaymentIdIntegrated = view.findViewById(R.id.tvPaymentIdIntegrated);
|
||||
llPaymentId = view.findViewById(R.id.llPaymentId);
|
||||
llXmrTo = view.findViewById(R.id.llXmrTo);
|
||||
tvXmrTo = view.findViewById(R.id.tvXmrTo);
|
||||
tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto)));
|
||||
@@ -140,28 +135,12 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
next = null;
|
||||
} else {
|
||||
// maybe a bip72 or 70 URI
|
||||
String bip70 = PaymentProtocolHelper.getBip70(enteredAddress);
|
||||
final String bip70 = PaymentProtocolHelper.getBip70(enteredAddress);
|
||||
if (bip70 != null) {
|
||||
// looks good - resolve through xmr.to
|
||||
processBip70(bip70);
|
||||
next = null;
|
||||
} else if (checkAddress()) {
|
||||
if (llPaymentId.getVisibility() == View.VISIBLE) {
|
||||
next = etPaymentId;
|
||||
} else {
|
||||
next = etNotes;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (next != null) {
|
||||
final View focus = next;
|
||||
etAddress.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
focus.requestFocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -174,8 +153,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
etAddress.setError(null);
|
||||
if (isIntegratedAddress()) {
|
||||
Timber.d("isIntegratedAddress");
|
||||
etPaymentId.getEditText().getText().clear();
|
||||
llPaymentId.setVisibility(View.INVISIBLE);
|
||||
etAddress.setError(getString(R.string.info_paymentid_integrated));
|
||||
tvPaymentIdIntegrated.setVisibility(View.VISIBLE);
|
||||
llXmrTo.setVisibility(View.INVISIBLE);
|
||||
sendListener.setMode(SendFragment.Mode.XMR);
|
||||
@@ -184,7 +162,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
setBtcMode();
|
||||
} else {
|
||||
Timber.d("isStandardAddress or other");
|
||||
llPaymentId.setVisibility(View.VISIBLE);
|
||||
tvPaymentIdIntegrated.setVisibility(View.INVISIBLE);
|
||||
llXmrTo.setVisibility(View.INVISIBLE);
|
||||
sendListener.setMode(SendFragment.Mode.XMR);
|
||||
@@ -208,47 +185,21 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
if (clip == null) return;
|
||||
// clean it up
|
||||
final String address = clip.replaceAll("[^0-9A-Z-a-z]", "");
|
||||
if (Wallet.isAddressValid(address) || BitcoinAddressValidator.validate(address))
|
||||
etAddress.getEditText().setText(address);
|
||||
else
|
||||
Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
if (Wallet.isAddressValid(address) || BitcoinAddressValidator.validate(address)) {
|
||||
final EditText et = etAddress.getEditText();
|
||||
et.setText(address);
|
||||
et.setSelection(et.getText().length());
|
||||
etAddress.requestFocus();
|
||||
} else {
|
||||
final String bip70 = PaymentProtocolHelper.getBip70(clip);
|
||||
if (bip70 != null) {
|
||||
final EditText et = etAddress.getEditText();
|
||||
et.setText(clip);
|
||||
et.setSelection(et.getText().length());
|
||||
processBip70(bip70);
|
||||
} else
|
||||
Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
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) {
|
||||
etPaymentId.getEditText().setText((Wallet.generatePaymentId()));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -259,7 +210,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||
etDummy.requestFocus();
|
||||
Helper.hideKeyboard(getActivity());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -277,7 +227,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
etDummy = view.findViewById(R.id.etDummy);
|
||||
etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
etDummy.requestFocus();
|
||||
Helper.hideKeyboard(getActivity());
|
||||
|
||||
View tvNfc = view.findViewById(R.id.tvNfc);
|
||||
NfcManager manager = (NfcManager) getContext().getSystemService(Context.NFC_SERVICE);
|
||||
@@ -289,8 +238,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
|
||||
private void setBtcMode() {
|
||||
Timber.d("setBtcMode");
|
||||
etPaymentId.getEditText().getText().clear();
|
||||
llPaymentId.setVisibility(View.INVISIBLE);
|
||||
tvPaymentIdIntegrated.setVisibility(View.INVISIBLE);
|
||||
llXmrTo.setVisibility(View.VISIBLE);
|
||||
sendListener.setMode(SendFragment.Mode.BTC);
|
||||
@@ -345,7 +292,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
|
||||
final BarcodeData barcodeData =
|
||||
new BarcodeData(BarcodeData.Asset.BTC, address, null,
|
||||
resolvedBip70, null, null, String.valueOf(amount),
|
||||
resolvedBip70, null, String.valueOf(amount),
|
||||
BarcodeData.Security.BIP70);
|
||||
etNotes.post(new Runnable() {
|
||||
@Override
|
||||
@@ -406,22 +353,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
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() {
|
||||
etAddress.startAnimation(Helper.getShakeAnimation(getContext()));
|
||||
}
|
||||
@@ -444,11 +375,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!checkPaymentId()) {
|
||||
etPaymentId.startAnimation(Helper.getShakeAnimation(getContext()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sendListener != null) {
|
||||
TxData txData = sendListener.getTxData();
|
||||
if (txData instanceof TxDataBtc) {
|
||||
@@ -462,10 +388,8 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
((TxDataBtc) txData).setBip70(null);
|
||||
}
|
||||
txData.setDestinationAddress(null);
|
||||
txData.setPaymentId("");
|
||||
} else {
|
||||
txData.setDestinationAddress(etAddress.getEditText().getText().toString());
|
||||
txData.setPaymentId(etPaymentId.getEditText().getText().toString());
|
||||
}
|
||||
txData.setUserNotes(new UserNotes(etNotes.getEditText().getText().toString()));
|
||||
txData.setPriority(PendingTransaction.Priority.Priority_Default);
|
||||
@@ -528,14 +452,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
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;
|
||||
if (scannedNotes != null) {
|
||||
etNotes.getEditText().setText(scannedNotes);
|
||||
@@ -551,7 +467,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
public void onResumeFragment() {
|
||||
super.onResumeFragment();
|
||||
Timber.d("onResumeFragment()");
|
||||
Helper.hideKeyboard(getActivity());
|
||||
etDummy.requestFocus();
|
||||
}
|
||||
|
||||
|
@@ -21,8 +21,6 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.m2049r.xmrwallet.R;
|
||||
@@ -30,8 +28,7 @@ import com.m2049r.xmrwallet.data.BarcodeData;
|
||||
import com.m2049r.xmrwallet.data.TxData;
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.widget.ExchangeTextView;
|
||||
import com.m2049r.xmrwallet.widget.NumberPadView;
|
||||
import com.m2049r.xmrwallet.widget.ExchangeEditText;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
@@ -59,8 +56,7 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
||||
}
|
||||
|
||||
private TextView tvFunds;
|
||||
private ExchangeTextView evAmount;
|
||||
private View llAmount;
|
||||
private ExchangeEditText etAmount;
|
||||
private View rlSweep;
|
||||
private ImageButton ibSweep;
|
||||
|
||||
@@ -75,12 +71,9 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
||||
View view = inflater.inflate(R.layout.fragment_send_amount, container, false);
|
||||
|
||||
tvFunds = view.findViewById(R.id.tvFunds);
|
||||
|
||||
evAmount = view.findViewById(R.id.evAmount);
|
||||
((NumberPadView) view.findViewById(R.id.numberPad)).setListener(evAmount);
|
||||
|
||||
etAmount = view.findViewById(R.id.etAmount);
|
||||
rlSweep = view.findViewById(R.id.rlSweep);
|
||||
llAmount = view.findViewById(R.id.llAmount);
|
||||
|
||||
view.findViewById(R.id.ivSweep).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@@ -97,8 +90,7 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
||||
}
|
||||
});
|
||||
|
||||
Helper.hideKeyboard(getActivity());
|
||||
|
||||
etAmount.requestFocus();
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -107,11 +99,11 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
||||
private void sweepAll(boolean spendAllMode) {
|
||||
if (spendAllMode) {
|
||||
ibSweep.setVisibility(View.INVISIBLE);
|
||||
llAmount.setVisibility(View.GONE);
|
||||
etAmount.setVisibility(View.GONE);
|
||||
rlSweep.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
ibSweep.setVisibility(View.VISIBLE);
|
||||
llAmount.setVisibility(View.VISIBLE);
|
||||
etAmount.setVisibility(View.VISIBLE);
|
||||
rlSweep.setVisibility(View.GONE);
|
||||
}
|
||||
this.spendAllMode = spendAllMode;
|
||||
@@ -124,12 +116,12 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
||||
sendListener.getTxData().setAmount(Wallet.SWEEP_ALL);
|
||||
}
|
||||
} else {
|
||||
if (!evAmount.validate(maxFunds)) {
|
||||
if (!etAmount.validate(maxFunds, 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sendListener != null) {
|
||||
String xmr = evAmount.getAmount();
|
||||
String xmr = etAmount.getNativeAmount();
|
||||
if (xmr != null) {
|
||||
sendListener.getTxData().setAmount(Wallet.getAmountFromString(xmr));
|
||||
} else {
|
||||
@@ -146,7 +138,7 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
||||
public void onResumeFragment() {
|
||||
super.onResumeFragment();
|
||||
Timber.d("onResumeFragment()");
|
||||
Helper.hideKeyboard(getActivity());
|
||||
Helper.showKeyboard(getActivity());
|
||||
final long funds = getTotalFunds();
|
||||
maxFunds = 1.0 * funds / 1000000000000L;
|
||||
if (!sendListener.getActivityCallback().isStreetMode()) {
|
||||
@@ -156,11 +148,11 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
||||
tvFunds.setText(getString(R.string.send_available,
|
||||
getString(R.string.unknown_amount)));
|
||||
}
|
||||
// getAmount is null if exchange is in progress
|
||||
if ((evAmount.getAmount() != null) && evAmount.getAmount().isEmpty()) {
|
||||
// getNativeAmount is null if exchange is in progress
|
||||
if ((etAmount.getNativeAmount() != null) && etAmount.getNativeAmount().isEmpty()) {
|
||||
final BarcodeData data = sendListener.popBarcodeData();
|
||||
if ((data != null) && (data.amount != null)) {
|
||||
evAmount.setAmount(data.amount);
|
||||
etAmount.setAmount(data.amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -31,8 +31,8 @@ import com.m2049r.xmrwallet.data.TxDataBtc;
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.OkHttpHelper;
|
||||
import com.m2049r.xmrwallet.widget.ExchangeBtcTextView;
|
||||
import com.m2049r.xmrwallet.widget.NumberPadView;
|
||||
import com.m2049r.xmrwallet.widget.ExchangeEditText;
|
||||
import com.m2049r.xmrwallet.widget.ExchangeOtherEditText;
|
||||
import com.m2049r.xmrwallet.widget.SendProgressView;
|
||||
import com.m2049r.xmrwallet.xmrto.XmrToError;
|
||||
import com.m2049r.xmrwallet.xmrto.XmrToException;
|
||||
@@ -62,8 +62,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
||||
}
|
||||
|
||||
private TextView tvFunds;
|
||||
private ExchangeBtcTextView evAmount;
|
||||
private NumberPadView numberPad;
|
||||
private ExchangeOtherEditText etAmount;
|
||||
|
||||
private TextView tvXmrToParms;
|
||||
private SendProgressView evParams;
|
||||
@@ -86,24 +85,20 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
||||
|
||||
tvXmrToParms = view.findViewById(R.id.tvXmrToParms);
|
||||
|
||||
evAmount = view.findViewById(R.id.evAmount);
|
||||
numberPad = view.findViewById(R.id.numberPad);
|
||||
numberPad.setListener(evAmount);
|
||||
|
||||
Helper.hideKeyboard(getActivity());
|
||||
|
||||
etAmount = view.findViewById(R.id.etAmount);
|
||||
etAmount.requestFocus();
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onValidateFields() {
|
||||
if (!evAmount.validate(maxBtc, minBtc)) {
|
||||
if (!etAmount.validate(maxBtc, minBtc)) {
|
||||
return false;
|
||||
}
|
||||
if (sendListener != null) {
|
||||
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
|
||||
String btcString = evAmount.getAmount();
|
||||
String btcString = etAmount.getNativeAmount();
|
||||
if (btcString != null) {
|
||||
try {
|
||||
double btc = Double.parseDouble(btcString);
|
||||
@@ -122,10 +117,12 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
||||
|
||||
private void setBip70Mode() {
|
||||
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
|
||||
if (txDataBtc.getBip70() != null) {
|
||||
numberPad.setVisibility(View.INVISIBLE);
|
||||
if (txDataBtc.getBip70() == null) {
|
||||
etAmount.setEditable(true);
|
||||
Helper.showKeyboard(getActivity());
|
||||
} else {
|
||||
numberPad.setVisibility(View.VISIBLE);
|
||||
etAmount.setEditable(false);
|
||||
Helper.hideKeyboard(getActivity());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +138,6 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
||||
public void onResumeFragment() {
|
||||
super.onResumeFragment();
|
||||
Timber.d("onResumeFragment()");
|
||||
Helper.hideKeyboard(getActivity());
|
||||
final long funds = getTotalFunds();
|
||||
if (!sendListener.getActivityCallback().isStreetMode()) {
|
||||
tvFunds.setText(getString(R.string.send_available,
|
||||
@@ -153,7 +149,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
||||
final BarcodeData data = sendListener.popBarcodeData();
|
||||
if (data != null) {
|
||||
if (data.amount != null) {
|
||||
evAmount.setAmount(data.amount);
|
||||
etAmount.setAmount(data.amount);
|
||||
}
|
||||
}
|
||||
setBip70Mode();
|
||||
@@ -171,7 +167,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
||||
getView().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
evAmount.setRate(1.0d / orderParameters.getPrice());
|
||||
etAmount.setExchangeRate(1.0d / orderParameters.getPrice());
|
||||
NumberFormat df = NumberFormat.getInstance(Locale.US);
|
||||
df.setMaximumFractionDigits(6);
|
||||
String min = df.format(orderParameters.getLowerLimit());
|
||||
@@ -211,7 +207,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
||||
}
|
||||
|
||||
private void processOrderParmsError(final Exception ex) {
|
||||
evAmount.setRate(0);
|
||||
etAmount.setExchangeRate(0);
|
||||
orderParameters = null;
|
||||
maxBtc = 0;
|
||||
minBtc = 0;
|
||||
|
@@ -457,9 +457,8 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
||||
}
|
||||
showProgress(3, getString(R.string.label_send_progress_create_tx));
|
||||
TxData txData = sendListener.getTxData();
|
||||
txData.setDestinationAddress(xmrtoStatus.getXmrReceivingAddress());
|
||||
txData.setPaymentId(xmrtoStatus.getXmrRequiredPaymentIdShort());
|
||||
txData.setAmount(Wallet.getAmountFromDouble(xmrtoStatus.getXmrAmountTotal()));
|
||||
txData.setDestinationAddress(xmrtoStatus.getReceivingSubaddress());
|
||||
txData.setAmount(Wallet.getAmountFromDouble(xmrtoStatus.getIncomingAmountTotal()));
|
||||
getActivityCallback().onPrepareSend(xmrtoStatus.getUuid(), txData);
|
||||
}
|
||||
|
||||
@@ -573,22 +572,22 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
||||
NumberFormat df = NumberFormat.getInstance(Locale.US);
|
||||
df.setMaximumFractionDigits(12);
|
||||
String btcAmount = df.format(status.getBtcAmount());
|
||||
String xmrAmountTotal = df.format(status.getXmrAmountTotal());
|
||||
String xmrAmountTotal = df.format(status.getIncomingAmountTotal());
|
||||
tvTxBtcAmount.setText(getString(R.string.text_send_btc_amount, btcAmount, xmrAmountTotal));
|
||||
String xmrPriceBtc = df.format(status.getXmrPriceBtc());
|
||||
String xmrPriceBtc = df.format(status.getIncomingPriceBtc());
|
||||
tvTxBtcRate.setText(getString(R.string.text_send_btc_rate, xmrPriceBtc));
|
||||
|
||||
double calcRate = status.getBtcAmount() / status.getXmrPriceBtc();
|
||||
Timber.i("Rates: %f / %f", calcRate, status.getXmrPriceBtc());
|
||||
double calcRate = status.getBtcAmount() / status.getIncomingPriceBtc();
|
||||
Timber.d("Rates: %f / %f", calcRate, status.getIncomingPriceBtc());
|
||||
|
||||
tvTxBtcAddress.setText(status.getBtcDestAddress()); // TODO test if this is different?
|
||||
|
||||
Timber.i("Expires @ %s, in %s seconds", status.getExpiresAt().toString(), status.getSecondsTillTimeout());
|
||||
Timber.d("Expires @ %s, in %s seconds", status.getExpiresAt().toString(), status.getSecondsTillTimeout());
|
||||
|
||||
Timber.i("Status = %s", status.getState().toString());
|
||||
Timber.d("Status = %s", status.getState().toString());
|
||||
tvTxXmrToKey.setText(status.getUuid());
|
||||
|
||||
Timber.d("AmountRemaining=%f, XmrAmountTotal=%f", status.getXmrAmountRemaining(), status.getXmrAmountTotal());
|
||||
Timber.d("AmountRemaining=%f, XmrAmountTotal=%f", status.getRemainingAmountIncoming(), status.getIncomingAmountTotal());
|
||||
hideProgress();
|
||||
startSendTimer();
|
||||
prepareSend();
|
||||
|
@@ -138,12 +138,6 @@ public class SendBtcSuccessWizardFragment extends SendWizardFragment {
|
||||
|
||||
btcData = (TxDataBtc) sendListener.getTxData();
|
||||
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();
|
||||
if (committedTx != null) {
|
||||
|
@@ -70,7 +70,6 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
|
||||
}
|
||||
|
||||
private TextView tvTxAddress;
|
||||
private TextView tvTxPaymentId;
|
||||
private TextView tvTxNotes;
|
||||
private TextView tvTxAmount;
|
||||
private TextView tvTxFee;
|
||||
@@ -90,7 +89,6 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
|
||||
R.layout.fragment_send_confirm, container, false);
|
||||
|
||||
tvTxAddress = view.findViewById(R.id.tvTxAddress);
|
||||
tvTxPaymentId = view.findViewById(R.id.tvTxPaymentId);
|
||||
tvTxNotes = view.findViewById(R.id.tvTxNotes);
|
||||
tvTxAmount = view.findViewById(R.id.tvTxAmount);
|
||||
tvTxFee = view.findViewById(R.id.tvTxFee);
|
||||
@@ -192,12 +190,6 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
|
||||
|
||||
final TxData txData = sendListener.getTxData();
|
||||
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();
|
||||
if ((notes != null) && (!notes.note.isEmpty())) {
|
||||
tvTxNotes.setText(notes.note);
|
||||
|
@@ -111,12 +111,6 @@ public class SendSuccessWizardFragment extends SendWizardFragment {
|
||||
|
||||
final TxData txData = sendListener.getTxData();
|
||||
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();
|
||||
if (committedTx != null) {
|
||||
|
@@ -130,9 +130,9 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
|
||||
|
||||
private void showStar() {
|
||||
if (nodeItem.isFavourite()) {
|
||||
ibBookmark.setImageResource(R.drawable.ic_bookmark_24dp);
|
||||
ibBookmark.setImageResource(R.drawable.ic_favorite_24dp);
|
||||
} else {
|
||||
ibBookmark.setImageResource(R.drawable.ic_bookmark_border_24dp);
|
||||
ibBookmark.setImageResource(R.drawable.ic_favorite_border_24dp);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -248,7 +248,13 @@ public class Wallet {
|
||||
|
||||
public native long getDaemonBlockChainTargetHeight();
|
||||
|
||||
public native boolean isSynchronized();
|
||||
public native boolean isSynchronizedJ();
|
||||
|
||||
public boolean isSynchronized() {
|
||||
final long daemonHeight = getDaemonBlockChainHeight();
|
||||
if (daemonHeight == 0) return false;
|
||||
return isSynchronizedJ() && (getBlockChainHeight() == daemonHeight);
|
||||
}
|
||||
|
||||
public static native String getDisplayAmount(long amount);
|
||||
|
||||
@@ -278,6 +284,8 @@ public class Wallet {
|
||||
|
||||
public native void refreshAsync();
|
||||
|
||||
public native void rescanBlockchainAsync();
|
||||
|
||||
//TODO virtual void setAutoRefreshInterval(int millis) = 0;
|
||||
//TODO virtual int autoRefreshInterval() const = 0;
|
||||
|
||||
@@ -298,22 +306,21 @@ public class Wallet {
|
||||
public PendingTransaction createTransaction(TxData txData) {
|
||||
return createTransaction(
|
||||
txData.getDestinationAddress(),
|
||||
txData.getPaymentId(),
|
||||
txData.getAmount(),
|
||||
txData.getMixin(),
|
||||
txData.getPriority());
|
||||
}
|
||||
|
||||
public PendingTransaction createTransaction(String dst_addr, String payment_id,
|
||||
public PendingTransaction createTransaction(String dst_addr,
|
||||
long amount, int mixin_count,
|
||||
PendingTransaction.Priority priority) {
|
||||
disposePendingTransaction();
|
||||
int _priority = priority.getValue();
|
||||
long txHandle =
|
||||
(amount == SWEEP_ALL ?
|
||||
createSweepTransaction(dst_addr, payment_id, mixin_count, _priority,
|
||||
createSweepTransaction(dst_addr, "", mixin_count, _priority,
|
||||
accountIndex) :
|
||||
createTransactionJ(dst_addr, payment_id, amount, mixin_count, _priority,
|
||||
createTransactionJ(dst_addr, "", amount, mixin_count, _priority,
|
||||
accountIndex));
|
||||
pendingTransaction = new PendingTransaction(txHandle);
|
||||
return pendingTransaction;
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,7 +14,7 @@
|
||||
* 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.VisibleForTesting;
|
||||
@@ -36,9 +36,9 @@ import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ExchangeApiImpl implements ExchangeApi {
|
||||
static final String CRYPTO_ID = "328";
|
||||
|
||||
@NonNull
|
||||
private final OkHttpClient okHttpClient;
|
||||
@@ -47,14 +47,13 @@ public class ExchangeApiImpl implements ExchangeApi {
|
||||
|
||||
//so we can inject the mockserver url
|
||||
@VisibleForTesting
|
||||
ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient, final HttpUrl baseUrl) {
|
||||
|
||||
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient, final HttpUrl baseUrl) {
|
||||
this.okHttpClient = okHttpClient;
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
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
|
||||
@@ -66,29 +65,25 @@ public class ExchangeApiImpl implements ExchangeApi {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean inverse = false;
|
||||
String fiat = null;
|
||||
boolean invertQuery;
|
||||
|
||||
if (baseCurrency.equals(Helper.CRYPTO)) {
|
||||
fiat = quoteCurrency;
|
||||
inverse = false;
|
||||
}
|
||||
|
||||
if (quoteCurrency.equals(Helper.CRYPTO)) {
|
||||
fiat = baseCurrency;
|
||||
inverse = true;
|
||||
}
|
||||
|
||||
if (fiat == null) {
|
||||
callback.onError(new IllegalArgumentException("no fiat specified"));
|
||||
if (Helper.BASE_CRYPTO.equals(baseCurrency)) {
|
||||
invertQuery = false;
|
||||
} else if (Helper.BASE_CRYPTO.equals(quoteCurrency)) {
|
||||
invertQuery = true;
|
||||
} else {
|
||||
callback.onError(new IllegalArgumentException("no crypto specified"));
|
||||
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()
|
||||
.addEncodedPathSegments(CRYPTO_ID + "/")
|
||||
.addQueryParameter("convert", fiat)
|
||||
.addQueryParameter("pair", base + (quote.equals("BTC") ? "XBT" : quote))
|
||||
.build();
|
||||
|
||||
final Request httpRequest = createHttpRequest(url);
|
||||
@@ -104,13 +99,13 @@ public class ExchangeApiImpl implements ExchangeApi {
|
||||
if (response.isSuccessful()) {
|
||||
try {
|
||||
final JSONObject json = new JSONObject(response.body().string());
|
||||
final JSONObject metadata = json.getJSONObject("metadata");
|
||||
if (!metadata.isNull("error")) {
|
||||
final String errorMsg = metadata.getString("error");
|
||||
final JSONArray jsonError = json.getJSONArray("error");
|
||||
if (jsonError.length() > 0) {
|
||||
final String errorMsg = jsonError.getString(0);
|
||||
callback.onError(new ExchangeException(response.code(), errorMsg));
|
||||
} else {
|
||||
final JSONObject jsonResult = json.getJSONObject("data");
|
||||
reportSuccess(jsonResult, swapAssets, callback);
|
||||
final JSONObject jsonResult = json.getJSONObject("result");
|
||||
reportSuccess(jsonResult, invert, callback);
|
||||
}
|
||||
} catch (JSONException ex) {
|
||||
callback.onError(new ExchangeException(ex.getLocalizedMessage()));
|
@@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.m2049r.xmrwallet.service.exchange.coinmarketcap;
|
||||
package com.m2049r.xmrwallet.service.exchange.kraken;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
@@ -25,7 +25,6 @@ import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -38,7 +37,7 @@ class ExchangeRateImpl implements ExchangeRate {
|
||||
|
||||
@Override
|
||||
public String getServiceName() {
|
||||
return "coinmarketcap.com";
|
||||
return "kraken.com";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -65,21 +64,29 @@ class ExchangeRateImpl implements ExchangeRate {
|
||||
|
||||
ExchangeRateImpl(final JSONObject jsonObject, final boolean swapAssets) throws JSONException, ExchangeException {
|
||||
try {
|
||||
final String baseC = jsonObject.getString("symbol");
|
||||
final JSONObject quotes = jsonObject.getJSONObject("quotes");
|
||||
final Iterator<String> keys = quotes.keys();
|
||||
String key = null;
|
||||
// get key which is not USD unless it is the only one
|
||||
while (keys.hasNext()) {
|
||||
key = keys.next();
|
||||
if (!key.equals("USD")) break;
|
||||
final String key = jsonObject.keys().next(); // we expect only one
|
||||
Pattern pattern = Pattern.compile("^X(.*?)Z(.*?)$");
|
||||
Matcher matcher = pattern.matcher(key);
|
||||
if (matcher.find()) {
|
||||
baseCurrency = swapAssets ? matcher.group(2) : matcher.group(1);
|
||||
quoteCurrency = swapAssets ? matcher.group(1) : matcher.group(2);
|
||||
} else {
|
||||
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) {
|
||||
throw new ExchangeException(ex.getLocalizedMessage());
|
||||
}
|
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -58,7 +58,6 @@ import android.widget.TextView;
|
||||
import com.m2049r.xmrwallet.BuildConfig;
|
||||
import com.m2049r.xmrwallet.R;
|
||||
import com.m2049r.xmrwallet.model.NetworkType;
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
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 CRYPTO = "XMR";
|
||||
static public final String BASE_CRYPTO = "XMR";
|
||||
|
||||
static private final String WALLET_DIR = "monerujo" + FLAVOR_SUFFIX;
|
||||
static private final String HOME_DIR = "monero" + FLAVOR_SUFFIX;
|
||||
@@ -207,22 +206,33 @@ public class Helper {
|
||||
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
|
||||
String displayB;
|
||||
if (isXmr) { // XMR
|
||||
long xmr = Wallet.getAmountFromDouble(amount);
|
||||
if ((xmr > 0) || (amount == 0)) {
|
||||
if (isCrypto) {
|
||||
if ((amount >= 0) || (amount == 0)) {
|
||||
displayB = String.format(Locale.US, "%,.5f", amount);
|
||||
} else {
|
||||
displayB = null;
|
||||
}
|
||||
} else { // not XMR
|
||||
} else { // not crypto
|
||||
displayB = String.format(Locale.US, "%,.2f", amount);
|
||||
}
|
||||
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) {
|
||||
Drawable drawable = ContextCompat.getDrawable(context, drawableId);
|
||||
if (drawable instanceof BitmapDrawable) {
|
||||
@@ -313,9 +323,9 @@ public class Helper {
|
||||
static public HttpUrl getXmrToBaseUrl() {
|
||||
if ((WalletManager.getInstance() == null)
|
||||
|| (WalletManager.getInstance().getNetworkType() != NetworkType.NetworkType_Mainnet)) {
|
||||
return HttpUrl.parse("https://test.xmr.to/api/v2/xmr2btc/");
|
||||
return HttpUrl.parse("https://test.xmr.to/api/v3/xmr2btc/");
|
||||
} else {
|
||||
return HttpUrl.parse("https://xmr.to/api/v2/xmr2btc/");
|
||||
return HttpUrl.parse("https://xmr.to/api/v3/xmr2btc/");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,7 +634,7 @@ public class Helper {
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@@ -105,6 +105,17 @@ public class RestoreHeight {
|
||||
blockheight.put("2019-05-01", 1824671L);
|
||||
blockheight.put("2019-06-01", 1847005L);
|
||||
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);
|
||||
blockheight.put("2020-04-01", 2066806L);
|
||||
blockheight.put("2020-05-01", 2088411L);
|
||||
blockheight.put("2020-06-01", 2110702L);
|
||||
}
|
||||
|
||||
public long getHeight(String date) {
|
||||
|
@@ -1,198 +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.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.ArrayAdapter;
|
||||
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 ExchangeBtcTextView extends LinearLayout
|
||||
implements NumberPadView.NumberPadListener {
|
||||
|
||||
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() {
|
||||
tvAmountA.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;
|
||||
tvAmountA.setText(btcAmount);
|
||||
xmrAmount = null;
|
||||
exchange();
|
||||
}
|
||||
|
||||
public String getAmount() {
|
||||
return btcAmount;
|
||||
}
|
||||
|
||||
TextView tvAmountA;
|
||||
TextView tvAmountB;
|
||||
Spinner sCurrencyA;
|
||||
Spinner sCurrencyB;
|
||||
|
||||
public ExchangeBtcTextView(Context context) {
|
||||
super(context);
|
||||
initializeViews(context);
|
||||
}
|
||||
|
||||
public ExchangeBtcTextView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initializeViews(context);
|
||||
}
|
||||
|
||||
public ExchangeBtcTextView(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_text, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
tvAmountA = findViewById(R.id.tvAmountA);
|
||||
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);
|
||||
}
|
||||
|
||||
double xmrBtcRate = 0;
|
||||
|
||||
public void exchange() {
|
||||
btcAmount = tvAmountA.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);
|
||||
}
|
||||
|
||||
// deal with attached numpad
|
||||
@Override
|
||||
public void onDigitPressed(final int digit) {
|
||||
tvAmountA.append(String.valueOf(digit));
|
||||
exchange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPointPressed() {
|
||||
//TODO locale?
|
||||
if (tvAmountA.getText().toString().indexOf('.') == -1) {
|
||||
if (tvAmountA.getText().toString().isEmpty()) {
|
||||
tvAmountA.append("0");
|
||||
}
|
||||
tvAmountA.append(".");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackSpacePressed() {
|
||||
String entry = tvAmountA.getText().toString();
|
||||
int length = entry.length();
|
||||
if (length > 0) {
|
||||
tvAmountA.setText(entry.substring(0, entry.length() - 1));
|
||||
exchange();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClearAll() {
|
||||
tvAmountA.setText(null);
|
||||
exchange();
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user