diff --git a/app/build.gradle b/app/build.gradle
index b03d3d4..be9bf7e 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -8,8 +8,8 @@ android {
applicationId "com.m2049r.xmrwallet"
minSdkVersion 21
targetSdkVersion 25
- versionCode 51
- versionName "1.2.11"
+ versionCode 62
+ versionName "1.3.2 'Satoshis Dream'"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
diff --git a/app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java b/app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java
index 09430e3..52d51c7 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java
@@ -46,6 +46,7 @@ import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper;
@@ -304,14 +305,14 @@ public class ReceiveFragment extends Fragment {
return;
}
StringBuffer sb = new StringBuffer();
- sb.append(ScannerFragment.QR_SCHEME).append(address);
+ sb.append(BarcodeData.XMR_SCHEME).append(address);
boolean first = true;
if (!paymentId.isEmpty()) {
if (first) {
sb.append("?");
first = false;
}
- sb.append(ScannerFragment.QR_PAYMENTID).append('=').append(paymentId);
+ sb.append(BarcodeData.XMR_PAYMENTID).append('=').append(paymentId);
}
if (!xmrAmount.isEmpty()) {
if (first) {
@@ -319,7 +320,7 @@ public class ReceiveFragment extends Fragment {
} else {
sb.append("&");
}
- sb.append(ScannerFragment.QR_AMOUNT).append('=').append(xmrAmount);
+ sb.append(BarcodeData.XMR_AMOUNT).append('=').append(xmrAmount);
}
String text = sb.toString();
int size = Math.min(qrCode.getHeight(), qrCode.getWidth());
diff --git a/app/src/main/java/com/m2049r/xmrwallet/ScannerFragment.java b/app/src/main/java/com/m2049r/xmrwallet/ScannerFragment.java
index e9997cf..31de64e 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/ScannerFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/ScannerFragment.java
@@ -36,7 +36,7 @@ public class ScannerFragment extends Fragment implements ZXingScannerView.Result
private OnScannedListener onScannedListener;
public interface OnScannedListener {
- boolean onScanned(String uri);
+ boolean onScanned(String qrCode);
}
private ZXingScannerView mScannerView;
@@ -56,10 +56,6 @@ public class ScannerFragment extends Fragment implements ZXingScannerView.Result
mScannerView.startCamera();
}
- static final String QR_SCHEME = "monero:";
- static final String QR_PAYMENTID = "tx_payment_id";
- static final String QR_AMOUNT = "tx_amount";
-
@Override
public void handleResult(Result rawResult) {
if ((rawResult.getBarcodeFormat() == BarcodeFormat.QR_CODE)) {
diff --git a/app/src/main/java/com/m2049r/xmrwallet/SecureActivity.java b/app/src/main/java/com/m2049r/xmrwallet/SecureActivity.java
index eb4549b..dc9ac1a 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/SecureActivity.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/SecureActivity.java
@@ -20,6 +20,10 @@ import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
+import com.m2049r.xmrwallet.util.Helper;
+
+import java.io.File;
+
import static android.view.WindowManager.LayoutParams;
public abstract class SecureActivity extends AppCompatActivity {
@@ -27,8 +31,9 @@ public abstract class SecureActivity extends AppCompatActivity {
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ File f = new File(Helper.getStorageRoot(this), "screenshot");
// set FLAG_SECURE to prevent screenshots in Release Mode
- if (!BuildConfig.DEBUG) {
+ if ((!BuildConfig.DEBUG) && (!f.exists())) {
getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE);
}
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java b/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java
index b530587..9b0dc19 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java
@@ -30,10 +30,13 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
+import android.widget.Toast;
import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Transfer;
import com.m2049r.xmrwallet.model.Wallet;
+import com.m2049r.xmrwallet.util.Helper;
+import com.m2049r.xmrwallet.util.UserNotes;
import com.m2049r.xmrwallet.widget.Toolbar;
import java.text.SimpleDateFormat;
@@ -68,12 +71,23 @@ public class TxFragment extends Fragment {
private TextView etTxNotes;
private Button bTxNotes;
+ // XMRTO stuff
+ private View cvXmrTo;
+ private TextView tvTxXmrToKey;
+ private TextView tvDestinationBtc;
+ private TextView tvTxAmountBtc;
+
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_tx_info, container, false);
+ cvXmrTo = view.findViewById(R.id.cvXmrTo);
+ tvTxXmrToKey = (TextView) view.findViewById(R.id.tvTxXmrToKey);
+ tvDestinationBtc = (TextView) view.findViewById(R.id.tvDestinationBtc);
+ tvTxAmountBtc = (TextView) view.findViewById(R.id.tvTxAmountBtc);
+
tvTxTimestamp = (TextView) view.findViewById(R.id.tvTxTimestamp);
tvTxId = (TextView) view.findViewById(R.id.tvTxId);
tvTxKey = (TextView) view.findViewById(R.id.tvTxKey);
@@ -94,7 +108,16 @@ public class TxFragment extends Fragment {
info.notes = null; // force reload on next view
bTxNotes.setEnabled(false);
etTxNotes.setEnabled(false);
- activityCallback.onSetNote(info.hash, etTxNotes.getText().toString());
+ userNotes.setNote(etTxNotes.getText().toString());
+ activityCallback.onSetNote(info.hash, userNotes.txNotes);
+ }
+ });
+
+ tvTxXmrToKey.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_xmrtokey), tvTxXmrToKey.getText().toString());
+ Toast.makeText(getActivity(), getString(R.string.message_copy_xmrtokey), Toast.LENGTH_SHORT).show();
}
});
@@ -175,12 +198,14 @@ public class TxFragment extends Fragment {
}
TransactionInfo info = null;
+ UserNotes userNotes = null;
void loadNotes(TransactionInfo info) {
- if (info.notes == null) {
+ if ((userNotes == null) || (info.notes == null)) {
info.notes = activityCallback.getTxNotes(info.hash);
}
- etTxNotes.setText(info.notes);
+ userNotes = new UserNotes(info.notes);
+ etTxNotes.setText(userNotes.note);
}
private void setTxColour(int clr) {
@@ -266,8 +291,21 @@ public class TxFragment extends Fragment {
tvTxTransfers.setText(sb.toString());
tvDestination.setText(dstSb.toString());
this.info = info;
+ showBtcInfo();
}
+ void showBtcInfo() {
+ if (userNotes.xmrtoKey != null) {
+ cvXmrTo.setVisibility(View.VISIBLE);
+ tvTxXmrToKey.setText(userNotes.xmrtoKey);
+ tvDestinationBtc.setText(userNotes.xmrtoDestination);
+ tvTxAmountBtc.setText(userNotes.xmrtoAmount + " BTC");
+ } else {
+ cvXmrTo.setVisibility(View.GONE);
+ }
+ }
+
+
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
diff --git a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java
index 5fa4e44..f4682bf 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java
@@ -23,7 +23,6 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
-import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManager;
@@ -38,17 +37,18 @@ import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.data.TxData;
import com.m2049r.xmrwallet.dialog.DonationFragment;
import com.m2049r.xmrwallet.dialog.HelpFragment;
+import com.m2049r.xmrwallet.fragment.send.SendAddressWizardFragment;
+import com.m2049r.xmrwallet.fragment.send.SendFragment;
import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.service.WalletService;
import com.m2049r.xmrwallet.util.Helper;
+import com.m2049r.xmrwallet.util.UserNotes;
import com.m2049r.xmrwallet.widget.Toolbar;
import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
import timber.log.Timber;
@@ -465,7 +465,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
}
@Override
- public void onTransactionCreated(final PendingTransaction pendingTransaction) {
+ public void onTransactionCreated(final String txTag, final PendingTransaction pendingTransaction) {
try {
final SendFragment sendFragment = (SendFragment)
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
@@ -477,7 +477,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
getWallet().disposePendingTransaction();
sendFragment.onCreateTransactionFailed(errorText);
} else {
- sendFragment.onTransactionCreated(pendingTransaction);
+ sendFragment.onTransactionCreated(txTag, pendingTransaction);
}
}
});
@@ -588,11 +588,11 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
///////////////////////////
@Override
- public void onSend(String notes) {
+ public void onSend(UserNotes notes) {
if (mIsBound) { // no point in talking to unbound service
Intent intent = new Intent(getApplicationContext(), WalletService.class);
intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_SEND);
- intent.putExtra(WalletService.REQUEST_CMD_SEND_NOTES, notes);
+ intent.putExtra(WalletService.REQUEST_CMD_SEND_NOTES, notes.txNotes);
startService(intent);
Timber.d("SEND TX request sent");
} else {
@@ -617,11 +617,12 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
}
@Override
- public void onPrepareSend(TxData txData) {
+ public void onPrepareSend(final String tag, final TxData txData) {
if (mIsBound) { // no point in talking to unbound service
Intent intent = new Intent(getApplicationContext(), WalletService.class);
intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_TX);
intent.putExtra(WalletService.REQUEST_CMD_TX_DATA, txData);
+ intent.putExtra(WalletService.REQUEST_CMD_TX_TAG, tag);
startService(intent);
Timber.d("CREATE TX request sent");
} else {
@@ -694,6 +695,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
@Override
public void onDisposeRequest() {
+ //TODO consider doing this through the WalletService to avoid concurrency issues
getWallet().disposePendingTransaction();
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/data/BarcodeData.java b/app/src/main/java/com/m2049r/xmrwallet/data/BarcodeData.java
index 22ceca7..3ff9e04 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/data/BarcodeData.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/data/BarcodeData.java
@@ -19,6 +19,8 @@ package com.m2049r.xmrwallet.data;
import android.net.Uri;
import com.m2049r.xmrwallet.model.Wallet;
+import com.m2049r.xmrwallet.model.WalletManager;
+import com.m2049r.xmrwallet.util.BitcoinAddressValidator;
import java.util.HashMap;
import java.util.Map;
@@ -34,7 +36,7 @@ public class BarcodeData {
static final String BTC_AMOUNT = "amount";
public enum Asset {
- XMR
+ XMR, BTC
}
public Asset asset = null;
@@ -67,6 +69,14 @@ public class BarcodeData {
if (bcData == null) {
bcData = parseMoneroNaked(qrCode);
}
+ // check for btc uri
+ if (bcData == null) {
+ bcData = parseBitcoinUri(qrCode);
+ }
+ // check for naked btc addres
+ if (bcData == null) {
+ bcData = parseBitcoinNaked(qrCode);
+ }
return bcData;
}
@@ -134,4 +144,57 @@ public class BarcodeData {
return new BarcodeData(Asset.XMR, address);
}
+
+ // bitcoin:mpQ84J43EURZHkCnXbyQ4PpNDLLBqdsMW2?amount=0.01
+ static public BarcodeData parseBitcoinUri(String uri) {
+ Timber.d("parseBitcoinUri=%s", uri);
+
+ if (uri == null) return null;
+
+ if (!uri.startsWith(BTC_SCHEME)) return null;
+
+ String noScheme = uri.substring(BTC_SCHEME.length());
+ Uri bitcoin = Uri.parse(noScheme);
+ Map parms = new HashMap<>();
+ String query = bitcoin.getQuery();
+ if (query != null) {
+ String[] args = query.split("&");
+ for (String arg : args) {
+ String[] namevalue = arg.split("=");
+ if (namevalue.length == 0) {
+ continue;
+ }
+ parms.put(Uri.decode(namevalue[0]).toLowerCase(),
+ namevalue.length > 1 ? Uri.decode(namevalue[1]) : "");
+ }
+ }
+ String address = bitcoin.getPath();
+ String amount = parms.get(BTC_AMOUNT);
+ if (amount != null) {
+ try {
+ Double.parseDouble(amount);
+ } catch (NumberFormatException ex) {
+ Timber.d(ex.getLocalizedMessage());
+ return null; // we have an amount but its not a number!
+ }
+ }
+ if (!BitcoinAddressValidator.validate(address, WalletManager.getInstance().isTestNet())) {
+ Timber.d("address invalid");
+ return null;
+ }
+ return new BarcodeData(BarcodeData.Asset.BTC, address, amount);
+ }
+
+ static public BarcodeData parseBitcoinNaked(String address) {
+ Timber.d("parseBitcoinNaked=%s", address);
+
+ if (address == null) return null;
+
+ if (!BitcoinAddressValidator.validate(address, WalletManager.getInstance().isTestNet())) {
+ Timber.d("address invalid");
+ return null;
+ }
+
+ return new BarcodeData(BarcodeData.Asset.BTC, address);
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/m2049r/xmrwallet/data/TxData.java b/app/src/main/java/com/m2049r/xmrwallet/data/TxData.java
index d15c54d..55505da 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/data/TxData.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/data/TxData.java
@@ -20,36 +20,38 @@ import android.os.Parcel;
import android.os.Parcelable;
import com.m2049r.xmrwallet.model.PendingTransaction;
+import com.m2049r.xmrwallet.util.UserNotes;
+
+import timber.log.Timber;
// https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents
public class TxData implements Parcelable {
+ public TxData() {
+ }
+
public TxData(TxData txData) {
- this.dst_addr = txData.dst_addr;
+ this.dstAddr = txData.dstAddr;
this.paymentId = txData.paymentId;
this.amount = txData.amount;
this.mixin = txData.mixin;
this.priority = txData.priority;
}
- public TxData(String dst_addr,
+ public TxData(String dstAddr,
String paymentId,
long amount,
int mixin,
PendingTransaction.Priority priority) {
- this.dst_addr = dst_addr;
+ this.dstAddr = dstAddr;
this.paymentId = paymentId;
this.amount = amount;
this.mixin = mixin;
this.priority = priority;
}
- public long getFee() {
- return 0L;
- }
-
public String getDestinationAddress() {
- return dst_addr;
+ return dstAddr;
}
public String getPaymentId() {
@@ -68,15 +70,45 @@ public class TxData implements Parcelable {
return priority;
}
- final private String dst_addr;
- final private String paymentId;
- final private long amount;
- final private int mixin;
- final private PendingTransaction.Priority priority;
+ public void setDestinationAddress(String dstAddr) {
+ this.dstAddr = dstAddr;
+ }
+
+ public void setPaymentId(String paymentId) {
+ this.paymentId = paymentId;
+ }
+
+ public void setAmount(long amount) {
+ this.amount = amount;
+ }
+
+ public void setMixin(int mixin) {
+ this.mixin = mixin;
+ }
+
+ public void setPriority(PendingTransaction.Priority priority) {
+ this.priority = priority;
+ }
+
+ public UserNotes getUserNotes() {
+ return userNotes;
+ }
+
+ public void setUserNotes(UserNotes userNotes) {
+ this.userNotes = userNotes;
+ }
+
+ private String dstAddr;
+ private String paymentId;
+ private long amount;
+ private int mixin;
+ private PendingTransaction.Priority priority;
+
+ private UserNotes userNotes;
@Override
public void writeToParcel(Parcel out, int flags) {
- out.writeString(dst_addr);
+ out.writeString(dstAddr);
out.writeString(paymentId);
out.writeLong(amount);
out.writeInt(mixin);
@@ -94,8 +126,8 @@ public class TxData implements Parcelable {
}
};
- private TxData(Parcel in) {
- dst_addr = in.readString();
+ protected TxData(Parcel in) {
+ dstAddr = in.readString();
paymentId = in.readString();
amount = in.readLong();
mixin = in.readInt();
@@ -111,8 +143,8 @@ public class TxData implements Parcelable {
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
- sb.append("dst_addr:");
- sb.append(dst_addr);
+ sb.append("dstAddr:");
+ sb.append(dstAddr);
sb.append(",paymentId:");
sb.append(paymentId);
sb.append(",amount:");
diff --git a/app/src/main/java/com/m2049r/xmrwallet/data/TxDataBtc.java b/app/src/main/java/com/m2049r/xmrwallet/data/TxDataBtc.java
new file mode 100644
index 0000000..6b61e10
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/data/TxDataBtc.java
@@ -0,0 +1,98 @@
+/*
+ * 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.
+ */
+
+package com.m2049r.xmrwallet.data;
+
+import android.os.Parcel;
+
+import com.m2049r.xmrwallet.model.PendingTransaction;
+
+public class TxDataBtc extends TxData {
+
+ private String xmrtoUuid;
+ private String btcAddress;
+ private double btcAmount;
+
+ public TxDataBtc() {
+ super();
+ }
+
+ public TxDataBtc(TxDataBtc txDataBtc) {
+ super(txDataBtc);
+ }
+
+ public String getXmrtoUuid() {
+ return xmrtoUuid;
+ }
+
+ public void setXmrtoUuid(String xmrtoUuid) {
+ this.xmrtoUuid = xmrtoUuid;
+ }
+
+ public String getBtcAddress() {
+ return btcAddress;
+ }
+
+ public void setBtcAddress(String btcAddress) {
+ this.btcAddress = btcAddress;
+ }
+
+ public double getBtcAmount() {
+ return btcAmount;
+ }
+
+ public void setBtcAmount(double btcAmount) {
+ this.btcAmount = btcAmount;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeString(xmrtoUuid);
+ out.writeString(btcAddress);
+ out.writeDouble(btcAmount);
+ }
+
+ // this is used to regenerate your object. All Parcelables must have a CREATOR that implements these two methods
+ public static final Creator CREATOR = new Creator() {
+ public TxDataBtc createFromParcel(Parcel in) {
+ return new TxDataBtc(in);
+ }
+
+ public TxDataBtc[] newArray(int size) {
+ return new TxDataBtc[size];
+ }
+ };
+
+ protected TxDataBtc(Parcel in) {
+ super(in);
+ xmrtoUuid = in.readString();
+ btcAddress = in.readString();
+ btcAmount = in.readDouble();
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(",xmrtoUuid:");
+ sb.append(xmrtoUuid);
+ sb.append(",btcAddress:");
+ sb.append(btcAddress);
+ sb.append(",btcAmount:");
+ sb.append(btcAmount);
+ return sb.toString();
+ }
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/SendAddressWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAddressWizardFragment.java
similarity index 77%
rename from app/src/main/java/com/m2049r/xmrwallet/SendAddressWizardFragment.java
rename to app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAddressWizardFragment.java
index 3b5a9d3..77ad8cd 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/SendAddressWizardFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAddressWizardFragment.java
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-package com.m2049r.xmrwallet;
+package com.m2049r.xmrwallet.fragment.send;
import android.content.Context;
import android.os.Bundle;
import android.support.design.widget.TextInputLayout;
import android.support.v7.widget.CardView;
import android.text.Editable;
+import android.text.Html;
import android.text.InputType;
import android.text.TextWatcher;
import android.view.KeyEvent;
@@ -32,9 +33,13 @@ import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
+import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.BarcodeData;
+import com.m2049r.xmrwallet.data.TxData;
+import com.m2049r.xmrwallet.data.TxDataBtc;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
+import com.m2049r.xmrwallet.util.BitcoinAddressValidator;
import com.m2049r.xmrwallet.util.Helper;
import timber.log.Timber;
@@ -56,12 +61,12 @@ public class SendAddressWizardFragment extends SendWizardFragment {
return this;
}
- interface Listener {
- void setAddress(final String address);
-
- void setPaymentId(final String paymentId);
-
+ public interface Listener {
void setBarcodeData(BarcodeData data);
+
+ void setMode(SendFragment.Mode mode);
+
+ TxData getTxData();
}
private EditText etDummy;
@@ -71,6 +76,8 @@ public class SendAddressWizardFragment extends SendWizardFragment {
private CardView cvScan;
private View tvPaymentIdIntegrated;
private View llPaymentId;
+ private TextView tvXmrTo;
+ private View llXmrTo;
OnScanListener onScanListener;
@@ -89,6 +96,9 @@ public class SendAddressWizardFragment extends SendWizardFragment {
tvPaymentIdIntegrated = view.findViewById(R.id.tvPaymentIdIntegrated);
llPaymentId = view.findViewById(R.id.llPaymentId);
+ llXmrTo = view.findViewById(R.id.llXmrTo);
+ tvXmrTo = (TextView) view.findViewById(R.id.tvXmrTo);
+ tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto)));
etAddress = (TextInputLayout) view.findViewById(R.id.etAddress);
etAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
@@ -113,12 +123,25 @@ public class SendAddressWizardFragment extends SendWizardFragment {
public void afterTextChanged(Editable editable) {
etAddress.setError(null);
if (isIntegratedAddress()) {
+ Timber.d("isIntegratedAddress");
etPaymentId.getEditText().getText().clear();
- llPaymentId.setVisibility(View.GONE);
+ llPaymentId.setVisibility(View.INVISIBLE);
tvPaymentIdIntegrated.setVisibility(View.VISIBLE);
- } else { // we don't
+ llXmrTo.setVisibility(View.INVISIBLE);
+ sendListener.setMode(SendFragment.Mode.XMR);
+ } else if (isBitcoinAddress()) {
+ Timber.d("isBitcoinAddress");
+ etPaymentId.getEditText().getText().clear();
+ llPaymentId.setVisibility(View.INVISIBLE);
+ tvPaymentIdIntegrated.setVisibility(View.INVISIBLE);
+ llXmrTo.setVisibility(View.VISIBLE);
+ sendListener.setMode(SendFragment.Mode.BTC);
+ } else {
+ Timber.d("isStandardAddress");
llPaymentId.setVisibility(View.VISIBLE);
- tvPaymentIdIntegrated.setVisibility(View.GONE);
+ tvPaymentIdIntegrated.setVisibility(View.INVISIBLE);
+ llXmrTo.setVisibility(View.INVISIBLE);
+ sendListener.setMode(SendFragment.Mode.XMR);
}
}
@@ -188,13 +211,14 @@ public class SendAddressWizardFragment extends SendWizardFragment {
private boolean checkAddressNoError() {
String address = etAddress.getEditText().getText().toString();
- return Wallet.isAddressValid(address);
+ return Wallet.isAddressValid(address)
+ || BitcoinAddressValidator.validate(address, WalletManager.getInstance().isTestNet());
}
private boolean checkAddress() {
boolean ok = checkAddressNoError();
if (!ok) {
- etAddress.setError(getString(R.string.send_qr_address_invalid));
+ etAddress.setError(getString(R.string.send_address_invalid));
} else {
etAddress.setError(null);
}
@@ -203,7 +227,16 @@ public class SendAddressWizardFragment extends SendWizardFragment {
private boolean isIntegratedAddress() {
String address = etAddress.getEditText().getText().toString();
- return (address.length() == INTEGRATED_ADDRESS_LENGTH) && Wallet.isAddressValid(address);
+ return (address.length() == INTEGRATED_ADDRESS_LENGTH)
+ && Wallet.isAddressValid(address, WalletManager.getInstance().isTestNet());
+ }
+
+ private boolean isBitcoinAddress() {
+ String address = etAddress.getEditText().getText().toString();
+ if ((address.length() >= 27) && (address.length() <= 34))
+ return BitcoinAddressValidator.validate(address, WalletManager.getInstance().isTestNet());
+ else
+ return false;
}
private boolean checkPaymentId() {
@@ -235,8 +268,15 @@ public class SendAddressWizardFragment extends SendWizardFragment {
}
if (!ok) return false;
if (sendListener != null) {
- sendListener.setAddress(etAddress.getEditText().getText().toString());
- sendListener.setPaymentId(etPaymentId.getEditText().getText().toString());
+ TxData txData = sendListener.getTxData();
+ if (isBitcoinAddress()) {
+ ((TxDataBtc) txData).setBtcAddress(etAddress.getEditText().getText().toString());
+ txData.setDestinationAddress(null);
+ txData.setPaymentId("");
+ } else {
+ txData.setDestinationAddress(etAddress.getEditText().getText().toString());
+ txData.setPaymentId(etPaymentId.getEditText().getText().toString());
+ }
}
return true;
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/SendAmountWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAmountWizardFragment.java
similarity index 93%
rename from app/src/main/java/com/m2049r/xmrwallet/SendAmountWizardFragment.java
rename to app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAmountWizardFragment.java
index 033dfea..2ea8207 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/SendAmountWizardFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAmountWizardFragment.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.m2049r.xmrwallet;
+package com.m2049r.xmrwallet.fragment.send;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -22,10 +22,12 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
+import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.BarcodeData;
-import com.m2049r.xmrwallet.widget.ExchangeTextView;
+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 timber.log.Timber;
@@ -48,7 +50,7 @@ public class SendAmountWizardFragment extends SendWizardFragment {
interface Listener {
SendFragment.Listener getActivityCallback();
- void setAmount(final long amount);
+ TxData getTxData();
BarcodeData popBarcodeData();
}
@@ -99,9 +101,9 @@ public class SendAmountWizardFragment extends SendWizardFragment {
if (sendListener != null) {
String xmr = evAmount.getAmount();
if (xmr != null) {
- sendListener.setAmount(Wallet.getAmountFromString(xmr));
+ sendListener.getTxData().setAmount(Wallet.getAmountFromString(xmr));
} else {
- sendListener.setAmount(0L);
+ sendListener.getTxData().setAmount(0L);
}
}
return true;
diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcAmountWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcAmountWizardFragment.java
new file mode 100644
index 0000000..88fe821
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcAmountWizardFragment.java
@@ -0,0 +1,261 @@
+/*
+ * 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.
+ */
+
+package com.m2049r.xmrwallet.fragment.send;
+
+import android.os.Bundle;
+import android.text.Html;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.m2049r.xmrwallet.R;
+import com.m2049r.xmrwallet.data.BarcodeData;
+import com.m2049r.xmrwallet.data.TxDataBtc;
+import com.m2049r.xmrwallet.model.Wallet;
+import com.m2049r.xmrwallet.util.Helper;
+import com.m2049r.xmrwallet.util.OkHttpClientSingleton;
+import com.m2049r.xmrwallet.widget.ExchangeBtcTextView;
+import com.m2049r.xmrwallet.widget.NumberPadView;
+import com.m2049r.xmrwallet.widget.SendProgressView;
+import com.m2049r.xmrwallet.xmrto.XmrToError;
+import com.m2049r.xmrwallet.xmrto.XmrToException;
+import com.m2049r.xmrwallet.xmrto.api.QueryOrderParameters;
+import com.m2049r.xmrwallet.xmrto.api.XmrToApi;
+import com.m2049r.xmrwallet.xmrto.api.XmrToCallback;
+import com.m2049r.xmrwallet.xmrto.network.XmrToApiImpl;
+
+import java.text.NumberFormat;
+import java.util.Locale;
+
+import okhttp3.HttpUrl;
+import timber.log.Timber;
+
+public class SendBtcAmountWizardFragment extends SendWizardFragment {
+
+ public static SendBtcAmountWizardFragment newInstance(SendAmountWizardFragment.Listener listener) {
+ SendBtcAmountWizardFragment instance = new SendBtcAmountWizardFragment();
+ instance.setSendListener(listener);
+ return instance;
+ }
+
+ SendAmountWizardFragment.Listener sendListener;
+
+ public SendBtcAmountWizardFragment setSendListener(SendAmountWizardFragment.Listener listener) {
+ this.sendListener = listener;
+ return this;
+ }
+
+ private TextView tvFunds;
+ private ExchangeBtcTextView evAmount;
+ private NumberPadView numberPad;
+
+ private TextView tvXmrToParms;
+ private SendProgressView evParams;
+ private View llXmrToParms;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ Timber.d("onCreateView() %s", (String.valueOf(savedInstanceState)));
+
+ sendListener = (SendAmountWizardFragment.Listener) getParentFragment();
+
+ View view = inflater.inflate(R.layout.fragment_send_btc_amount, container, false);
+
+ tvFunds = (TextView) view.findViewById(R.id.tvFunds);
+
+ evParams = (SendProgressView) view.findViewById(R.id.evXmrToParms);
+ llXmrToParms = view.findViewById(R.id.llXmrToParms);
+
+ tvXmrToParms = (TextView) view.findViewById(R.id.tvXmrToParms);
+
+ evAmount = (ExchangeBtcTextView) view.findViewById(R.id.evAmount);
+ numberPad = (NumberPadView) view.findViewById(R.id.numberPad);
+ numberPad.setListener(evAmount);
+
+ Helper.hideKeyboard(getActivity());
+
+ return view;
+ }
+
+
+ @Override
+ public boolean onValidateFields() {
+ if (!evAmount.validate(maxBtc, minBtc)) {
+ return false;
+ }
+ if (sendListener != null) {
+ TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
+ String btcString = evAmount.getAmount();
+ if (btcString != null) {
+ try {
+ double btc = Double.parseDouble(btcString);
+ Timber.d("setAmount %f", btc);
+ txDataBtc.setBtcAmount(btc);
+ } catch (NumberFormatException ex) {
+ Timber.d(ex.getLocalizedMessage());
+ txDataBtc.setBtcAmount(0);
+ }
+ } else {
+ txDataBtc.setBtcAmount(0);
+ }
+ }
+ return true;
+ }
+
+ double maxBtc = 0;
+ double minBtc = 0;
+
+ @Override
+ public void onPauseFragment() {
+ llXmrToParms.setVisibility(View.INVISIBLE);
+ }
+
+ @Override
+ public void onResumeFragment() {
+ super.onResumeFragment();
+ Timber.d("onResumeFragment()");
+ Helper.hideKeyboard(getActivity());
+ final long funds = getTotalFunds();
+ tvFunds.setText(getString(R.string.send_available,
+ Wallet.getDisplayAmount(funds)));
+ if ((evAmount.getAmount() == null) || evAmount.getAmount().isEmpty()) {
+ final BarcodeData data = sendListener.popBarcodeData();
+ if ((data != null) && (data.amount != null)) {
+ evAmount.setAmount(data.amount);
+ }
+ }
+ callXmrTo();
+ }
+
+ long getTotalFunds() {
+ return sendListener.getActivityCallback().getTotalFunds();
+ }
+
+ private QueryOrderParameters orderParameters = null;
+
+ private void processOrderParms(final QueryOrderParameters orderParameters) {
+ this.orderParameters = orderParameters;
+ getView().post(new Runnable() {
+ @Override
+ public void run() {
+ evAmount.setRate(1.0d / orderParameters.getPrice());
+ NumberFormat df = NumberFormat.getInstance(Locale.US);
+ df.setMaximumFractionDigits(6);
+ String min = df.format(orderParameters.getLowerLimit());
+ String max = df.format(orderParameters.getUpperLimit());
+ String rate = df.format(orderParameters.getPrice());
+ Spanned xmrParmText = Html.fromHtml(getString(R.string.info_send_xmrto_parms, min, max, rate));
+ if (orderParameters.isZeroConfEnabled()) {
+ String zeroConf = df.format(orderParameters.getZeroConfMaxAmount());
+ Spanned zeroConfText = Html.fromHtml(getString(R.string.info_send_xmrto_zeroconf, zeroConf));
+ xmrParmText = (Spanned) TextUtils.concat(xmrParmText, " ", zeroConfText);
+ }
+ tvXmrToParms.setText(xmrParmText);
+ maxBtc = orderParameters.getUpperLimit();
+ minBtc = orderParameters.getLowerLimit();
+ Timber.d("minBtc=%f / maxBtc=%f", minBtc, maxBtc);
+
+ final long funds = getTotalFunds();
+ double availableXmr = 1.0 * funds / 1000000000000L;
+ maxBtc = Math.min(maxBtc, availableXmr * orderParameters.getPrice());
+
+ String availBtcString = df.format(availableXmr * orderParameters.getPrice());
+ String availXmrString = df.format(availableXmr);
+ tvFunds.setText(getString(R.string.send_available_btc,
+ availXmrString,
+ availBtcString));
+ llXmrToParms.setVisibility(View.VISIBLE);
+ evParams.hideProgress();
+ }
+ });
+ }
+
+ private void processOrderParmsError(final Exception ex) {
+ evAmount.setRate(0);
+ orderParameters = null;
+ maxBtc = 0;
+ minBtc = 0;
+ Timber.e(ex);
+ getView().post(new Runnable() {
+ @Override
+ public void run() {
+ if (ex instanceof XmrToException) {
+ XmrToException xmrEx = (XmrToException) ex;
+ XmrToError xmrErr = xmrEx.getError();
+ if (xmrErr != null) {
+ if (xmrErr.isRetryable()) {
+ evParams.showMessage(xmrErr.getErrorId().toString(), xmrErr.getErrorMsg(),
+ getString(R.string.text_retry));
+ evParams.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ evParams.setOnClickListener(null);
+ callXmrTo();
+ }
+ });
+ } else {
+ evParams.showMessage(xmrErr.getErrorId().toString(), xmrErr.getErrorMsg(),
+ getString(R.string.text_noretry));
+ }
+ } else {
+ evParams.showMessage(getString(R.string.label_generic_xmrto_error),
+ getString(R.string.text_generic_xmrto_error, xmrEx.getCode()),
+ getString(R.string.text_noretry));
+ }
+ } else {
+ evParams.showMessage(getString(R.string.label_generic_xmrto_error),
+ ex.getLocalizedMessage(),
+ getString(R.string.text_noretry));
+ }
+ }
+ });
+ }
+
+ private void callXmrTo() {
+ evParams.showProgress(getString(R.string.label_send_progress_queryparms));
+ getXmrToApi().queryOrderParameters(new XmrToCallback() {
+ @Override
+ public void onSuccess(final QueryOrderParameters orderParameters) {
+ processOrderParms(orderParameters);
+ }
+
+ @Override
+ public void onError(final Exception e) {
+ processOrderParmsError(e);
+ }
+ });
+ }
+
+ private XmrToApi xmrToApi = null;
+
+ private final XmrToApi getXmrToApi() {
+ if (xmrToApi == null) {
+ synchronized (this) {
+ if (xmrToApi == null) {
+ xmrToApi = new XmrToApiImpl(OkHttpClientSingleton.getOkHttpClient(),
+ Helper.getXmrToBaseUrl());
+ }
+ }
+ }
+ return xmrToApi;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcConfirmWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcConfirmWizardFragment.java
new file mode 100644
index 0000000..3f1bd5e
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcConfirmWizardFragment.java
@@ -0,0 +1,680 @@
+/*
+ * 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.
+ */
+
+package com.m2049r.xmrwallet.fragment.send;
+
+import android.app.Activity;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.design.widget.TextInputLayout;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.m2049r.xmrwallet.R;
+import com.m2049r.xmrwallet.data.TxData;
+import com.m2049r.xmrwallet.data.TxDataBtc;
+import com.m2049r.xmrwallet.model.PendingTransaction;
+import com.m2049r.xmrwallet.model.Wallet;
+import com.m2049r.xmrwallet.util.Helper;
+import com.m2049r.xmrwallet.util.OkHttpClientSingleton;
+import com.m2049r.xmrwallet.widget.SendProgressView;
+import com.m2049r.xmrwallet.xmrto.XmrToError;
+import com.m2049r.xmrwallet.xmrto.XmrToException;
+import com.m2049r.xmrwallet.xmrto.api.CreateOrder;
+import com.m2049r.xmrwallet.xmrto.api.QueryOrderStatus;
+import com.m2049r.xmrwallet.xmrto.api.XmrToApi;
+import com.m2049r.xmrwallet.xmrto.api.XmrToCallback;
+import com.m2049r.xmrwallet.xmrto.network.XmrToApiImpl;
+
+import java.text.NumberFormat;
+import java.util.Locale;
+
+import okhttp3.HttpUrl;
+import timber.log.Timber;
+
+public class SendBtcConfirmWizardFragment extends SendWizardFragment implements SendConfirm {
+ private final int QUERY_INTERVAL = 500;//ms
+
+ public static SendBtcConfirmWizardFragment newInstance(SendConfirmWizardFragment.Listener listener) {
+ SendBtcConfirmWizardFragment instance = new SendBtcConfirmWizardFragment();
+ instance.setSendListener(listener);
+ return instance;
+ }
+
+ SendConfirmWizardFragment.Listener sendListener;
+
+ public SendBtcConfirmWizardFragment setSendListener(SendConfirmWizardFragment.Listener listener) {
+ this.sendListener = listener;
+ return this;
+ }
+
+ private View llStageA;
+ private SendProgressView evStageA;
+ private View llStageB;
+ private SendProgressView evStageB;
+ private View llStageC;
+ private SendProgressView evStageC;
+ private TextView tvTxBtcAmount;
+ private TextView tvTxBtcRate;
+ private TextView tvTxBtcAddress;
+ private TextView tvTxXmrToKey;
+ private TextView tvTxFee;
+ private TextView tvTxTotal;
+ private View llConfirmSend;
+ private Button bSend;
+ private View pbProgressSend;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ Timber.d("onCreateView(%s)", (String.valueOf(savedInstanceState)));
+
+ View view = inflater.inflate(
+ R.layout.fragment_send_btc_confirm, container, false);
+
+ tvTxBtcAddress = (TextView) view.findViewById(R.id.tvTxBtcAddress);
+ tvTxBtcAmount = ((TextView) view.findViewById(R.id.tvTxBtcAmount));
+ tvTxBtcRate = (TextView) view.findViewById(R.id.tvTxBtcRate);
+ tvTxXmrToKey = (TextView) view.findViewById(R.id.tvTxXmrToKey);
+
+ tvTxFee = (TextView) view.findViewById(R.id.tvTxFee);
+ tvTxTotal = (TextView) view.findViewById(R.id.tvTxTotal);
+
+
+ llStageA = view.findViewById(R.id.llStageA);
+ evStageA = (SendProgressView) view.findViewById(R.id.evStageA);
+ llStageB = view.findViewById(R.id.llStageB);
+ evStageB = (SendProgressView) view.findViewById(R.id.evStageB);
+ llStageC = view.findViewById(R.id.llStageC);
+ evStageC = (SendProgressView) view.findViewById(R.id.evStageC);
+
+ tvTxXmrToKey.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_xmrtokey), tvTxXmrToKey.getText().toString());
+ Toast.makeText(getActivity(), getString(R.string.message_copy_xmrtokey), Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ llConfirmSend = view.findViewById(R.id.llConfirmSend);
+ pbProgressSend = view.findViewById(R.id.pbProgressSend);
+
+ bSend = (Button) view.findViewById(R.id.bSend);
+ bSend.setEnabled(false);
+
+ bSend.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Timber.d("bSend.setOnClickListener");
+ bSend.setEnabled(false);
+ preSend();
+ }
+ });
+
+ return view;
+ }
+
+ int inProgress = 0;
+ final static int STAGE_X = 0;
+ final static int STAGE_A = 1;
+ final static int STAGE_B = 2;
+ final static int STAGE_C = 3;
+
+ private void showProgress(int stage, String progressText) {
+ Timber.d("showProgress(%d)", stage);
+ inProgress = stage;
+ switch (stage) {
+ case STAGE_A:
+ evStageA.showProgress(progressText);
+ break;
+ case STAGE_B:
+ evStageB.showProgress(progressText);
+ break;
+ case STAGE_C:
+ evStageC.showProgress(progressText);
+ break;
+ default:
+ throw new IllegalStateException("unknown stage " + stage);
+ }
+ }
+
+ public void hideProgress() {
+ Timber.d("hideProgress(%d)", inProgress);
+ switch (inProgress) {
+ case STAGE_A:
+ evStageA.hideProgress();
+ llStageA.setVisibility(View.VISIBLE);
+ break;
+ case STAGE_B:
+ evStageB.hideProgress();
+ llStageB.setVisibility(View.VISIBLE);
+ break;
+ case STAGE_C:
+ evStageC.hideProgress();
+ llStageC.setVisibility(View.VISIBLE);
+ break;
+ default:
+ throw new IllegalStateException("unknown stage " + inProgress);
+ }
+ inProgress = STAGE_X;
+ }
+
+ public void showStageError(String code, String message, String solution) {
+ switch (inProgress) {
+ case STAGE_A:
+ evStageA.showMessage(code, message, solution);
+ break;
+ case STAGE_B:
+ evStageB.showMessage(code, message, solution);
+ break;
+ case STAGE_C:
+ evStageC.showMessage(code, message, solution);
+ break;
+ default:
+ throw new IllegalStateException("unknown stage");
+ }
+ inProgress = STAGE_X;
+ }
+
+ PendingTransaction pendingTransaction = null;
+
+ void send() {
+ Timber.d("SEND @%d", sendCountdown);
+ if (sendCountdown <= 0) {
+ Timber.i("User waited too long in password dialog.");
+ Toast.makeText(getContext(), getString(R.string.send_xmrto_timeout), Toast.LENGTH_SHORT).show();
+ return;
+ }
+ sendListener.getTxData().getUserNotes().setXmrtoStatus(xmrtoStatus);
+ ((TxDataBtc) sendListener.getTxData()).setXmrtoUuid(xmrtoStatus.getUuid());
+ // TODO make method in TxDataBtc to set both of the above in one go
+ sendListener.commitTransaction();
+ pbProgressSend.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void sendFailed() {
+ Timber.e("SEND FAILED");
+ pbProgressSend.setVisibility(View.INVISIBLE);
+ }
+
+ @Override
+ // callback from wallet when PendingTransaction created (started by prepareSend() here
+ public void transactionCreated(final String txTag, final PendingTransaction pendingTransaction) {
+ if (isResumed
+ && (inProgress == STAGE_C)
+ && (xmrtoStatus != null)
+ && (xmrtoStatus.isCreated()
+ && (xmrtoStatus.getUuid().equals(txTag)))) {
+ this.pendingTransaction = pendingTransaction;
+ getView().post(new Runnable() {
+ @Override
+ public void run() {
+ hideProgress();
+ tvTxFee.setText(Wallet.getDisplayAmount(pendingTransaction.getFee()));
+ tvTxTotal.setText(Wallet.getDisplayAmount(
+ pendingTransaction.getFee() + pendingTransaction.getAmount()));
+ updateSendButton();
+ }
+ });
+ } else {
+ this.pendingTransaction = null;
+ sendListener.disposeTransaction();
+ }
+ }
+
+ @Override
+ public void createTransactionFailed(String errorText) {
+ Timber.e("CREATE TX FAILED");
+ if (pendingTransaction != null) {
+ throw new IllegalStateException("pendingTransaction is not null");
+ }
+ showStageError(getString(R.string.send_create_tx_error_title),
+ errorText,
+ getString(R.string.text_noretry_monero));
+ }
+
+ @Override
+ public boolean onValidateFields() {
+ return true;
+ }
+
+ private boolean isResumed = false;
+
+ @Override
+ public void onPauseFragment() {
+ isResumed = false;
+ stopSendTimer();
+ sendListener.disposeTransaction();
+ pendingTransaction = null;
+ inProgress = STAGE_X;
+ updateSendButton();
+ super.onPauseFragment();
+ }
+
+ @Override
+ public void onResumeFragment() {
+ super.onResumeFragment();
+ Timber.d("onResumeFragment()");
+ if (sendListener.getMode() != SendFragment.Mode.BTC) {
+ throw new IllegalStateException("Mode is not BTC!");
+ }
+ Helper.hideKeyboard(getActivity());
+ llStageA.setVisibility(View.INVISIBLE);
+ evStageA.hideProgress();
+ llStageB.setVisibility(View.INVISIBLE);
+ evStageB.hideProgress();
+ llStageC.setVisibility(View.INVISIBLE);
+ evStageC.hideProgress();
+ isResumed = true;
+ if ((pendingTransaction == null) && (inProgress == STAGE_X)) {
+ createOrder();
+ } // otherwise just sit there blank
+ // TODO: don't sit there blank - can this happen? should we just die?
+ }
+
+ private int sendCountdown = 0;
+ private static final int XMRTO_COUNTDOWN = 10 * 60; // 10 minutes
+ private static final int XMRTO_COUNTDOWN_STEP = 1; // 1 second
+
+ Runnable updateRunnable = null;
+
+ void startSendTimer() {
+ Timber.d("startSendTimer()");
+ sendCountdown = XMRTO_COUNTDOWN;
+ updateRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (!isAdded())
+ return;
+ Timber.d("updateTimer()");
+ if (sendCountdown <= 0) {
+ bSend.setEnabled(false);
+ sendCountdown = 0;
+ Toast.makeText(getContext(), getString(R.string.send_xmrto_timeout), Toast.LENGTH_SHORT).show();
+ }
+ int minutes = sendCountdown / 60;
+ int seconds = sendCountdown % 60;
+ String t = String.format("%d:%02d", minutes, seconds);
+ bSend.setText(getString(R.string.send_send_timed_label, t));
+ if (sendCountdown > 0) {
+ sendCountdown -= XMRTO_COUNTDOWN_STEP;
+ getView().postDelayed(this, XMRTO_COUNTDOWN_STEP * 1000);
+ }
+ }
+ };
+ getView().post(updateRunnable);
+ }
+
+ void stopSendTimer() {
+ getView().removeCallbacks(updateRunnable);
+ }
+
+ void updateSendButton() {
+ Timber.d("updateSendButton()");
+ if (pendingTransaction != null) {
+ llConfirmSend.setVisibility(View.VISIBLE);
+ bSend.setEnabled(sendCountdown > 0);
+ } else {
+ llConfirmSend.setVisibility(View.GONE);
+ bSend.setEnabled(false);
+ }
+ }
+
+ public void preSend() {
+ final Activity activity = getActivity();
+ View promptsView = getLayoutInflater().inflate(R.layout.prompt_password, null);
+ android.app.AlertDialog.Builder alertDialogBuilder = new android.app.AlertDialog.Builder(activity);
+ alertDialogBuilder.setView(promptsView);
+
+ final TextInputLayout etPassword = (TextInputLayout) promptsView.findViewById(R.id.etPassword);
+ etPassword.setHint(getString(R.string.prompt_send_password));
+
+ etPassword.getEditText().addTextChangedListener(new TextWatcher() {
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (etPassword.getError() != null) {
+ etPassword.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) {
+ }
+ });
+
+ alertDialogBuilder
+ .setCancelable(false)
+ .setPositiveButton("OK", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ String pass = etPassword.getEditText().getText().toString();
+ if (getActivityCallback().verifyWalletPassword(pass)) {
+ dialog.dismiss();
+ Helper.hideKeyboardAlways(activity);
+ send();
+ } else {
+ etPassword.setError(getString(R.string.bad_password));
+ }
+ }
+ })
+ .setNegativeButton("Cancel",
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ Helper.hideKeyboardAlways(activity);
+ dialog.cancel();
+ bSend.setEnabled(sendCountdown > 0); // allow to try again
+ }
+ });
+
+ final android.app.AlertDialog passwordDialog = alertDialogBuilder.create();
+ passwordDialog.setOnShowListener(new DialogInterface.OnShowListener() {
+ @Override
+ public void onShow(DialogInterface dialog) {
+ Button button = ((android.app.AlertDialog) dialog).getButton(android.app.AlertDialog.BUTTON_POSITIVE);
+ button.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ String pass = etPassword.getEditText().getText().toString();
+ if (getActivityCallback().verifyWalletPassword(pass)) {
+ Helper.hideKeyboardAlways(activity);
+ passwordDialog.dismiss();
+ send();
+ } else {
+ etPassword.setError(getString(R.string.bad_password));
+ }
+ }
+ });
+ }
+ });
+
+ Helper.showKeyboard(passwordDialog);
+
+ // accept keyboard "ok"
+ etPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
+ String pass = etPassword.getEditText().getText().toString();
+ if (getActivityCallback().verifyWalletPassword(pass)) {
+ Helper.hideKeyboardAlways(activity);
+ passwordDialog.dismiss();
+ send();
+ } else {
+ etPassword.setError(getString(R.string.bad_password));
+ }
+ return true;
+ }
+ return false;
+ }
+ });
+ passwordDialog.show();
+ }
+
+ // creates a pending transaction and calls us back with transactionCreated()
+ // or createTransactionFailed()
+ void prepareSend() {
+ if (!isResumed) return;
+ if ((xmrtoStatus == null)) {
+ throw new IllegalStateException("xmrtoStatus is null");
+ }
+ if ((!xmrtoStatus.isCreated())) {
+ throw new IllegalStateException("order is not created");
+ }
+ showProgress(3, getString(R.string.label_send_progress_create_tx));
+ TxData txData = sendListener.getTxData();
+ txData.setDestinationAddress(xmrtoStatus.getXmrReceivingAddress());
+ txData.setPaymentId(xmrtoStatus.getXmrRequiredPaymentIdShort());
+ txData.setAmount(Wallet.getAmountFromDouble(xmrtoStatus.getXmrAmountTotal()));
+ getActivityCallback().onPrepareSend(xmrtoStatus.getUuid(), txData);
+ }
+
+ SendFragment.Listener getActivityCallback() {
+ return sendListener.getActivityCallback();
+ }
+
+ private CreateOrder xmrtoOrder = null;
+
+ private void processCreateOrder(final CreateOrder createOrder) {
+ Timber.d("processCreateOrder %s", createOrder.getUuid());
+ xmrtoOrder = createOrder;
+ if (QueryOrderStatus.State.TO_BE_CREATED.toString().equals(createOrder.getState())) {
+ getView().post(new Runnable() {
+ @Override
+ public void run() {
+ tvTxXmrToKey.setText(createOrder.getUuid());
+ tvTxBtcAddress.setText(createOrder.getBtcDestAddress());
+ hideProgress();
+ }
+ });
+ queryOrder(createOrder.getUuid());
+ } else {
+ throw new IllegalStateException("Create Order is not TO_BE_CREATED");
+ }
+ }
+
+ private void processCreateOrderError(final Exception ex) {
+ Timber.e("processCreateOrderError %s", ex.getLocalizedMessage());
+ getView().post(new Runnable() {
+ @Override
+ public void run() {
+ if (ex instanceof XmrToException) {
+ XmrToException xmrEx = (XmrToException) ex;
+ XmrToError xmrErr = xmrEx.getError();
+ if (xmrErr != null) {
+ if (xmrErr.isRetryable()) {
+ showStageError(xmrErr.getErrorId().toString(), xmrErr.getErrorMsg(),
+ getString(R.string.text_retry));
+ evStageA.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ evStageA.setOnClickListener(null);
+ createOrder();
+ }
+ });
+ } else {
+ showStageError(xmrErr.getErrorId().toString(), xmrErr.getErrorMsg(),
+ getString(R.string.text_noretry));
+ }
+ } else {
+ showStageError(getString(R.string.label_generic_xmrto_error),
+ getString(R.string.text_generic_xmrto_error, xmrEx.getCode()),
+ getString(R.string.text_noretry));
+ }
+ } else {
+ evStageA.showMessage(getString(R.string.label_generic_xmrto_error),
+ ex.getLocalizedMessage(),
+ getString(R.string.text_noretry));
+ }
+ }
+ });
+ }
+
+ private void createOrder() {
+ if (!isResumed) return;
+ Timber.d("createOrder");
+ xmrtoOrder = null;
+ xmrtoStatus = null;
+ showProgress(1, getString(R.string.label_send_progress_xmrto_create));
+ TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
+ double btcAmount = txDataBtc.getBtcAmount();
+ getXmrToApi().createOrder(btcAmount, txDataBtc.getBtcAddress(), new XmrToCallback() {
+ @Override
+ public void onSuccess(CreateOrder createOrder) {
+ if (!isResumed) return;
+ if (xmrtoOrder != null) {
+ Timber.w("another ongoing create order request");
+ return;
+ }
+ processCreateOrder(createOrder);
+ }
+
+ @Override
+ public void onError(Exception ex) {
+ if (!isResumed) return;
+ if (xmrtoOrder != null) {
+ Timber.w("another ongoing create order request");
+ return;
+ }
+ processCreateOrderError(ex);
+ }
+ });
+ }
+
+ private QueryOrderStatus xmrtoStatus = null;
+
+ private void processQueryOrder(final QueryOrderStatus status) {
+ Timber.d("processQueryOrder %s for %s", status.getState().toString(), status.getUuid());
+ xmrtoStatus = status;
+ if (status.isCreated()) {
+ getView().post(new Runnable() {
+ @Override
+ public void run() {
+ NumberFormat df = NumberFormat.getInstance(Locale.US);
+ df.setMaximumFractionDigits(12);
+ String btcAmount = df.format(status.getBtcAmount());
+ String xmrAmountTotal = df.format(status.getXmrAmountTotal());
+ tvTxBtcAmount.setText(getString(R.string.text_send_btc_amount, btcAmount, xmrAmountTotal));
+ String xmrPriceBtc = df.format(status.getXmrPriceBtc());
+ tvTxBtcRate.setText(getString(R.string.text_send_btc_rate, xmrPriceBtc));
+
+ double calcRate = status.getBtcAmount() / status.getXmrPriceBtc();
+ Timber.i("Rates: %f / %f", calcRate, status.getXmrPriceBtc());
+
+ tvTxBtcAddress.setText(status.getBtcDestAddress()); // TODO test if this is different?
+
+ Timber.i("Expires @ %s, in %s seconds", status.getExpiresAt().toString(), status.getSecondsTillTimeout());
+
+ Timber.i("Status = %s", status.getState().toString());
+ tvTxXmrToKey.setText(status.getUuid());
+
+ Timber.d("AmountRemaining=%f, XmrAmountTotal=%f", status.getXmrAmountRemaining(), status.getXmrAmountTotal());
+ hideProgress();
+ startSendTimer();
+ prepareSend();
+ }
+ });
+ } else {
+ Timber.d("try again!");
+ handler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ queryOrder(status.getUuid());
+ }
+ }, QUERY_INTERVAL);
+ }
+ }
+
+ Handler handler = new Handler();
+
+ private void processQueryOrderError(final Exception ex) {
+ Timber.e("processQueryOrderError %s", ex.getLocalizedMessage());
+ getView().post(new Runnable() {
+ @Override
+ public void run() {
+ if (ex instanceof XmrToException) {
+ XmrToException xmrEx = (XmrToException) ex;
+ XmrToError xmrErr = xmrEx.getError();
+ if (xmrErr != null) {
+ if (xmrErr.isRetryable()) {
+ showStageError(xmrErr.getErrorId().toString(), xmrErr.getErrorMsg(),
+ getString(R.string.text_retry));
+ evStageB.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ evStageB.setOnClickListener(null);
+ queryOrder(xmrtoOrder.getUuid());
+ }
+ });
+ } else {
+ showStageError(xmrErr.getErrorId().toString(), xmrErr.getErrorMsg(),
+ getString(R.string.text_noretry));
+ }
+ } else {
+ showStageError(getString(R.string.label_generic_xmrto_error),
+ getString(R.string.text_generic_xmrto_error, xmrEx.getCode()),
+ getString(R.string.text_noretry));
+ }
+ } else {
+ evStageB.showMessage(getString(R.string.label_generic_xmrto_error),
+ ex.getLocalizedMessage(),
+ getString(R.string.text_noretry));
+ }
+ }
+ });
+ }
+
+ private void queryOrder(final String uuid) {
+ Timber.d("queryOrder(%s)", uuid);
+ if (!isResumed) return;
+ getView().post(new Runnable() {
+ @Override
+ public void run() {
+ xmrtoStatus = null;
+ showProgress(2, getString(R.string.label_send_progress_xmrto_query));
+ getXmrToApi().queryOrderStatus(uuid, new XmrToCallback() {
+ @Override
+ public void onSuccess(QueryOrderStatus status) {
+ if (!isResumed) return;
+ if (xmrtoOrder == null) return;
+ if (!status.getUuid().equals(xmrtoOrder.getUuid())) {
+ Timber.d("Query UUID does not match");
+ // ignore (we got a response to a stale request)
+ return;
+ }
+ if (xmrtoStatus != null)
+ throw new IllegalStateException("xmrtoStatus must be null here!");
+ processQueryOrder(status);
+ }
+
+ @Override
+ public void onError(Exception ex) {
+ if (!isResumed) return;
+ processQueryOrderError(ex);
+ }
+ });
+ }
+ });
+ }
+
+ private XmrToApi xmrToApi = null;
+
+ private final XmrToApi getXmrToApi() {
+ if (xmrToApi == null) {
+ synchronized (this) {
+ if (xmrToApi == null) {
+ xmrToApi = new XmrToApiImpl(OkHttpClientSingleton.getOkHttpClient(),
+ Helper.getXmrToBaseUrl());
+ }
+ }
+ }
+ return xmrToApi;
+ }
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcSuccessWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcSuccessWizardFragment.java
new file mode 100644
index 0000000..ca56e2a
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcSuccessWizardFragment.java
@@ -0,0 +1,267 @@
+/*
+ * 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.
+ */
+
+package com.m2049r.xmrwallet.fragment.send;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.m2049r.xmrwallet.R;
+import com.m2049r.xmrwallet.data.PendingTx;
+import com.m2049r.xmrwallet.data.TxDataBtc;
+import com.m2049r.xmrwallet.util.Helper;
+import com.m2049r.xmrwallet.util.OkHttpClientSingleton;
+import com.m2049r.xmrwallet.xmrto.XmrToException;
+import com.m2049r.xmrwallet.xmrto.api.QueryOrderStatus;
+import com.m2049r.xmrwallet.xmrto.api.XmrToApi;
+import com.m2049r.xmrwallet.xmrto.api.XmrToCallback;
+import com.m2049r.xmrwallet.xmrto.network.XmrToApiImpl;
+
+import java.text.NumberFormat;
+import java.util.Locale;
+
+import okhttp3.HttpUrl;
+import timber.log.Timber;
+
+public class SendBtcSuccessWizardFragment extends SendWizardFragment {
+
+ public static SendBtcSuccessWizardFragment newInstance(SendSuccessWizardFragment.Listener listener) {
+ SendBtcSuccessWizardFragment instance = new SendBtcSuccessWizardFragment();
+ instance.setSendListener(listener);
+ return instance;
+ }
+
+ SendSuccessWizardFragment.Listener sendListener;
+
+ public SendBtcSuccessWizardFragment setSendListener(SendSuccessWizardFragment.Listener listener) {
+ this.sendListener = listener;
+ return this;
+ }
+
+ ImageButton bCopyTxId;
+ private TextView tvTxId;
+ private TextView tvTxAddress;
+ private TextView tvTxPaymentId;
+ private TextView tvTxAmount;
+ private TextView tvTxFee;
+ private TextView tvXmrToAmount;
+ private TextView tvXmrToStatus;
+ private ImageView ivXmrToStatus;
+ private ImageView ivXmrToStatusBig;
+ private ProgressBar pbXmrto;
+ private TextView tvTxXmrToKey;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ Timber.d("onCreateView() %s", (String.valueOf(savedInstanceState)));
+
+ View view = inflater.inflate(
+ R.layout.fragment_send_btc_success, container, false);
+
+ bCopyTxId = (ImageButton) view.findViewById(R.id.bCopyTxId);
+ bCopyTxId.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Helper.clipBoardCopy(getActivity(), getString(R.string.label_send_txid), tvTxId.getText().toString());
+ Toast.makeText(getActivity(), getString(R.string.message_copy_txid), Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ tvXmrToAmount = (TextView) view.findViewById(R.id.tvXmrToAmount);
+ tvXmrToStatus = (TextView) view.findViewById(R.id.tvXmrToStatus);
+ ivXmrToStatus = (ImageView) view.findViewById(R.id.ivXmrToStatus);
+ ivXmrToStatusBig = (ImageView) view.findViewById(R.id.ivXmrToStatusBig);
+
+ tvTxId = (TextView) view.findViewById(R.id.tvTxId);
+ tvTxAddress = (TextView) view.findViewById(R.id.tvTxAddress);
+ tvTxPaymentId = (TextView) view.findViewById(R.id.tvTxPaymentId);
+ tvTxAmount = ((TextView) view.findViewById(R.id.tvTxAmount));
+ tvTxFee = (TextView) view.findViewById(R.id.tvTxFee);
+
+ pbXmrto = (ProgressBar) view.findViewById(R.id.pbXmrto);
+ pbXmrto.getIndeterminateDrawable().setColorFilter(0x61000000, android.graphics.PorterDuff.Mode.MULTIPLY);
+
+ tvTxXmrToKey = (TextView) view.findViewById(R.id.tvTxXmrToKey);
+ tvTxXmrToKey.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_xmrtokey), tvTxXmrToKey.getText().toString());
+ Toast.makeText(getActivity(), getString(R.string.message_copy_xmrtokey), Toast.LENGTH_SHORT).show();
+ ;
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public boolean onValidateFields() {
+ return true;
+ }
+
+ private boolean isResumed = false;
+
+ @Override
+ public void onPauseFragment() {
+ isResumed = false;
+ super.onPauseFragment();
+ }
+
+ TxDataBtc btcData = null;
+
+ @Override
+ public void onResumeFragment() {
+ super.onResumeFragment();
+ Timber.d("onResumeFragment()");
+ Helper.hideKeyboard(getActivity());
+ isResumed = true;
+
+ btcData = (TxDataBtc) sendListener.getTxData();
+ tvTxAddress.setText(btcData.getDestinationAddress());
+ String paymentId = btcData.getPaymentId();
+ if ((paymentId != null) && (!paymentId.isEmpty())) {
+ tvTxPaymentId.setText(btcData.getPaymentId());
+ } else {
+ tvTxPaymentId.setText("-");
+ }
+
+ final PendingTx committedTx = sendListener.getCommittedTx();
+ if (committedTx != null) {
+ tvTxId.setText(committedTx.txId);
+ bCopyTxId.setEnabled(true);
+ bCopyTxId.setImageResource(R.drawable.ic_content_copy_black_24dp);
+ tvTxAmount.setText(getString(R.string.send_amount, Helper.getDisplayAmount(committedTx.amount)));
+ tvTxFee.setText(getString(R.string.send_fee, Helper.getDisplayAmount(committedTx.fee)));
+ if (btcData != null) {
+ NumberFormat df = NumberFormat.getInstance(Locale.US);
+ df.setMaximumFractionDigits(12);
+ String btcAmount = df.format(btcData.getBtcAmount());
+ tvXmrToAmount.setText(getString(R.string.info_send_xmrto_success_btc, btcAmount));
+ //TODO btcData.getBtcAddress();
+ tvTxXmrToKey.setText(btcData.getXmrtoUuid());
+ queryOrder();
+ } else {
+ throw new IllegalStateException("btcData is null");
+ }
+ }
+ sendListener.enableDone();
+ }
+
+ private final int QUERY_INTERVAL = 1000; // ms
+
+ private void processQueryOrder(final QueryOrderStatus status) {
+ Timber.d("processQueryOrder %s for %s", status.getState().toString(), status.getUuid());
+ if (!btcData.getXmrtoUuid().equals(status.getUuid()))
+ throw new IllegalStateException("UUIDs do not match!");
+ if (isResumed && (getView() != null))
+ getView().post(new Runnable() {
+ @Override
+ public void run() {
+ showXmrToStatus(status);
+ if (!status.isTerminal()) {
+ getView().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ queryOrder();
+ }
+ }, QUERY_INTERVAL);
+ }
+ }
+ });
+ }
+
+ private void queryOrder() {
+ Timber.d("queryOrder(%s)", btcData.getXmrtoUuid());
+ if (!isResumed) return;
+ getXmrToApi().queryOrderStatus(btcData.getXmrtoUuid(), new XmrToCallback() {
+ @Override
+ public void onSuccess(QueryOrderStatus status) {
+ if (!isAdded()) return;
+ processQueryOrder(status);
+ }
+
+ @Override
+ public void onError(final Exception ex) {
+ if (!isResumed) return;
+ Timber.e(ex);
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (ex instanceof XmrToException) {
+ Toast.makeText(getActivity(), ((XmrToException) ex).getError().getErrorMsg(), Toast.LENGTH_LONG).show();
+ } else {
+ Toast.makeText(getActivity(), ex.getLocalizedMessage(), Toast.LENGTH_LONG).show();
+ }
+ }
+ });
+ }
+ });
+ }
+
+ private int statusResource = 0;
+
+ void showXmrToStatus(final QueryOrderStatus status) {
+ if (status.isError()) {
+ tvXmrToStatus.setText(getString(R.string.info_send_xmrto_error, status.toString()));
+ statusResource = R.drawable.ic_error_red_24dp;
+ pbXmrto.getIndeterminateDrawable().setColorFilter(0xff8b0000, android.graphics.PorterDuff.Mode.MULTIPLY);
+ } else if (status.isSent()) {
+ tvXmrToStatus.setText(getString(R.string.info_send_xmrto_sent));
+ statusResource = R.drawable.ic_success_green_24dp;
+ pbXmrto.getIndeterminateDrawable().setColorFilter(0xFF417505, android.graphics.PorterDuff.Mode.MULTIPLY);
+ } else if (status.isPending()) {
+ if (status.isPaid()) {
+ tvXmrToStatus.setText(getString(R.string.info_send_xmrto_paid));
+ } else {
+ tvXmrToStatus.setText(getString(R.string.info_send_xmrto_unpaid));
+ }
+ statusResource = R.drawable.ic_pending_orange_24dp;
+ pbXmrto.getIndeterminateDrawable().setColorFilter(0xFFFF6105, android.graphics.PorterDuff.Mode.MULTIPLY);
+ } else {
+ throw new IllegalStateException("status is broken: " + status.toString());
+ }
+ ivXmrToStatus.setImageResource(statusResource);
+ if (status.isTerminal()) {
+ pbXmrto.setVisibility(View.INVISIBLE);
+ ivXmrToStatus.setVisibility(View.GONE);
+ ivXmrToStatusBig.setImageResource(statusResource);
+ ivXmrToStatusBig.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private XmrToApi xmrToApi = null;
+
+ private final XmrToApi getXmrToApi() {
+ if (xmrToApi == null) {
+ synchronized (this) {
+ if (xmrToApi == null) {
+ xmrToApi = new XmrToApiImpl(OkHttpClientSingleton.getOkHttpClient(),
+ Helper.getXmrToBaseUrl());
+ }
+ }
+ }
+ return xmrToApi;
+ }
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendConfirm.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendConfirm.java
new file mode 100644
index 0000000..965ca4c
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendConfirm.java
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+package com.m2049r.xmrwallet.fragment.send;
+
+import com.m2049r.xmrwallet.model.PendingTransaction;
+
+interface SendConfirm {
+ void sendFailed();
+
+ void createTransactionFailed(String errorText);
+
+ void transactionCreated(String txTag, PendingTransaction pendingTransaction);
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/SendConfirmWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendConfirmWizardFragment.java
similarity index 89%
rename from app/src/main/java/com/m2049r/xmrwallet/SendConfirmWizardFragment.java
rename to app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendConfirmWizardFragment.java
index 2d779b0..73e3d91 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/SendConfirmWizardFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendConfirmWizardFragment.java
@@ -14,12 +14,13 @@
* limitations under the License.
*/
-package com.m2049r.xmrwallet;
+package com.m2049r.xmrwallet.fragment.send;
import android.app.Activity;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.design.widget.TextInputLayout;
+import android.support.v7.app.AlertDialog;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
@@ -30,14 +31,16 @@ import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.TextView;
+import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.TxData;
import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.Helper;
+import com.m2049r.xmrwallet.util.UserNotes;
import timber.log.Timber;
-public class SendConfirmWizardFragment extends SendWizardFragment {
+public class SendConfirmWizardFragment extends SendWizardFragment implements SendConfirm {
public static SendConfirmWizardFragment newInstance(Listener listener) {
SendConfirmWizardFragment instance = new SendConfirmWizardFragment();
@@ -57,11 +60,11 @@ public class SendConfirmWizardFragment extends SendWizardFragment {
TxData getTxData();
- String getNotes();
-
void commitTransaction();
void disposeTransaction();
+
+ SendFragment.Mode getMode();
}
private TextView tvTxAddress;
@@ -122,8 +125,11 @@ public class SendConfirmWizardFragment extends SendWizardFragment {
PendingTransaction pendingTransaction = null;
+ @Override
// callback from wallet when PendingTransaction created
- void transactionCreated(PendingTransaction pendingTransaction) {
+ public void transactionCreated(String txTag, PendingTransaction pendingTransaction) {
+ // ignore txTag - the app flow ensures this is the correct tx
+ // TODO: use the txTag
hideProgress();
if (isResumed) {
this.pendingTransaction = pendingTransaction;
@@ -138,10 +144,22 @@ public class SendConfirmWizardFragment extends SendWizardFragment {
pbProgressSend.setVisibility(View.VISIBLE);
}
- void sendFailed() {
+ @Override
+ public void sendFailed() {
pbProgressSend.setVisibility(View.INVISIBLE);
}
+ @Override
+ public void createTransactionFailed(String errorText) {
+ hideProgress();
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setCancelable(true).
+ setTitle(getString(R.string.send_create_tx_error_title)).
+ setMessage(errorText).
+ create().
+ show();
+ }
+
@Override
public boolean onValidateFields() {
return true;
@@ -173,9 +191,9 @@ public class SendConfirmWizardFragment extends SendWizardFragment {
} else {
tvTxPaymentId.setText("-");
}
- String notes = sendListener.getNotes();
- if ((notes != null) && (!notes.isEmpty())) {
- tvTxNotes.setText(sendListener.getNotes());
+ UserNotes notes = sendListener.getTxData().getUserNotes();
+ if ((notes != null) && (!notes.note.isEmpty())) {
+ tvTxNotes.setText(notes.note);
} else {
tvTxNotes.setText("-");
}
@@ -296,8 +314,10 @@ public class SendConfirmWizardFragment extends SendWizardFragment {
passwordDialog.show();
}
+ // creates a pending transaction and calls us back with transactionCreated()
+ // or createTransactionFailed()
void prepareSend(TxData txData) {
- getActivityCallback().onPrepareSend(txData);
+ getActivityCallback().onPrepareSend(null, txData);
}
SendFragment.Listener getActivityCallback() {
diff --git a/app/src/main/java/com/m2049r/xmrwallet/SendFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendFragment.java
similarity index 75%
rename from app/src/main/java/com/m2049r/xmrwallet/SendFragment.java
rename to app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendFragment.java
index d8dd537..664ee60 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/SendFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendFragment.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.m2049r.xmrwallet;
+package com.m2049r.xmrwallet.fragment.send;
import android.content.Context;
import android.graphics.drawable.Drawable;
@@ -22,9 +22,8 @@ import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentPagerAdapter;
+import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
-import android.support.v7.app.AlertDialog;
import android.text.InputType;
import android.util.SparseArray;
import android.view.LayoutInflater;
@@ -36,12 +35,16 @@ import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
+import com.m2049r.xmrwallet.OnBackPressedListener;
+import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.data.PendingTx;
import com.m2049r.xmrwallet.data.TxData;
+import com.m2049r.xmrwallet.data.TxDataBtc;
import com.m2049r.xmrwallet.layout.SpendViewPager;
import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.util.Helper;
+import com.m2049r.xmrwallet.util.UserNotes;
import com.m2049r.xmrwallet.widget.DotBar;
import com.m2049r.xmrwallet.widget.Toolbar;
@@ -62,11 +65,11 @@ public class SendFragment extends Fragment
public interface Listener {
long getTotalFunds();
- void onPrepareSend(TxData data);
+ void onPrepareSend(String tag, TxData data);
boolean verifyWalletPassword(String password);
- void onSend(String notes);
+ void onSend(UserNotes notes);
void onDisposeRequest();
@@ -231,7 +234,42 @@ public class SendFragment extends Fragment
}
}
- public class SpendPagerAdapter extends FragmentPagerAdapter {
+ enum Mode {
+ XMR, BTC
+ }
+
+ Mode mode = Mode.XMR;
+
+ @Override
+ public void setMode(Mode aMode) {
+ if (mode != aMode) {
+ mode = aMode;
+ switch (aMode) {
+ case XMR:
+ txData = new TxData();
+ break;
+ case BTC:
+ txData = new TxDataBtc();
+ break;
+ default:
+ throw new IllegalArgumentException("Mode " + String.valueOf(aMode) + " unknown!");
+ }
+ getView().post(new Runnable() {
+ @Override
+ public void run() {
+ pagerAdapter.notifyDataSetChanged();
+ }
+ });
+ Timber.d("New Mode = " + mode.toString());
+ }
+ }
+
+ @Override
+ public Mode getMode() {
+ return mode;
+ }
+
+ public class SpendPagerAdapter extends FragmentStatePagerAdapter {
private static final int POS_ADDRESS = 0;
private static final int POS_AMOUNT = 1;
private static final int POS_SETTINGS = 2;
@@ -257,6 +295,7 @@ public class SendFragment extends Fragment
@Override
public Object instantiateItem(ViewGroup container, int position) {
+ Timber.d("instantiateItem %d", position);
SendWizardFragment fragment = (SendWizardFragment) super.instantiateItem(container, position);
myFragments.put(position, new WeakReference<>(fragment));
return fragment;
@@ -264,6 +303,7 @@ public class SendFragment extends Fragment
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
+ Timber.d("destroyItem %d", position);
myFragments.remove(position);
super.destroyItem(container, position, object);
}
@@ -279,19 +319,39 @@ public class SendFragment extends Fragment
@Override
public SendWizardFragment getItem(int position) {
Timber.d("getItem(%d) CREATE", position);
- switch (position) {
- case POS_ADDRESS:
- return SendAddressWizardFragment.newInstance(SendFragment.this);
- case POS_AMOUNT:
- return SendAmountWizardFragment.newInstance(SendFragment.this);
- case POS_SETTINGS:
- return SendSettingsWizardFragment.newInstance(SendFragment.this);
- case POS_CONFIRM:
- return SendConfirmWizardFragment.newInstance(SendFragment.this);
- case POS_SUCCESS:
- return SendSuccessWizardFragment.newInstance(SendFragment.this);
- default:
- throw new IllegalArgumentException("no such send position(" + position + ")");
+ Timber.d("Mode=" + mode.toString());
+ if (mode == Mode.XMR) {
+ switch (position) {
+ case POS_ADDRESS:
+ return SendAddressWizardFragment.newInstance(SendFragment.this);
+ case POS_AMOUNT:
+ return SendAmountWizardFragment.newInstance(SendFragment.this);
+ case POS_SETTINGS:
+ return SendSettingsWizardFragment.newInstance(SendFragment.this);
+ case POS_CONFIRM:
+ return SendConfirmWizardFragment.newInstance(SendFragment.this);
+ case POS_SUCCESS:
+ return SendSuccessWizardFragment.newInstance(SendFragment.this);
+ default:
+ throw new IllegalArgumentException("no such send position(" + position + ")");
+ }
+ } else if (mode == Mode.BTC) {
+ switch (position) {
+ case POS_ADDRESS:
+ return SendAddressWizardFragment.newInstance(SendFragment.this);
+ case POS_AMOUNT:
+ return SendBtcAmountWizardFragment.newInstance(SendFragment.this);
+ case POS_SETTINGS:
+ return SendSettingsWizardFragment.newInstance(SendFragment.this);
+ case POS_CONFIRM:
+ return SendBtcConfirmWizardFragment.newInstance(SendFragment.this);
+ case POS_SUCCESS:
+ return SendBtcSuccessWizardFragment.newInstance(SendFragment.this);
+ default:
+ throw new IllegalArgumentException("no such send position(" + position + ")");
+ }
+ } else {
+ throw new IllegalStateException("Unknown mode!");
}
}
@@ -314,24 +374,25 @@ public class SendFragment extends Fragment
return null;
}
}
+
+ @Override
+ public int getItemPosition(Object object) {
+ Timber.d("getItemPosition %s", String.valueOf(object));
+ if ((object instanceof SendAddressWizardFragment) || (object instanceof SendSettingsWizardFragment)) {
+ return POSITION_UNCHANGED;
+ } else {
+ return POSITION_NONE;
+ }
+ }
}
@Override
public TxData getTxData() {
- return new TxData(sendAddress, sendPaymentId, sendAmount, sendMixin, sendPriority);
+ return txData;
}
- @Override
- public String getNotes() {
- return sendNotes;
- }
+ private TxData txData = new TxData();
- private String sendAddress;
- private String sendPaymentId;
- private long sendAmount;
- private PendingTransaction.Priority sendPriority;
- private int sendMixin;
- private String sendNotes;
private BarcodeData barcodeData;
// Listeners
@@ -347,36 +408,6 @@ public class SendFragment extends Fragment
return data;
}
- @Override
- public void setAddress(final String address) {
- sendAddress = address;
- }
-
- @Override
- public void setPaymentId(final String paymentId) {
- sendPaymentId = paymentId;
- }
-
- @Override
- public void setAmount(final long amount) {
- sendAmount = amount;
- }
-
- @Override
- public void setPriority(final PendingTransaction.Priority priority) {
- sendPriority = priority;
- }
-
- @Override
- public void setMixin(final int mixin) {
- sendMixin = mixin;
- }
-
- @Override
- public void setNotes(final String notes) {
- sendNotes = notes;
- }
-
boolean isComitted() {
return committedTx != null;
}
@@ -391,9 +422,9 @@ public class SendFragment extends Fragment
@Override
public void commitTransaction() {
- Timber.d("REALLY SEND A %s", getNotes());
+ Timber.d("REALLY SEND");
disableNavigation(); // committed - disable all navigation
- activityCallback.onSend(getNotes());
+ activityCallback.onSend(txData.getUserNotes());
committedTx = pendingTx;
}
@@ -418,12 +449,11 @@ public class SendFragment extends Fragment
// callbacks from send service
- public void onTransactionCreated(PendingTransaction pendingTransaction) {
- //public void onTransactionCreated(TestTransaction pendingTransaction) {
- final SendConfirmWizardFragment confirmFragment = getConfirmFragment();
- if (confirmFragment != null) {
+ public void onTransactionCreated(final String txTag, final PendingTransaction pendingTransaction) {
+ final SendConfirm confirm = getSendConfirm();
+ if (confirm != null) {
pendingTx = new PendingTx(pendingTransaction);
- confirmFragment.transactionCreated(pendingTransaction);
+ confirm.transactionCreated(txTag, pendingTransaction);
} else {
// not in confirm fragment => dispose & move on
disposeTransaction();
@@ -443,23 +473,16 @@ public class SendFragment extends Fragment
}
public void onCreateTransactionFailed(String errorText) {
- final SendConfirmWizardFragment confirmFragment = getConfirmFragment();
- if (confirmFragment != null) {
- confirmFragment.hideProgress();
-
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setCancelable(true).
- setTitle(getString(R.string.send_create_tx_error_title)).
- setMessage(errorText).
- create().
- show();
+ final SendConfirm confirm = getSendConfirm();
+ if (confirm != null) {
+ confirm.createTransactionFailed(errorText);
}
}
- SendConfirmWizardFragment getConfirmFragment() {
+ SendConfirm getSendConfirm() {
final SendWizardFragment fragment = pagerAdapter.getFragment(SpendPagerAdapter.POS_CONFIRM);
- if (fragment instanceof SendConfirmWizardFragment) {
- return (SendConfirmWizardFragment) fragment;
+ if (fragment instanceof SendConfirm) {
+ return (SendConfirm) fragment;
} else {
return null;
}
@@ -469,8 +492,8 @@ public class SendFragment extends Fragment
Timber.d("txid=%s", txId);
pagerAdapter.addSuccess();
Timber.d("numPages=%d", spendViewPager.getAdapter().getCount());
- spendViewPager.setCurrentItem(SpendPagerAdapter.POS_SUCCESS);
activityCallback.setToolbarButton(Toolbar.BUTTON_NONE);
+ spendViewPager.setCurrentItem(SpendPagerAdapter.POS_SUCCESS);
}
public void onSendTransactionFailed(final String error) {
@@ -478,7 +501,7 @@ public class SendFragment extends Fragment
committedTx = null;
Toast.makeText(getContext(), getString(R.string.status_transaction_failed, error), Toast.LENGTH_SHORT).show();
enableNavigation();
- final SendConfirmWizardFragment fragment = getConfirmFragment();
+ final SendConfirm fragment = getSendConfirm();
if (fragment != null) {
fragment.sendFailed();
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/SendSettingsWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendSettingsWizardFragment.java
similarity index 86%
rename from app/src/main/java/com/m2049r/xmrwallet/SendSettingsWizardFragment.java
rename to app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendSettingsWizardFragment.java
index 8746c22..42da401 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/SendSettingsWizardFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendSettingsWizardFragment.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.m2049r.xmrwallet;
+package com.m2049r.xmrwallet.fragment.send;
import android.os.Bundle;
import android.text.InputType;
@@ -27,8 +27,11 @@ import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
+import com.m2049r.xmrwallet.R;
+import com.m2049r.xmrwallet.data.TxData;
import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.util.Helper;
+import com.m2049r.xmrwallet.util.UserNotes;
import timber.log.Timber;
@@ -48,12 +51,7 @@ public class SendSettingsWizardFragment extends SendWizardFragment {
}
interface Listener {
-
- void setPriority(final PendingTransaction.Priority priority);
-
- void setMixin(final int mixin);
-
- void setNotes(final String notes);
+ TxData getTxData();
}
final static int Mixins[] = {4, 7, 12, 25}; // must match the layout XML
@@ -103,13 +101,10 @@ public class SendSettingsWizardFragment extends SendWizardFragment {
@Override
public boolean onValidateFields() {
if (sendListener != null) {
- int mixin = Mixins[sMixin.getSelectedItemPosition()];
- int priorityIndex = sPriority.getSelectedItemPosition();
- PendingTransaction.Priority priority = Priorities[priorityIndex];
- sendListener.setPriority(priority);
- sendListener.setMixin(mixin);
- String notes = etNotes.getText().toString();
- sendListener.setNotes(notes);
+ TxData txData = sendListener.getTxData();
+ txData.setPriority(Priorities[sPriority.getSelectedItemPosition()]);
+ txData.setMixin(Mixins[sMixin.getSelectedItemPosition()]);
+ txData.setUserNotes(new UserNotes(etNotes.getText().toString()));
}
return true;
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/SendSuccessWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendSuccessWizardFragment.java
similarity index 70%
rename from app/src/main/java/com/m2049r/xmrwallet/SendSuccessWizardFragment.java
rename to app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendSuccessWizardFragment.java
index 9a21dcf..65cf20d 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/SendSuccessWizardFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendSuccessWizardFragment.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.m2049r.xmrwallet;
+package com.m2049r.xmrwallet.fragment.send;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -24,10 +24,10 @@ import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
-import com.m2049r.xmrwallet.model.Wallet;
-import com.m2049r.xmrwallet.util.Helper;
+import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.PendingTx;
import com.m2049r.xmrwallet.data.TxData;
+import com.m2049r.xmrwallet.util.Helper;
import timber.log.Timber;
@@ -47,23 +47,21 @@ public class SendSuccessWizardFragment extends SendWizardFragment {
}
interface Listener {
- String getNotes();
-
TxData getTxData();
PendingTx getCommittedTx();
void enableDone();
+
+ SendFragment.Mode getMode();
}
- ImageButton bCopyAddress;
+ ImageButton bCopyTxId;
private TextView tvTxId;
private TextView tvTxAddress;
private TextView tvTxPaymentId;
- private TextView tvTxNotes;
private TextView tvTxAmount;
private TextView tvTxFee;
- private TextView tvTxTotal;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
@@ -74,21 +72,20 @@ public class SendSuccessWizardFragment extends SendWizardFragment {
View view = inflater.inflate(
R.layout.fragment_send_success, container, false);
- bCopyAddress = (ImageButton) view.findViewById(R.id.bCopyAddress);
- bCopyAddress.setOnClickListener(new View.OnClickListener() {
+ bCopyTxId = (ImageButton) view.findViewById(R.id.bCopyTxId);
+ bCopyTxId.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- copyAddress();
+ Helper.clipBoardCopy(getActivity(), getString(R.string.label_send_txid), tvTxId.getText().toString());
+ Toast.makeText(getActivity(), getString(R.string.message_copy_address), Toast.LENGTH_SHORT).show();
}
});
tvTxId = (TextView) view.findViewById(R.id.tvTxId);
tvTxAddress = (TextView) view.findViewById(R.id.tvTxAddress);
tvTxPaymentId = (TextView) view.findViewById(R.id.tvTxPaymentId);
- tvTxNotes = (TextView) view.findViewById(R.id.tvTxNotes);
tvTxAmount = ((TextView) view.findViewById(R.id.tvTxAmount));
tvTxFee = (TextView) view.findViewById(R.id.tvTxFee);
- tvTxTotal = (TextView) view.findViewById(R.id.tvTxTotal);
return view;
}
@@ -117,30 +114,15 @@ public class SendSuccessWizardFragment extends SendWizardFragment {
} else {
tvTxPaymentId.setText("-");
}
- String notes = sendListener.getNotes();
- if ((notes != null) && (!notes.isEmpty())) {
- tvTxNotes.setText(sendListener.getNotes());
- } else {
- tvTxNotes.setText("-");
- }
final PendingTx committedTx = sendListener.getCommittedTx();
if (committedTx != null) {
tvTxId.setText(committedTx.txId);
- bCopyAddress.setEnabled(true);
- bCopyAddress.setImageResource(R.drawable.ic_content_copy_black_24dp);
- tvTxAmount.setText(Wallet.getDisplayAmount(committedTx.amount));
- tvTxFee.setText(Wallet.getDisplayAmount(committedTx.fee));
- //tvTxDust.setText(Wallet.getDisplayAmount(pendingTransaction.getDust()));
- tvTxTotal.setText(Wallet.getDisplayAmount(
- committedTx.fee + committedTx.amount));
+ bCopyTxId.setEnabled(true);
+ bCopyTxId.setImageResource(R.drawable.ic_content_copy_black_24dp);
+ tvTxAmount.setText(getString(R.string.send_amount, Helper.getDisplayAmount(committedTx.amount)));
+ tvTxFee.setText(getString(R.string.send_fee, Helper.getDisplayAmount(committedTx.fee)));
}
sendListener.enableDone();
}
-
- void copyAddress() {
- Helper.clipBoardCopy(getActivity(), getString(R.string.label_send_txid), tvTxId.getText().toString());
- Toast.makeText(getActivity(), getString(R.string.message_copy_address), Toast.LENGTH_SHORT).show();
- }
-
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/SendWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendWizardFragment.java
similarity index 95%
rename from app/src/main/java/com/m2049r/xmrwallet/SendWizardFragment.java
rename to app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendWizardFragment.java
index ae99bd7..0ef911d 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/SendWizardFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendWizardFragment.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.m2049r.xmrwallet;
+package com.m2049r.xmrwallet.fragment.send;
import android.support.v4.app.Fragment;
diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/SpendViewPager.java b/app/src/main/java/com/m2049r/xmrwallet/layout/SpendViewPager.java
index 4fee6b3..1ddde3e 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/layout/SpendViewPager.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/layout/SpendViewPager.java
@@ -21,7 +21,7 @@ import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
-import com.m2049r.xmrwallet.SendFragment;
+import com.m2049r.xmrwallet.fragment.send.SendFragment;
public class SpendViewPager extends ViewPager {
diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java b/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java
index 8c4157c..b44d84d 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java
@@ -22,11 +22,13 @@ import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.ImageView;
import android.widget.TextView;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.util.Helper;
+import com.m2049r.xmrwallet.util.UserNotes;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -101,6 +103,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter 0)) {
String fee = Helper.getDisplayAmount(infoItem.fee, 5);
- this.tvFee.setText(context.getString(R.string.tx_list_fee, fee));
+ tvFee.setText(context.getString(R.string.tx_list_fee, fee));
+ tvFee.setVisibility(View.VISIBLE);
} else {
- this.tvFee.setText("");
+ tvFee.setText("");
+ tvFee.setVisibility(View.GONE);
}
if (infoItem.isFailed) {
this.tvAmount.setText(context.getString(R.string.tx_list_amount_failed, displayAmount));
this.tvFee.setText(context.getString(R.string.tx_list_failed_text));
+ tvFee.setVisibility(View.VISIBLE);
setTxColour(failedColour);
} else if (infoItem.isPending) {
setTxColour(pendingColour);
@@ -156,11 +162,20 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter 35)
+ return false;
+ byte[] decoded = decodeBase58To25Bytes(addrress);
+ if (decoded == null)
+ return false;
+
+ if (!testnet) {
+ if ((decoded[0] != 0x00) && (decoded[0] != 0x05)) return false;
+ } else {
+ if ((decoded[0] != 0x6f) && (decoded[0] != 0xc4)) return false;
+ }
+
+ byte[] hash1 = sha256(Arrays.copyOfRange(decoded, 0, 21));
+ byte[] hash2 = sha256(hash1);
+
+ return Arrays.equals(Arrays.copyOfRange(hash2, 0, 4), Arrays.copyOfRange(decoded, 21, 25));
+ }
+
+ private static byte[] decodeBase58To25Bytes(String input) {
+ BigInteger num = BigInteger.ZERO;
+ for (char t : input.toCharArray()) {
+ int p = ALPHABET.indexOf(t);
+ if (p == -1)
+ return null;
+ num = num.multiply(BigInteger.valueOf(58)).add(BigInteger.valueOf(p));
+ }
+
+ byte[] result = new byte[25];
+ byte[] numBytes = num.toByteArray();
+ System.arraycopy(numBytes, 0, result, result.length - numBytes.length, numBytes.length);
+ return result;
+ }
+
+ private static byte[] sha256(byte[] data) {
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA-256");
+ md.update(data);
+ return md.digest();
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java
index ec05c68..118b7b0 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java
@@ -38,6 +38,7 @@ import android.view.inputmethod.InputMethodManager;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.model.Wallet;
+import com.m2049r.xmrwallet.model.WalletManager;
import java.io.File;
import java.io.IOException;
@@ -49,6 +50,7 @@ import java.util.Locale;
import javax.net.ssl.HttpsURLConnection;
+import okhttp3.HttpUrl;
import timber.log.Timber;
public class Helper {
@@ -160,7 +162,7 @@ public class Helper {
}
static public String getDisplayAmount(long amount) {
- return getDisplayAmount(amount, 20);
+ return getDisplayAmount(amount, 12);
}
static public String getDisplayAmount(long amount, int maxDecimals) {
@@ -271,4 +273,11 @@ public class Helper {
return ShakeAnimation;
}
+ static public HttpUrl getXmrToBaseUrl() {
+ if ((WalletManager.getInstance() == null) || WalletManager.getInstance().isTestNet()) {
+ return HttpUrl.parse("https://test.xmr.to/api/v2/xmr2btc/");
+ } else {
+ return HttpUrl.parse("https://xmr.to/api/v2/xmr2btc/");
+ }
+ }
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/UserNotes.java b/app/src/main/java/com/m2049r/xmrwallet/util/UserNotes.java
new file mode 100644
index 0000000..e6e463c
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/util/UserNotes.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+package com.m2049r.xmrwallet.util;
+
+import com.m2049r.xmrwallet.xmrto.api.QueryOrderStatus;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import timber.log.Timber;
+
+public class UserNotes {
+ public String txNotes = "";
+ public String note = "";
+ public String xmrtoKey = null;
+ public String xmrtoAmount = null; // could be a double - but we are not doing any calculations
+ public String xmrtoDestination = null;
+
+ public UserNotes(final String txNotes) {
+ if (txNotes == null) {
+ return;
+ }
+ this.txNotes = txNotes;
+ Pattern p = Pattern.compile("^\\{(xmrto-\\w{6}),([0-9.]*)BTC,(\\w*)\\} ?(.*)");
+ Matcher m = p.matcher(txNotes);
+ if (m.find()) {
+ xmrtoKey = m.group(1);
+ xmrtoAmount = m.group(2);
+ xmrtoDestination = m.group(3);
+ note = m.group(4);
+ } else {
+ note = txNotes;
+ }
+ }
+
+ public void setNote(String newNote) {
+ if (newNote != null) {
+ note = newNote;
+ } else {
+ note = "";
+ }
+ txNotes = buildTxNote();
+ }
+
+ public void setXmrtoStatus(QueryOrderStatus xmrtoStatus) {
+ if (xmrtoStatus != null) {
+ xmrtoKey = xmrtoStatus.getUuid();
+ xmrtoAmount = String.valueOf(xmrtoStatus.getBtcAmount());
+ xmrtoDestination = xmrtoStatus.getBtcDestAddress();
+ } else {
+ xmrtoKey = null;
+ xmrtoAmount = null;
+ xmrtoDestination = null;
+ }
+ txNotes = buildTxNote();
+ }
+
+ private String buildTxNote() {
+ StringBuffer sb = new StringBuffer();
+ if (xmrtoKey != null) {
+ if ((xmrtoAmount == null) || (xmrtoDestination == null))
+ throw new IllegalArgumentException("Broken notes");
+ sb.append("{");
+ sb.append(xmrtoKey);
+ sb.append(",");
+ sb.append(xmrtoAmount);
+ sb.append("BTC,");
+ sb.append(xmrtoDestination);
+ sb.append("}");
+ if ((note != null) && (!note.isEmpty()))
+ sb.append(" ");
+ }
+ sb.append(note);
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeBtcTextView.java b/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeBtcTextView.java
new file mode 100644
index 0000000..5a86ed1
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeBtcTextView.java
@@ -0,0 +1,198 @@
+/*
+ * 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 = (TextView) findViewById(R.id.tvAmountA);
+ tvAmountB = (TextView) findViewById(R.id.tvAmountB);
+ sCurrencyA = (Spinner) findViewById(R.id.sCurrencyA);
+ sCurrencyB = (Spinner) findViewById(R.id.sCurrencyB);
+
+ ArrayAdapter btcAdapter = new ArrayAdapter(getContext(),
+ android.R.layout.simple_spinner_item,
+ new String[]{"BTC"});
+ sCurrencyA.setAdapter(btcAdapter);
+ sCurrencyA.setEnabled(false);
+ ArrayAdapter xmrAdapter = new ArrayAdapter(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();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeTextView.java b/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeTextView.java
index 1a6edbf..25a1225 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeTextView.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeTextView.java
@@ -436,6 +436,9 @@ public class ExchangeTextView extends LinearLayout
public void onPointPressed() {
//TODO locale?
if (tvAmountA.getText().toString().indexOf('.') == -1) {
+ if (tvAmountA.getText().toString().isEmpty()) {
+ tvAmountA.append("0");
+ }
tvAmountA.append(".");
}
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/widget/SendProgressView.java b/app/src/main/java/com/m2049r/xmrwallet/widget/SendProgressView.java
new file mode 100644
index 0000000..1ffcdab
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/widget/SendProgressView.java
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+package com.m2049r.xmrwallet.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.m2049r.xmrwallet.R;
+
+public class SendProgressView extends LinearLayout {
+
+ public SendProgressView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initializeViews(context);
+ }
+
+ public SendProgressView(Context context,
+ AttributeSet attrs,
+ int defStyle) {
+ super(context, attrs, defStyle);
+ initializeViews(context);
+ }
+
+ private void initializeViews(Context context) {
+ LayoutInflater inflater = (LayoutInflater) context
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ inflater.inflate(R.layout.view_send_progress, this);
+ }
+
+
+ View pbProgress;
+ View llMessage;
+ TextView tvCode;
+ TextView tvMessage;
+ TextView tvSolution;
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ pbProgress = findViewById(R.id.pbProgress);
+ llMessage = findViewById(R.id.llMessage);
+ tvCode = (TextView) findViewById(R.id.tvCode);
+ tvMessage = (TextView) findViewById(R.id.tvMessage);
+ tvSolution = (TextView) findViewById(R.id.tvSolution);
+ }
+
+ public void showProgress(String progressText) {
+ pbProgress.setVisibility(VISIBLE);
+ tvCode.setVisibility(INVISIBLE);
+ tvMessage.setText(progressText);
+ llMessage.setVisibility(VISIBLE);
+ tvSolution.setVisibility(INVISIBLE);
+ }
+
+ public void hideProgress() {
+ pbProgress.setVisibility(INVISIBLE);
+ llMessage.setVisibility(INVISIBLE);
+ }
+
+ public void showMessage(String code, String message, String solution) {
+ tvCode.setText(code);
+ tvMessage.setText(message);
+ tvSolution.setText(solution);
+ tvCode.setVisibility(VISIBLE);
+ llMessage.setVisibility(VISIBLE);
+ tvSolution.setVisibility(VISIBLE);
+ pbProgress.setVisibility(INVISIBLE);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/m2049r/xmrwallet/xmrto/XmrToError.java b/app/src/main/java/com/m2049r/xmrwallet/xmrto/XmrToError.java
new file mode 100644
index 0000000..2ac5bc2
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/xmrto/XmrToError.java
@@ -0,0 +1,107 @@
+/*
+ * 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.
+ * 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.xmrto;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class XmrToError {
+ private final Error errorId;
+ private final String errorMsg;
+
+ public enum Error {
+ XMRTO_ERROR_UNDEF,
+ XMRTO_ERROR_001, // (503) internal services not available try again later
+ XMRTO_ERROR_002, // (400) malformed bitcoin address check address validity
+ XMRTO_ERROR_003, // (400) invalid bitcoin amount check amount data type
+ XMRTO_ERROR_004, // (400) bitcoin amount out of bounds check min and max amount
+ XMRTO_ERROR_005, // (400) unexpected validation error contact support
+ XMRTO_ERROR_006, // (404) requested order not found check order UUID
+ XMRTO_ERROR_007, // (503) third party service not available try again later
+ XMRTO_ERROR_008, // (503) insufficient funds available try again later
+ XMRTO_ERROR_009 // (400) invalid request check request parameters
+ }
+
+ public boolean isRetryable() {
+ return (errorId == Error.XMRTO_ERROR_001)
+ || (errorId == Error.XMRTO_ERROR_007)
+ || (errorId == Error.XMRTO_ERROR_008);
+ }
+
+ public static String getErrorIdString(Error anError) {
+ return anError.toString().replace('_', '-');
+ }
+
+ // mostly for testing
+ public XmrToError(String errorId, String message) {
+ Error error;
+ try {
+ error = Error.valueOf(errorId.replace('-', '_'));
+ } catch (IllegalArgumentException ex) {
+ error = Error.XMRTO_ERROR_UNDEF;
+ }
+ this.errorId = error;
+ this.errorMsg = message;
+ }
+
+ public XmrToError(final JSONObject jsonObject) throws JSONException {
+ final String errorId = jsonObject.getString("error");
+ Error error;
+ try {
+ error = Error.valueOf(errorId.replace('-', '_'));
+ } catch (IllegalArgumentException ex) {
+ error = Error.XMRTO_ERROR_UNDEF;
+ }
+ this.errorId = error;
+ this.errorMsg = jsonObject.getString("error_msg");
+ }
+
+ public Error getErrorId() {
+ return errorId;
+ }
+
+ public String getErrorMsg() {
+ return errorMsg;
+ }
+
+ @Override
+ public String toString() {
+ return getErrorIdString(getErrorId()) + ": " + getErrorMsg();
+ }
+
+ /* Errors from Query Parameters
+ HTTP code XMR.TO error code Error message/reason Solution
+ 503 XMRTO-ERROR-001 internal services not available try again later
+ 503 XMRTO-ERROR-007 third party service not available try again later
+ 503 XMRTO-ERROR-008 insufficient funds available try again later
+ */
+
+ /* Errors from Create Order
+ HTTP code XMR.TO error code Error message/reason Solution
+ 503 XMRTO-ERROR-001 internal services not available try again later
+ 400 XMRTO-ERROR-002 malformed bitcoin address check address validity
+ 400 XMRTO-ERROR-003 invalid bitcoin amount check amount data type
+ 503 XMRTO-ERROR-004 bitcoin amount out of bounds check min and max amount
+ 400 XMRTO-ERROR-005 unexpected validation error contact support
+ */
+
+ /* Errors from Query Order
+ HTTP code XMR.TO error code Error message/reason Solution
+ 400 XMRTO-ERROR-009 invalid request check request parameters
+ 404 XMRTO-ERROR-006 requested order not found check order UUID
+ */
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/xmrto/XmrToException.java b/app/src/main/java/com/m2049r/xmrwallet/xmrto/XmrToException.java
new file mode 100644
index 0000000..05f810e
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/xmrto/XmrToException.java
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ * 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.xmrto;
+
+public class XmrToException extends Exception {
+ private int code;
+ private XmrToError error;
+
+ public XmrToException(final int code) {
+ super();
+ this.code = code;
+ this.error = null;
+ }
+
+ public XmrToException(final int code, final XmrToError error) {
+ super();
+ this.code = code;
+ this.error = error;
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public XmrToError getError() {
+ return error;
+ }
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/xmrto/api/CreateOrder.java b/app/src/main/java/com/m2049r/xmrwallet/xmrto/api/CreateOrder.java
new file mode 100644
index 0000000..b03abe7
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/xmrto/api/CreateOrder.java
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ * 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.xmrto.api;
+
+public interface CreateOrder {
+ Double getBtcAmount();
+
+ String getBtcDestAddress();
+
+ String getState();
+
+ String getUuid();
+
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/xmrto/api/QueryOrderParameters.java b/app/src/main/java/com/m2049r/xmrwallet/xmrto/api/QueryOrderParameters.java
new file mode 100644
index 0000000..e27c458
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/xmrto/api/QueryOrderParameters.java
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ * 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.xmrto.api;
+
+public interface QueryOrderParameters {
+
+ double getLowerLimit(); // "lower_limit": ,
+
+ double getPrice(); // "price": ,
+
+ double getUpperLimit(); // "upper_limit": ,
+
+ boolean isZeroConfEnabled(); // "zero_conf_enabled": ,
+
+ double getZeroConfMaxAmount(); // "zero_conf_max_amount":
+
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/xmrto/api/QueryOrderStatus.java b/app/src/main/java/com/m2049r/xmrwallet/xmrto/api/QueryOrderStatus.java
new file mode 100644
index 0000000..21603a9
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/xmrto/api/QueryOrderStatus.java
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ * 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.xmrto.api;
+
+import java.util.Date;
+
+public interface QueryOrderStatus {
+ enum State {
+ UNDEF,
+ TO_BE_CREATED, // order creation pending
+ UNPAID, // waiting for Monero payment by user
+ UNDERPAID, // order partially paid
+ PAID_UNCONFIRMED, // order paid, waiting for enough confirmations
+ PAID, // order paid and sufficiently confirmed
+ BTC_SENT, // bitcoin payment sent
+ TIMED_OUT, // order timed out before payment was complete
+ NOT_FOUND // order wasn’t found in system (never existed or was purged)
+ }
+
+ boolean isCreated();
+
+ boolean isTerminal();
+
+ boolean isPending();
+
+ boolean isPaid();
+
+ boolean isSent();
+
+ boolean isError();
+
+ State getState(); // "state": "",
+
+ double getBtcAmount(); // "btc_amount": ,
+
+ String getBtcDestAddress(); // "btc_dest_address": "",
+
+ String getUuid(); // "uuid": ""
+
+ int getBtcNumConfirmations(); // "btc_num_confirmations": ,
+
+ int getBtcNumConfirmationsBeforePurge(); // "btc_num_confirmations_before_purge": ,
+
+ String getBtcTransactionId(); // "btc_transaction_id": "",
+
+ Date getCreatedAt(); // "created_at": "",
+
+ Date getExpiresAt(); // "expires_at": "",
+
+ int getSecondsTillTimeout(); // "seconds_till_timeout": ,
+
+ double getXmrAmountTotal(); // "xmr_amount_total": ,
+
+ double getXmrAmountRemaining(); // "xmr_amount_remaining": ,
+
+ int getXmrNumConfirmationsRemaining(); // "xmr_num_confirmations_remaining": ,
+
+ double getXmrPriceBtc(); // "xmr_price_btc": ,
+
+ String getXmrReceivingAddress(); // "xmr_receiving_address": "xmr_old_style_address_user_can_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": ,
+
+ @Deprecated
+ double getXmrRequiredAmount(); // "xmr_required_amount": ,
+
+ String getXmrRequiredPaymentIdLong(); // "xmr_required_payment_id_long": "xmr_payment_id_user_needs_to_include_when_using_old_stlye_address_as_string"
+
+ String getXmrRequiredPaymentIdShort(); // "xmr_required_payment_id_short": "xmr_payment_id_included_in_integrated_address_as_string"
+
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/xmrto/api/XmrToApi.java b/app/src/main/java/com/m2049r/xmrwallet/xmrto/api/XmrToApi.java
new file mode 100644
index 0000000..daaf071
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/xmrto/api/XmrToApi.java
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ * 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.xmrto.api;
+
+import android.support.annotation.NonNull;
+
+public interface XmrToApi {
+
+ /**
+ * Queries the order parameter.
+ *
+ * @param callback the callback with the OrderParameter object
+ */
+ void queryOrderParameters(@NonNull final XmrToCallback callback);
+
+ /**
+ * Creates an order
+ *
+ * @param amount the desired amount
+ * @param address the target bitcoin address
+ */
+ void createOrder(final double amount, @NonNull final String address, @NonNull final XmrToCallback callback);
+
+ /**
+ * Queries the order status for given current order
+ *
+ * @param uuid the order uuid
+ * @param callback the callback with the OrderStatus object
+ */
+ void queryOrderStatus(@NonNull final String uuid, @NonNull final XmrToCallback callback);
+
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/xmrto/api/XmrToCallback.java b/app/src/main/java/com/m2049r/xmrwallet/xmrto/api/XmrToCallback.java
new file mode 100644
index 0000000..1758363
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/xmrto/api/XmrToCallback.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ * 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.xmrto.api;
+
+public interface XmrToCallback {
+
+ void onSuccess(T t);
+
+ void onError(Exception ex);
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/xmrto/network/CreateOrderImpl.java b/app/src/main/java/com/m2049r/xmrwallet/xmrto/network/CreateOrderImpl.java
new file mode 100644
index 0000000..f6faf98
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/xmrto/network/CreateOrderImpl.java
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ * 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.xmrto.network;
+
+import android.support.annotation.NonNull;
+
+import com.m2049r.xmrwallet.xmrto.api.XmrToCallback;
+import com.m2049r.xmrwallet.xmrto.api.CreateOrder;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+class CreateOrderImpl implements CreateOrder {
+
+ private final String state;
+ private final double btcAmount;
+ private final String btcDestAddress;
+ private final String uuid;
+
+ public Double getBtcAmount() {
+ return btcAmount;
+ }
+
+ public String getBtcDestAddress() {
+ return btcDestAddress;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public String getState() {
+ return state;
+ }
+
+ CreateOrderImpl(final JSONObject jsonObject) throws JSONException {
+ this.state = jsonObject.getString("state");
+ this.btcAmount = jsonObject.getDouble("btc_amount");
+ this.btcDestAddress = jsonObject.getString("btc_dest_address");
+ this.uuid = jsonObject.getString("uuid");
+ }
+
+ public static void call(@NonNull final XmrToApiCall api, final double amount, @NonNull final String address,
+ @NonNull final XmrToCallback callback) {
+ try {
+ final JSONObject request = createRequest(amount, address);
+ api.call("order_create", request, new NetworkCallback() {
+ @Override
+ public void onSuccess(JSONObject jsonObject) {
+ try {
+ callback.onSuccess(new CreateOrderImpl(jsonObject));
+ } catch (JSONException ex) {
+ callback.onError(ex);
+ }
+ }
+
+ @Override
+ public void onError(Exception ex) {
+ callback.onError(ex);
+ }
+ });
+ } catch (JSONException ex) {
+ callback.onError(ex);
+ }
+ }
+
+ static JSONObject createRequest(final double amount, final String address) throws JSONException {
+ final JSONObject jsonObject = new JSONObject();
+ jsonObject.put("btc_amount", amount);
+ jsonObject.put("btc_dest_address", address);
+ return jsonObject;
+ }
+
+
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/xmrto/network/NetworkCallback.java b/app/src/main/java/com/m2049r/xmrwallet/xmrto/network/NetworkCallback.java
new file mode 100644
index 0000000..ba7caab
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/xmrto/network/NetworkCallback.java
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ * 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.xmrto.network;
+
+import org.json.JSONObject;
+
+interface NetworkCallback {
+
+ void onSuccess(JSONObject jsonObject);
+
+ void onError(Exception ex);
+
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/xmrto/network/QueryOrderParametersImpl.java b/app/src/main/java/com/m2049r/xmrwallet/xmrto/network/QueryOrderParametersImpl.java
new file mode 100644
index 0000000..dc413e5
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/xmrto/network/QueryOrderParametersImpl.java
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ * 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.xmrto.network;
+
+import android.support.annotation.NonNull;
+
+import com.m2049r.xmrwallet.xmrto.api.XmrToCallback;
+import com.m2049r.xmrwallet.xmrto.api.QueryOrderParameters;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+class QueryOrderParametersImpl implements QueryOrderParameters {
+
+ private double lowerLimit; // "lower_limit": ,
+ private double price; // "price": ,
+ private double upperLimit; // "upper_limit": ,
+ private boolean isZeroConfEnabled; // "zero_conf_enabled": ,
+ private double zeroConfMaxAmount; // "zero_conf_max_amount":
+
+ public double getLowerLimit() {
+ return lowerLimit;
+ }
+
+ public double getPrice() {
+ return price;
+ }
+
+ public double getUpperLimit() {
+ return upperLimit;
+ }
+
+ public boolean isZeroConfEnabled() {
+ return isZeroConfEnabled;
+ }
+
+ public double getZeroConfMaxAmount() {
+ return zeroConfMaxAmount;
+ }
+
+ QueryOrderParametersImpl(final JSONObject jsonObject) throws JSONException {
+ lowerLimit = jsonObject.getDouble("lower_limit");
+ price = jsonObject.getDouble("price");
+ upperLimit = jsonObject.getDouble("upper_limit");
+ isZeroConfEnabled = jsonObject.getBoolean("zero_conf_enabled");
+ zeroConfMaxAmount = jsonObject.getDouble("zero_conf_max_amount");
+ }
+
+ public static void call(@NonNull final XmrToApiCall api,
+ @NonNull final XmrToCallback callback) {
+ api.call("order_parameter_query", new NetworkCallback() {
+ @Override
+ public void onSuccess(JSONObject jsonObject) {
+ try {
+ callback.onSuccess(new QueryOrderParametersImpl(jsonObject));
+ } catch (JSONException ex) {
+ callback.onError(ex);
+ }
+ }
+
+ @Override
+ public void onError(Exception ex) {
+ callback.onError(ex);
+ }
+ });
+ }
+ //{"zero_conf_enabled":true,"price":0.015537,"upper_limit":20.0,"lower_limit":0.001,"zero_conf_max_amount":0.1}
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/xmrto/network/QueryOrderStatusImpl.java b/app/src/main/java/com/m2049r/xmrwallet/xmrto/network/QueryOrderStatusImpl.java
new file mode 100644
index 0000000..91b721d
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/xmrto/network/QueryOrderStatusImpl.java
@@ -0,0 +1,255 @@
+/*
+ * 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.
+ * 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.xmrto.network;
+
+import android.support.annotation.NonNull;
+
+import com.m2049r.xmrwallet.xmrto.api.XmrToCallback;
+import com.m2049r.xmrwallet.xmrto.api.QueryOrderStatus;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import timber.log.Timber;
+
+class QueryOrderStatusImpl implements QueryOrderStatus {
+ public static final SimpleDateFormat DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
+
+ public static Date parseDate(String dateString) throws ParseException {
+ return DATETIME_FORMATTER.parse(dateString.replaceAll("Z$", "+0000"));
+ }
+
+ private QueryOrderStatus.State state; // "state": "",
+ private double btcAmount; // "btc_amount": ,
+ private String btcDestAddress; // "btc_dest_address": "",
+ private String uuid; // "uuid": ""
+ // the following are only returned if the state is "after" TO_BE_CREATED
+ private int btcNumConfirmations; // "btc_num_confirmations": ,
+ private int btcNumConfirmationsBeforePurge; // "btc_num_confirmations_before_purge": ,
+ private String btcTransactionId; // "btc_transaction_id": "",
+ private Date createdAt; // "created_at": "",
+ private Date expiresAt; // "expires_at": "",
+ private int secondsTillTimeout; // "seconds_till_timeout": ,
+ private double xmrAmountTotal; // "xmr_amount_total": ,
+ private double xmrAmountRemaining; // "xmr_amount_remaining": ,
+ private int xmrNumConfirmationsRemaining; // "xmr_num_confirmations_remaining": ,
+ private double xmrPriceBtc; // "xmr_price_btc": ,
+ 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": ,
+ private double xmrRequiredAmount; // "xmr_required_amount": ,
+ private String xmrRequiredPaymentIdLong; // "xmr_required_payment_id_long": "xmr_payment_id_user_needs_to_include_when_using_old_stlye_address_as_string"
+ private String xmrRequiredPaymentIdShort; // "xmr_required_payment_id_short": "xmr_payment_id_included_in_integrated_address_as_string"
+
+ public QueryOrderStatus.State getState() {
+ return state;
+ }
+
+ public double getBtcAmount() {
+ return btcAmount;
+ }
+
+ public String getBtcDestAddress() {
+ return btcDestAddress;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public int getBtcNumConfirmations() {
+ return btcNumConfirmations;
+ }
+
+ public int getBtcNumConfirmationsBeforePurge() {
+ return btcNumConfirmationsBeforePurge;
+ }
+
+ public String getBtcTransactionId() {
+ return btcTransactionId;
+ }
+
+ public Date getCreatedAt() {
+ return createdAt;
+ }
+
+ public Date getExpiresAt() {
+ return expiresAt;
+ }
+
+ public int getSecondsTillTimeout() {
+ return secondsTillTimeout;
+ }
+
+ public double getXmrAmountTotal() {
+ return xmrAmountTotal;
+ }
+
+ public double getXmrAmountRemaining() {
+ return xmrAmountRemaining;
+ }
+
+ public int getXmrNumConfirmationsRemaining() {
+ return xmrNumConfirmationsRemaining;
+ }
+
+ public double getXmrPriceBtc() {
+ return xmrPriceBtc;
+ }
+
+ public String getXmrReceivingAddress() {
+ return xmrReceivingAddress;
+ }
+
+ public String getXmrReceivingIntegratedAddress() {
+ return xmrReceivingIntegratedAddress;
+ }
+
+ public int getXmrRecommendedMixin() {
+ return xmrRecommendedMixin;
+ }
+
+ public double getXmrRequiredAmount() {
+ return xmrRequiredAmount;
+ }
+
+ public String getXmrRequiredPaymentIdLong() {
+ return xmrRequiredPaymentIdLong;
+ }
+
+ public String getXmrRequiredPaymentIdShort() {
+ return xmrRequiredPaymentIdShort;
+ }
+
+ public boolean isCreated() {
+ return (state.equals(State.UNPAID) ||
+ state.equals(State.BTC_SENT) ||
+ state.equals(State.PAID) ||
+ state.equals(State.PAID_UNCONFIRMED) ||
+ state.equals(State.UNDERPAID));
+ }
+
+ public boolean isTerminal() {
+ return (state.equals(State.BTC_SENT) || isError());
+ }
+
+
+ public boolean isError() {
+ return (state.equals(State.UNDEF) ||
+ state.equals(State.NOT_FOUND) ||
+ state.equals(State.TIMED_OUT));
+ }
+
+ public boolean isPending() {
+ return (state.equals(State.TO_BE_CREATED) ||
+ state.equals(State.UNPAID) ||
+ state.equals(State.UNDERPAID) ||
+ state.equals(State.PAID_UNCONFIRMED) ||
+ state.equals(State.PAID));
+ }
+
+ public boolean isPaid() {
+ return (state.equals(State.PAID_UNCONFIRMED) ||
+ state.equals(State.PAID));
+ }
+
+ public boolean isSent() {
+ return state.equals(State.BTC_SENT);
+ }
+
+
+ QueryOrderStatusImpl(final JSONObject jsonObject) throws JSONException {
+ Timber.d(jsonObject.toString(4));
+ String stateName = jsonObject.getString("state"); // "state": "",
+ State state;
+ try {
+ state = State.valueOf(stateName);
+ } catch (IllegalArgumentException ex) {
+ state = State.UNDEF; //TODO: throws IllegalArgumentException?
+ }
+ this.state = state;
+
+ btcAmount = jsonObject.getDouble("btc_amount"); // "btc_amount": ,
+ btcDestAddress = jsonObject.getString("btc_dest_address"); // "btc_dest_address": "",
+ uuid = jsonObject.getString("uuid"); // "uuid": ""
+
+ if (isCreated()) {
+ btcNumConfirmations = jsonObject.getInt("btc_num_confirmations"); // "btc_num_confirmations": ,
+ btcNumConfirmationsBeforePurge = jsonObject.getInt("btc_num_confirmations_before_purge"); // "btc_num_confirmations_before_purge": ,
+ btcTransactionId = jsonObject.getString("btc_transaction_id"); // "btc_transaction_id": "",
+ try {
+ String created = jsonObject.getString("created_at"); // "created_at": "",
+ createdAt = parseDate(created);
+ String expires = jsonObject.getString("expires_at"); // "expires_at": "",
+ expiresAt = parseDate(expires);
+ } catch (ParseException ex) {
+ throw new JSONException(ex.getLocalizedMessage());
+ }
+ secondsTillTimeout = jsonObject.getInt("seconds_till_timeout"); // "seconds_till_timeout": ,
+ xmrAmountTotal = jsonObject.getDouble("xmr_amount_total"); // "xmr_amount_total": ,
+ xmrAmountRemaining = jsonObject.getDouble("xmr_amount_remaining"); // "xmr_amount_remaining": ,
+ xmrNumConfirmationsRemaining = jsonObject.getInt("xmr_num_confirmations_remaining"); // "xmr_num_confirmations_remaining": ,
+ xmrPriceBtc = jsonObject.getDouble("xmr_price_btc"); // "xmr_price_btc": ,
+ 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": ,
+ xmrRequiredAmount = jsonObject.getDouble("xmr_required_amount"); // "xmr_required_amount": ,
+ xmrRequiredPaymentIdLong = jsonObject.getString("xmr_required_payment_id_long"); // "xmr_required_payment_id_long": "xmr_payment_id_user_needs_to_include_when_using_old_stlye_address_as_string"
+ xmrRequiredPaymentIdShort = jsonObject.getString("xmr_required_payment_id_short"); // "xmr_required_payment_id_short": "xmr_payment_id_included_in_integrated_address_as_string"
+ }
+ }
+
+ public static void call(@NonNull final XmrToApiCall api, @NonNull final String uuid,
+ @NonNull final XmrToCallback callback) {
+ try {
+ final JSONObject request = createRequest(uuid);
+ api.call("order_status_query", request, new NetworkCallback() {
+ @Override
+ public void onSuccess(JSONObject jsonObject) {
+ try {
+ callback.onSuccess(new QueryOrderStatusImpl(jsonObject));
+ } catch (JSONException ex) {
+ callback.onError(ex);
+ }
+ }
+
+ @Override
+ public void onError(Exception ex) {
+ callback.onError(ex);
+ }
+ });
+ } catch (JSONException ex) {
+ callback.onError(ex);
+ }
+ }
+
+ /**
+ * Create JSON request object
+ *
+ * @param uuid unique_order_identifier_as_12_character_string
+ */
+
+ static JSONObject createRequest(final String uuid) throws JSONException {
+ final JSONObject jsonObject = new JSONObject();
+ jsonObject.put("uuid", uuid);
+ return jsonObject;
+ }
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/xmrto/network/XmrToApiCall.java b/app/src/main/java/com/m2049r/xmrwallet/xmrto/network/XmrToApiCall.java
new file mode 100644
index 0000000..b4816a1
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/xmrto/network/XmrToApiCall.java
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ * 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.xmrto.network;
+
+import android.support.annotation.NonNull;
+
+import org.json.JSONObject;
+
+interface XmrToApiCall {
+
+ void call(@NonNull final String path, @NonNull final NetworkCallback callback);
+
+ void call(@NonNull final String path, final JSONObject request, @NonNull final NetworkCallback callback);
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/xmrto/network/XmrToApiImpl.java b/app/src/main/java/com/m2049r/xmrwallet/xmrto/network/XmrToApiImpl.java
new file mode 100644
index 0000000..8d8946b
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/xmrto/network/XmrToApiImpl.java
@@ -0,0 +1,143 @@
+/*
+ * 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.
+ * 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.xmrto.network;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
+
+import com.m2049r.xmrwallet.xmrto.api.XmrToCallback;
+import com.m2049r.xmrwallet.xmrto.XmrToError;
+import com.m2049r.xmrwallet.xmrto.XmrToException;
+import com.m2049r.xmrwallet.xmrto.api.CreateOrder;
+import com.m2049r.xmrwallet.xmrto.api.QueryOrderParameters;
+import com.m2049r.xmrwallet.xmrto.api.QueryOrderStatus;
+import com.m2049r.xmrwallet.xmrto.api.XmrToApi;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+
+import okhttp3.Call;
+import okhttp3.HttpUrl;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import timber.log.Timber;
+
+public class XmrToApiImpl implements XmrToApi, XmrToApiCall {
+
+ @NonNull
+ private final OkHttpClient okHttpClient;
+
+ private final HttpUrl baseUrl;
+
+ public XmrToApiImpl(@NonNull final OkHttpClient okHttpClient, final HttpUrl baseUrl) {
+ this.okHttpClient = okHttpClient;
+ this.baseUrl = baseUrl;
+ }
+
+ public XmrToApiImpl(@NonNull final OkHttpClient okHttpClient) {
+ this(okHttpClient, HttpUrl.parse("https://xmr.to/api/v2/xmr2btc/"));
+ }
+
+
+ @Override
+ public void createOrder(final double amount, @NonNull final String address,
+ @NonNull final XmrToCallback callback) {
+ CreateOrderImpl.call(this, amount, address, callback);
+ }
+
+ @Override
+ public void queryOrderStatus(@NonNull final String uuid,
+ @NonNull final XmrToCallback callback) {
+ QueryOrderStatusImpl.call(this, uuid, callback);
+ }
+
+ @Override
+ public void queryOrderParameters(@NonNull final XmrToCallback callback) {
+ QueryOrderParametersImpl.call(this, callback);
+ }
+
+
+ @Override
+ public void call(@NonNull final String path, @NonNull final NetworkCallback callback) {
+ call(path, null, callback);
+ }
+
+ @Override
+ public void call(@NonNull final String path, final JSONObject request, @NonNull final NetworkCallback callback) {
+ final HttpUrl url = baseUrl.newBuilder()
+ .addPathSegment(path)
+ .addPathSegment("") // xmr.to needs a trailing slash!
+ .build();
+
+ Timber.d(url.toString());
+ final Request httpRequest = createHttpRequest(request, url);
+ Timber.d(httpRequest.toString());
+ Timber.d(request == null ? "null request" : request.toString());
+
+ okHttpClient.newCall(httpRequest).enqueue(new okhttp3.Callback() {
+ @Override
+ public void onFailure(final Call call, final IOException ex) {
+ Timber.d("A");
+ callback.onError(ex);
+ }
+
+ @Override
+ public void onResponse(final Call call, final Response response) throws IOException {
+ Timber.d("onResponse code=%d", response.code());
+ if (response.isSuccessful()) {
+ try {
+ final JSONObject json = new JSONObject(response.body().string());
+ callback.onSuccess(json);
+ } catch (JSONException ex) {
+ callback.onError(ex);
+ }
+ } else {
+ try {
+ final JSONObject json = new JSONObject(response.body().string());
+ final XmrToError error = new XmrToError(json);
+ Timber.e("xmr.to says %d/%s", response.code(), error.toString());
+ callback.onError(new XmrToException(response.code(), error));
+ } catch (JSONException ex) {
+ callback.onError(new XmrToException(response.code()));
+ }
+ }
+ }
+ });
+ }
+
+ private Request createHttpRequest(final JSONObject request, final HttpUrl url) {
+ if (request != null) {
+ final RequestBody body = RequestBody.create(
+ MediaType.parse("application/json"), request.toString());
+
+ return new Request.Builder()
+ .url(url)
+ .post(body)
+ .build();
+ } else {
+ return new Request.Builder()
+ .url(url)
+ .get()
+ .build();
+ }
+ }
+}
diff --git a/app/src/main/res/drawable-v24/ic_check_circle_xmr.xml b/app/src/main/res/drawable-v24/ic_check_circle_xmr.xml
new file mode 100644
index 0000000..7458d13
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_check_circle_xmr.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/gunther_24dp.png b/app/src/main/res/drawable/gunther_24dp.png
new file mode 100644
index 0000000..33e0c0f
Binary files /dev/null and b/app/src/main/res/drawable/gunther_24dp.png differ
diff --git a/app/src/main/res/drawable/ic_arrow_right_orange_24dp.xml b/app/src/main/res/drawable/ic_arrow_right_orange_24dp.xml
new file mode 100644
index 0000000..89be915
--- /dev/null
+++ b/app/src/main/res/drawable/ic_arrow_right_orange_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_check_circle_xmr.xml b/app/src/main/res/drawable/ic_check_circle_xmr.xml
new file mode 100644
index 0000000..c48c1ee
--- /dev/null
+++ b/app/src/main/res/drawable/ic_check_circle_xmr.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_content_copy_black_24dp.xml b/app/src/main/res/drawable/ic_content_copy_black_24dp.xml
index 53a5858..48836c4 100644
--- a/app/src/main/res/drawable/ic_content_copy_black_24dp.xml
+++ b/app/src/main/res/drawable/ic_content_copy_black_24dp.xml
@@ -1,9 +1,9 @@
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0">
+ android:fillColor="#FF000000"
+ android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z" />
diff --git a/app/src/main/res/drawable/ic_content_copy_white_24dp.xml b/app/src/main/res/drawable/ic_content_copy_white_24dp.xml
new file mode 100644
index 0000000..127c9d3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_content_copy_white_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_error_red_24dp.xml b/app/src/main/res/drawable/ic_error_red_24dp.xml
new file mode 100644
index 0000000..5b0e5c8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_error_red_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_keyboard_arrow_right_black_24dp.xml b/app/src/main/res/drawable/ic_keyboard_arrow_right_black_24dp.xml
deleted file mode 100644
index a3d1622..0000000
--- a/app/src/main/res/drawable/ic_keyboard_arrow_right_black_24dp.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_logo_horizontol_xmrujo.xml b/app/src/main/res/drawable/ic_logo_horizontol_xmrujo.xml
index cf1dde3..cd2b3ec 100644
--- a/app/src/main/res/drawable/ic_logo_horizontol_xmrujo.xml
+++ b/app/src/main/res/drawable/ic_logo_horizontol_xmrujo.xml
@@ -24,9 +24,6 @@
-
diff --git a/app/src/main/res/drawable/ic_pending_orange_24dp.xml b/app/src/main/res/drawable/ic_pending_orange_24dp.xml
new file mode 100644
index 0000000..68bb8e0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_pending_orange_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_success_green_24dp.xml b/app/src/main/res/drawable/ic_success_green_24dp.xml
new file mode 100644
index 0000000..8b33e98
--- /dev/null
+++ b/app/src/main/res/drawable/ic_success_green_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_xmrto_32dp.png b/app/src/main/res/drawable/ic_xmrto_32dp.png
new file mode 100644
index 0000000..b9cec90
Binary files /dev/null and b/app/src/main/res/drawable/ic_xmrto_32dp.png differ
diff --git a/app/src/main/res/drawable/ic_xmrto_whitestroke_24px.png b/app/src/main/res/drawable/ic_xmrto_whitestroke_24px.png
new file mode 100644
index 0000000..a1650c3
Binary files /dev/null and b/app/src/main/res/drawable/ic_xmrto_whitestroke_24px.png differ
diff --git a/app/src/main/res/layout/fragment_about.xml b/app/src/main/res/layout/fragment_about.xml
index d46dbd2..7f2e426 100644
--- a/app/src/main/res/layout/fragment_about.xml
+++ b/app/src/main/res/layout/fragment_about.xml
@@ -17,6 +17,13 @@
android:layout_height="48dp"
android:src="@mipmap/ic_launcher" />
+
+
diff --git a/app/src/main/res/layout/fragment_send.xml b/app/src/main/res/layout/fragment_send.xml
index 6d2a3c0..610db21 100644
--- a/app/src/main/res/layout/fragment_send.xml
+++ b/app/src/main/res/layout/fragment_send.xml
@@ -277,7 +277,7 @@
android:layout_marginTop="4dp"
android:enabled="false"
android:minHeight="36dp"
- android:text="@string/send_send_hint" />
+ android:text="@string/send_send_label" />
]]>
- Open Source Licenses
Licensed under the Apache License, Version 2.0
monerujo (https://github.com/m2049r/xmrwallet)
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 8768c7f..36c23d0 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -8,6 +8,8 @@
#FFFF6105
#FFF0006B
+ #FFFFD700
+
#9B9B9B
#FF417505
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 823fe37..278a6cf 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,6 +1,6 @@
- monerujo
- monerujo
+ monerujo
+ monerujo
Wallet
Testnet
@@ -21,16 +21,75 @@
Cancel
Touch for detailed information
- Success!
+ Successfully sent
Done
Touch for QR Code
Higher Priority = Higher Fees
+ You entered a Bitcoin address.
+ You'll send XMR and the receiver will get BTC using the XMR.TO service.
+ ]]>
+
+ XMR.TO Order
+
+ Sending
+ Status:
+ %1$s BTC
+ %1$s is %1$s (#%1$d)
+
+ Key:
+ Address:
+ Status:
+ %1$s BTC
+
+
+ Confirmation Pending
+ Payment Pending
+ XMR.TO Error (%1$s)
+ BTC Sent!
+ Querying …
+
+ You can send %1$s — %2$s BTC.
+ XMR.TO is giving you an exchange rate of %3$s BTC right now.
+ ]]>
+ Amounts up to %1$s BTC will be sent instantly!
+ ]]>
+
+ Balance: %2$s BTC (%1$s XMR)
+
Payment ID integrated
Preparing your transaction
- Transaction ID copied to clipboard!
+ Creating XMR.TO Order
+ Querying XMR.TO Order
+ Preparing Monero Transaction
+
+ Querying xmr.to parameters
+
+ XMR.TO ERROR
+ Code: %1$d
+
+ Touch to retry
+ Now we\'re stuck here!
+ Uh-oh, XMR.TO does not seem be available right now!
+
+ %1$s BTC = %2$s XMR
+ (Rate: %1$s BTC/XMR)
+
+ Click for more details
+ Visit xmr.to for support & tracking
+ See TX details for more details
+ Secret Key\nXMR.TO
+ XMR.TO Secret Key
+ Destination BTC Address
+ Amount
+ Creating XMR.TO Order
+
+ Touch to retry
Transaction ID
Destination Address
@@ -74,6 +133,8 @@
Transaction failed: %1$s
Could not create transaction!
+ Dude, you waited too long!
+
I am still busy with your last wallet …
Rename %1$s
@@ -96,8 +157,7 @@
Amount
Date
+ %1$s XMR unconfirmed
-
- XMR
+ XMR
Transactions
Daemon connected!
@@ -119,8 +179,11 @@
View Key
Public Address
+ XMR.TO Key
View Key copied to clipboard!
+ XMR.TO Key copied to clipboard!
Wallet Address copied to clipboard!
+ Transaction ID copied to clipboard!
Selecting seed disabled for security reasons!
Selecting spend key disabled for security reasons!
Copy disabled for security reasons!
@@ -171,11 +234,11 @@
Mnemonic Seed
Restore Height:
-
Enter valid Key
Enter valid Address
Enter your 25 word seed
-
+
+ %1$s (indicative)
Receiver\'s Address
Payment ID (optional)
@@ -188,13 +251,15 @@
Scan
Prepare
Dispose (Undo)
- Spend my sweet Moneroj
+ Spend my sweet Moneroj
+ Spend my sweet Moneroj (%1$s)
Last Step - Confirm!
- Not a monero QR Code
- Invalid Monero address
+ Not a QR Code
+ Not a valid payment QR code
+ Not a valid address
Preparing transaction
Send
- Available funds: %1$s XMR
+ Balance: %1$s XMR
Address
Amount
Settings
@@ -204,10 +269,15 @@
Amount > Funds
Amount
+ Fee (XMR)
Fee
Dust
+ Total (XMR)
Total
+ %1$s XMR
+ +%1$s Fee
+
Create Transaction Error
incl. %1$s fee
@@ -217,14 +287,18 @@
- %1$s
+ %1$s
+ Status
Address
Timestamp
TX ID
+ TX ID\n(BTC)
TX Key
Destination
+ Destination\n(BTC)
Payment ID
Block
Amount
+ Amount\n(BTC)
Fee
Transfers
Notes
@@ -248,6 +322,7 @@
Could not open wallet!
16 or 64 Hex characters (0-9,a-f)
Must be empty with integrated address
+ Must be empty with bitcoin address
Must be > 0
Enter value
@@ -266,9 +341,9 @@
Yes, do that!
No thanks!
- 999999.999999999999
+ 999999.999999999999
-
+
- Ringsize 5
- Ringsize 8
- Ringsize 13
@@ -282,7 +357,7 @@
- Priority High
-
+
- XMR
- EUR
- USD
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index ec1f2e5..6212b4f 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -82,9 +82,7 @@
@@ -175,15 +173,15 @@
@@ -193,15 +191,15 @@
-
+
+