mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-04 17:28:42 +02:00
Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c14486306e | ||
![]() |
c2ef25c377 | ||
![]() |
b7164ef200 | ||
![]() |
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 |
@@ -155,6 +155,18 @@ add_library(net STATIC IMPORTED)
|
||||
set_target_properties(net PROPERTIES IMPORTED_LOCATION
|
||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libnet.a)
|
||||
|
||||
add_library(hardforks STATIC IMPORTED)
|
||||
set_target_properties(hardforks PROPERTIES IMPORTED_LOCATION
|
||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libhardforks.a)
|
||||
|
||||
add_library(randomx STATIC IMPORTED)
|
||||
set_target_properties(randomx PROPERTIES IMPORTED_LOCATION
|
||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/librandomx.a)
|
||||
|
||||
add_library(rpc_base STATIC IMPORTED)
|
||||
set_target_properties(rpc_base PROPERTIES IMPORTED_LOCATION
|
||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/librpc_base.a)
|
||||
|
||||
#############
|
||||
# System
|
||||
#############
|
||||
@@ -188,6 +200,9 @@ target_link_libraries( monerujo
|
||||
device_trezor
|
||||
multisig
|
||||
version
|
||||
randomx
|
||||
hardforks
|
||||
rpc_base
|
||||
|
||||
boost_chrono
|
||||
boost_date_time
|
||||
|
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "com.m2049r.xmrwallet"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 28
|
||||
versionCode 182
|
||||
versionName "1.11.12 'Chernushka'"
|
||||
versionCode 198
|
||||
versionName "1.12.8 'Caerbannog'"
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
externalNativeBuild {
|
||||
|
@@ -1310,7 +1310,6 @@ Java_com_m2049r_xmrwallet_model_PendingTransaction_getFirstTxIdJ(JNIEnv *env, jo
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_m2049r_xmrwallet_model_PendingTransaction_getTxCount(JNIEnv *env, jobject 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);
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_m2049r_xmrwallet_model_WalletManager_moneroVersion(JNIEnv *env, jclass clazz) {
|
||||
return env->NewStringUTF(MONERO_VERSION);
|
||||
}
|
||||
|
||||
//
|
||||
// Ledger Stuff
|
||||
//
|
||||
|
@@ -54,6 +54,8 @@ extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
extern const char* const MONERO_VERSION; // the actual monero core version
|
||||
|
||||
// from monero-core crypto/hash-ops.h - avoid #including monero code here
|
||||
enum {
|
||||
HASH_SIZE = 32,
|
||||
|
@@ -46,8 +46,12 @@ public class BTChipTransportAndroidHID implements BTChipTransport {
|
||||
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
|
||||
for (UsbDevice device : deviceList.values()) {
|
||||
Timber.d("%04X:%04X %s, %s", device.getVendorId(), device.getProductId(), device.getManufacturerName(), device.getProductName());
|
||||
if ((device.getVendorId() == VID) && (device.getProductId() == PID_HID)) {
|
||||
return device;
|
||||
if (device.getVendorId() == VID) {
|
||||
final int deviceProductId = device.getProductId();
|
||||
for (int pid : PID_HIDS) {
|
||||
if (deviceProductId == pid)
|
||||
return device;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -74,7 +78,7 @@ public class BTChipTransportAndroidHID implements BTChipTransport {
|
||||
}
|
||||
|
||||
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 UsbInterface dongleInterface;
|
||||
|
@@ -79,6 +79,23 @@ public class GenerateFragment extends Fragment {
|
||||
|
||||
private String type = null;
|
||||
|
||||
private void clearErrorOnTextEntry(final TextInputLayout textInputLayout) {
|
||||
textInputLayout.getEditText().addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
textInputLayout.setError(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
@@ -110,6 +127,23 @@ public class GenerateFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
});
|
||||
clearErrorOnTextEntry(etWalletName);
|
||||
|
||||
etWalletPassword.getEditText().addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
checkPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
});
|
||||
|
||||
etWalletMnemonic.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
@@ -118,6 +152,8 @@ public class GenerateFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
});
|
||||
clearErrorOnTextEntry(etWalletMnemonic);
|
||||
|
||||
etWalletAddress.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
@@ -126,6 +162,8 @@ public class GenerateFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
});
|
||||
clearErrorOnTextEntry(etWalletAddress);
|
||||
|
||||
etWalletViewKey.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
@@ -134,6 +172,8 @@ public class GenerateFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
});
|
||||
clearErrorOnTextEntry(etWalletViewKey);
|
||||
|
||||
etWalletSpendKey.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
@@ -142,6 +182,7 @@ public class GenerateFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
});
|
||||
clearErrorOnTextEntry(etWalletSpendKey);
|
||||
|
||||
Helper.showKeyboard(getActivity());
|
||||
|
||||
@@ -310,21 +351,6 @@ public class GenerateFragment extends Fragment {
|
||||
}
|
||||
});
|
||||
|
||||
etWalletPassword.getEditText().addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
checkPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
});
|
||||
|
||||
etWalletName.requestFocus();
|
||||
initZxcvbn();
|
||||
|
||||
|
@@ -55,6 +55,8 @@ import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
||||
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class GenerateReviewFragment extends Fragment {
|
||||
@@ -72,6 +74,7 @@ public class GenerateReviewFragment extends Fragment {
|
||||
private TextView tvWalletPassword;
|
||||
private TextView tvWalletAddress;
|
||||
private TextView tvWalletMnemonic;
|
||||
private TextView tvWalletHeight;
|
||||
private TextView tvWalletViewKey;
|
||||
private TextView tvWalletSpendKey;
|
||||
private ImageButton bCopyAddress;
|
||||
@@ -99,6 +102,7 @@ public class GenerateReviewFragment extends Fragment {
|
||||
tvWalletViewKey = view.findViewById(R.id.tvWalletViewKey);
|
||||
tvWalletSpendKey = view.findViewById(R.id.tvWalletSpendKey);
|
||||
tvWalletMnemonic = view.findViewById(R.id.tvWalletMnemonic);
|
||||
tvWalletHeight = view.findViewById(R.id.tvWalletHeight);
|
||||
bCopyAddress = view.findViewById(R.id.bCopyAddress);
|
||||
bAdvancedInfo = view.findViewById(R.id.bAdvancedInfo);
|
||||
llAdvancedInfo = view.findViewById(R.id.llAdvancedInfo);
|
||||
@@ -188,6 +192,7 @@ public class GenerateReviewFragment extends Fragment {
|
||||
private class AsyncShow extends AsyncTask<String, Void, Boolean> {
|
||||
String name;
|
||||
String address;
|
||||
long height;
|
||||
String seed;
|
||||
String viewKey;
|
||||
String spendKey;
|
||||
@@ -232,6 +237,7 @@ public class GenerateReviewFragment extends Fragment {
|
||||
}
|
||||
|
||||
address = wallet.getAddress();
|
||||
height = wallet.getRestoreHeight();
|
||||
seed = wallet.getSeed();
|
||||
switch (wallet.getDeviceType()) {
|
||||
case Device_Ledger:
|
||||
@@ -264,6 +270,7 @@ public class GenerateReviewFragment extends Fragment {
|
||||
llPassword.setVisibility(View.VISIBLE);
|
||||
tvWalletPassword.setText(getPassword());
|
||||
tvWalletAddress.setText(address);
|
||||
tvWalletHeight.setText(NumberFormat.getInstance().format(height));
|
||||
if (!seed.isEmpty()) {
|
||||
llMnemonic.setVisibility(View.VISIBLE);
|
||||
tvWalletMnemonic.setText(seed);
|
||||
@@ -288,6 +295,7 @@ public class GenerateReviewFragment extends Fragment {
|
||||
} else {
|
||||
// TODO show proper error message and/or end the fragment?
|
||||
tvWalletAddress.setText(walletStatus.toString());
|
||||
tvWalletHeight.setText(walletStatus.toString());
|
||||
tvWalletMnemonic.setText(walletStatus.toString());
|
||||
tvWalletViewKey.setText(walletStatus.toString());
|
||||
tvWalletSpendKey.setText(walletStatus.toString());
|
||||
|
@@ -940,8 +940,9 @@ public class LoginActivity extends BaseActivity
|
||||
@Override
|
||||
public boolean createWallet(File aFile, String password) {
|
||||
NodeInfo currentNode = getNode();
|
||||
// get it from the connected node if we have one, and go back ca. 4 days
|
||||
final long restoreHeight =
|
||||
(currentNode != null) ? currentNode.getHeight() - 20 : -1;
|
||||
(currentNode != null) ? currentNode.getHeight() - 2000 : -1;
|
||||
Wallet newWallet = WalletManager.getInstance()
|
||||
.createWallet(aFile, password, MNEMONIC_LANGUAGE, restoreHeight);
|
||||
return checkAndCloseWallet(newWallet);
|
||||
@@ -1359,17 +1360,30 @@ public class LoginActivity extends BaseActivity
|
||||
if (Ledger.ENABLED)
|
||||
try {
|
||||
Ledger.connect(usbManager, usbDevice);
|
||||
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();
|
||||
}
|
||||
});
|
||||
if (!Ledger.check()) {
|
||||
Ledger.disconnect();
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(LoginActivity.this,
|
||||
getString(R.string.toast_ledger_start_app, usbDevice.getProductName()),
|
||||
Toast.LENGTH_SHORT)
|
||||
.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) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
|
@@ -266,6 +266,7 @@ public class NodeFragment extends Fragment
|
||||
seedList.add(new NodeInfo(new InetSocketAddress("198.74.231.92", 18080)));
|
||||
seedList.add(new NodeInfo(new InetSocketAddress("195.154.123.123", 18080)));
|
||||
seedList.add(new NodeInfo(new InetSocketAddress("212.83.172.165", 18080)));
|
||||
seedList.add(new NodeInfo(new InetSocketAddress("192.110.160.146", 18080)));
|
||||
d.seedPeers(seedList);
|
||||
d.awaitTermination(NODES_TO_FIND);
|
||||
}
|
||||
|
@@ -50,9 +50,8 @@ import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
@@ -114,9 +113,12 @@ public class WalletFragment extends Fragment
|
||||
ivSynced = view.findViewById(R.id.ivSynced);
|
||||
|
||||
sCurrency = view.findViewById(R.id.sCurrency);
|
||||
ArrayAdapter currencyAdapter = ArrayAdapter.createFromResource(getContext(), R.array.currency, R.layout.item_spinner_balance);
|
||||
currencyAdapter.setDropDownViewResource(R.layout.item_spinner_dropdown_item);
|
||||
sCurrency.setAdapter(currencyAdapter);
|
||||
List<String> currencies = new ArrayList<>();
|
||||
currencies.add(Helper.BASE_CRYPTO);
|
||||
currencies.addAll(Arrays.asList(getResources().getStringArray(R.array.currency)));
|
||||
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(getContext(), R.layout.item_spinner_balance, currencies);
|
||||
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
sCurrency.setAdapter(spinnerAdapter);
|
||||
|
||||
bSend = view.findViewById(R.id.bSend);
|
||||
bReceive = view.findViewById(R.id.bReceive);
|
||||
@@ -220,7 +222,7 @@ public class WalletFragment extends Fragment
|
||||
// at this point selection is XMR in case of error
|
||||
String displayB;
|
||||
double amountA = Helper.getDecimalAmount(unlockedBalance).doubleValue();
|
||||
if (!Helper.CRYPTO.equals(balanceCurrency)) { // not XMR
|
||||
if (!Helper.BASE_CRYPTO.equals(balanceCurrency)) { // not XMR
|
||||
double amountB = amountA * balanceRate;
|
||||
displayB = Helper.getFormattedAmount(amountB, false);
|
||||
} else { // XMR
|
||||
@@ -229,7 +231,7 @@ public class WalletFragment extends Fragment
|
||||
showBalance(displayB);
|
||||
}
|
||||
|
||||
String balanceCurrency = Helper.CRYPTO;
|
||||
String balanceCurrency = Helper.BASE_CRYPTO;
|
||||
double balanceRate = 1.0;
|
||||
|
||||
private final ExchangeApi exchangeApi = Helper.getExchangeApi();
|
||||
@@ -245,7 +247,7 @@ public class WalletFragment extends Fragment
|
||||
Timber.d(currency);
|
||||
if (!currency.equals(balanceCurrency) || (balanceRate <= 0)) {
|
||||
showExchanging();
|
||||
exchangeApi.queryExchangeRate(Helper.CRYPTO, currency,
|
||||
exchangeApi.queryExchangeRate(Helper.BASE_CRYPTO, currency,
|
||||
new ExchangeCallback() {
|
||||
@Override
|
||||
public void onSuccess(final ExchangeRate exchangeRate) {
|
||||
@@ -301,10 +303,10 @@ public class WalletFragment extends Fragment
|
||||
|
||||
public void exchange(final ExchangeRate exchangeRate) {
|
||||
hideExchanging();
|
||||
if (!Helper.CRYPTO.equals(exchangeRate.getBaseCurrency())) {
|
||||
if (!Helper.BASE_CRYPTO.equals(exchangeRate.getBaseCurrency())) {
|
||||
Timber.e("Not XMR");
|
||||
sCurrency.setSelection(0, true);
|
||||
balanceCurrency = Helper.CRYPTO;
|
||||
balanceCurrency = Helper.BASE_CRYPTO;
|
||||
balanceRate = 1.0;
|
||||
} else {
|
||||
int spinnerPosition = ((ArrayAdapter) sCurrency.getAdapter()).getPosition(exchangeRate.getQuoteCurrency());
|
||||
|
@@ -235,8 +235,10 @@ public class NodeInfo extends Node {
|
||||
String rpcVersion = json.getString("jsonrpc");
|
||||
if (!RPC_VERSION.equals(rpcVersion))
|
||||
return false;
|
||||
final JSONObject header = json.getJSONObject(
|
||||
"result").getJSONObject("block_header");
|
||||
final JSONObject result = json.getJSONObject("result");
|
||||
if (!result.has("credits")) // introduced in monero v0.15.0
|
||||
return false;
|
||||
final JSONObject header = result.getJSONObject("block_header");
|
||||
height = header.getLong("height");
|
||||
timestamp = header.getLong("timestamp");
|
||||
majorVersion = header.getInt("major_version");
|
||||
|
@@ -140,28 +140,12 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
next = null;
|
||||
} else {
|
||||
// maybe a bip72 or 70 URI
|
||||
String bip70 = PaymentProtocolHelper.getBip70(enteredAddress);
|
||||
final String bip70 = PaymentProtocolHelper.getBip70(enteredAddress);
|
||||
if (bip70 != null) {
|
||||
// looks good - resolve through xmr.to
|
||||
processBip70(bip70);
|
||||
next = null;
|
||||
} else if (checkAddress()) {
|
||||
if (llPaymentId.getVisibility() == View.VISIBLE) {
|
||||
next = etPaymentId;
|
||||
} else {
|
||||
next = etNotes;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (next != null) {
|
||||
final View focus = next;
|
||||
etAddress.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
focus.requestFocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -176,6 +160,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
Timber.d("isIntegratedAddress");
|
||||
etPaymentId.getEditText().getText().clear();
|
||||
llPaymentId.setVisibility(View.INVISIBLE);
|
||||
etAddress.setError(getString(R.string.info_paymentid_integrated));
|
||||
tvPaymentIdIntegrated.setVisibility(View.VISIBLE);
|
||||
llXmrTo.setVisibility(View.INVISIBLE);
|
||||
sendListener.setMode(SendFragment.Mode.XMR);
|
||||
@@ -208,10 +193,21 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
if (clip == null) return;
|
||||
// clean it up
|
||||
final String address = clip.replaceAll("[^0-9A-Z-a-z]", "");
|
||||
if (Wallet.isAddressValid(address) || BitcoinAddressValidator.validate(address))
|
||||
etAddress.getEditText().setText(address);
|
||||
else
|
||||
Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show();
|
||||
if (Wallet.isAddressValid(address) || BitcoinAddressValidator.validate(address)) {
|
||||
final EditText et = etAddress.getEditText();
|
||||
et.setText(address);
|
||||
et.setSelection(et.getText().length());
|
||||
etAddress.requestFocus();
|
||||
} else {
|
||||
final String bip70 = PaymentProtocolHelper.getBip70(clip);
|
||||
if (bip70 != null) {
|
||||
final EditText et = etAddress.getEditText();
|
||||
et.setText(clip);
|
||||
et.setSelection(et.getText().length());
|
||||
processBip70(bip70);
|
||||
} else
|
||||
Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -248,7 +244,10 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
bPaymentId.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
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))
|
||||
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||
etDummy.requestFocus();
|
||||
Helper.hideKeyboard(getActivity());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -277,7 +275,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
etDummy = view.findViewById(R.id.etDummy);
|
||||
etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
etDummy.requestFocus();
|
||||
Helper.hideKeyboard(getActivity());
|
||||
|
||||
View tvNfc = view.findViewById(R.id.tvNfc);
|
||||
NfcManager manager = (NfcManager) getContext().getSystemService(Context.NFC_SERVICE);
|
||||
@@ -551,7 +548,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
public void onResumeFragment() {
|
||||
super.onResumeFragment();
|
||||
Timber.d("onResumeFragment()");
|
||||
Helper.hideKeyboard(getActivity());
|
||||
etDummy.requestFocus();
|
||||
}
|
||||
|
||||
|
@@ -21,8 +21,6 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.m2049r.xmrwallet.R;
|
||||
@@ -30,8 +28,7 @@ import com.m2049r.xmrwallet.data.BarcodeData;
|
||||
import com.m2049r.xmrwallet.data.TxData;
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.widget.ExchangeTextView;
|
||||
import com.m2049r.xmrwallet.widget.NumberPadView;
|
||||
import com.m2049r.xmrwallet.widget.ExchangeEditText;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
@@ -59,8 +56,7 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
||||
}
|
||||
|
||||
private TextView tvFunds;
|
||||
private ExchangeTextView evAmount;
|
||||
private View llAmount;
|
||||
private ExchangeEditText etAmount;
|
||||
private View rlSweep;
|
||||
private ImageButton ibSweep;
|
||||
|
||||
@@ -75,12 +71,9 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
||||
View view = inflater.inflate(R.layout.fragment_send_amount, container, false);
|
||||
|
||||
tvFunds = view.findViewById(R.id.tvFunds);
|
||||
|
||||
evAmount = view.findViewById(R.id.evAmount);
|
||||
((NumberPadView) view.findViewById(R.id.numberPad)).setListener(evAmount);
|
||||
|
||||
etAmount = view.findViewById(R.id.etAmount);
|
||||
rlSweep = view.findViewById(R.id.rlSweep);
|
||||
llAmount = view.findViewById(R.id.llAmount);
|
||||
|
||||
view.findViewById(R.id.ivSweep).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@@ -97,8 +90,7 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
||||
}
|
||||
});
|
||||
|
||||
Helper.hideKeyboard(getActivity());
|
||||
|
||||
etAmount.requestFocus();
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -107,11 +99,11 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
||||
private void sweepAll(boolean spendAllMode) {
|
||||
if (spendAllMode) {
|
||||
ibSweep.setVisibility(View.INVISIBLE);
|
||||
llAmount.setVisibility(View.GONE);
|
||||
etAmount.setVisibility(View.GONE);
|
||||
rlSweep.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
ibSweep.setVisibility(View.VISIBLE);
|
||||
llAmount.setVisibility(View.VISIBLE);
|
||||
etAmount.setVisibility(View.VISIBLE);
|
||||
rlSweep.setVisibility(View.GONE);
|
||||
}
|
||||
this.spendAllMode = spendAllMode;
|
||||
@@ -124,12 +116,12 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
||||
sendListener.getTxData().setAmount(Wallet.SWEEP_ALL);
|
||||
}
|
||||
} else {
|
||||
if (!evAmount.validate(maxFunds)) {
|
||||
if (!etAmount.validate(maxFunds, 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sendListener != null) {
|
||||
String xmr = evAmount.getAmount();
|
||||
String xmr = etAmount.getNativeAmount();
|
||||
if (xmr != null) {
|
||||
sendListener.getTxData().setAmount(Wallet.getAmountFromString(xmr));
|
||||
} else {
|
||||
@@ -146,7 +138,7 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
||||
public void onResumeFragment() {
|
||||
super.onResumeFragment();
|
||||
Timber.d("onResumeFragment()");
|
||||
Helper.hideKeyboard(getActivity());
|
||||
Helper.showKeyboard(getActivity());
|
||||
final long funds = getTotalFunds();
|
||||
maxFunds = 1.0 * funds / 1000000000000L;
|
||||
if (!sendListener.getActivityCallback().isStreetMode()) {
|
||||
@@ -156,11 +148,11 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
||||
tvFunds.setText(getString(R.string.send_available,
|
||||
getString(R.string.unknown_amount)));
|
||||
}
|
||||
// getAmount is null if exchange is in progress
|
||||
if ((evAmount.getAmount() != null) && evAmount.getAmount().isEmpty()) {
|
||||
// getNativeAmount is null if exchange is in progress
|
||||
if ((etAmount.getNativeAmount() != null) && etAmount.getNativeAmount().isEmpty()) {
|
||||
final BarcodeData data = sendListener.popBarcodeData();
|
||||
if ((data != null) && (data.amount != null)) {
|
||||
evAmount.setAmount(data.amount);
|
||||
etAmount.setAmount(data.amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -31,8 +31,8 @@ import com.m2049r.xmrwallet.data.TxDataBtc;
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.OkHttpHelper;
|
||||
import com.m2049r.xmrwallet.widget.ExchangeBtcTextView;
|
||||
import com.m2049r.xmrwallet.widget.NumberPadView;
|
||||
import com.m2049r.xmrwallet.widget.ExchangeEditText;
|
||||
import com.m2049r.xmrwallet.widget.ExchangeOtherEditText;
|
||||
import com.m2049r.xmrwallet.widget.SendProgressView;
|
||||
import com.m2049r.xmrwallet.xmrto.XmrToError;
|
||||
import com.m2049r.xmrwallet.xmrto.XmrToException;
|
||||
@@ -62,8 +62,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
||||
}
|
||||
|
||||
private TextView tvFunds;
|
||||
private ExchangeBtcTextView evAmount;
|
||||
private NumberPadView numberPad;
|
||||
private ExchangeOtherEditText etAmount;
|
||||
|
||||
private TextView tvXmrToParms;
|
||||
private SendProgressView evParams;
|
||||
@@ -86,24 +85,20 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
||||
|
||||
tvXmrToParms = view.findViewById(R.id.tvXmrToParms);
|
||||
|
||||
evAmount = view.findViewById(R.id.evAmount);
|
||||
numberPad = view.findViewById(R.id.numberPad);
|
||||
numberPad.setListener(evAmount);
|
||||
|
||||
Helper.hideKeyboard(getActivity());
|
||||
|
||||
etAmount = view.findViewById(R.id.etAmount);
|
||||
etAmount.requestFocus();
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onValidateFields() {
|
||||
if (!evAmount.validate(maxBtc, minBtc)) {
|
||||
if (!etAmount.validate(maxBtc, minBtc)) {
|
||||
return false;
|
||||
}
|
||||
if (sendListener != null) {
|
||||
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
|
||||
String btcString = evAmount.getAmount();
|
||||
String btcString = etAmount.getNativeAmount();
|
||||
if (btcString != null) {
|
||||
try {
|
||||
double btc = Double.parseDouble(btcString);
|
||||
@@ -122,10 +117,12 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
||||
|
||||
private void setBip70Mode() {
|
||||
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
|
||||
if (txDataBtc.getBip70() != null) {
|
||||
numberPad.setVisibility(View.INVISIBLE);
|
||||
if (txDataBtc.getBip70() == null) {
|
||||
etAmount.setEditable(true);
|
||||
Helper.showKeyboard(getActivity());
|
||||
} else {
|
||||
numberPad.setVisibility(View.VISIBLE);
|
||||
etAmount.setEditable(false);
|
||||
Helper.hideKeyboard(getActivity());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +138,6 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
||||
public void onResumeFragment() {
|
||||
super.onResumeFragment();
|
||||
Timber.d("onResumeFragment()");
|
||||
Helper.hideKeyboard(getActivity());
|
||||
final long funds = getTotalFunds();
|
||||
if (!sendListener.getActivityCallback().isStreetMode()) {
|
||||
tvFunds.setText(getString(R.string.send_available,
|
||||
@@ -153,7 +149,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
||||
final BarcodeData data = sendListener.popBarcodeData();
|
||||
if (data != null) {
|
||||
if (data.amount != null) {
|
||||
evAmount.setAmount(data.amount);
|
||||
etAmount.setAmount(data.amount);
|
||||
}
|
||||
}
|
||||
setBip70Mode();
|
||||
@@ -171,7 +167,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
||||
getView().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
evAmount.setRate(1.0d / orderParameters.getPrice());
|
||||
etAmount.setExchangeRate(1.0d / orderParameters.getPrice());
|
||||
NumberFormat df = NumberFormat.getInstance(Locale.US);
|
||||
df.setMaximumFractionDigits(6);
|
||||
String min = df.format(orderParameters.getLowerLimit());
|
||||
@@ -211,7 +207,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
||||
}
|
||||
|
||||
private void processOrderParmsError(final Exception ex) {
|
||||
evAmount.setRate(0);
|
||||
etAmount.setExchangeRate(0);
|
||||
orderParameters = null;
|
||||
maxBtc = 0;
|
||||
minBtc = 0;
|
||||
|
@@ -457,8 +457,7 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
||||
}
|
||||
showProgress(3, getString(R.string.label_send_progress_create_tx));
|
||||
TxData txData = sendListener.getTxData();
|
||||
txData.setDestinationAddress(xmrtoStatus.getXmrReceivingAddress());
|
||||
txData.setPaymentId(xmrtoStatus.getXmrRequiredPaymentIdShort());
|
||||
txData.setDestinationAddress(xmrtoStatus.getXmrReceivingSubaddress());
|
||||
txData.setAmount(Wallet.getAmountFromDouble(xmrtoStatus.getXmrAmountTotal()));
|
||||
getActivityCallback().onPrepareSend(xmrtoStatus.getUuid(), txData);
|
||||
}
|
||||
|
@@ -134,6 +134,10 @@ public enum Instruction {
|
||||
return value;
|
||||
}
|
||||
|
||||
public byte getByteValue() {
|
||||
return (byte) (value & 0xFF);
|
||||
}
|
||||
|
||||
private int value;
|
||||
|
||||
Instruction(int value) {
|
||||
|
@@ -27,9 +27,11 @@ import com.btchip.BTChipException;
|
||||
import com.btchip.comm.BTChipTransport;
|
||||
import com.btchip.comm.android.BTChipTransportAndroidHID;
|
||||
import com.m2049r.xmrwallet.BuildConfig;
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
@@ -40,9 +42,11 @@ public class Ledger {
|
||||
static public final int LOOKAHEAD_SUBADDRESSES = 20;
|
||||
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_INS_NOT_SUPPORTED = 0x6D00;
|
||||
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) {
|
||||
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 String name;
|
||||
private int lastSW = 0;
|
||||
@@ -112,7 +131,7 @@ public class Ledger {
|
||||
synchronized private byte[] exchangeRaw(byte[] apdu) {
|
||||
if (transport == null)
|
||||
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]);
|
||||
if (listener != null) listener.onInstructionSend(ins, apdu);
|
||||
sniffOut(ins, apdu);
|
||||
@@ -120,7 +139,6 @@ public class Ledger {
|
||||
if (listener != null) listener.onInstructionReceive(ins, data);
|
||||
sniffIn(data);
|
||||
return data;
|
||||
|
||||
}
|
||||
|
||||
private byte[] exchange(byte[] apdu) throws BTChipException {
|
||||
@@ -148,68 +166,19 @@ public class Ledger {
|
||||
throw new BTChipException("Invalid status", lastSW);
|
||||
}
|
||||
|
||||
private byte[] exchangeApdu(byte cla, byte ins, byte p1, byte p2, byte[] data, int acceptedSW[]) throws BTChipException {
|
||||
byte[] apdu = new byte[data.length + 5];
|
||||
apdu[0] = cla;
|
||||
apdu[1] = ins;
|
||||
apdu[2] = p1;
|
||||
apdu[3] = p2;
|
||||
apdu[4] = (byte) (data.length);
|
||||
System.arraycopy(data, 0, apdu, 5, data.length);
|
||||
private byte[] exchangeApduNoOpt(Instruction instruction, byte[] data, int acceptedSW[])
|
||||
throws BTChipException {
|
||||
byte[] apdu = new byte[data.length + 6];
|
||||
apdu[0] = PROTOCOL_VERSION;
|
||||
apdu[1] = instruction.getByteValue();
|
||||
apdu[2] = 0; // p1
|
||||
apdu[3] = 0; // p2
|
||||
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);
|
||||
}
|
||||
|
||||
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 {
|
||||
void onInstructionSend(Instruction ins, byte[] apdu);
|
||||
|
||||
@@ -251,7 +220,6 @@ public class Ledger {
|
||||
if (ins == Instruction.INS_GET_KEY) {
|
||||
snoopKey = (apdu[2] == 2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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 logError(String category, String message);
|
||||
|
||||
static public native String moneroVersion();
|
||||
}
|
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright (c) 2019 m2049r@monerujo.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// https://developer.android.com/training/basics/network-ops/xml
|
||||
|
||||
package com.m2049r.xmrwallet.service.exchange.ecb;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
|
||||
import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
|
||||
import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
|
||||
import com.m2049r.xmrwallet.service.exchange.api.ExchangeException;
|
||||
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ExchangeApiImpl implements ExchangeApi {
|
||||
@NonNull
|
||||
private final OkHttpClient okHttpClient;
|
||||
@NonNull
|
||||
private final HttpUrl baseUrl;
|
||||
|
||||
//so we can inject the mockserver url
|
||||
@VisibleForTesting
|
||||
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient, @NonNull final HttpUrl baseUrl) {
|
||||
this.okHttpClient = okHttpClient;
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient) {
|
||||
this(okHttpClient, HttpUrl.parse("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml"));
|
||||
// data is daily and is refreshed around 16:00 CET every working day
|
||||
}
|
||||
|
||||
public static boolean isSameDay(Calendar calendar, Calendar anotherCalendar) {
|
||||
return (calendar.get(Calendar.YEAR) == anotherCalendar.get(Calendar.YEAR)) &&
|
||||
(calendar.get(Calendar.DAY_OF_YEAR) == anotherCalendar.get(Calendar.DAY_OF_YEAR));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency,
|
||||
@NonNull final ExchangeCallback callback) {
|
||||
if (!baseCurrency.equals("EUR")) {
|
||||
callback.onError(new IllegalArgumentException("Only EUR supported as base"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (baseCurrency.equals(quoteCurrency)) {
|
||||
callback.onSuccess(new ExchangeRateImpl(quoteCurrency, 1.0, new Date()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (fetchDate != null) { // we have data
|
||||
boolean useCache = false;
|
||||
// figure out if we can use the cached values
|
||||
// data is daily and is refreshed around 16:00 CET every working day
|
||||
Calendar now = Calendar.getInstance(TimeZone.getTimeZone("CET"));
|
||||
|
||||
int fetchWeekday = fetchDate.get(Calendar.DAY_OF_WEEK);
|
||||
int fetchDay = fetchDate.get(Calendar.DAY_OF_YEAR);
|
||||
int fetchHour = fetchDate.get(Calendar.HOUR_OF_DAY);
|
||||
|
||||
int today = now.get(Calendar.DAY_OF_YEAR);
|
||||
int nowHour = now.get(Calendar.HOUR_OF_DAY);
|
||||
|
||||
if (
|
||||
// was it fetched today before 16:00? assume no new data iff now < 16:00 as well
|
||||
((today == fetchDay) && (fetchHour < 16) && (nowHour < 16))
|
||||
// was it fetched after, 17:00? we can assume there is no newer data
|
||||
|| ((today == fetchDay) && (fetchHour > 17))
|
||||
|| ((today == fetchDay + 1) && (fetchHour > 17) && (nowHour < 16))
|
||||
// is the data itself from today? there can be no newer data
|
||||
|| (fxDate.get(Calendar.DAY_OF_YEAR) == today)
|
||||
// was it fetched Sat/Sun? we can assume there is no newer data
|
||||
|| ((fetchWeekday == Calendar.SATURDAY) || (fetchWeekday == Calendar.SUNDAY))
|
||||
) { // return cached rate
|
||||
try {
|
||||
callback.onSuccess(getRate(quoteCurrency));
|
||||
} catch (ExchangeException ex) {
|
||||
callback.onError(ex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final Request httpRequest = createHttpRequest(baseUrl);
|
||||
|
||||
okHttpClient.newCall(httpRequest).enqueue(new okhttp3.Callback() {
|
||||
@Override
|
||||
public void onFailure(final Call call, final IOException ex) {
|
||||
callback.onError(ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(final Call call, final Response response) throws IOException {
|
||||
if (response.isSuccessful()) {
|
||||
try {
|
||||
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
|
||||
Document doc = dBuilder.parse(response.body().byteStream());
|
||||
doc.getDocumentElement().normalize();
|
||||
parse(doc);
|
||||
try {
|
||||
callback.onSuccess(getRate(quoteCurrency));
|
||||
} catch (ExchangeException ex) {
|
||||
callback.onError(ex);
|
||||
}
|
||||
} catch (ParserConfigurationException | SAXException ex) {
|
||||
Timber.w(ex);
|
||||
callback.onError(new ExchangeException(ex.getLocalizedMessage()));
|
||||
}
|
||||
} else {
|
||||
callback.onError(new ExchangeException(response.code(), response.message()));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Request createHttpRequest(final HttpUrl url) {
|
||||
return new Request.Builder()
|
||||
.url(url)
|
||||
.get()
|
||||
.build();
|
||||
}
|
||||
|
||||
final private Map<String, Double> fxEntries = new HashMap<>();
|
||||
private Calendar fxDate = null;
|
||||
private Calendar fetchDate = null;
|
||||
|
||||
synchronized private ExchangeRate getRate(String currency) throws ExchangeException {
|
||||
Timber.e("Getting %s", currency);
|
||||
final Double rate = fxEntries.get(currency);
|
||||
if (rate == null) throw new ExchangeException(404, "Currency not supported: " + currency);
|
||||
return new ExchangeRateImpl(currency, rate, fxDate.getTime());
|
||||
}
|
||||
|
||||
private final static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
|
||||
|
||||
{
|
||||
DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
private void parse(final Document xmlRootDoc) {
|
||||
final Map<String, Double> entries = new HashMap<>();
|
||||
Calendar date = Calendar.getInstance(TimeZone.getTimeZone("CET"));
|
||||
try {
|
||||
NodeList cubes = xmlRootDoc.getElementsByTagName("Cube");
|
||||
for (int i = 0; i < cubes.getLength(); i++) {
|
||||
Node node = cubes.item(i);
|
||||
if (node.getNodeType() == Node.ELEMENT_NODE) {
|
||||
Element cube = (Element) node;
|
||||
if (cube.hasAttribute("time")) { // a time Cube
|
||||
final Date time = DATE_FORMAT.parse(cube.getAttribute("time"));
|
||||
date.setTime(time);
|
||||
} else if (cube.hasAttribute("currency")
|
||||
&& cube.hasAttribute("rate")) { // a rate Cube
|
||||
String currency = cube.getAttribute("currency");
|
||||
double rate = Double.valueOf(cube.getAttribute("rate"));
|
||||
entries.put(currency, rate);
|
||||
} // else an empty Cube - ignore
|
||||
}
|
||||
}
|
||||
} catch (ParseException ex) {
|
||||
Timber.d(ex);
|
||||
}
|
||||
synchronized (this) {
|
||||
if (date != null) {
|
||||
fetchDate = Calendar.getInstance(TimeZone.getTimeZone("CET"));
|
||||
fxDate = date;
|
||||
fxEntries.clear();
|
||||
fxEntries.putAll(entries);
|
||||
}
|
||||
// else don't change what we have
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2019 m2049r et al.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.m2049r.xmrwallet.service.exchange.ecb;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
class ExchangeRateImpl implements ExchangeRate {
|
||||
private final Date date;
|
||||
private final String baseCurrency = "EUR";
|
||||
private final String quoteCurrency;
|
||||
private final double rate;
|
||||
|
||||
@Override
|
||||
public String getServiceName() {
|
||||
return "ecb.europa.eu";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBaseCurrency() {
|
||||
return baseCurrency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQuoteCurrency() {
|
||||
return quoteCurrency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getRate() {
|
||||
return rate;
|
||||
}
|
||||
|
||||
ExchangeRateImpl(@NonNull final String quoteCurrency, double rate, @NonNull final Date date) {
|
||||
super();
|
||||
this.quoteCurrency = quoteCurrency;
|
||||
this.rate = rate;
|
||||
this.date = date;
|
||||
}
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018 m2049r et al.
|
||||
* Copyright (c) 2017-2019 m2049r et al.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.m2049r.xmrwallet.service.exchange.coinmarketcap;
|
||||
package com.m2049r.xmrwallet.service.exchange.kraken;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
@@ -36,9 +36,9 @@ import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ExchangeApiImpl implements ExchangeApi {
|
||||
static final String CRYPTO_ID = "328";
|
||||
|
||||
@NonNull
|
||||
private final OkHttpClient okHttpClient;
|
||||
@@ -47,14 +47,13 @@ public class ExchangeApiImpl implements ExchangeApi {
|
||||
|
||||
//so we can inject the mockserver url
|
||||
@VisibleForTesting
|
||||
ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient, final HttpUrl baseUrl) {
|
||||
|
||||
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient, final HttpUrl baseUrl) {
|
||||
this.okHttpClient = okHttpClient;
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient) {
|
||||
this(okHttpClient, HttpUrl.parse("https://api.coinmarketcap.com/v2/ticker/"));
|
||||
this(okHttpClient, HttpUrl.parse("https://api.kraken.com/0/public/Ticker"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,29 +65,25 @@ public class ExchangeApiImpl implements ExchangeApi {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean inverse = false;
|
||||
String fiat = null;
|
||||
boolean invertQuery;
|
||||
|
||||
if (baseCurrency.equals(Helper.CRYPTO)) {
|
||||
fiat = quoteCurrency;
|
||||
inverse = false;
|
||||
}
|
||||
|
||||
if (quoteCurrency.equals(Helper.CRYPTO)) {
|
||||
fiat = baseCurrency;
|
||||
inverse = true;
|
||||
}
|
||||
|
||||
if (fiat == null) {
|
||||
callback.onError(new IllegalArgumentException("no fiat specified"));
|
||||
if (Helper.BASE_CRYPTO.equals(baseCurrency)) {
|
||||
invertQuery = false;
|
||||
} else if (Helper.BASE_CRYPTO.equals(quoteCurrency)) {
|
||||
invertQuery = true;
|
||||
} else {
|
||||
callback.onError(new IllegalArgumentException("no crypto specified"));
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean swapAssets = inverse;
|
||||
Timber.d("queryExchangeRate: i %b, b %s, q %s", invertQuery, baseCurrency, quoteCurrency);
|
||||
final boolean invert = invertQuery;
|
||||
final String base = invert ? quoteCurrency : baseCurrency;
|
||||
final String quote = invert ? baseCurrency : quoteCurrency;
|
||||
|
||||
final HttpUrl url = baseUrl.newBuilder()
|
||||
.addEncodedPathSegments(CRYPTO_ID + "/")
|
||||
.addQueryParameter("convert", fiat)
|
||||
.addQueryParameter("pair", base + (quote.equals("BTC") ? "XBT" : quote))
|
||||
.build();
|
||||
|
||||
final Request httpRequest = createHttpRequest(url);
|
||||
@@ -104,13 +99,13 @@ public class ExchangeApiImpl implements ExchangeApi {
|
||||
if (response.isSuccessful()) {
|
||||
try {
|
||||
final JSONObject json = new JSONObject(response.body().string());
|
||||
final JSONObject metadata = json.getJSONObject("metadata");
|
||||
if (!metadata.isNull("error")) {
|
||||
final String errorMsg = metadata.getString("error");
|
||||
final JSONArray jsonError = json.getJSONArray("error");
|
||||
if (jsonError.length() > 0) {
|
||||
final String errorMsg = jsonError.getString(0);
|
||||
callback.onError(new ExchangeException(response.code(), errorMsg));
|
||||
} else {
|
||||
final JSONObject jsonResult = json.getJSONObject("data");
|
||||
reportSuccess(jsonResult, swapAssets, callback);
|
||||
final JSONObject jsonResult = json.getJSONObject("result");
|
||||
reportSuccess(jsonResult, invert, callback);
|
||||
}
|
||||
} catch (JSONException ex) {
|
||||
callback.onError(new ExchangeException(ex.getLocalizedMessage()));
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2018 m2049r et al.
|
||||
* Copyright (c) 2017 m2049r et al.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.m2049r.xmrwallet.service.exchange.coinmarketcap;
|
||||
package com.m2049r.xmrwallet.service.exchange.kraken;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
@@ -25,7 +25,6 @@ import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -38,7 +37,7 @@ class ExchangeRateImpl implements ExchangeRate {
|
||||
|
||||
@Override
|
||||
public String getServiceName() {
|
||||
return "coinmarketcap.com";
|
||||
return "kraken.com";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -65,21 +64,29 @@ class ExchangeRateImpl implements ExchangeRate {
|
||||
|
||||
ExchangeRateImpl(final JSONObject jsonObject, final boolean swapAssets) throws JSONException, ExchangeException {
|
||||
try {
|
||||
final String baseC = jsonObject.getString("symbol");
|
||||
final JSONObject quotes = jsonObject.getJSONObject("quotes");
|
||||
final Iterator<String> keys = quotes.keys();
|
||||
String key = null;
|
||||
// get key which is not USD unless it is the only one
|
||||
while (keys.hasNext()) {
|
||||
key = keys.next();
|
||||
if (!key.equals("USD")) break;
|
||||
final String key = jsonObject.keys().next(); // we expect only one
|
||||
Pattern pattern = Pattern.compile("^X(.*?)Z(.*?)$");
|
||||
Matcher matcher = pattern.matcher(key);
|
||||
if (matcher.find()) {
|
||||
baseCurrency = swapAssets ? matcher.group(2) : matcher.group(1);
|
||||
quoteCurrency = swapAssets ? matcher.group(1) : matcher.group(2);
|
||||
} else {
|
||||
throw new ExchangeException("no pair returned!");
|
||||
}
|
||||
|
||||
JSONObject pair = jsonObject.getJSONObject(key);
|
||||
JSONArray close = pair.getJSONArray("c");
|
||||
String closePrice = close.getString(0);
|
||||
if (closePrice != null) {
|
||||
try {
|
||||
double rate = Double.parseDouble(closePrice);
|
||||
this.rate = swapAssets ? (1 / rate) : rate;
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new ExchangeException(ex.getLocalizedMessage());
|
||||
}
|
||||
} else {
|
||||
throw new ExchangeException("no close price returned!");
|
||||
}
|
||||
final String quoteC = key;
|
||||
baseCurrency = swapAssets ? quoteC : baseC;
|
||||
quoteCurrency = swapAssets ? baseC : quoteC;
|
||||
JSONObject quote = quotes.getJSONObject(key);
|
||||
double price = quote.getDouble("price");
|
||||
this.rate = swapAssets ? (1d / price) : price;
|
||||
} catch (NoSuchElementException ex) {
|
||||
throw new ExchangeException(ex.getLocalizedMessage());
|
||||
}
|
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) 2019 m2049r@monerujo.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// https://developer.android.com/training/basics/network-ops/xml
|
||||
|
||||
package com.m2049r.xmrwallet.service.exchange.krakenEcb;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
|
||||
import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
|
||||
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import timber.log.Timber;
|
||||
|
||||
/*
|
||||
Gets the XMR/EUR rate from kraken and then gets the EUR/fiat rate from the ECB
|
||||
*/
|
||||
|
||||
public class ExchangeApiImpl implements ExchangeApi {
|
||||
static public final String BASE_FIAT = "EUR";
|
||||
|
||||
@NonNull
|
||||
private final OkHttpClient okHttpClient;
|
||||
|
||||
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient) {
|
||||
this.okHttpClient = okHttpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency,
|
||||
@NonNull final ExchangeCallback callback) {
|
||||
Timber.d("B=%s Q=%s", baseCurrency, quoteCurrency);
|
||||
if (baseCurrency.equals(quoteCurrency)) {
|
||||
Timber.d("BASE=QUOTE=1");
|
||||
callback.onSuccess(new ExchangeRateImpl(baseCurrency, quoteCurrency, 1.0));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Helper.BASE_CRYPTO.equals(baseCurrency)
|
||||
&& !Helper.BASE_CRYPTO.equals(quoteCurrency)) {
|
||||
callback.onError(new IllegalArgumentException("no " + Helper.BASE_CRYPTO + " specified"));
|
||||
return;
|
||||
}
|
||||
|
||||
final String quote = Helper.BASE_CRYPTO.equals(baseCurrency) ? quoteCurrency : baseCurrency;
|
||||
|
||||
final ExchangeApi krakenApi =
|
||||
new com.m2049r.xmrwallet.service.exchange.kraken.ExchangeApiImpl(okHttpClient);
|
||||
krakenApi.queryExchangeRate(Helper.BASE_CRYPTO, BASE_FIAT, new ExchangeCallback() {
|
||||
@Override
|
||||
public void onSuccess(final ExchangeRate krakenRate) {
|
||||
Timber.d("kraken = %f", krakenRate.getRate());
|
||||
final ExchangeApi ecbApi =
|
||||
new com.m2049r.xmrwallet.service.exchange.ecb.ExchangeApiImpl(okHttpClient);
|
||||
ecbApi.queryExchangeRate(BASE_FIAT, quote, new ExchangeCallback() {
|
||||
@Override
|
||||
public void onSuccess(final ExchangeRate ecbRate) {
|
||||
Timber.d("ECB = %f", ecbRate.getRate());
|
||||
double rate = ecbRate.getRate() * krakenRate.getRate();
|
||||
Timber.d("Q=%s QC=%s", quote, quoteCurrency);
|
||||
if (!quote.equals(quoteCurrency)) rate = 1.0d / rate;
|
||||
Timber.d("rate = %f", rate);
|
||||
final ExchangeRate exchangeRate =
|
||||
new ExchangeRateImpl(baseCurrency, quoteCurrency, rate);
|
||||
callback.onSuccess(exchangeRate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception ex) {
|
||||
Timber.d(ex);
|
||||
callback.onError(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception ex) {
|
||||
Timber.d(ex);
|
||||
callback.onError(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2019 m2049r et al.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.m2049r.xmrwallet.service.exchange.krakenEcb;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
class ExchangeRateImpl implements ExchangeRate {
|
||||
private final String baseCurrency;
|
||||
private final String quoteCurrency;
|
||||
private final double rate;
|
||||
|
||||
@Override
|
||||
public String getServiceName() {
|
||||
return "kraken+ecb";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBaseCurrency() {
|
||||
return baseCurrency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQuoteCurrency() {
|
||||
return quoteCurrency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getRate() {
|
||||
return rate;
|
||||
}
|
||||
|
||||
ExchangeRateImpl(@NonNull final String baseCurrency, @NonNull final String quoteCurrency, double rate) {
|
||||
super();
|
||||
this.baseCurrency = baseCurrency;
|
||||
this.quoteCurrency = quoteCurrency;
|
||||
this.rate = rate;
|
||||
}
|
||||
}
|
@@ -58,7 +58,6 @@ import android.widget.TextView;
|
||||
import com.m2049r.xmrwallet.BuildConfig;
|
||||
import com.m2049r.xmrwallet.R;
|
||||
import com.m2049r.xmrwallet.model.NetworkType;
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
|
||||
|
||||
@@ -85,7 +84,7 @@ public class Helper {
|
||||
|
||||
static public final String NOCRAZYPASS_FLAGFILE = ".nocrazypass";
|
||||
|
||||
static public final String CRYPTO = "XMR";
|
||||
static public final String BASE_CRYPTO = "XMR";
|
||||
|
||||
static private final String WALLET_DIR = "monerujo" + FLAVOR_SUFFIX;
|
||||
static private final String HOME_DIR = "monero" + FLAVOR_SUFFIX;
|
||||
@@ -207,22 +206,32 @@ public class Helper {
|
||||
return d.toPlainString();
|
||||
}
|
||||
|
||||
static public String getFormattedAmount(double amount, boolean isXmr) {
|
||||
static public String getFormattedAmount(double amount, boolean isCrypto) {
|
||||
// at this point selection is XMR in case of error
|
||||
String displayB;
|
||||
if (isXmr) { // XMR
|
||||
long xmr = Wallet.getAmountFromDouble(amount);
|
||||
if ((xmr > 0) || (amount == 0)) {
|
||||
if (isCrypto) {
|
||||
if ((amount >= 0) || (amount == 0)) {
|
||||
displayB = String.format(Locale.US, "%,.5f", amount);
|
||||
} else {
|
||||
displayB = null;
|
||||
}
|
||||
} else { // not XMR
|
||||
} else { // not crypto
|
||||
displayB = String.format(Locale.US, "%,.2f", amount);
|
||||
}
|
||||
return displayB;
|
||||
}
|
||||
|
||||
// min 2 significant digits after decimal point
|
||||
static public String getFormattedAmount(double amount) {
|
||||
if ((amount >= 1.0d) || (amount == 0))
|
||||
return String.format(Locale.US, "%,.2f", amount);
|
||||
else { // amount < 1
|
||||
int decimals = 1 - (int) Math.floor(Math.log10(amount));
|
||||
if (decimals > 12) decimals = 12;
|
||||
return String.format(Locale.US, "%,." + decimals + "f", amount);
|
||||
}
|
||||
}
|
||||
|
||||
static public Bitmap getBitmap(Context context, int drawableId) {
|
||||
Drawable drawable = ContextCompat.getDrawable(context, drawableId);
|
||||
if (drawable instanceof BitmapDrawable) {
|
||||
@@ -624,7 +633,7 @@ public class Helper {
|
||||
}
|
||||
|
||||
static public ExchangeApi getExchangeApi() {
|
||||
return new com.m2049r.xmrwallet.service.exchange.coinmarketcap.ExchangeApiImpl(OkHttpHelper.getOkHttpClient());
|
||||
return new com.m2049r.xmrwallet.service.exchange.krakenEcb.ExchangeApiImpl(OkHttpHelper.getOkHttpClient());
|
||||
}
|
||||
|
||||
public interface Action {
|
||||
|
@@ -104,6 +104,11 @@ public class RestoreHeight {
|
||||
blockheight.put("2019-04-01", 1803081L);
|
||||
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) {
|
||||
|
@@ -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
@@ -44,22 +44,16 @@ 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.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
// TODO combine this with ExchangeTextView
|
||||
|
||||
public class ExchangeView extends LinearLayout
|
||||
implements NumberPadView.NumberPadListener {
|
||||
|
||||
public void enableSoftKeyboard(final boolean isEnabled) {
|
||||
etAmount.getEditText().setShowSoftInputOnFocus(isEnabled);
|
||||
}
|
||||
|
||||
public boolean focus() {
|
||||
return etAmount.requestFocus();
|
||||
}
|
||||
public class ExchangeView extends LinearLayout {
|
||||
String xmrAmount = null;
|
||||
String notXmrAmount = null;
|
||||
|
||||
public void enable(boolean enable) {
|
||||
etAmount.setEnabled(enable);
|
||||
@@ -67,9 +61,6 @@ public class ExchangeView extends LinearLayout
|
||||
sCurrencyB.setEnabled(enable);
|
||||
}
|
||||
|
||||
String xmrAmount = null;
|
||||
String notXmrAmount = null;
|
||||
|
||||
void setXmr(String xmr) {
|
||||
xmrAmount = xmr;
|
||||
if (onNewAmountListener != null) {
|
||||
@@ -159,6 +150,15 @@ public class ExchangeView extends LinearLayout
|
||||
inflater.inflate(R.layout.view_exchange, this);
|
||||
}
|
||||
|
||||
void setCurrencyAdapter(Spinner spinner) {
|
||||
List<String> currencies = new ArrayList<>();
|
||||
currencies.add(Helper.BASE_CRYPTO);
|
||||
currencies.addAll(Arrays.asList(getResources().getStringArray(R.array.currency)));
|
||||
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, currencies);
|
||||
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinner.setAdapter(spinnerAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
@@ -173,6 +173,9 @@ public class ExchangeView extends LinearLayout
|
||||
evExchange = findViewById(R.id.evExchange);
|
||||
pbExchange = findViewById(R.id.pbExchange);
|
||||
|
||||
setCurrencyAdapter(sCurrencyA);
|
||||
setCurrencyAdapter(sCurrencyB);
|
||||
|
||||
// make progress circle gray
|
||||
pbExchange.getIndeterminateDrawable().
|
||||
setColorFilter(getResources().getColor(R.color.trafficGray),
|
||||
@@ -482,31 +485,4 @@ public class ExchangeView extends LinearLayout
|
||||
public void setOnFailedExchangeListener(OnFailedExchangeListener listener) {
|
||||
onFailedExchangeListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDigitPressed(final int digit) {
|
||||
etAmount.getEditText().append(String.valueOf(digit));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPointPressed() {
|
||||
//TODO locale?
|
||||
if (etAmount.getEditText().getText().toString().indexOf('.') == -1) {
|
||||
etAmount.getEditText().append(".");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackSpacePressed() {
|
||||
Editable editable = etAmount.getEditText().getText();
|
||||
int length = editable.length();
|
||||
if (length > 0) {
|
||||
editable.delete(length - 1, length);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClearAll() {
|
||||
etAmount.getEditText().getText().clear();
|
||||
}
|
||||
}
|
||||
|
@@ -1,87 +0,0 @@
|
||||
package com.m2049r.xmrwallet.widget;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import com.m2049r.xmrwallet.R;
|
||||
|
||||
public class NumberPadView extends LinearLayout
|
||||
implements View.OnClickListener, View.OnLongClickListener {
|
||||
|
||||
@Override
|
||||
public void onClick(final View view) {
|
||||
if (listener == null) {
|
||||
throw new IllegalArgumentException("NumberPadListener has to be set, use setListener() to set it.");
|
||||
}
|
||||
switch (view.getId()) {
|
||||
case R.id.numberPadPoint:
|
||||
listener.onPointPressed();
|
||||
break;
|
||||
case R.id.numberPadBackSpace:
|
||||
listener.onBackSpacePressed();
|
||||
break;
|
||||
default:
|
||||
if (view.getTag() != null) {
|
||||
listener.onDigitPressed(Integer.parseInt(view.getTag().toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(final View view) {
|
||||
if (view.getId() == R.id.numberPadBackSpace) {
|
||||
listener.onClearAll();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setListener(final NumberPadListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public interface NumberPadListener {
|
||||
void onDigitPressed(final int digit);
|
||||
|
||||
void onBackSpacePressed();
|
||||
|
||||
void onPointPressed();
|
||||
|
||||
void onClearAll();
|
||||
}
|
||||
|
||||
private NumberPadListener listener;
|
||||
|
||||
public NumberPadView(final Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public NumberPadView(final Context context,
|
||||
@Nullable final AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public NumberPadView(final Context context, @Nullable final AttributeSet attrs,
|
||||
final int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
final View view = View.inflate(context, R.layout.view_number_pad, this);
|
||||
setOrientation(VERTICAL);
|
||||
view.findViewById(R.id.numberPad0).setOnClickListener(this);
|
||||
view.findViewById(R.id.numberPad1).setOnClickListener(this);
|
||||
view.findViewById(R.id.numberPad2).setOnClickListener(this);
|
||||
view.findViewById(R.id.numberPad3).setOnClickListener(this);
|
||||
view.findViewById(R.id.numberPad4).setOnClickListener(this);
|
||||
view.findViewById(R.id.numberPad5).setOnClickListener(this);
|
||||
view.findViewById(R.id.numberPad6).setOnClickListener(this);
|
||||
view.findViewById(R.id.numberPad7).setOnClickListener(this);
|
||||
view.findViewById(R.id.numberPad8).setOnClickListener(this);
|
||||
view.findViewById(R.id.numberPad9).setOnClickListener(this);
|
||||
view.findViewById(R.id.numberPadPoint).setOnClickListener(this);
|
||||
view.findViewById(R.id.numberPadBackSpace).setOnClickListener(this);
|
||||
view.findViewById(R.id.numberPadBackSpace).setOnLongClickListener(this);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user