mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-03 08:23:04 +02:00
Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
67f3c5f948 | ||
![]() |
cd67a7e2bf | ||
![]() |
fa5fe313ea | ||
![]() |
ed4957a3cc | ||
![]() |
3e0eeebd51 | ||
![]() |
0d213a1eb4 | ||
![]() |
39d048fd5e | ||
![]() |
1a5d2d0399 | ||
![]() |
028057a672 | ||
![]() |
909ff8ca5e | ||
![]() |
ffd61e4495 | ||
![]() |
003dee382e | ||
![]() |
be0498c67d | ||
![]() |
06227a4a83 | ||
![]() |
4409087bd0 | ||
![]() |
965e52d8a5 | ||
![]() |
beba0f497b |
@@ -3,11 +3,13 @@ jobs:
|
||||
build:
|
||||
working_directory: ~/code
|
||||
docker:
|
||||
- image: bitriseio/android-ndk
|
||||
- image: circleci/android:api-28-ndk
|
||||
environment:
|
||||
JVM_OPTS: -Xmx3200m
|
||||
steps:
|
||||
- checkout
|
||||
- run: yes | sdkmanager --licenses || exit 0
|
||||
- run: yes | sdkmanager --update || exit 0
|
||||
- restore_cache:
|
||||
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
|
||||
- run:
|
||||
|
3
.idea/.gitignore
generated
vendored
3
.idea/.gitignore
generated
vendored
@@ -1,3 +0,0 @@
|
||||
workspace.xml
|
||||
markdown-*
|
||||
misc.xml
|
1
.idea/.name
generated
1
.idea/.name
generated
@@ -1 +0,0 @@
|
||||
xmrwallet
|
22
.idea/compiler.xml
generated
22
.idea/compiler.xml
generated
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<resourceExtensions />
|
||||
<wildcardResourcePatterns>
|
||||
<entry name="!?*.java" />
|
||||
<entry name="!?*.form" />
|
||||
<entry name="!?*.class" />
|
||||
<entry name="!?*.groovy" />
|
||||
<entry name="!?*.scala" />
|
||||
<entry name="!?*.flex" />
|
||||
<entry name="!?*.kt" />
|
||||
<entry name="!?*.clj" />
|
||||
<entry name="!?*.aj" />
|
||||
</wildcardResourcePatterns>
|
||||
<annotationProcessing>
|
||||
<profile default="true" name="Default" enabled="false">
|
||||
<processorPath useClasspath="true" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
</component>
|
||||
</project>
|
3
.idea/copyright/profiles_settings.xml
generated
3
.idea/copyright/profiles_settings.xml
generated
@@ -1,3 +0,0 @@
|
||||
<component name="CopyrightManager">
|
||||
<settings default="" />
|
||||
</component>
|
19
.idea/gradle.xml
generated
19
.idea/gradle.xml
generated
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="disableWrapperSourceDistributionNotification" value="true" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
9
.idea/modules.xml
generated
9
.idea/modules.xml
generated
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/xmrwallet.iml" filepath="$PROJECT_DIR$/xmrwallet.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
12
.idea/runConfigurations.xml
generated
12
.idea/runConfigurations.xml
generated
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "com.m2049r.xmrwallet"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 28
|
||||
versionCode 162
|
||||
versionName "1.10.12 'Node-O-matiC'"
|
||||
versionCode 165
|
||||
versionName "1.10.15 'Node-O-matiC'"
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
externalNativeBuild {
|
||||
@@ -113,10 +113,12 @@ dependencies {
|
||||
implementation 'dnsjava:dnsjava:2.1.8'
|
||||
implementation 'org.jitsi:dnssecjava:1.1.3'
|
||||
implementation 'org.slf4j:slf4j-nop:1.7.25'
|
||||
implementation 'com.github.brnunes:swipeablerecyclerview:1.0.2'
|
||||
|
||||
testImplementation "junit:junit:$rootProject.ext.junitVersion"
|
||||
testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
|
||||
testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion"
|
||||
testImplementation 'org.json:json:20140107'
|
||||
testImplementation 'net.jodah:concurrentunit:0.4.2'
|
||||
testImplementation 'org.json:json:20180813'
|
||||
testImplementation 'net.jodah:concurrentunit:0.4.4'
|
||||
|
||||
}
|
||||
|
@@ -43,6 +43,9 @@ Copyright (c) 2014 Dushyanth Maguluru
|
||||
<h3>AndroidLicensesPage (https://github.com/adamsp/AndroidLicensesPage)</h3>
|
||||
Copyright (c) 2013 Adam Speakman
|
||||
|
||||
<h3>SwipeableRecyclerView (https://github.com/brnunes/SwipeableRecyclerView)</h3>
|
||||
Copyright (c) 2015 Bruno R. Nunes
|
||||
|
||||
<h3>Apache License, Version 2.0, January 2004</h3>
|
||||
http://www.apache.org/licenses/<br/>
|
||||
<br/>
|
||||
|
@@ -442,12 +442,6 @@ Java_com_m2049r_xmrwallet_model_WalletManager_findWallets(JNIEnv *env, jobject i
|
||||
return cpp2java(env, walletPaths);
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_m2049r_xmrwallet_model_WalletManager_getErrorString(JNIEnv *env, jobject instance) {
|
||||
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
|
||||
return env->NewStringUTF(wallet->errorString().c_str());
|
||||
}
|
||||
|
||||
//TODO virtual bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const = 0;
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
|
@@ -36,7 +36,7 @@ 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.data.UserNotes;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
|
@@ -59,7 +59,7 @@ import com.m2049r.xmrwallet.model.WalletManager;
|
||||
import com.m2049r.xmrwallet.service.WalletService;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
|
||||
import com.m2049r.xmrwallet.util.UserNotes;
|
||||
import com.m2049r.xmrwallet.data.UserNotes;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -144,6 +144,9 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
} else {
|
||||
streetMode = 0;
|
||||
}
|
||||
final WalletFragment walletFragment = (WalletFragment)
|
||||
getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName());
|
||||
if (walletFragment != null) walletFragment.resetDismissedTransactions();
|
||||
updateAccountsBalance();
|
||||
forceUpdate();
|
||||
}
|
||||
@@ -286,7 +289,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
showNet();
|
||||
}
|
||||
invalidateOptionsMenu();
|
||||
|
||||
}
|
||||
|
||||
private void onEnableStreetMode() {
|
||||
@@ -956,8 +958,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
}
|
||||
if (!processed || (onUriScannedListener == null)) {
|
||||
Toast.makeText(this, getString(R.string.nfc_tag_read_what), Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
Toast.makeText(this, getString(R.string.nfc_tag_read_success), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -38,6 +38,7 @@ import android.widget.ProgressBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.github.brnunes.swipeablerecyclerview.SwipeableRecyclerViewTouchListener;
|
||||
import com.m2049r.xmrwallet.layout.TransactionInfoAdapter;
|
||||
import com.m2049r.xmrwallet.model.TransactionInfo;
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
@@ -73,6 +74,12 @@ public class WalletFragment extends Fragment
|
||||
|
||||
private Spinner sCurrency;
|
||||
|
||||
private List<String> dismissedTransactions = new ArrayList<>();
|
||||
|
||||
public void resetDismissedTransactions() {
|
||||
dismissedTransactions.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -116,9 +123,44 @@ public class WalletFragment extends Fragment
|
||||
|
||||
RecyclerView recyclerView = view.findViewById(R.id.list);
|
||||
|
||||
this.adapter = new TransactionInfoAdapter(getActivity(), this);
|
||||
adapter = new TransactionInfoAdapter(getActivity(), this);
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
SwipeableRecyclerViewTouchListener swipeTouchListener =
|
||||
new SwipeableRecyclerViewTouchListener(recyclerView,
|
||||
new SwipeableRecyclerViewTouchListener.SwipeListener() {
|
||||
@Override
|
||||
public boolean canSwipeLeft(int position) {
|
||||
return activityCallback.isStreetMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSwipeRight(int position) {
|
||||
return activityCallback.isStreetMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismissedBySwipeLeft(RecyclerView recyclerView, int[] reverseSortedPositions) {
|
||||
for (int position : reverseSortedPositions) {
|
||||
dismissedTransactions.add(adapter.getItem(position).hash);
|
||||
adapter.removeItem(position);
|
||||
}
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismissedBySwipeRight(RecyclerView recyclerView, int[] reverseSortedPositions) {
|
||||
for (int position : reverseSortedPositions) {
|
||||
dismissedTransactions.add(adapter.getItem(position).hash);
|
||||
adapter.removeItem(position);
|
||||
}
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
|
||||
recyclerView.addOnItemTouchListener(swipeTouchListener);
|
||||
|
||||
|
||||
bSend.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@@ -294,7 +336,9 @@ public class WalletFragment extends Fragment
|
||||
Timber.d("StreetHeight=%d", streetHeight);
|
||||
for (TransactionInfo info : wallet.getHistory().getAll()) {
|
||||
Timber.d("TxHeight=%d", info.blockheight);
|
||||
if (info.isPending || (info.blockheight >= streetHeight)) list.add(info);
|
||||
if ((info.isPending || (info.blockheight >= streetHeight))
|
||||
&& !dismissedTransactions.contains(info.hash))
|
||||
list.add(info);
|
||||
}
|
||||
adapter.setInfos(list);
|
||||
adapter.notifyDataSetChanged();
|
||||
|
@@ -21,7 +21,10 @@ import android.net.Uri;
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.util.BitcoinAddressValidator;
|
||||
import com.m2049r.xmrwallet.util.OpenAliasHelper;
|
||||
import com.m2049r.xmrwallet.util.PaymentProtocolHelper;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -37,9 +40,10 @@ public class BarcodeData {
|
||||
public static final String OA_XMR_ASSET = "xmr";
|
||||
public static final String OA_BTC_ASSET = "btc";
|
||||
|
||||
static final String BTC_SCHEME = "bitcoin:";
|
||||
static final String BTC_SCHEME = "bitcoin";
|
||||
static final String BTC_DESCRIPTION = "message";
|
||||
static final String BTC_AMOUNT = "amount";
|
||||
static final String BTC_BIP70_PARM = "r";
|
||||
|
||||
public enum Asset {
|
||||
XMR, BTC
|
||||
@@ -48,7 +52,8 @@ public class BarcodeData {
|
||||
public enum Security {
|
||||
NORMAL,
|
||||
OA_NO_DNSSEC,
|
||||
OA_DNSSEC
|
||||
OA_DNSSEC,
|
||||
BIP70
|
||||
}
|
||||
|
||||
final public Asset asset;
|
||||
@@ -58,57 +63,44 @@ public class BarcodeData {
|
||||
final public String amount;
|
||||
final public String description;
|
||||
final public Security security;
|
||||
final public String bip70;
|
||||
|
||||
public BarcodeData(Asset asset, String address) {
|
||||
this.asset = asset;
|
||||
this.address = address;
|
||||
amount = null;
|
||||
paymentId = null;
|
||||
addressName = null;
|
||||
description = null;
|
||||
this.security = Security.NORMAL;
|
||||
this(asset, address, null, null, null, null, Security.NORMAL);
|
||||
}
|
||||
|
||||
public BarcodeData(Asset asset, String address, String amount) {
|
||||
this.asset = asset;
|
||||
this.address = address;
|
||||
this.amount = amount;
|
||||
paymentId = null;
|
||||
addressName = null;
|
||||
description = null;
|
||||
this.security = Security.NORMAL;
|
||||
this(asset, address, null, null, null, amount, Security.NORMAL);
|
||||
}
|
||||
|
||||
public BarcodeData(Asset asset, String address, String amount, String description, Security security) {
|
||||
this(asset, address, null, null, description, amount, security);
|
||||
}
|
||||
|
||||
public BarcodeData(Asset asset, String address, String paymentId, String amount) {
|
||||
this.asset = asset;
|
||||
this.address = address;
|
||||
this.paymentId = paymentId;
|
||||
this.amount = amount;
|
||||
addressName = null;
|
||||
description = null;
|
||||
this.security = Security.NORMAL;
|
||||
this(asset, address, null, paymentId, null, amount, Security.NORMAL);
|
||||
}
|
||||
|
||||
public BarcodeData(Asset asset, String address, String paymentId, String description, String amount) {
|
||||
this.asset = asset;
|
||||
this.address = address;
|
||||
this.paymentId = paymentId;
|
||||
this.description = description;
|
||||
this.amount = amount;
|
||||
addressName = null;
|
||||
this.security = Security.NORMAL;
|
||||
this(asset, address, null, paymentId, description, amount, Security.NORMAL);
|
||||
}
|
||||
|
||||
public BarcodeData(Asset asset, String address, String addressName, String paymentId, String description, String amount, Security sec) {
|
||||
public BarcodeData(Asset asset, String address, String addressName, String paymentId, String description, String amount, Security security) {
|
||||
this(asset, address, addressName, null, paymentId, description, amount, security);
|
||||
}
|
||||
|
||||
public BarcodeData(Asset asset, String address, String addressName, String bip70, String paymentId, String description, String amount, Security security) {
|
||||
this.asset = asset;
|
||||
this.address = address;
|
||||
this.bip70 = bip70;
|
||||
this.addressName = addressName;
|
||||
this.paymentId = paymentId;
|
||||
this.description = description;
|
||||
this.amount = amount;
|
||||
this.security = sec;
|
||||
this.security = security;
|
||||
}
|
||||
|
||||
|
||||
public Uri getUri() {
|
||||
return Uri.parse(getUriString());
|
||||
}
|
||||
@@ -146,6 +138,10 @@ public class BarcodeData {
|
||||
if (bcData == null) {
|
||||
bcData = parseBitcoinUri(qrCode);
|
||||
}
|
||||
// check for btc payment uri (like bitpay)
|
||||
if (bcData == null) {
|
||||
bcData = parseBitcoinPaymentUrl(qrCode);
|
||||
}
|
||||
// check for naked btc address
|
||||
if (bcData == null) {
|
||||
bcData = parseBitcoinNaked(qrCode);
|
||||
@@ -228,19 +224,28 @@ public class BarcodeData {
|
||||
}
|
||||
|
||||
// bitcoin:mpQ84J43EURZHkCnXbyQ4PpNDLLBqdsMW2?amount=0.01
|
||||
static public BarcodeData parseBitcoinUri(String uri) {
|
||||
Timber.d("parseBitcoinUri=%s", uri);
|
||||
// bitcoin:?r=https://bitpay.com/i/xxx
|
||||
static public BarcodeData parseBitcoinUri(String uriString) {
|
||||
Timber.d("parseBitcoinUri=%s", uriString);
|
||||
|
||||
if (uri == null) return null;
|
||||
if (uriString == null) return null;
|
||||
URI uri;
|
||||
try {
|
||||
uri = new URI(uriString);
|
||||
} catch (URISyntaxException ex) {
|
||||
return null;
|
||||
}
|
||||
if (!uri.isOpaque() ||
|
||||
!uri.getScheme().equals(BTC_SCHEME)) return null;
|
||||
|
||||
if (!uri.startsWith(BTC_SCHEME)) return null;
|
||||
|
||||
String noScheme = uri.substring(BTC_SCHEME.length());
|
||||
Uri bitcoin = Uri.parse(noScheme);
|
||||
String[] parts = uri.getRawSchemeSpecificPart().split("[?]");
|
||||
if ((parts.length <= 0) || (parts.length > 2)) {
|
||||
Timber.d("invalid number of parts %d", parts.length);
|
||||
return null;
|
||||
}
|
||||
Map<String, String> parms = new HashMap<>();
|
||||
String query = bitcoin.getQuery();
|
||||
if (query != null) {
|
||||
String[] args = query.split("&");
|
||||
if (parts.length == 2) {
|
||||
String[] args = parts[1].split("&");
|
||||
for (String arg : args) {
|
||||
String[] namevalue = arg.split("=");
|
||||
if (namevalue.length == 0) {
|
||||
@@ -250,10 +255,26 @@ public class BarcodeData {
|
||||
namevalue.length > 1 ? Uri.decode(namevalue[1]) : "");
|
||||
}
|
||||
}
|
||||
String address = bitcoin.getPath();
|
||||
String description = parms.get(BTC_DESCRIPTION);
|
||||
String address = parts[0]; // no need to decode as there can bo no special characters
|
||||
if (address.isEmpty()) { // possibly a BIP72 uri
|
||||
String bip70 = parms.get(BTC_BIP70_PARM);
|
||||
if (bip70 == null) {
|
||||
Timber.d("no address and can't find pp url");
|
||||
return null;
|
||||
}
|
||||
if (!PaymentProtocolHelper.isHttp(bip70)) {
|
||||
Timber.d("[%s] is not http url", bip70);
|
||||
return null;
|
||||
}
|
||||
return new BarcodeData(BarcodeData.Asset.BTC, null, null, bip70, null, description, null, Security.NORMAL);
|
||||
}
|
||||
if (!BitcoinAddressValidator.validate(address)) {
|
||||
Timber.d("BTC address (%s) invalid", address);
|
||||
return null;
|
||||
}
|
||||
String amount = parms.get(BTC_AMOUNT);
|
||||
if (amount != null) {
|
||||
if ((amount != null) && (!amount.isEmpty())) {
|
||||
try {
|
||||
Double.parseDouble(amount);
|
||||
} catch (NumberFormatException ex) {
|
||||
@@ -261,11 +282,22 @@ public class BarcodeData {
|
||||
return null; // we have an amount but its not a number!
|
||||
}
|
||||
}
|
||||
if (!BitcoinAddressValidator.validate(address)) {
|
||||
Timber.d("address invalid");
|
||||
return new BarcodeData(BarcodeData.Asset.BTC, address, null, description, amount);
|
||||
}
|
||||
|
||||
// https://bitpay.com/invoice?id=xxx
|
||||
// https://bitpay.com/i/KbMdd4EhnLXSbpWGKsaeo6
|
||||
static public BarcodeData parseBitcoinPaymentUrl(String url) {
|
||||
Timber.d("parseBitcoinUri=%s", url);
|
||||
|
||||
if (url == null) return null;
|
||||
|
||||
if (!PaymentProtocolHelper.isHttp(url)) {
|
||||
Timber.d("[%s] is not http url", url);
|
||||
return null;
|
||||
}
|
||||
return new BarcodeData(BarcodeData.Asset.BTC, address, null, description, amount);
|
||||
|
||||
return new BarcodeData(Asset.BTC, url);
|
||||
}
|
||||
|
||||
static public BarcodeData parseBitcoinNaked(String address) {
|
||||
|
@@ -20,9 +20,6 @@ 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 {
|
||||
|
@@ -18,12 +18,11 @@ 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 String bip70;
|
||||
private double btcAmount;
|
||||
|
||||
public TxDataBtc() {
|
||||
@@ -50,6 +49,14 @@ public class TxDataBtc extends TxData {
|
||||
this.btcAddress = btcAddress;
|
||||
}
|
||||
|
||||
public String getBip70() {
|
||||
return bip70;
|
||||
}
|
||||
|
||||
public void setBip70(String bip70) {
|
||||
this.bip70 = bip70;
|
||||
}
|
||||
|
||||
public double getBtcAmount() {
|
||||
return btcAmount;
|
||||
}
|
||||
@@ -63,6 +70,7 @@ public class TxDataBtc extends TxData {
|
||||
super.writeToParcel(out, flags);
|
||||
out.writeString(xmrtoUuid);
|
||||
out.writeString(btcAddress);
|
||||
out.writeString(bip70);
|
||||
out.writeDouble(btcAmount);
|
||||
}
|
||||
|
||||
@@ -81,16 +89,19 @@ public class TxDataBtc extends TxData {
|
||||
super(in);
|
||||
xmrtoUuid = in.readString();
|
||||
btcAddress = in.readString();
|
||||
bip70 = in.readString();
|
||||
btcAmount = in.readDouble();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(",xmrtoUuid:");
|
||||
sb.append(xmrtoUuid);
|
||||
sb.append(",btcAddress:");
|
||||
sb.append(btcAddress);
|
||||
sb.append(",bip70:");
|
||||
sb.append(bip70);
|
||||
sb.append(",btcAmount:");
|
||||
sb.append(btcAmount);
|
||||
return sb.toString();
|
||||
|
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.m2049r.xmrwallet.util;
|
||||
package com.m2049r.xmrwallet.data;
|
||||
|
||||
import com.m2049r.xmrwallet.xmrto.api.QueryOrderStatus;
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -120,6 +120,15 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setBip70Mode() {
|
||||
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
|
||||
if (txDataBtc.getBip70() != null) {
|
||||
numberPad.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
numberPad.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
double maxBtc = 0;
|
||||
double minBtc = 0;
|
||||
|
||||
@@ -141,12 +150,13 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
||||
tvFunds.setText(getString(R.string.send_available,
|
||||
getString(R.string.unknown_amount)));
|
||||
}
|
||||
if ((evAmount.getAmount() == null) || evAmount.getAmount().isEmpty()) {
|
||||
final BarcodeData data = sendListener.popBarcodeData();
|
||||
if ((data != null) && (data.amount != null)) {
|
||||
final BarcodeData data = sendListener.popBarcodeData();
|
||||
if (data != null) {
|
||||
if (data.amount != null) {
|
||||
evAmount.setAmount(data.amount);
|
||||
}
|
||||
}
|
||||
setBip70Mode();
|
||||
callXmrTo();
|
||||
}
|
||||
|
||||
|
@@ -524,8 +524,8 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
||||
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<CreateOrder>() {
|
||||
|
||||
XmrToCallback<CreateOrder> callback = new XmrToCallback<CreateOrder>() {
|
||||
@Override
|
||||
public void onSuccess(CreateOrder createOrder) {
|
||||
if (!isResumed) return;
|
||||
@@ -545,7 +545,13 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
||||
}
|
||||
processCreateOrderError(ex);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (txDataBtc.getBip70() != null) {
|
||||
getXmrToApi().createOrder(txDataBtc.getBip70(), callback);
|
||||
} else {
|
||||
getXmrToApi().createOrder(txDataBtc.getBtcAmount(), txDataBtc.getBtcAddress(), callback);
|
||||
}
|
||||
}
|
||||
|
||||
private QueryOrderStatus xmrtoStatus = null;
|
||||
@@ -666,7 +672,7 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
||||
|
||||
private XmrToApi xmrToApi = null;
|
||||
|
||||
private final XmrToApi getXmrToApi() {
|
||||
private XmrToApi getXmrToApi() {
|
||||
if (xmrToApi == null) {
|
||||
synchronized (this) {
|
||||
if (xmrToApi == null) {
|
||||
|
@@ -36,7 +36,7 @@ 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 com.m2049r.xmrwallet.data.UserNotes;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
@@ -214,11 +214,16 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
|
||||
if (pendingTransaction != null) {
|
||||
llConfirmSend.setVisibility(View.VISIBLE);
|
||||
bSend.setEnabled(true);
|
||||
tvTxAmount.setText(Wallet.getDisplayAmount(pendingTransaction.getAmount()));
|
||||
tvTxFee.setText(Wallet.getDisplayAmount(pendingTransaction.getFee()));
|
||||
//tvTxDust.setText(Wallet.getDisplayAmount(pendingTransaction.getDust()));
|
||||
tvTxTotal.setText(Wallet.getDisplayAmount(
|
||||
pendingTransaction.getFee() + pendingTransaction.getAmount()));
|
||||
if (getActivityCallback().isStreetMode()
|
||||
&& (sendListener.getTxData().getAmount() == Wallet.SWEEP_ALL)) {
|
||||
tvTxAmount.setText(getString(R.string.street_sweep_amount));
|
||||
tvTxTotal.setText(getString(R.string.street_sweep_amount));
|
||||
} else {
|
||||
tvTxAmount.setText(Wallet.getDisplayAmount(pendingTransaction.getAmount()));
|
||||
tvTxTotal.setText(Wallet.getDisplayAmount(
|
||||
pendingTransaction.getFee() + pendingTransaction.getAmount()));
|
||||
}
|
||||
} else {
|
||||
llConfirmSend.setVisibility(View.GONE);
|
||||
bSend.setEnabled(false);
|
||||
|
@@ -34,7 +34,6 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.m2049r.xmrwallet.OnBackPressedListener;
|
||||
import com.m2049r.xmrwallet.OnUriScannedListener;
|
||||
@@ -47,7 +46,7 @@ import com.m2049r.xmrwallet.layout.SpendViewPager;
|
||||
import com.m2049r.xmrwallet.model.PendingTransaction;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.Notice;
|
||||
import com.m2049r.xmrwallet.util.UserNotes;
|
||||
import com.m2049r.xmrwallet.data.UserNotes;
|
||||
import com.m2049r.xmrwallet.widget.DotBar;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
|
||||
|
@@ -27,6 +27,7 @@ import android.widget.Toast;
|
||||
import com.m2049r.xmrwallet.R;
|
||||
import com.m2049r.xmrwallet.data.PendingTx;
|
||||
import com.m2049r.xmrwallet.data.TxData;
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
|
||||
import timber.log.Timber;
|
||||
@@ -54,6 +55,8 @@ public class SendSuccessWizardFragment extends SendWizardFragment {
|
||||
void enableDone();
|
||||
|
||||
SendFragment.Mode getMode();
|
||||
|
||||
SendFragment.Listener getActivityCallback();
|
||||
}
|
||||
|
||||
ImageButton bCopyTxId;
|
||||
@@ -120,7 +123,13 @@ public class SendSuccessWizardFragment extends SendWizardFragment {
|
||||
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)));
|
||||
|
||||
if (sendListener.getActivityCallback().isStreetMode()
|
||||
&& (sendListener.getTxData().getAmount() == Wallet.SWEEP_ALL)) {
|
||||
tvTxAmount.setText(getString(R.string.street_sweep_amount));
|
||||
} else {
|
||||
tvTxAmount.setText(getString(R.string.send_amount, Helper.getDisplayAmount(committedTx.amount)));
|
||||
}
|
||||
tvTxFee.setText(getString(R.string.send_fee, Helper.getDisplayAmount(committedTx.fee)));
|
||||
}
|
||||
sendListener.enableDone();
|
||||
|
@@ -28,7 +28,7 @@ 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 com.m2049r.xmrwallet.data.UserNotes;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
@@ -91,7 +91,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
|
||||
public void setInfos(List<TransactionInfo> data) {
|
||||
// TODO do stuff with data so we can really recycle elements (i.e. add only new tx)
|
||||
// as the TransactionInfo items are always recreated, we cannot recycle
|
||||
this.infoItems.clear();
|
||||
infoItems.clear();
|
||||
if (data != null) {
|
||||
Timber.d("setInfos %s", data.size());
|
||||
infoItems.addAll(data);
|
||||
@@ -102,6 +102,15 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void removeItem(int position) {
|
||||
infoItems.remove(position);
|
||||
notifyItemRemoved(position);
|
||||
}
|
||||
|
||||
public TransactionInfo getItem(int position) {
|
||||
return infoItems.get(position);
|
||||
}
|
||||
|
||||
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||
final ImageView ivTxType;
|
||||
final TextView tvAmount;
|
||||
|
@@ -264,8 +264,6 @@ public class WalletManager {
|
||||
return wallets;
|
||||
}
|
||||
|
||||
public native String getErrorString();
|
||||
|
||||
//TODO virtual bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const = 0;
|
||||
|
||||
private String daemonAddress = null;
|
||||
|
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (c) 2018 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.
|
||||
*/
|
||||
|
||||
// Specs from https://openalias.org/
|
||||
|
||||
package com.m2049r.xmrwallet.util;
|
||||
|
||||
import com.m2049r.xmrwallet.data.BarcodeData;
|
||||
import com.m2049r.xmrwallet.xmrto.api.CreateOrder;
|
||||
import com.m2049r.xmrwallet.xmrto.api.XmrToApi;
|
||||
import com.m2049r.xmrwallet.xmrto.api.XmrToCallback;
|
||||
import com.m2049r.xmrwallet.xmrto.network.XmrToApiImpl;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class PaymentProtocolHelper {
|
||||
|
||||
//https://bitpay.com/i/xxx
|
||||
public static boolean isHttp(String string) {
|
||||
if ((string == null) || (string.isEmpty())) return false;
|
||||
try {
|
||||
// new URL(string).toURI() checks if its a real url
|
||||
return new URL(string).toURI().getScheme().startsWith("http");
|
||||
} catch (MalformedURLException | URISyntaxException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// guess if the string may be a valid payment uri
|
||||
//https://bitpay.com/i/xxx
|
||||
//bitcoin:?r=https://bitpay.com/i/xxx
|
||||
public static String getBip70(String bip72Or70) {
|
||||
if ((bip72Or70 == null) || (bip72Or70.isEmpty())) return null;
|
||||
if (isHttp(bip72Or70)) return bip72Or70;
|
||||
BarcodeData bc = BarcodeData.parseBitcoinUri(bip72Or70);
|
||||
if (bc == null) return null;
|
||||
return bc.bip70;
|
||||
}
|
||||
|
||||
public interface OnResolvedListener {
|
||||
void onResolved(BarcodeData.Asset asset, String address, double amount, String bip70);
|
||||
|
||||
void onFailure(Exception ex);
|
||||
|
||||
}
|
||||
|
||||
static public boolean resolve(final String bip70, final OnResolvedListener resolvedListener) {
|
||||
if ((bip70 == null) || (bip70.isEmpty()))
|
||||
return false; //pointless trying to lookup nothing
|
||||
Timber.d("Resolving %s", bip70);
|
||||
getXmrToApi().createOrder(bip70, new XmrToCallback<CreateOrder>() {
|
||||
@Override
|
||||
public void onSuccess(CreateOrder createOrder) {
|
||||
if (resolvedListener != null) {
|
||||
resolvedListener.onResolved(BarcodeData.Asset.BTC,
|
||||
createOrder.getBtcDestAddress(),
|
||||
createOrder.getBtcAmount(),
|
||||
createOrder.getBtcBip70());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception ex) {
|
||||
if (resolvedListener != null) {
|
||||
resolvedListener.onFailure(ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
static private XmrToApi xmrToApi = null;
|
||||
|
||||
static private XmrToApi getXmrToApi() {
|
||||
if (xmrToApi == null) {
|
||||
synchronized (PaymentProtocolHelper.class) {
|
||||
if (xmrToApi == null) {
|
||||
xmrToApi = new XmrToApiImpl(OkHttpHelper.getOkHttpClient(),
|
||||
Helper.getXmrToBaseUrl());
|
||||
}
|
||||
}
|
||||
}
|
||||
return xmrToApi;
|
||||
}
|
||||
}
|
@@ -99,6 +99,7 @@ public class RestoreHeight {
|
||||
blockheight.put("2018-11-01", 1695128L);
|
||||
blockheight.put("2018-12-01", 1716687L);
|
||||
blockheight.put("2019-01-01", 1738923L);
|
||||
blockheight.put("2019-02-01", 1761435L);
|
||||
}
|
||||
|
||||
public long getHeight(String date) {
|
||||
|
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.m2049r.xmrwallet.xmrto;
|
||||
|
||||
import com.m2049r.xmrwallet.R;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
@@ -33,7 +35,10 @@ public class XmrToError {
|
||||
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
|
||||
XMRTO_ERROR_009, // (400) invalid request check request parameters
|
||||
XMRTO_ERROR_010, // (400) payment protocol failed invalid or outdated data served by url
|
||||
XMRTO_ERROR_011, // (400) malformed payment protocol url url is malformed or cannot be contacted
|
||||
XMRTO_ERROR_012 // (403) too many requests
|
||||
}
|
||||
|
||||
public boolean isRetryable() {
|
||||
@@ -78,6 +83,21 @@ public class XmrToError {
|
||||
return errorMsg;
|
||||
}
|
||||
|
||||
public int getErrorMsgId() {
|
||||
switch (errorId) {
|
||||
case XMRTO_ERROR_001:
|
||||
return R.string.xmrto_error_001;
|
||||
case XMRTO_ERROR_004:
|
||||
return R.string.xmrto_error_004;
|
||||
case XMRTO_ERROR_010:
|
||||
return R.string.xmrto_error_010;
|
||||
case XMRTO_ERROR_012:
|
||||
return R.string.xmrto_error_012;
|
||||
default:
|
||||
return R.string.xmrto_error;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getErrorIdString(getErrorId()) + ": " + getErrorMsg();
|
||||
@@ -97,6 +117,10 @@ public class XmrToError {
|
||||
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 Create Order Payment Protocol
|
||||
400 XMRTO-ERROR-010 payment protocol failed invalid or outdated data served by url
|
||||
400 XMRTO-ERROR-011 malformed payment protocol url url is malformed or cannot be contacted
|
||||
*/
|
||||
|
||||
/* Errors from Query Order
|
||||
@@ -104,4 +128,8 @@ public class XmrToError {
|
||||
400 XMRTO-ERROR-009 invalid request check request parameters
|
||||
404 XMRTO-ERROR-006 requested order not found check order UUID
|
||||
*/
|
||||
|
||||
/* General
|
||||
403 XMRTO-ERROR-012 too many requests
|
||||
*/
|
||||
}
|
||||
|
@@ -21,6 +21,8 @@ public interface CreateOrder {
|
||||
|
||||
String getBtcDestAddress();
|
||||
|
||||
String getBtcBip70();
|
||||
|
||||
String getState();
|
||||
|
||||
String getUuid();
|
||||
|
@@ -35,6 +35,13 @@ public interface XmrToApi {
|
||||
*/
|
||||
void createOrder(final double amount, @NonNull final String address, @NonNull final XmrToCallback<CreateOrder> callback);
|
||||
|
||||
/**
|
||||
* Creates an order through BIP70 payment protocol like bitpay
|
||||
*
|
||||
* @param url the BIP70 URL
|
||||
*/
|
||||
void createOrder(@NonNull final String url, @NonNull final XmrToCallback<CreateOrder> callback);
|
||||
|
||||
/**
|
||||
* Queries the order status for given current order
|
||||
*
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user