Merge branch 'bugfix/pay-to-many-paymentids'

# Conflicts:
#	app/src/main/res/values/strings.xml
This commit is contained in:
pokkst 2023-04-26 10:09:03 -05:00
commit 2ff408e622
No known key found for this signature in database
GPG key ID: 90C2ED85E67A50FF
4 changed files with 155 additions and 56 deletions

View file

@ -4,6 +4,8 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -137,45 +139,19 @@ public class SendFragment extends Fragment {
}
});
sendMaxButton.setOnClickListener(view1 -> {
addOutputImageView.setVisibility(View.INVISIBLE);
boolean currentValue = mViewModel.sendingMax.getValue() != null ? mViewModel.sendingMax.getValue() : false;
mViewModel.setSendingMax(!currentValue);
mViewModel.setSendingMax(!isSendAll());
});
createButton.setOnClickListener(view1 -> {
boolean sendAll = mViewModel.sendingMax.getValue() != null ? mViewModel.sendingMax.getValue() : false;
ArrayList<Pair<String, String>> dests = new ArrayList<>();
for(int i = 0; i < getDestCount(); i++) {
ConstraintLayout entryView = getDestView(i);
EditText amountField = entryView.findViewById(R.id.amount_edittext);
EditText addressField = entryView.findViewById(R.id.address_edittext);
String amount = amountField.getText().toString().trim();
if(!sendAll) {
if(amount.isEmpty()) {
Toast.makeText(getActivity(), getString(R.string.send_amount_empty), Toast.LENGTH_SHORT).show();
return;
}
boolean outputsValid = checkDestsValidity(isSendAll());
long amountRaw = Wallet.getAmountFromString(amount);
long balance = BalanceService.getInstance().getUnlockedBalanceRaw();
if (amountRaw >= balance || amountRaw <= 0) {
Toast.makeText(getActivity(), getString(R.string.send_amount_invalid), Toast.LENGTH_SHORT).show();
return;
}
}
String address = addressField.getText().toString().trim();
boolean isValidAddress = Wallet.isAddressValid(address);
if(!isValidAddress) {
Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show();
return;
}
dests.add(new Pair<>(address, amount));
if(outputsValid) {
Toast.makeText(getActivity(), getString(R.string.creating_tx), Toast.LENGTH_SHORT).show();
createButton.setEnabled(false);
sendMaxButton.setEnabled(false);
createTx(getRawDests(), isSendAll(), priority);
} else {
Toast.makeText(getActivity(), getString(R.string.creating_tx_failed_invalid_outputs), Toast.LENGTH_SHORT).show();
}
Toast.makeText(getActivity(), getString(R.string.creating_tx), Toast.LENGTH_SHORT).show();
createButton.setEnabled(false);
sendMaxButton.setEnabled(false);
createTx(dests, sendAll, priority);
});
sendButton.setOnClickListener(view1 -> {
@ -188,21 +164,72 @@ public class SendFragment extends Fragment {
});
}
private boolean checkDestsValidity(boolean sendAll) {
List<Pair<String, String>> dests = getRawDests();
for(Pair<String, String> dest : dests) {
String address = dest.component1();
String amount = dest.component2();
if(!sendAll) {
if(amount.isEmpty()) {
Toast.makeText(getActivity(), getString(R.string.send_amount_empty), Toast.LENGTH_SHORT).show();
return false;
}
long amountRaw = Wallet.getAmountFromString(amount);
long balance = BalanceService.getInstance().getUnlockedBalanceRaw();
if (amountRaw >= balance || amountRaw <= 0) {
Toast.makeText(getActivity(), getString(R.string.send_amount_invalid), Toast.LENGTH_SHORT).show();
return false;
}
} else if(dests.size() > 1) {
Toast.makeText(getActivity(), getString(R.string.send_amount_invalid_sendall_paytomany), Toast.LENGTH_SHORT).show();
return false;
}
UriData uriData = UriData.parse(address);
boolean isValidAddress = uriData != null;
if(!isValidAddress) {
Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show();
return false;
}
if(dests.size() > 1 && uriData.hasPaymentId()) {
Toast.makeText(getActivity(), getString(R.string.paymentid_paytomany), Toast.LENGTH_SHORT).show();
return false;
}
}
return true;
}
private boolean destsHasPaymentId() {
List<Pair<String, String>> dests = getRawDests();
for(Pair<String, String> dest : dests) {
String address = dest.component1();
UriData uriData = UriData.parse(address);
if(uriData == null) return false;
if(uriData.hasPaymentId()) return true;
}
return false;
}
private void bindObservers() {
mViewModel.sendingMax.observe(getViewLifecycleOwner(), sendingMax -> {
if (mViewModel.pendingTransaction.getValue() == null) {
if (sendingMax) {
addOutputImageView.setVisibility(View.GONE);
prepareOutputsForMaxSend();
sendMaxButton.setText(getText(R.string.undo));
} else {
addOutputImageView.setVisibility(View.VISIBLE);
unprepareMaxSend();
sendMaxButton.setText(getText(R.string.send_max));
}
}
});
mViewModel.showAddOutputButton.observe(getViewLifecycleOwner(), show -> {
setAddOutputButtonVisibility((show && !destsHasPaymentId()) ? View.VISIBLE : View.INVISIBLE);
});
mViewModel.pendingTransaction.observe(getViewLifecycleOwner(), pendingTx -> {
showConfirmationLayout(pendingTx != null);
@ -220,7 +247,31 @@ public class SendFragment extends Fragment {
int index = getDestCount();
ConstraintLayout entryView = (ConstraintLayout)inflater.inflate(R.layout.transaction_output_item, null);
ImageButton removeOutputImageButton = entryView.findViewById(R.id.remove_output_imagebutton);
EditText addressField = entryView.findViewById(R.id.address_edittext);
addressField.addTextChangedListener(new TextWatcher() {
@Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void afterTextChanged(Editable editable) {
int currentOutputs = getDestCount();
UriData uriData = UriData.parse(editable.toString());
if(uriData != null) {
// we have valid address
boolean hasPaymentId = uriData.hasPaymentId();
if(currentOutputs > 1 && hasPaymentId) {
// multiple outputs when pasting/editing in integrated address. this is not allowed
Toast.makeText(getActivity(), getString(R.string.paymentid_paytomany), Toast.LENGTH_SHORT).show();
addressField.setText(null);
} else if(currentOutputs == 1 && hasPaymentId) {
// show add output button: we are sending to integrated address
mViewModel.setShowAddOutputButton(false);
}
} else if(currentOutputs == 1 && !isSendAll()) {
// when send-all is false and this is our only dest and address is invalid, then show add output button
mViewModel.setShowAddOutputButton(true);
}
}
});
entryView.findViewById(R.id.paste_amount_imagebutton).setOnClickListener(view1 -> {
Context ctx = getContext();
if (ctx != null) {
@ -261,6 +312,24 @@ public class SendFragment extends Fragment {
return destList.getChildCount();
}
private List<Pair<String, String>> getRawDests() {
ArrayList<Pair<String, String>> dests = new ArrayList<>();
for(int i = 0; i < getDestCount(); i++) {
ConstraintLayout entryView = getDestView(i);
EditText amountField = entryView.findViewById(R.id.amount_edittext);
EditText addressField = entryView.findViewById(R.id.address_edittext);
String amount = amountField.getText().toString().trim();
String address = addressField.getText().toString().trim();
dests.add(new Pair<>(address, amount));
}
return dests;
}
private boolean isSendAll() {
return mViewModel.sendingMax.getValue() != null ? mViewModel.sendingMax.getValue() : false;
}
private ConstraintLayout getDestView(int pos) {
return (ConstraintLayout) destList.getChildAt(pos);
}
@ -284,7 +353,7 @@ public class SendFragment extends Fragment {
private void showConfirmationLayout(boolean show) {
if (show) {
destList.setVisibility(View.GONE);
addOutputImageView.setVisibility(View.GONE);
setAddOutputButtonVisibility(View.GONE);
sendMaxButton.setVisibility(View.GONE);
createButton.setVisibility(View.GONE);
feeRadioGroup.setVisibility(View.GONE);
@ -296,7 +365,7 @@ public class SendFragment extends Fragment {
amountTextView.setVisibility(View.VISIBLE);
} else {
destList.setVisibility(View.VISIBLE);
addOutputImageView.setVisibility(View.VISIBLE);
setAddOutputButtonVisibility(View.VISIBLE);
sendMaxButton.setVisibility(View.VISIBLE);
createButton.setVisibility(View.VISIBLE);
feeRadioGroup.setVisibility(View.VISIBLE);
@ -322,31 +391,41 @@ public class SendFragment extends Fragment {
}
private void pasteAddress(ConstraintLayout entryView, String clipboard, boolean pastingAmount) {
if(pastingAmount) {
try {
Double.parseDouble(clipboard);
setAmount(entryView, clipboard);
} catch (Exception e) {
Toast.makeText(getActivity(), getString(R.string.send_amount_invalid), Toast.LENGTH_SHORT).show();
return;
}
}
UriData uriData = UriData.parse(clipboard);
if (uriData != null) {
int currentOutputs = getDestCount();
if(currentOutputs > 1 && uriData.hasPaymentId()) {
Toast.makeText(getActivity(), getString(R.string.paymentid_paytomany), Toast.LENGTH_SHORT).show();
return;
} else if(currentOutputs == 1 && uriData.hasPaymentId()) {
mViewModel.setShowAddOutputButton(false);
}
EditText addressField = entryView.findViewById(R.id.address_edittext);
addressField.setText(uriData.getAddress());
if (uriData.hasAmount()) {
sendMaxButton.setEnabled(false);
EditText amountField = entryView.findViewById(R.id.amount_edittext);
amountField.setText(uriData.getAmount());
setAmount(entryView, uriData.getAmount());
}
} else {
if(pastingAmount) {
try {
Double.parseDouble(clipboard);
sendMaxButton.setEnabled(false);
EditText amountField = entryView.findViewById(R.id.amount_edittext);
amountField.setText(clipboard);
} catch (Exception e) {
Toast.makeText(getActivity(), getString(R.string.send_amount_invalid), Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show();
}
Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show();
}
}
private void setAmount(ConstraintLayout entryView, String amount) {
sendMaxButton.setEnabled(false);
EditText amountField = entryView.findViewById(R.id.amount_edittext);
amountField.setText(amount);
}
private void createTx(List<Pair<String, String>> dests, boolean sendAll, PendingTransaction.Priority feePriority) {
((MoneroApplication)getActivity().getApplication()).getExecutor().execute(() -> {
try {
@ -393,4 +472,8 @@ public class SendFragment extends Fragment {
}
});
}
private void setAddOutputButtonVisibility(int visibility) {
addOutputImageView.setVisibility(visibility);
}
}

View file

@ -9,11 +9,18 @@ import net.mynero.wallet.model.PendingTransaction;
public class SendViewModel extends ViewModel {
private final MutableLiveData<Boolean> _sendingMax = new MutableLiveData<>(false);
public LiveData<Boolean> sendingMax = _sendingMax;
private final MutableLiveData<Boolean> _showAddOutputButton = new MutableLiveData<>(true);
public LiveData<Boolean> showAddOutputButton = _showAddOutputButton;
private final MutableLiveData<PendingTransaction> _pendingTransaction = new MutableLiveData<>(null);
public LiveData<PendingTransaction> pendingTransaction = _pendingTransaction;
public void setSendingMax(boolean value) {
_sendingMax.setValue(value);
setShowAddOutputButton(!value);
}
public void setShowAddOutputButton(boolean value) {
_showAddOutputButton.setValue(value);
}
public void setPendingTransaction(PendingTransaction pendingTx) {

View file

@ -1,6 +1,7 @@
package net.mynero.wallet.util;
import net.mynero.wallet.model.Wallet;
import net.mynero.wallet.model.WalletManager;
import java.util.HashMap;
@ -46,6 +47,10 @@ public class UriData {
return address;
}
public boolean hasPaymentId() {
return !Wallet.getPaymentIdFromAddress(this.address, WalletManager.getInstance().getWallet().nettype()).isEmpty();
}
public String getAmount() {
String txAmount = params.get(Constants.URI_ARG_AMOUNT);
if (txAmount == null) {

View file

@ -5,6 +5,8 @@
<string name="bad_password">Incorrect password!</string>
<string name="send_address_invalid">Not a valid address</string>
<string name="send_amount_invalid">Not a valid amount</string>
<string name="send_amount_invalid_sendall_paytomany">Cannot send-all in pay-to-many</string>
<string name="tx_list_failed_text">failed</string>
<string name="tx_list_amount_negative">- %1$s</string>
@ -24,7 +26,6 @@
<string name="wallet_locked_balance_text">+ %1$s confirming</string>
<string name="send_amount_empty">Please enter an amount</string>
<string name="send_amount_invalid">Please enter a valid amount</string>
<string name="send_max">Max</string>
<string name="undo">Undo</string>
<string name="error_sending_tx">Error sending transaction</string>
@ -61,6 +62,7 @@
<string name="tx_fee_text">Fee: %1$s XMR</string>
<string name="receive">Receive</string>
<string name="creating_tx">Creating transaction…</string>
<string name="creating_tx_failed_invalid_outputs">Invalid destination combination</string>
<string name="sending_tx">Sending transaction…</string>
<string name="sent_tx">Sent transaction!</string>
<string name="no_camera_permission">No camera permission</string>
@ -124,4 +126,6 @@
<string name="auth">[ auth ]</string>
<string name="to">To</string>
<string name="max_outputs_allowed">Maximum allowed outputs</string>
<string name="paymentid_paytomany">Cannot send to integrated addresses in a pay-to-many transaction</string>
</resources>