mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-06 02:27:11 +02:00
Compare commits
32 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f94a366d51 | ||
![]() |
286a04b5ef | ||
![]() |
1209295a8c | ||
![]() |
037b019d4d | ||
![]() |
7a1d788f2a | ||
![]() |
87d9a8cd95 | ||
![]() |
f637d7f617 | ||
![]() |
a4b9a7c6fb | ||
![]() |
9f01155cb7 | ||
![]() |
08e8a48138 | ||
![]() |
551c3b9fb6 | ||
![]() |
2258cb7096 | ||
![]() |
6d61841cf3 | ||
![]() |
c65508d288 | ||
![]() |
2c3f582672 | ||
![]() |
f46ba75771 | ||
![]() |
0ce5f2b6ca | ||
![]() |
110057c294 | ||
![]() |
7553d3c5f4 | ||
![]() |
317976b34a | ||
![]() |
6ad423567f | ||
![]() |
d497158856 | ||
![]() |
f4cada5fa1 | ||
![]() |
352f0ad09c | ||
![]() |
ff1a9c1570 | ||
![]() |
fa811a39a2 | ||
![]() |
cf5018be33 | ||
![]() |
8ec027f9d4 | ||
![]() |
f28428e677 | ||
![]() |
294084bec5 | ||
![]() |
4349907627 | ||
![]() |
f7cef24a83 |
@@ -155,6 +155,18 @@ add_library(net STATIC IMPORTED)
|
|||||||
set_target_properties(net PROPERTIES IMPORTED_LOCATION
|
set_target_properties(net PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libnet.a)
|
${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
|
# System
|
||||||
#############
|
#############
|
||||||
@@ -188,6 +200,9 @@ target_link_libraries( monerujo
|
|||||||
device_trezor
|
device_trezor
|
||||||
multisig
|
multisig
|
||||||
version
|
version
|
||||||
|
randomx
|
||||||
|
hardforks
|
||||||
|
rpc_base
|
||||||
|
|
||||||
boost_chrono
|
boost_chrono
|
||||||
boost_date_time
|
boost_date_time
|
||||||
|
@@ -7,8 +7,8 @@ android {
|
|||||||
applicationId "com.m2049r.xmrwallet"
|
applicationId "com.m2049r.xmrwallet"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 28
|
targetSdkVersion 28
|
||||||
versionCode 180
|
versionCode 197
|
||||||
versionName "1.11.10 'Chernushka'"
|
versionName "1.12.7 'Caerbannog'"
|
||||||
|
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
|
@@ -1310,7 +1310,6 @@ Java_com_m2049r_xmrwallet_model_PendingTransaction_getFirstTxIdJ(JNIEnv *env, jo
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
JNIEXPORT jlong JNICALL
|
JNIEXPORT jlong JNICALL
|
||||||
Java_com_m2049r_xmrwallet_model_PendingTransaction_getTxCount(JNIEnv *env, jobject instance) {
|
Java_com_m2049r_xmrwallet_model_PendingTransaction_getTxCount(JNIEnv *env, jobject instance) {
|
||||||
Bitmonero::PendingTransaction *tx = getHandle<Bitmonero::PendingTransaction>(env, instance);
|
Bitmonero::PendingTransaction *tx = getHandle<Bitmonero::PendingTransaction>(env, instance);
|
||||||
@@ -1396,6 +1395,11 @@ Java_com_m2049r_xmrwallet_model_WalletManager_setLogLevel(JNIEnv *env, jclass cl
|
|||||||
Bitmonero::WalletManagerFactory::setLogLevel(level);
|
Bitmonero::WalletManagerFactory::setLogLevel(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jstring JNICALL
|
||||||
|
Java_com_m2049r_xmrwallet_model_WalletManager_moneroVersion(JNIEnv *env, jclass clazz) {
|
||||||
|
return env->NewStringUTF(MONERO_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Ledger Stuff
|
// Ledger Stuff
|
||||||
//
|
//
|
||||||
|
@@ -54,6 +54,8 @@ extern "C"
|
|||||||
{
|
{
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
extern const char* const MONERO_VERSION; // the actual monero core version
|
||||||
|
|
||||||
// from monero-core crypto/hash-ops.h - avoid #including monero code here
|
// from monero-core crypto/hash-ops.h - avoid #including monero code here
|
||||||
enum {
|
enum {
|
||||||
HASH_SIZE = 32,
|
HASH_SIZE = 32,
|
||||||
|
@@ -46,8 +46,12 @@ public class BTChipTransportAndroidHID implements BTChipTransport {
|
|||||||
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
|
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
|
||||||
for (UsbDevice device : deviceList.values()) {
|
for (UsbDevice device : deviceList.values()) {
|
||||||
Timber.d("%04X:%04X %s, %s", device.getVendorId(), device.getProductId(), device.getManufacturerName(), device.getProductName());
|
Timber.d("%04X:%04X %s, %s", device.getVendorId(), device.getProductId(), device.getManufacturerName(), device.getProductName());
|
||||||
if ((device.getVendorId() == VID) && (device.getProductId() == PID_HID)) {
|
if (device.getVendorId() == VID) {
|
||||||
return device;
|
final int deviceProductId = device.getProductId();
|
||||||
|
for (int pid : PID_HIDS) {
|
||||||
|
if (deviceProductId == pid)
|
||||||
|
return device;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -74,7 +78,7 @@ public class BTChipTransportAndroidHID implements BTChipTransport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final int VID = 0x2C97;
|
private static final int VID = 0x2C97;
|
||||||
private static final int PID_HID = 0x0001;
|
private static final int[] PID_HIDS = {0x0001, 0x0004};
|
||||||
|
|
||||||
private UsbDeviceConnection connection;
|
private UsbDeviceConnection connection;
|
||||||
private UsbInterface dongleInterface;
|
private UsbInterface dongleInterface;
|
||||||
|
@@ -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();
|
||||||
|
|
||||||
|
@@ -55,6 +55,8 @@ import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
|||||||
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
|
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
|
||||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||||
|
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class GenerateReviewFragment extends Fragment {
|
public class GenerateReviewFragment extends Fragment {
|
||||||
@@ -72,6 +74,7 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
private TextView tvWalletPassword;
|
private TextView tvWalletPassword;
|
||||||
private TextView tvWalletAddress;
|
private TextView tvWalletAddress;
|
||||||
private TextView tvWalletMnemonic;
|
private TextView tvWalletMnemonic;
|
||||||
|
private TextView tvWalletHeight;
|
||||||
private TextView tvWalletViewKey;
|
private TextView tvWalletViewKey;
|
||||||
private TextView tvWalletSpendKey;
|
private TextView tvWalletSpendKey;
|
||||||
private ImageButton bCopyAddress;
|
private ImageButton bCopyAddress;
|
||||||
@@ -99,6 +102,7 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
tvWalletViewKey = view.findViewById(R.id.tvWalletViewKey);
|
tvWalletViewKey = view.findViewById(R.id.tvWalletViewKey);
|
||||||
tvWalletSpendKey = view.findViewById(R.id.tvWalletSpendKey);
|
tvWalletSpendKey = view.findViewById(R.id.tvWalletSpendKey);
|
||||||
tvWalletMnemonic = view.findViewById(R.id.tvWalletMnemonic);
|
tvWalletMnemonic = view.findViewById(R.id.tvWalletMnemonic);
|
||||||
|
tvWalletHeight = view.findViewById(R.id.tvWalletHeight);
|
||||||
bCopyAddress = view.findViewById(R.id.bCopyAddress);
|
bCopyAddress = view.findViewById(R.id.bCopyAddress);
|
||||||
bAdvancedInfo = view.findViewById(R.id.bAdvancedInfo);
|
bAdvancedInfo = view.findViewById(R.id.bAdvancedInfo);
|
||||||
llAdvancedInfo = view.findViewById(R.id.llAdvancedInfo);
|
llAdvancedInfo = view.findViewById(R.id.llAdvancedInfo);
|
||||||
@@ -188,6 +192,7 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
private class AsyncShow extends AsyncTask<String, Void, Boolean> {
|
private class AsyncShow extends AsyncTask<String, Void, Boolean> {
|
||||||
String name;
|
String name;
|
||||||
String address;
|
String address;
|
||||||
|
long height;
|
||||||
String seed;
|
String seed;
|
||||||
String viewKey;
|
String viewKey;
|
||||||
String spendKey;
|
String spendKey;
|
||||||
@@ -232,6 +237,7 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
address = wallet.getAddress();
|
address = wallet.getAddress();
|
||||||
|
height = wallet.getRestoreHeight();
|
||||||
seed = wallet.getSeed();
|
seed = wallet.getSeed();
|
||||||
switch (wallet.getDeviceType()) {
|
switch (wallet.getDeviceType()) {
|
||||||
case Device_Ledger:
|
case Device_Ledger:
|
||||||
@@ -264,6 +270,7 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
llPassword.setVisibility(View.VISIBLE);
|
llPassword.setVisibility(View.VISIBLE);
|
||||||
tvWalletPassword.setText(getPassword());
|
tvWalletPassword.setText(getPassword());
|
||||||
tvWalletAddress.setText(address);
|
tvWalletAddress.setText(address);
|
||||||
|
tvWalletHeight.setText(NumberFormat.getInstance().format(height));
|
||||||
if (!seed.isEmpty()) {
|
if (!seed.isEmpty()) {
|
||||||
llMnemonic.setVisibility(View.VISIBLE);
|
llMnemonic.setVisibility(View.VISIBLE);
|
||||||
tvWalletMnemonic.setText(seed);
|
tvWalletMnemonic.setText(seed);
|
||||||
@@ -288,6 +295,7 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
} else {
|
} else {
|
||||||
// TODO show proper error message and/or end the fragment?
|
// TODO show proper error message and/or end the fragment?
|
||||||
tvWalletAddress.setText(walletStatus.toString());
|
tvWalletAddress.setText(walletStatus.toString());
|
||||||
|
tvWalletHeight.setText(walletStatus.toString());
|
||||||
tvWalletMnemonic.setText(walletStatus.toString());
|
tvWalletMnemonic.setText(walletStatus.toString());
|
||||||
tvWalletViewKey.setText(walletStatus.toString());
|
tvWalletViewKey.setText(walletStatus.toString());
|
||||||
tvWalletSpendKey.setText(walletStatus.toString());
|
tvWalletSpendKey.setText(walletStatus.toString());
|
||||||
|
@@ -940,8 +940,9 @@ public class LoginActivity extends BaseActivity
|
|||||||
@Override
|
@Override
|
||||||
public boolean createWallet(File aFile, String password) {
|
public boolean createWallet(File aFile, String password) {
|
||||||
NodeInfo currentNode = getNode();
|
NodeInfo currentNode = getNode();
|
||||||
|
// get it from the connected node if we have one, and go back ca. 4 days
|
||||||
final long restoreHeight =
|
final long restoreHeight =
|
||||||
(currentNode != null) ? currentNode.getHeight() - 20 : -1;
|
(currentNode != null) ? currentNode.getHeight() - 2000 : -1;
|
||||||
Wallet newWallet = WalletManager.getInstance()
|
Wallet newWallet = WalletManager.getInstance()
|
||||||
.createWallet(aFile, password, MNEMONIC_LANGUAGE, restoreHeight);
|
.createWallet(aFile, password, MNEMONIC_LANGUAGE, restoreHeight);
|
||||||
return checkAndCloseWallet(newWallet);
|
return checkAndCloseWallet(newWallet);
|
||||||
@@ -1359,17 +1360,30 @@ public class LoginActivity extends BaseActivity
|
|||||||
if (Ledger.ENABLED)
|
if (Ledger.ENABLED)
|
||||||
try {
|
try {
|
||||||
Ledger.connect(usbManager, usbDevice);
|
Ledger.connect(usbManager, usbDevice);
|
||||||
registerDetachReceiver();
|
if (!Ledger.check()) {
|
||||||
onLedgerAction();
|
Ledger.disconnect();
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
Toast.makeText(LoginActivity.this,
|
Toast.makeText(LoginActivity.this,
|
||||||
getString(R.string.toast_ledger_attached, usbDevice.getProductName()),
|
getString(R.string.toast_ledger_start_app, usbDevice.getProductName()),
|
||||||
Toast.LENGTH_SHORT)
|
Toast.LENGTH_SHORT)
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
registerDetachReceiver();
|
||||||
|
onLedgerAction();
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Toast.makeText(LoginActivity.this,
|
||||||
|
getString(R.string.toast_ledger_attached, usbDevice.getProductName()),
|
||||||
|
Toast.LENGTH_SHORT)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
|
@@ -439,10 +439,13 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
}
|
}
|
||||||
Collections.sort(nodesToTest, NodeInfo.BestNodeComparator);
|
Collections.sort(nodesToTest, NodeInfo.BestNodeComparator);
|
||||||
NodeInfo bestNode = nodesToTest.get(0);
|
NodeInfo bestNode = nodesToTest.get(0);
|
||||||
if (bestNode.isValid())
|
if (bestNode.isValid()) {
|
||||||
|
activityCallback.setNode(bestNode);
|
||||||
return bestNode;
|
return bestNode;
|
||||||
else
|
} else {
|
||||||
|
activityCallback.setNode(null);
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -450,7 +453,6 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
if (!isAdded()) return;
|
if (!isAdded()) return;
|
||||||
pbNode.setVisibility(View.INVISIBLE);
|
pbNode.setVisibility(View.INVISIBLE);
|
||||||
llNode.setVisibility(View.VISIBLE);
|
llNode.setVisibility(View.VISIBLE);
|
||||||
activityCallback.setNode(result);
|
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
Timber.d("found a good node %s", result.toString());
|
Timber.d("found a good node %s", result.toString());
|
||||||
showNode(result);
|
showNode(result);
|
||||||
|
@@ -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("198.74.231.92", 18080)));
|
||||||
seedList.add(new NodeInfo(new InetSocketAddress("195.154.123.123", 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("212.83.172.165", 18080)));
|
||||||
|
seedList.add(new NodeInfo(new InetSocketAddress("192.110.160.146", 18080)));
|
||||||
d.seedPeers(seedList);
|
d.seedPeers(seedList);
|
||||||
d.awaitTermination(NODES_TO_FIND);
|
d.awaitTermination(NODES_TO_FIND);
|
||||||
}
|
}
|
||||||
|
@@ -50,9 +50,7 @@ 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.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
@@ -220,7 +218,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 +227,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 +243,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 +299,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());
|
||||||
|
@@ -235,8 +235,10 @@ public class NodeInfo extends Node {
|
|||||||
String rpcVersion = json.getString("jsonrpc");
|
String rpcVersion = json.getString("jsonrpc");
|
||||||
if (!RPC_VERSION.equals(rpcVersion))
|
if (!RPC_VERSION.equals(rpcVersion))
|
||||||
return false;
|
return false;
|
||||||
final JSONObject header = json.getJSONObject(
|
final JSONObject result = json.getJSONObject("result");
|
||||||
"result").getJSONObject("block_header");
|
if (!result.has("credits")) // introduced in monero v0.15.0
|
||||||
|
return false;
|
||||||
|
final JSONObject header = result.getJSONObject("block_header");
|
||||||
height = header.getLong("height");
|
height = header.getLong("height");
|
||||||
timestamp = header.getLong("timestamp");
|
timestamp = header.getLong("timestamp");
|
||||||
majorVersion = header.getInt("major_version");
|
majorVersion = header.getInt("major_version");
|
||||||
|
@@ -140,28 +140,12 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
|||||||
next = null;
|
next = null;
|
||||||
} else {
|
} else {
|
||||||
// maybe a bip72 or 70 URI
|
// maybe a bip72 or 70 URI
|
||||||
String bip70 = PaymentProtocolHelper.getBip70(enteredAddress);
|
final String bip70 = PaymentProtocolHelper.getBip70(enteredAddress);
|
||||||
if (bip70 != null) {
|
if (bip70 != null) {
|
||||||
// looks good - resolve through xmr.to
|
// looks good - resolve through xmr.to
|
||||||
processBip70(bip70);
|
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -176,6 +160,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
|||||||
Timber.d("isIntegratedAddress");
|
Timber.d("isIntegratedAddress");
|
||||||
etPaymentId.getEditText().getText().clear();
|
etPaymentId.getEditText().getText().clear();
|
||||||
llPaymentId.setVisibility(View.INVISIBLE);
|
llPaymentId.setVisibility(View.INVISIBLE);
|
||||||
|
etAddress.setError(getString(R.string.info_paymentid_integrated));
|
||||||
tvPaymentIdIntegrated.setVisibility(View.VISIBLE);
|
tvPaymentIdIntegrated.setVisibility(View.VISIBLE);
|
||||||
llXmrTo.setVisibility(View.INVISIBLE);
|
llXmrTo.setVisibility(View.INVISIBLE);
|
||||||
sendListener.setMode(SendFragment.Mode.XMR);
|
sendListener.setMode(SendFragment.Mode.XMR);
|
||||||
@@ -208,10 +193,21 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
|||||||
if (clip == null) return;
|
if (clip == null) return;
|
||||||
// clean it up
|
// clean it up
|
||||||
final String address = clip.replaceAll("[^0-9A-Z-a-z]", "");
|
final String address = clip.replaceAll("[^0-9A-Z-a-z]", "");
|
||||||
if (Wallet.isAddressValid(address) || BitcoinAddressValidator.validate(address))
|
if (Wallet.isAddressValid(address) || BitcoinAddressValidator.validate(address)) {
|
||||||
etAddress.getEditText().setText(address);
|
final EditText et = etAddress.getEditText();
|
||||||
else
|
et.setText(address);
|
||||||
Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -248,7 +244,10 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
|||||||
bPaymentId.setOnClickListener(new View.OnClickListener() {
|
bPaymentId.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
etPaymentId.getEditText().setText((Wallet.generatePaymentId()));
|
final EditText et = etPaymentId.getEditText();
|
||||||
|
et.setText((Wallet.generatePaymentId()));
|
||||||
|
et.setSelection(et.getText().length());
|
||||||
|
etPaymentId.requestFocus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -259,7 +258,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
|||||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||||
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
|
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||||
etDummy.requestFocus();
|
etDummy.requestFocus();
|
||||||
Helper.hideKeyboard(getActivity());
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -277,7 +275,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
|||||||
etDummy = view.findViewById(R.id.etDummy);
|
etDummy = view.findViewById(R.id.etDummy);
|
||||||
etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||||
etDummy.requestFocus();
|
etDummy.requestFocus();
|
||||||
Helper.hideKeyboard(getActivity());
|
|
||||||
|
|
||||||
View tvNfc = view.findViewById(R.id.tvNfc);
|
View tvNfc = view.findViewById(R.id.tvNfc);
|
||||||
NfcManager manager = (NfcManager) getContext().getSystemService(Context.NFC_SERVICE);
|
NfcManager manager = (NfcManager) getContext().getSystemService(Context.NFC_SERVICE);
|
||||||
@@ -551,7 +548,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
|||||||
public void onResumeFragment() {
|
public void onResumeFragment() {
|
||||||
super.onResumeFragment();
|
super.onResumeFragment();
|
||||||
Timber.d("onResumeFragment()");
|
Timber.d("onResumeFragment()");
|
||||||
Helper.hideKeyboard(getActivity());
|
|
||||||
etDummy.requestFocus();
|
etDummy.requestFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -21,8 +21,6 @@ import android.view.LayoutInflater;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.m2049r.xmrwallet.R;
|
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.data.TxData;
|
||||||
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.widget.ExchangeTextView;
|
import com.m2049r.xmrwallet.widget.ExchangeEditText;
|
||||||
import com.m2049r.xmrwallet.widget.NumberPadView;
|
|
||||||
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
@@ -59,8 +56,7 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private TextView tvFunds;
|
private TextView tvFunds;
|
||||||
private ExchangeTextView evAmount;
|
private ExchangeEditText etAmount;
|
||||||
private View llAmount;
|
|
||||||
private View rlSweep;
|
private View rlSweep;
|
||||||
private ImageButton ibSweep;
|
private ImageButton ibSweep;
|
||||||
|
|
||||||
@@ -75,12 +71,9 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
|||||||
View view = inflater.inflate(R.layout.fragment_send_amount, container, false);
|
View view = inflater.inflate(R.layout.fragment_send_amount, container, false);
|
||||||
|
|
||||||
tvFunds = view.findViewById(R.id.tvFunds);
|
tvFunds = view.findViewById(R.id.tvFunds);
|
||||||
|
etAmount = view.findViewById(R.id.etAmount);
|
||||||
evAmount = view.findViewById(R.id.evAmount);
|
|
||||||
((NumberPadView) view.findViewById(R.id.numberPad)).setListener(evAmount);
|
|
||||||
|
|
||||||
rlSweep = view.findViewById(R.id.rlSweep);
|
rlSweep = view.findViewById(R.id.rlSweep);
|
||||||
llAmount = view.findViewById(R.id.llAmount);
|
|
||||||
view.findViewById(R.id.ivSweep).setOnClickListener(new View.OnClickListener() {
|
view.findViewById(R.id.ivSweep).setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
@@ -97,8 +90,7 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Helper.hideKeyboard(getActivity());
|
etAmount.requestFocus();
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,11 +99,11 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
|||||||
private void sweepAll(boolean spendAllMode) {
|
private void sweepAll(boolean spendAllMode) {
|
||||||
if (spendAllMode) {
|
if (spendAllMode) {
|
||||||
ibSweep.setVisibility(View.INVISIBLE);
|
ibSweep.setVisibility(View.INVISIBLE);
|
||||||
llAmount.setVisibility(View.GONE);
|
etAmount.setVisibility(View.GONE);
|
||||||
rlSweep.setVisibility(View.VISIBLE);
|
rlSweep.setVisibility(View.VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
ibSweep.setVisibility(View.VISIBLE);
|
ibSweep.setVisibility(View.VISIBLE);
|
||||||
llAmount.setVisibility(View.VISIBLE);
|
etAmount.setVisibility(View.VISIBLE);
|
||||||
rlSweep.setVisibility(View.GONE);
|
rlSweep.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
this.spendAllMode = spendAllMode;
|
this.spendAllMode = spendAllMode;
|
||||||
@@ -124,12 +116,12 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
|||||||
sendListener.getTxData().setAmount(Wallet.SWEEP_ALL);
|
sendListener.getTxData().setAmount(Wallet.SWEEP_ALL);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!evAmount.validate(maxFunds)) {
|
if (!etAmount.validate(maxFunds, 0)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sendListener != null) {
|
if (sendListener != null) {
|
||||||
String xmr = evAmount.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 {
|
||||||
@@ -146,7 +138,7 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
|||||||
public void onResumeFragment() {
|
public void onResumeFragment() {
|
||||||
super.onResumeFragment();
|
super.onResumeFragment();
|
||||||
Timber.d("onResumeFragment()");
|
Timber.d("onResumeFragment()");
|
||||||
Helper.hideKeyboard(getActivity());
|
Helper.showKeyboard(getActivity());
|
||||||
final long funds = getTotalFunds();
|
final long funds = getTotalFunds();
|
||||||
maxFunds = 1.0 * funds / 1000000000000L;
|
maxFunds = 1.0 * funds / 1000000000000L;
|
||||||
if (!sendListener.getActivityCallback().isStreetMode()) {
|
if (!sendListener.getActivityCallback().isStreetMode()) {
|
||||||
@@ -156,11 +148,11 @@ 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 ((evAmount.getAmount() != null) && evAmount.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)) {
|
||||||
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.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.ExchangeBtcTextView;
|
import com.m2049r.xmrwallet.widget.ExchangeEditText;
|
||||||
import com.m2049r.xmrwallet.widget.NumberPadView;
|
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;
|
||||||
@@ -62,8 +62,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private TextView tvFunds;
|
private TextView tvFunds;
|
||||||
private ExchangeBtcTextView evAmount;
|
private ExchangeOtherEditText etAmount;
|
||||||
private NumberPadView numberPad;
|
|
||||||
|
|
||||||
private TextView tvXmrToParms;
|
private TextView tvXmrToParms;
|
||||||
private SendProgressView evParams;
|
private SendProgressView evParams;
|
||||||
@@ -86,24 +85,20 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
|||||||
|
|
||||||
tvXmrToParms = view.findViewById(R.id.tvXmrToParms);
|
tvXmrToParms = view.findViewById(R.id.tvXmrToParms);
|
||||||
|
|
||||||
evAmount = view.findViewById(R.id.evAmount);
|
etAmount = view.findViewById(R.id.etAmount);
|
||||||
numberPad = view.findViewById(R.id.numberPad);
|
etAmount.requestFocus();
|
||||||
numberPad.setListener(evAmount);
|
|
||||||
|
|
||||||
Helper.hideKeyboard(getActivity());
|
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onValidateFields() {
|
public boolean onValidateFields() {
|
||||||
if (!evAmount.validate(maxBtc, minBtc)) {
|
if (!etAmount.validate(maxBtc, minBtc)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (sendListener != null) {
|
if (sendListener != null) {
|
||||||
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
|
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
|
||||||
String btcString = evAmount.getAmount();
|
String btcString = etAmount.getNativeAmount();
|
||||||
if (btcString != null) {
|
if (btcString != null) {
|
||||||
try {
|
try {
|
||||||
double btc = Double.parseDouble(btcString);
|
double btc = Double.parseDouble(btcString);
|
||||||
@@ -122,10 +117,12 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
|||||||
|
|
||||||
private void setBip70Mode() {
|
private void setBip70Mode() {
|
||||||
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
|
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
|
||||||
if (txDataBtc.getBip70() != null) {
|
if (txDataBtc.getBip70() == null) {
|
||||||
numberPad.setVisibility(View.INVISIBLE);
|
etAmount.setEditable(true);
|
||||||
|
Helper.showKeyboard(getActivity());
|
||||||
} else {
|
} else {
|
||||||
numberPad.setVisibility(View.VISIBLE);
|
etAmount.setEditable(false);
|
||||||
|
Helper.hideKeyboard(getActivity());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +138,6 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
|||||||
public void onResumeFragment() {
|
public void onResumeFragment() {
|
||||||
super.onResumeFragment();
|
super.onResumeFragment();
|
||||||
Timber.d("onResumeFragment()");
|
Timber.d("onResumeFragment()");
|
||||||
Helper.hideKeyboard(getActivity());
|
|
||||||
final long funds = getTotalFunds();
|
final long funds = getTotalFunds();
|
||||||
if (!sendListener.getActivityCallback().isStreetMode()) {
|
if (!sendListener.getActivityCallback().isStreetMode()) {
|
||||||
tvFunds.setText(getString(R.string.send_available,
|
tvFunds.setText(getString(R.string.send_available,
|
||||||
@@ -153,7 +149,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
|||||||
final BarcodeData data = sendListener.popBarcodeData();
|
final BarcodeData data = sendListener.popBarcodeData();
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
if (data.amount != null) {
|
if (data.amount != null) {
|
||||||
evAmount.setAmount(data.amount);
|
etAmount.setAmount(data.amount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setBip70Mode();
|
setBip70Mode();
|
||||||
@@ -171,7 +167,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
|||||||
getView().post(new Runnable() {
|
getView().post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
evAmount.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());
|
||||||
@@ -211,7 +207,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void processOrderParmsError(final Exception ex) {
|
private void processOrderParmsError(final Exception ex) {
|
||||||
evAmount.setRate(0);
|
etAmount.setExchangeRate(0);
|
||||||
orderParameters = null;
|
orderParameters = null;
|
||||||
maxBtc = 0;
|
maxBtc = 0;
|
||||||
minBtc = 0;
|
minBtc = 0;
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
@@ -134,6 +134,10 @@ public enum Instruction {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte getByteValue() {
|
||||||
|
return (byte) (value & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
private int value;
|
private int value;
|
||||||
|
|
||||||
Instruction(int value) {
|
Instruction(int value) {
|
||||||
|
@@ -27,9 +27,11 @@ import com.btchip.BTChipException;
|
|||||||
import com.btchip.comm.BTChipTransport;
|
import com.btchip.comm.BTChipTransport;
|
||||||
import com.btchip.comm.android.BTChipTransportAndroidHID;
|
import com.btchip.comm.android.BTChipTransportAndroidHID;
|
||||||
import com.m2049r.xmrwallet.BuildConfig;
|
import com.m2049r.xmrwallet.BuildConfig;
|
||||||
|
import com.m2049r.xmrwallet.model.WalletManager;
|
||||||
import com.m2049r.xmrwallet.util.Helper;
|
import com.m2049r.xmrwallet.util.Helper;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
@@ -40,9 +42,11 @@ public class Ledger {
|
|||||||
static public final int LOOKAHEAD_SUBADDRESSES = 20;
|
static public final int LOOKAHEAD_SUBADDRESSES = 20;
|
||||||
static public final String SUBADDRESS_LOOKAHEAD = LOOKAHEAD_ACCOUNTS + ":" + LOOKAHEAD_SUBADDRESSES;
|
static public final String SUBADDRESS_LOOKAHEAD = LOOKAHEAD_ACCOUNTS + ":" + LOOKAHEAD_SUBADDRESSES;
|
||||||
|
|
||||||
|
private static final byte PROTOCOL_VERSION = 0x02;
|
||||||
public static final int SW_OK = 0x9000;
|
public static final int SW_OK = 0x9000;
|
||||||
public static final int SW_INS_NOT_SUPPORTED = 0x6D00;
|
public static final int SW_INS_NOT_SUPPORTED = 0x6D00;
|
||||||
public static final int OK[] = {SW_OK};
|
public static final int OK[] = {SW_OK};
|
||||||
|
public static final int MINIMUM_LEDGER_VERSION = (1 << 16) + (3 << 8) + (1); // 1.3.1
|
||||||
|
|
||||||
public static UsbDevice findDevice(UsbManager usbManager) {
|
public static UsbDevice findDevice(UsbManager usbManager) {
|
||||||
if (!ENABLED) return null;
|
if (!ENABLED) return null;
|
||||||
@@ -89,6 +93,21 @@ public class Ledger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static public boolean check() {
|
||||||
|
if (Instance == null) return false;
|
||||||
|
byte[] moneroVersion = WalletManager.moneroVersion().getBytes(StandardCharsets.US_ASCII);
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] resp = Instance.exchangeApduNoOpt(Instruction.INS_RESET, moneroVersion, OK);
|
||||||
|
int deviceVersion = (resp[0] << 16) + (resp[1] << 8) + (resp[2]);
|
||||||
|
if (deviceVersion < MINIMUM_LEDGER_VERSION)
|
||||||
|
return false;
|
||||||
|
} catch (BTChipException ex) { // comm error - probably wrong app started on device
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
final private BTChipTransport transport;
|
final private BTChipTransport transport;
|
||||||
final private String name;
|
final private String name;
|
||||||
private int lastSW = 0;
|
private int lastSW = 0;
|
||||||
@@ -112,7 +131,7 @@ public class Ledger {
|
|||||||
synchronized private byte[] exchangeRaw(byte[] apdu) {
|
synchronized private byte[] exchangeRaw(byte[] apdu) {
|
||||||
if (transport == null)
|
if (transport == null)
|
||||||
throw new IllegalStateException("No transport (probably closed previously)");
|
throw new IllegalStateException("No transport (probably closed previously)");
|
||||||
Timber.i("exchangeRaw %02x", apdu[1]);
|
Timber.d("exchangeRaw %02x", apdu[1]);
|
||||||
Instruction ins = Instruction.fromByte(apdu[1]);
|
Instruction ins = Instruction.fromByte(apdu[1]);
|
||||||
if (listener != null) listener.onInstructionSend(ins, apdu);
|
if (listener != null) listener.onInstructionSend(ins, apdu);
|
||||||
sniffOut(ins, apdu);
|
sniffOut(ins, apdu);
|
||||||
@@ -120,7 +139,6 @@ public class Ledger {
|
|||||||
if (listener != null) listener.onInstructionReceive(ins, data);
|
if (listener != null) listener.onInstructionReceive(ins, data);
|
||||||
sniffIn(data);
|
sniffIn(data);
|
||||||
return data;
|
return data;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] exchange(byte[] apdu) throws BTChipException {
|
private byte[] exchange(byte[] apdu) throws BTChipException {
|
||||||
@@ -148,68 +166,19 @@ public class Ledger {
|
|||||||
throw new BTChipException("Invalid status", lastSW);
|
throw new BTChipException("Invalid status", lastSW);
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] exchangeApdu(byte cla, byte ins, byte p1, byte p2, byte[] data, int acceptedSW[]) throws BTChipException {
|
private byte[] exchangeApduNoOpt(Instruction instruction, byte[] data, int acceptedSW[])
|
||||||
byte[] apdu = new byte[data.length + 5];
|
throws BTChipException {
|
||||||
apdu[0] = cla;
|
byte[] apdu = new byte[data.length + 6];
|
||||||
apdu[1] = ins;
|
apdu[0] = PROTOCOL_VERSION;
|
||||||
apdu[2] = p1;
|
apdu[1] = instruction.getByteValue();
|
||||||
apdu[3] = p2;
|
apdu[2] = 0; // p1
|
||||||
apdu[4] = (byte) (data.length);
|
apdu[3] = 0; // p2
|
||||||
System.arraycopy(data, 0, apdu, 5, data.length);
|
apdu[4] = (byte) (data.length + 1); // +1 because the opt byte is part of the data
|
||||||
|
apdu[5] = 0; // opt
|
||||||
|
System.arraycopy(data, 0, apdu, 6, data.length);
|
||||||
return exchangeCheck(apdu, acceptedSW);
|
return exchangeCheck(apdu, acceptedSW);
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] exchangeApdu(byte cla, byte ins, byte p1, byte p2, int length, int acceptedSW[]) throws BTChipException {
|
|
||||||
byte[] apdu = new byte[5];
|
|
||||||
apdu[0] = cla;
|
|
||||||
apdu[1] = ins;
|
|
||||||
apdu[2] = p1;
|
|
||||||
apdu[3] = p2;
|
|
||||||
apdu[4] = (byte) (length);
|
|
||||||
return exchangeCheck(apdu, acceptedSW);
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] exchangeApduSplit(byte cla, byte ins, byte p1, byte p2, byte[] data, int acceptedSW[]) throws BTChipException {
|
|
||||||
int offset = 0;
|
|
||||||
byte[] result = null;
|
|
||||||
while (offset < data.length) {
|
|
||||||
int blockLength = ((data.length - offset) > 255 ? 255 : data.length - offset);
|
|
||||||
byte[] apdu = new byte[blockLength + 5];
|
|
||||||
apdu[0] = cla;
|
|
||||||
apdu[1] = ins;
|
|
||||||
apdu[2] = p1;
|
|
||||||
apdu[3] = p2;
|
|
||||||
apdu[4] = (byte) (blockLength);
|
|
||||||
System.arraycopy(data, offset, apdu, 5, blockLength);
|
|
||||||
result = exchangeCheck(apdu, acceptedSW);
|
|
||||||
offset += blockLength;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] exchangeApduSplit2(byte cla, byte ins, byte p1, byte p2, byte[] data, byte[] data2, int acceptedSW[]) throws BTChipException {
|
|
||||||
int offset = 0;
|
|
||||||
byte[] result = null;
|
|
||||||
int maxBlockSize = 255 - data2.length;
|
|
||||||
while (offset < data.length) {
|
|
||||||
int blockLength = ((data.length - offset) > maxBlockSize ? maxBlockSize : data.length - offset);
|
|
||||||
boolean lastBlock = ((offset + blockLength) == data.length);
|
|
||||||
byte[] apdu = new byte[blockLength + 5 + (lastBlock ? data2.length : 0)];
|
|
||||||
apdu[0] = cla;
|
|
||||||
apdu[1] = ins;
|
|
||||||
apdu[2] = p1;
|
|
||||||
apdu[3] = p2;
|
|
||||||
apdu[4] = (byte) (blockLength + (lastBlock ? data2.length : 0));
|
|
||||||
System.arraycopy(data, offset, apdu, 5, blockLength);
|
|
||||||
if (lastBlock) {
|
|
||||||
System.arraycopy(data2, 0, apdu, 5 + blockLength, data2.length);
|
|
||||||
}
|
|
||||||
result = exchangeCheck(apdu, acceptedSW);
|
|
||||||
offset += blockLength;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Listener {
|
public interface Listener {
|
||||||
void onInstructionSend(Instruction ins, byte[] apdu);
|
void onInstructionSend(Instruction ins, byte[] apdu);
|
||||||
|
|
||||||
@@ -251,7 +220,6 @@ public class Ledger {
|
|||||||
if (ins == Instruction.INS_GET_KEY) {
|
if (ins == Instruction.INS_GET_KEY) {
|
||||||
snoopKey = (apdu[2] == 2);
|
snoopKey = (apdu[2] == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sniffIn(byte[] data) {
|
private void sniffIn(byte[] data) {
|
||||||
|
@@ -354,4 +354,6 @@ public class WalletManager {
|
|||||||
static public native void logWarning(String category, String message);
|
static public native void logWarning(String category, String message);
|
||||||
|
|
||||||
static public native void logError(String category, String message);
|
static public native void logError(String category, String message);
|
||||||
|
|
||||||
|
static public native String moneroVersion();
|
||||||
}
|
}
|
@@ -290,7 +290,7 @@ public class WalletService extends Service {
|
|||||||
showProgress(10);
|
showProgress(10);
|
||||||
Wallet.Status walletStatus = start(walletId, walletPw);
|
Wallet.Status walletStatus = start(walletId, walletPw);
|
||||||
if (observer != null) observer.onWalletStarted(walletStatus);
|
if (observer != null) observer.onWalletStarted(walletStatus);
|
||||||
if (!walletStatus.isOk()) {
|
if ((walletStatus == null) || !walletStatus.isOk()) {
|
||||||
errorState = true;
|
errorState = true;
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
|
@@ -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");
|
* 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()));
|
@@ -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());
|
||||||
}
|
}
|
@@ -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.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,32 @@ 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 > 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 +633,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 {
|
||||||
|
@@ -103,6 +103,12 @@ public class RestoreHeight {
|
|||||||
blockheight.put("2019-03-01", 1781681L);
|
blockheight.put("2019-03-01", 1781681L);
|
||||||
blockheight.put("2019-04-01", 1803081L);
|
blockheight.put("2019-04-01", 1803081L);
|
||||||
blockheight.put("2019-05-01", 1824671L);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getHeight(String date) {
|
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