1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-04 17:28:42 +02:00

Compare commits

...

22 Commits

Author SHA1 Message Date
m2049r
e076c19e3e v1.12.9 2019-11-21 10:42:45 +01:00
m2049r
35b717756d Fix amount bugs (#645)
* fix rounding error on send

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

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

* password entry on own line

* remove errors when typing; field spacing
2019-11-17 10:31:19 +01:00
m2049r
87d9a8cd95 use kraken for EURXMR exchange rate (#637)
combine with ECB rates for other fiat conversions
2019-11-17 00:42:57 +01:00
m2049r
f637d7f617 update block heights (#636) 2019-11-10 23:54:40 +01:00
m2049r
a4b9a7c6fb fix spend amount & new version (#635) 2019-11-10 15:23:33 +01:00
m2049r
9f01155cb7 v1.12.1 2019-11-10 12:14:52 +01:00
m2049r
08e8a48138 scan only for 0.15 nodes (#634) 2019-11-10 12:13:06 +01:00
m2049r
551c3b9fb6 scan only for 0.15 nodes (#633) 2019-11-10 12:11:01 +01:00
m2049r
2258cb7096 v1.12.0 (#632) 2019-11-10 11:08:19 +01:00
m2049r
6d61841cf3 textfields are filled (#631) 2019-11-10 11:06:34 +01:00
m2049r
c65508d288 upgrade to monero v0.15 (#630) 2019-11-09 22:45:54 +01:00
m2049r
2c3f582672 paste bip70 (#621) 2019-09-22 10:09:18 +02:00
m2049r
f46ba75771 update gradle version (#614) 2019-08-16 19:07:12 +02:00
m2049r
0ce5f2b6ca create ExchangeEditText & use without numpad (#613) 2019-08-04 13:19:42 +02:00
75 changed files with 1833 additions and 1470 deletions

View File

@@ -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

View File

@@ -7,8 +7,8 @@ android {
applicationId "com.m2049r.xmrwallet"
minSdkVersion 21
targetSdkVersion 28
versionCode 183
versionName "1.11.13 'Chernushka'"
versionCode 199
versionName "1.12.9 'Caerbannog'"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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());

View File

@@ -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");

View File

@@ -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();
}

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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);
}

View File

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

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2019 m2049r et al.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.service.exchange.ecb;
import android.support.annotation.NonNull;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import java.util.Date;
class ExchangeRateImpl implements ExchangeRate {
private final Date date;
private final String baseCurrency = "EUR";
private final String quoteCurrency;
private final double rate;
@Override
public String getServiceName() {
return "ecb.europa.eu";
}
@Override
public String getBaseCurrency() {
return baseCurrency;
}
@Override
public String getQuoteCurrency() {
return quoteCurrency;
}
@Override
public double getRate() {
return rate;
}
ExchangeRateImpl(@NonNull final String quoteCurrency, double rate, @NonNull final Date date) {
super();
this.quoteCurrency = quoteCurrency;
this.rate = rate;
this.date = date;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017-2018 m2049r et al.
* Copyright (c) 2017-2019 m2049r et al.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* 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()));

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017-2018 m2049r et al.
* Copyright (c) 2017 m2049r et al.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* 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());
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright (c) 2019 m2049r@monerujo.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// https://developer.android.com/training/basics/network-ops/xml
package com.m2049r.xmrwallet.service.exchange.krakenEcb;
import android.support.annotation.NonNull;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import com.m2049r.xmrwallet.util.Helper;
import okhttp3.OkHttpClient;
import timber.log.Timber;
/*
Gets the XMR/EUR rate from kraken and then gets the EUR/fiat rate from the ECB
*/
public class ExchangeApiImpl implements ExchangeApi {
static public final String BASE_FIAT = "EUR";
@NonNull
private final OkHttpClient okHttpClient;
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient) {
this.okHttpClient = okHttpClient;
}
@Override
public void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency,
@NonNull final ExchangeCallback callback) {
Timber.d("B=%s Q=%s", baseCurrency, quoteCurrency);
if (baseCurrency.equals(quoteCurrency)) {
Timber.d("BASE=QUOTE=1");
callback.onSuccess(new ExchangeRateImpl(baseCurrency, quoteCurrency, 1.0));
return;
}
if (!Helper.BASE_CRYPTO.equals(baseCurrency)
&& !Helper.BASE_CRYPTO.equals(quoteCurrency)) {
callback.onError(new IllegalArgumentException("no " + Helper.BASE_CRYPTO + " specified"));
return;
}
final String quote = Helper.BASE_CRYPTO.equals(baseCurrency) ? quoteCurrency : baseCurrency;
final ExchangeApi krakenApi =
new com.m2049r.xmrwallet.service.exchange.kraken.ExchangeApiImpl(okHttpClient);
krakenApi.queryExchangeRate(Helper.BASE_CRYPTO, BASE_FIAT, new ExchangeCallback() {
@Override
public void onSuccess(final ExchangeRate krakenRate) {
Timber.d("kraken = %f", krakenRate.getRate());
final ExchangeApi ecbApi =
new com.m2049r.xmrwallet.service.exchange.ecb.ExchangeApiImpl(okHttpClient);
ecbApi.queryExchangeRate(BASE_FIAT, quote, new ExchangeCallback() {
@Override
public void onSuccess(final ExchangeRate ecbRate) {
Timber.d("ECB = %f", ecbRate.getRate());
double rate = ecbRate.getRate() * krakenRate.getRate();
Timber.d("Q=%s QC=%s", quote, quoteCurrency);
if (!quote.equals(quoteCurrency)) rate = 1.0d / rate;
Timber.d("rate = %f", rate);
final ExchangeRate exchangeRate =
new ExchangeRateImpl(baseCurrency, quoteCurrency, rate);
callback.onSuccess(exchangeRate);
}
@Override
public void onError(Exception ex) {
Timber.d(ex);
callback.onError(ex);
}
});
}
@Override
public void onError(Exception ex) {
Timber.d(ex);
callback.onError(ex);
}
});
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) 2019 m2049r et al.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.service.exchange.krakenEcb;
import android.support.annotation.NonNull;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import java.util.Date;
class ExchangeRateImpl implements ExchangeRate {
private final String baseCurrency;
private final String quoteCurrency;
private final double rate;
@Override
public String getServiceName() {
return "kraken+ecb";
}
@Override
public String getBaseCurrency() {
return baseCurrency;
}
@Override
public String getQuoteCurrency() {
return quoteCurrency;
}
@Override
public double getRate() {
return rate;
}
ExchangeRateImpl(@NonNull final String baseCurrency, @NonNull final String quoteCurrency, double rate) {
super();
this.baseCurrency = baseCurrency;
this.quoteCurrency = quoteCurrency;
this.rate = rate;
}
}

View File

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

View File

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

View File

@@ -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

View File

@@ -0,0 +1,196 @@
/*
* Copyright (c) 2017-2019 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// based on https://code.tutsplus.com/tutorials/creating-compound-views-on-android--cms-22889
package com.m2049r.xmrwallet.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.widget.Spinner;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import com.m2049r.xmrwallet.util.Helper;
import java.util.ArrayList;
import java.util.List;
import timber.log.Timber;
public class ExchangeOtherEditText extends ExchangeEditText {
/*
all exchanges are done through XMR
baseCurrency is the native currency
*/
String baseCurrency = null; // not XMR
private double exchangeRate = 0; // baseCurrency to XMR
public void setExchangeRate(double rate) {
exchangeRate = rate;
post(new Runnable() {
@Override
public void run() {
startExchange();
}
});
}
private void setBaseCurrency(Context context, AttributeSet attrs) {
TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ExchangeEditText, 0, 0);
try {
baseCurrency = ta.getString(R.styleable.ExchangeEditText_baseSymbol);
if (baseCurrency == null)
throw new IllegalArgumentException("base currency must be set");
} finally {
ta.recycle();
}
}
public ExchangeOtherEditText(Context context, AttributeSet attrs) {
super(context, attrs);
setBaseCurrency(context, attrs);
}
public ExchangeOtherEditText(Context context,
AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
setBaseCurrency(context, attrs);
}
@Override
void setCurrencyAdapter(Spinner spinner) {
List<String> currencies = new ArrayList<>();
if (!baseCurrency.equals(Helper.BASE_CRYPTO)) currencies.add(baseCurrency);
currencies.add(Helper.BASE_CRYPTO);
setCurrencyAdapter(spinner, currencies);
}
@Override
void setInitialSpinnerSelections(Spinner baseSpinner, Spinner quoteSpinner) {
baseSpinner.setSelection(0, true);
quoteSpinner.setSelection(1, true);
}
private void localExchange(final String base, final String quote, final double rate) {
exchange(new ExchangeRate() {
@Override
public String getServiceName() {
return "Local";
}
@Override
public String getBaseCurrency() {
return base;
}
@Override
public String getQuoteCurrency() {
return quote;
}
@Override
public double getRate() {
return rate;
}
});
}
@Override
void execExchange(String currencyA, String currencyB) {
if (!currencyA.equals(baseCurrency) && !currencyB.equals(baseCurrency)) {
throw new IllegalStateException("I can only exchange " + baseCurrency);
}
showProgress();
Timber.d("execExchange(%s, %s)", currencyA, currencyB);
// first deal with XMR/baseCurrency & baseCurrency/XMR
if (currencyA.equals(Helper.BASE_CRYPTO) && (currencyB.equals(baseCurrency))) {
localExchange(currencyA, currencyB, 1.0d / exchangeRate);
return;
}
if (currencyA.equals(baseCurrency) && (currencyB.equals(Helper.BASE_CRYPTO))) {
localExchange(currencyA, currencyB, exchangeRate);
return;
}
// next, deal with XMR/baseCurrency
if (currencyA.equals(baseCurrency)) {
queryExchangeRate(Helper.BASE_CRYPTO, currencyB, exchangeRate, true);
} else {
queryExchangeRate(currencyA, Helper.BASE_CRYPTO, 1.0d / exchangeRate, false);
}
}
private void queryExchangeRate(final String base, final String quote, final double factor,
final boolean baseIsBaseCrypto) {
queryExchangeRate(base, quote,
new ExchangeCallback() {
@Override
public void onSuccess(final ExchangeRate exchangeRate) {
if (isAttachedToWindow())
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
ExchangeRate xchange = new ExchangeRate() {
@Override
public String getServiceName() {
return exchangeRate.getServiceName() + "+" + baseCurrency;
}
@Override
public String getBaseCurrency() {
return baseIsBaseCrypto ? baseCurrency : base;
}
@Override
public String getQuoteCurrency() {
return baseIsBaseCrypto ? quote : baseCurrency;
}
@Override
public double getRate() {
return exchangeRate.getRate() * factor;
}
};
exchange(xchange);
}
});
}
@Override
public void onError(final Exception e) {
Timber.e(e.getLocalizedMessage());
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
exchangeFailed();
}
});
}
});
}
}

View File

@@ -44,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();
}
}

View File

@@ -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);
}
}

View File

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

View File

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

View File

@@ -10,50 +10,43 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
<android.support.design.widget.TextInputLayout
android:id="@+id/etWalletName"
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="2">
android:layout_marginBottom="@dimen/header_top_first"
app:errorEnabled="true">
<android.support.design.widget.TextInputLayout
android:id="@+id/etWalletName"
android:layout_width="0dp"
<android.support.design.widget.TextInputEditText
style="@style/MoneroEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
app:counterEnabled="true"
app:counterMaxLength="20"
app:errorEnabled="true">
android:hint="@string/generate_name_hint"
android:imeOptions="actionNext"
android:inputType="text"
android:maxLines="1"
android:textAlignment="textStart" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputEditText
style="@style/MoneroEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/generate_name_hint"
android:imeOptions="actionNext"
android:inputType="text"
android:maxLines="1"
android:textAlignment="textStart" />
<android.support.design.widget.TextInputLayout
android:id="@+id/etWalletPassword"
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/header_top_first"
app:errorEnabled="true">
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputEditText
style="@style/MoneroEdit"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/generate_password_hint"
android:imeOptions="actionNext"
android:inputType="textVisiblePassword"
android:textAlignment="textStart" />
<android.support.design.widget.TextInputLayout
android:id="@+id/etWalletPassword"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<android.support.design.widget.TextInputEditText
style="@style/MoneroEdit"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/generate_password_hint"
android:imeOptions="actionNext"
android:inputType="textVisiblePassword"
android:textAlignment="textStart" />
</android.support.design.widget.TextInputLayout>
</LinearLayout>
</android.support.design.widget.TextInputLayout>
<LinearLayout
android:id="@+id/llFingerprintAuth"
@@ -78,8 +71,10 @@
<android.support.design.widget.TextInputLayout
android:id="@+id/etWalletMnemonic"
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/header_top_first"
android:visibility="gone"
app:errorEnabled="true">
@@ -95,10 +90,11 @@
<android.support.design.widget.TextInputLayout
android:id="@+id/etWalletAddress"
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/header_top_first"
android:visibility="gone"
app:counterEnabled="true"
app:counterMaxLength="95"
app:errorEnabled="true">
@@ -115,8 +111,10 @@
<android.support.design.widget.TextInputLayout
android:id="@+id/etWalletViewKey"
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/header_top_first"
android:visibility="gone"
app:counterEnabled="true"
app:counterMaxLength="64"
@@ -134,8 +132,10 @@
<android.support.design.widget.TextInputLayout
android:id="@+id/etWalletSpendKey"
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/header_top_first"
android:visibility="gone"
app:counterEnabled="true"
app:counterMaxLength="64"
@@ -153,8 +153,10 @@
<android.support.design.widget.TextInputLayout
android:id="@+id/etWalletRestoreHeight"
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/header_top_first"
android:visibility="gone"
app:errorEnabled="true">

View File

@@ -84,16 +84,17 @@
android:id="@+id/evAmount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:orientation="vertical" />
<android.support.design.widget.TextInputLayout
android:id="@+id/etNotes"
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
<android.support.design.widget.TextInputEditText
style="@style/MoneroEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -101,7 +102,6 @@
android:imeOptions="actionDone"
android:inputType="textMultiLine"
android:textAlignment="textStart" />
</android.support.design.widget.TextInputLayout>
<RelativeLayout
@@ -128,8 +128,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:drawablePadding="4dp"
android:drawableStart="@drawable/ic_info_outline_gray_24dp"
android:drawablePadding="4dp"
android:text="@string/label_receive_info_gen_qr_code"
android:textAlignment="center"
android:textSize="16sp"
@@ -149,8 +149,8 @@
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:drawablePadding="8dp"
android:drawableStart="@drawable/ic_nfc_black_24dp"
android:drawablePadding="8dp"
android:gravity="center"
android:text="@string/nfc_tag_tap"
android:visibility="visible" />

View File

@@ -29,6 +29,7 @@
android:orientation="horizontal">
<android.support.design.widget.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:id="@+id/etAddress"
android:layout_width="0dp"
android:layout_height="wrap_content"
@@ -75,7 +76,7 @@
android:drawableStart="@drawable/ic_check_gray_24dp"
android:drawablePadding="8dp"
android:gravity="center"
android:text="@string/info_paymentid_intergrated"
android:text="@string/info_paymentid_integrated"
android:textSize="18sp"
android:visibility="invisible" />
@@ -118,6 +119,7 @@
android:visibility="visible">
<android.support.design.widget.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:id="@+id/etPaymentId"
android:layout_width="0dp"
android:layout_height="wrap_content"
@@ -153,11 +155,12 @@
</FrameLayout>
<android.support.design.widget.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:id="@+id/etNotes"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
<android.support.design.widget.TextInputEditText
style="@style/MoneroEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -27,35 +28,19 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:paddingEnd="12dp"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:src="@drawable/ic_all_inclusive_24dp"
android:visibility="visible" />
</RelativeLayout>
<LinearLayout
android:id="@+id/llAmount"
<com.m2049r.xmrwallet.widget.ExchangeEditText
android:id="@+id/etAmount"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="visible">
<com.m2049r.xmrwallet.widget.ExchangeTextView
android:id="@+id/evAmount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginTop="16dp"
android:orientation="vertical" />
<com.m2049r.xmrwallet.widget.NumberPadView
android:id="@+id/numberPad"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:gravity="center" />
</LinearLayout>
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:orientation="vertical" />
<RelativeLayout
android:id="@+id/rlSweep"

View File

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

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -225,14 +226,20 @@
android:padding="8sp"
android:text="@string/tx_notes" />
<EditText
android:id="@+id/etTxNotes"
style="@style/MoneroEdit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="@color/moneroGray"
android:hint="@string/tx_notes_hint"
android:inputType="textMultiLine" />
<android.support.design.widget.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.design.widget.TextInputEditText
android:id="@+id/etTxNotes"
style="@style/MoneroEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/moneroGray"
android:hint="@string/tx_notes_hint"
android:inputType="textMultiLine" />
</android.support.design.widget.TextInputLayout>
</TableRow>
<TableRow>

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