1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-06 02:27:11 +02:00

Compare commits

...

14 Commits

Author SHA1 Message Date
m2049r
57ddddfce2 replace xmr.to with SideShift.ai (#710)
replace xmr.to with SideShift.ai
random bugfixes
upgrade gradle & dependencies
2021-02-13 00:01:19 +01:00
netrik182
ab6069058b new pt-br strings after review (#708) 2021-02-01 23:02:36 +01:00
m2049r
d94d6e6925 downgrade to APK 29 & bump version 2020-11-22 19:39:30 +01:00
m2049r
4a819cc159 bump version 2020-11-21 22:18:36 +01:00
m2049r
f40c3d6c6d catch cases where the qr code produces a null monero address (#702) 2020-11-21 22:08:23 +01:00
m2049r
82b25df7ad better node selection (#701) 2020-11-21 21:54:21 +01:00
m2049r
08f815e830 don't show connection error if we havent tested yet (#700) 2020-11-21 13:22:54 +01:00
m2049r
4e59be2dff New handling of nodes (#699)
* rework node handling

* update block heights
2020-11-19 16:36:01 +01:00
m2049r
45c5883e11 fix potential NPE in updateAccountsList() 2020-10-20 23:47:41 +02:00
m2049r
067a23e6a5 fix crash if back before new address is generated 2020-10-20 23:40:55 +02:00
m2049r
d21fd41c44 update dependencies & fix MethodNotFound exception 2020-10-20 23:31:29 +02:00
m2049r
6632547d1e bump version & fix versionCode (#694) 2020-10-19 23:44:40 +02:00
m2049r
f1b786ec3e fix keyboard crash when no view in focus (#693) 2020-10-19 23:42:22 +02:00
m2049r
a93e96d34c upgrade to monero v0.17.1.1 (#692) 2020-10-19 23:39:30 +02:00
156 changed files with 4147 additions and 3896 deletions

View File

@@ -1,14 +1,14 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 28 compileSdkVersion 29
buildToolsVersion '29.0.2' buildToolsVersion '29.0.2'
defaultConfig { defaultConfig {
applicationId "com.m2049r.xmrwallet" applicationId "com.m2049r.xmrwallet"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 28 targetSdkVersion 29
versionCode 502 versionCode 702
versionName "1.15.2 'Dark Fork'" versionName "1.17.2 'Druk'"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild { externalNativeBuild {
cmake { cmake {
@@ -56,6 +56,9 @@ android {
debug { debug {
applicationIdSuffix ".debug" applicationIdSuffix ".debug"
} }
applicationVariants.all { variant ->
variant.buildConfigField "String", "ID_A", "\"" + getId("ID_A") + "\""
}
} }
externalNativeBuild { externalNativeBuild {
@@ -92,8 +95,7 @@ android {
variant.outputs.all { variant.outputs.all {
output -> output ->
def abiName = output.getFilter(com.android.build.OutputFile.ABI) def abiName = output.getFilter(com.android.build.OutputFile.ABI)
output.versionCodeOverride = abiCodes.get(abiName, 0) + 10 * variant.versionCode output.versionCodeOverride = abiCodes.get(abiName, 0) + 10 * versionCode
//def flavor = output.getFilter(flavor)
if (abiName == null) abiName = "universal" if (abiName == null) abiName = "universal"
def v = "${variant.versionName}".replaceFirst(" '.*' ?", "") def v = "${variant.versionName}".replaceFirst(" '.*' ?", "")
@@ -110,20 +112,26 @@ android {
} }
} }
def getId(name) {
def Properties props = new Properties()
props.load(new FileInputStream(new File('monerujo.id')))
return props[name]
}
dependencies { dependencies {
implementation 'androidx.core:core:1.3.2' implementation 'androidx.core:core:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation group: 'com.google.android.material', name: 'material', version: '1.3.0-alpha03' implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'me.dm7.barcodescanner:zxing:1.9.8' implementation 'me.dm7.barcodescanner:zxing:1.9.8'
implementation "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion" implementation "com.squareup.okhttp3:okhttp:4.9.0"
implementation "com.burgstaller:okhttp-digest:2.0" implementation "com.burgstaller:okhttp-digest:2.1"
implementation "com.jakewharton.timber:timber:$rootProject.ext.timberVersion" implementation "com.jakewharton.timber:timber:4.7.1"
implementation 'com.nulab-inc:zxcvbn:1.3.0' implementation 'com.nulab-inc:zxcvbn:1.3.0'
@@ -132,12 +140,14 @@ dependencies {
implementation 'org.slf4j:slf4j-nop:1.7.30' implementation 'org.slf4j:slf4j-nop:1.7.30'
implementation 'com.github.brnunes:swipeablerecyclerview:1.0.2' implementation 'com.github.brnunes:swipeablerecyclerview:1.0.2'
// https://mvnrepository.com/artifact/com.github.aelstad/keccakj
implementation 'com.github.aelstad:keccakj:1.1.0' implementation 'com.github.aelstad:keccakj:1.1.0'
testImplementation "junit:junit:$rootProject.ext.junitVersion" testImplementation "junit:junit:$rootProject.ext.junitVersion"
testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion" testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion" testImplementation "com.squareup.okhttp3:mockwebserver:4.9.0"
testImplementation 'org.json:json:20180813' testImplementation 'org.json:json:20180813'
testImplementation 'net.jodah:concurrentunit:0.4.4' testImplementation 'net.jodah:concurrentunit:0.4.4'
compileOnly 'org.projectlombok:lombok:1.18.16'
annotationProcessor 'org.projectlombok:lombok:1.18.16'
} }

View File

@@ -13,6 +13,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application <application
android:requestLegacyExternalStorage="true"
android:name=".XmrWalletApplication" android:name=".XmrWalletApplication"
android:allowBackup="false" android:allowBackup="false"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"

View File

@@ -185,7 +185,6 @@ public class Dispatcher implements PeerRetriever.OnGetPeers {
public void seedPeers(Collection<NodeInfo> seedNodes) { public void seedPeers(Collection<NodeInfo> seedNodes) {
for (NodeInfo node : seedNodes) { for (NodeInfo node : seedNodes) {
node.clear();
if (node.isFavourite()) { if (node.isFavourite()) {
rpcNodes.add(node); rpcNodes.add(node);
if (listener != null) listener.onGet(node); if (listener != null) listener.onGet(node);

View File

@@ -1,3 +1,19 @@
/*
* 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.
*/
package com.m2049r.levin.scanner; package com.m2049r.levin.scanner;
import java.net.InetAddress; import java.net.InetAddress;

View File

@@ -1,3 +1,19 @@
/*
* Copyright (c) 2017-2020 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; package com.m2049r.xmrwallet;
import android.app.PendingIntent; import android.app.PendingIntent;

File diff suppressed because it is too large Load Diff

View File

@@ -45,6 +45,7 @@ import com.m2049r.xmrwallet.layout.WalletInfoAdapter;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.KeyStoreHelper; import com.m2049r.xmrwallet.util.KeyStoreHelper;
import com.m2049r.xmrwallet.util.NodePinger;
import com.m2049r.xmrwallet.util.Notice; import com.m2049r.xmrwallet.util.Notice;
import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.widget.Toolbar;
@@ -105,6 +106,8 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
Set<NodeInfo> getFavouriteNodes(); Set<NodeInfo> getFavouriteNodes();
Set<NodeInfo> getOrPopulateFavourites();
boolean hasLedger(); boolean hasLedger();
} }
@@ -132,11 +135,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
activityCallback.setTitle(null); activityCallback.setTitle(null);
activityCallback.setToolbarButton(Toolbar.BUTTON_CREDITS); activityCallback.setToolbarButton(Toolbar.BUTTON_CREDITS);
activityCallback.showNet(); activityCallback.showNet();
NodeInfo node = activityCallback.getNode(); pingSelectedNode();
if (node == null)
findBestNode();
else
showNode(node);
} }
@Override @Override
@@ -186,23 +185,10 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
pbNode = view.findViewById(R.id.pbNode); pbNode = view.findViewById(R.id.pbNode);
llNode = view.findViewById(R.id.llNode); llNode = view.findViewById(R.id.llNode);
llNode.setOnClickListener(new View.OnClickListener() { llNode.setOnClickListener(v -> startNodePrefs());
@Override
public void onClick(View v) {
if (activityCallback.getFavouriteNodes().isEmpty())
startNodePrefs();
else
findBestNode();
}
});
tvNodeName = view.findViewById(R.id.tvNodeName); tvNodeName = view.findViewById(R.id.tvNodeName);
tvNodeAddress = view.findViewById(R.id.tvNodeAddress); tvNodeAddress = view.findViewById(R.id.tvNodeAddress);
view.findViewById(R.id.ibOption).setOnClickListener(new View.OnClickListener() { view.findViewById(R.id.ibRenew).setOnClickListener(v -> findBestNode());
@Override
public void onClick(View v) {
startNodePrefs();
}
});
Helper.hideKeyboard(getActivity()); Helper.hideKeyboard(getActivity());
@@ -421,32 +407,57 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
} }
public void findBestNode() { public void findBestNode() {
new AsyncFindBestNode().execute(); new AsyncFindBestNode().execute(AsyncFindBestNode.FIND_BEST);
} }
private class AsyncFindBestNode extends AsyncTask<Void, Void, NodeInfo> { public void pingSelectedNode() {
new AsyncFindBestNode().execute(AsyncFindBestNode.PING_SELECTED);
}
private NodeInfo autoselect(Set<NodeInfo> nodes) {
if (nodes.isEmpty()) return null;
NodePinger.execute(nodes, null);
List<NodeInfo> nodeList = new ArrayList<>(nodes);
Collections.sort(nodeList, NodeInfo.BestNodeComparator);
return nodeList.get(0);
}
private class AsyncFindBestNode extends AsyncTask<Integer, Void, NodeInfo> {
final static int PING_SELECTED = 0;
final static int FIND_BEST = 1;
@Override @Override
protected void onPreExecute() { protected void onPreExecute() {
super.onPreExecute(); super.onPreExecute();
pbNode.setVisibility(View.VISIBLE); pbNode.setVisibility(View.VISIBLE);
llNode.setVisibility(View.INVISIBLE); llNode.setVisibility(View.INVISIBLE);
activityCallback.setNode(null);
} }
@Override @Override
protected NodeInfo doInBackground(Void... params) { protected NodeInfo doInBackground(Integer... params) {
List<NodeInfo> nodesToTest = new ArrayList<>(activityCallback.getFavouriteNodes()); Set<NodeInfo> favourites = activityCallback.getOrPopulateFavourites();
Timber.d("testing best node from %d", nodesToTest.size()); NodeInfo selectedNode;
if (nodesToTest.isEmpty()) return null; if (params[0] == FIND_BEST) {
for (NodeInfo node : nodesToTest) { selectedNode = autoselect(favourites);
node.testRpcService(); // TODO: do this in parallel? } else if (params[0] == PING_SELECTED) {
// no: it's better if it looks like it's doing something selectedNode = activityCallback.getNode();
if (!activityCallback.getFavouriteNodes().contains(selectedNode))
selectedNode = null; // it's not in the favourites (any longer)
if (selectedNode == null)
for (NodeInfo node : favourites) {
if (node.isSelected()) {
selectedNode = node;
break;
} }
Collections.sort(nodesToTest, NodeInfo.BestNodeComparator); }
NodeInfo bestNode = nodesToTest.get(0); if (selectedNode == null) { // autoselect
if (bestNode.isValid()) { selectedNode = autoselect(favourites);
activityCallback.setNode(bestNode); } else
return bestNode; selectedNode.testRpcService();
} else throw new IllegalStateException();
if ((selectedNode != null) && selectedNode.isValid()) {
activityCallback.setNode(selectedNode);
return selectedNode;
} else { } else {
activityCallback.setNode(null); activityCallback.setNode(null);
return null; return null;
@@ -461,12 +472,6 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
if (result != null) { if (result != null) {
Timber.d("found a good node %s", result.toString()); Timber.d("found a good node %s", result.toString());
showNode(result); showNode(result);
} else {
if (!activityCallback.getFavouriteNodes().isEmpty()) {
tvNodeName.setText(getResources().getText(R.string.node_refresh_hint));
tvNodeName.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_refresh_black_24dp, 0, 0, 0);
tvNodeAddress.setText(null);
tvNodeAddress.setVisibility(View.GONE);
} else { } else {
tvNodeName.setText(getResources().getText(R.string.node_create_hint)); tvNodeName.setText(getResources().getText(R.string.node_create_hint));
tvNodeName.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); tvNodeName.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
@@ -474,7 +479,6 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
tvNodeAddress.setVisibility(View.GONE); tvNodeAddress.setVisibility(View.GONE);
} }
} }
}
@Override @Override
protected void onCancelled(NodeInfo result) { //TODO: cancel this on exit from fragment protected void onCancelled(NodeInfo result) { //TODO: cancel this on exit from fragment
@@ -485,12 +489,11 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
private void showNode(NodeInfo nodeInfo) { private void showNode(NodeInfo nodeInfo) {
tvNodeName.setText(nodeInfo.getName()); tvNodeName.setText(nodeInfo.getName());
tvNodeName.setCompoundDrawablesWithIntrinsicBounds(NodeInfoAdapter.getPingIcon(nodeInfo), 0, 0, 0); tvNodeName.setCompoundDrawablesWithIntrinsicBounds(NodeInfoAdapter.getPingIcon(nodeInfo), 0, 0, 0);
tvNodeAddress.setText(nodeInfo.getAddress()); Helper.showTimeDifference(tvNodeAddress, nodeInfo.getTimestamp());
tvNodeAddress.setVisibility(View.VISIBLE); tvNodeAddress.setVisibility(View.VISIBLE);
} }
private void startNodePrefs() { private void startNodePrefs() {
activityCallback.setNode(null);
activityCallback.onNodePrefs(); activityCallback.onNodePrefs();
} }
} }

View File

@@ -20,15 +20,6 @@ import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.Nullable;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputLayout;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.recyclerview.widget.RecyclerView;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@@ -41,13 +32,23 @@ import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputLayout;
import com.m2049r.levin.scanner.Dispatcher; import com.m2049r.levin.scanner.Dispatcher;
import com.m2049r.xmrwallet.data.DefaultNodes;
import com.m2049r.xmrwallet.data.Node; import com.m2049r.xmrwallet.data.Node;
import com.m2049r.xmrwallet.data.NodeInfo; import com.m2049r.xmrwallet.data.NodeInfo;
import com.m2049r.xmrwallet.layout.NodeInfoAdapter; import com.m2049r.xmrwallet.layout.NodeInfoAdapter;
import com.m2049r.xmrwallet.model.NetworkType; import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.NodePinger;
import com.m2049r.xmrwallet.util.Notice; import com.m2049r.xmrwallet.util.Notice;
import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.widget.Toolbar;
@@ -55,8 +56,10 @@ import java.io.File;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import timber.log.Timber; import timber.log.Timber;
@@ -87,7 +90,11 @@ public class NodeFragment extends Fragment
Set<NodeInfo> getFavouriteNodes(); Set<NodeInfo> getFavouriteNodes();
void setFavouriteNodes(Set<NodeInfo> favouriteNodes); Set<NodeInfo> getOrPopulateFavourites();
void setFavouriteNodes(Collection<NodeInfo> favouriteNodes);
void setNode(NodeInfo node);
} }
void filterFavourites() { void filterFavourites() {
@@ -156,16 +163,13 @@ public class NodeFragment extends Fragment
tvPull = view.findViewById(R.id.tvPull); tvPull = view.findViewById(R.id.tvPull);
pullToRefresh = view.findViewById(R.id.pullToRefresh); pullToRefresh = view.findViewById(R.id.pullToRefresh);
pullToRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { pullToRefresh.setOnRefreshListener(() -> {
@Override
public void onRefresh() {
if (WalletManager.getInstance().getNetworkType() == NetworkType.NetworkType_Mainnet) { if (WalletManager.getInstance().getNetworkType() == NetworkType.NetworkType_Mainnet) {
refresh(); refresh(AsyncFindNodes.SCAN);
} else { } else {
Toast.makeText(getActivity(), getString(R.string.node_wrong_net), Toast.LENGTH_LONG).show(); Toast.makeText(getActivity(), getString(R.string.node_wrong_net), Toast.LENGTH_LONG).show();
pullToRefresh.setRefreshing(false); pullToRefresh.setRefreshing(false);
} }
}
}); });
Helper.hideKeyboard(getActivity()); Helper.hideKeyboard(getActivity());
@@ -176,16 +180,19 @@ public class NodeFragment extends Fragment
ViewGroup llNotice = view.findViewById(R.id.llNotice); ViewGroup llNotice = view.findViewById(R.id.llNotice);
Notice.showAll(llNotice, ".*_nodes"); Notice.showAll(llNotice, ".*_nodes");
refresh(AsyncFindNodes.PING); // start connection tests
return view; return view;
} }
private AsyncFindNodes asyncFindNodes = null; private AsyncFindNodes asyncFindNodes = null;
private void refresh() { private boolean refresh(int type) {
if (asyncFindNodes != null) return; // ignore refresh request as one is ongoing if (asyncFindNodes != null) return false; // ignore refresh request as one is ongoing
asyncFindNodes = new AsyncFindNodes(); asyncFindNodes = new AsyncFindNodes();
updateRefreshElements(); updateRefreshElements();
asyncFindNodes.execute(); asyncFindNodes.execute(type);
return true;
} }
@Override @Override
@@ -204,6 +211,25 @@ public class NodeFragment extends Fragment
@Override @Override
public void onInteraction(final View view, final NodeInfo nodeItem) { public void onInteraction(final View view, final NodeInfo nodeItem) {
Timber.d("onInteraction"); Timber.d("onInteraction");
if (!nodeItem.isFavourite()) {
nodeItem.setFavourite(true);
activityCallback.setFavouriteNodes(nodeList);
}
AsyncTask.execute(() -> {
activityCallback.setNode(nodeItem); // this marks it as selected & saves it as well
nodeItem.setSelecting(false);
try {
Objects.requireNonNull(getActivity()).runOnUiThread(() -> nodesAdapter.allowClick(true));
} catch (NullPointerException ex) {
// it's ok
}
});
}
// open up edit dialog
@Override
public void onLongInteraction(final View view, final NodeInfo nodeItem) {
Timber.d("onLongInteraction");
EditDialog diag = createEditDialog(nodeItem); EditDialog diag = createEditDialog(nodeItem);
if (diag != null) { if (diag != null) {
diag.show(); diag.show();
@@ -223,7 +249,12 @@ public class NodeFragment extends Fragment
} }
} }
private class AsyncFindNodes extends AsyncTask<Void, NodeInfo, Boolean> { private class AsyncFindNodes extends AsyncTask<Integer, NodeInfo, Boolean>
implements NodePinger.Listener {
final static int SCAN = 0;
final static int RESTORE_DEFAULTS = 1;
final static int PING = 2;
@Override @Override
protected void onPreExecute() { protected void onPreExecute() {
super.onPreExecute(); super.onPreExecute();
@@ -234,18 +265,28 @@ public class NodeFragment extends Fragment
} }
@Override @Override
protected Boolean doInBackground(Void... params) { protected Boolean doInBackground(Integer... params) {
if (params[0] == RESTORE_DEFAULTS) { // true = restore defaults
for (DefaultNodes node : DefaultNodes.values()) {
NodeInfo nodeInfo = NodeInfo.fromString(node.getUri());
if (nodeInfo != null) {
nodeInfo.setFavourite(true);
nodeList.add(nodeInfo);
}
}
NodePinger.execute(nodeList, this);
return true;
} else if (params[0] == PING) {
NodePinger.execute(nodeList, this);
return true;
} else if (params[0] == SCAN) {
// otherwise scan the network
Timber.d("scanning"); Timber.d("scanning");
Set<NodeInfo> seedList = new HashSet<>(); Set<NodeInfo> seedList = new HashSet<>();
seedList.addAll(nodeList); seedList.addAll(nodeList);
nodeList.clear(); nodeList.clear();
Timber.d("seed %d", seedList.size()); Timber.d("seed %d", seedList.size());
Dispatcher d = new Dispatcher(new Dispatcher.Listener() { Dispatcher d = new Dispatcher(info -> publishProgress(info));
@Override
public void onGet(NodeInfo info) {
publishProgress(info);
}
});
d.seedPeers(seedList); d.seedPeers(seedList);
d.awaitTermination(NODES_TO_FIND); d.awaitTermination(NODES_TO_FIND);
@@ -277,6 +318,8 @@ public class NodeFragment extends Fragment
nodeList.addAll(d.getRpcNodes()); nodeList.addAll(d.getRpcNodes());
return true; return true;
} }
return false;
}
@Override @Override
protected void onProgressUpdate(NodeInfo... values) { protected void onProgressUpdate(NodeInfo... values) {
@@ -310,6 +353,10 @@ public class NodeFragment extends Fragment
nodesAdapter.allowClick(true); nodesAdapter.allowClick(true);
updateRefreshElements(); updateRefreshElements();
} }
public void publish(NodeInfo nodeInfo) {
publishProgress(nodeInfo);
}
} }
@Override @Override
@@ -410,11 +457,6 @@ public class NodeFragment extends Fragment
NodeFragment.this.editDialog = null; NodeFragment.this.editDialog = null;
} }
private void undoChanges() {
if (nodeBackup != null)
nodeInfo.overwriteWith(nodeBackup);
}
private void show() { private void show() {
editDialog.show(); editDialog.show();
} }
@@ -480,12 +522,9 @@ public class NodeFragment extends Fragment
.setPositiveButton(getString(R.string.label_ok), null) .setPositiveButton(getString(R.string.label_ok), null)
.setNeutralButton(getString(R.string.label_test), null) .setNeutralButton(getString(R.string.label_test), null)
.setNegativeButton(getString(R.string.label_cancel), .setNegativeButton(getString(R.string.label_cancel),
new DialogInterface.OnClickListener() { (dialog, id) -> {
public void onClick(DialogInterface dialog, int id) {
undoChanges();
closeDialog(); closeDialog();
nodesAdapter.dataSetChanged(); // to refresh test results nodesAdapter.dataSetChanged(); // to refresh test results
}
}); });
editDialog = alertDialogBuilder.create(); editDialog = alertDialogBuilder.create();
@@ -531,7 +570,6 @@ public class NodeFragment extends Fragment
@Override @Override
protected void onPreExecute() { protected void onPreExecute() {
super.onPreExecute(); super.onPreExecute();
nodeInfo.clear();
tvResult.setText(getString(R.string.node_testing, nodeInfo.getHostAddress())); tvResult.setText(getString(R.string.node_testing, nodeInfo.getHostAddress()));
} }
@@ -556,4 +594,14 @@ public class NodeFragment extends Fragment
} }
} }
} }
void restoreDefaultNodes() {
if (WalletManager.getInstance().getNetworkType() == NetworkType.NetworkType_Mainnet) {
if (!refresh(AsyncFindNodes.RESTORE_DEFAULTS)) {
Toast.makeText(getActivity(), getString(R.string.toast_default_nodes), Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(getActivity(), getString(R.string.node_wrong_net), Toast.LENGTH_LONG).show();
}
}
} }

View File

@@ -56,6 +56,7 @@ import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix; import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter; import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.data.BarcodeData; import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.ledger.LedgerProgressDialog; import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
@@ -468,7 +469,7 @@ public class ReceiveFragment extends Fragment {
Timber.d("CLEARQR"); Timber.d("CLEARQR");
return; return;
} }
bcData = new BarcodeData(BarcodeData.Asset.XMR, address, null, notes, xmrAmount); bcData = new BarcodeData(BarcodeData.Asset.XMR, address, notes, xmrAmount);
int size = Math.max(ivQrCode.getWidth(), ivQrCode.getHeight()); int size = Math.max(ivQrCode.getWidth(), ivQrCode.getHeight());
Bitmap qr = generate(bcData.getUriString(), size, size); Bitmap qr = generate(bcData.getUriString(), size, size);
if (qr != null) { if (qr != null) {
@@ -610,6 +611,8 @@ public class ReceiveFragment extends Fragment {
super.onPostExecute(result); super.onPostExecute(result);
if (dialogOpened) if (dialogOpened)
progressCallback.dismissProgressDialog(); progressCallback.dismissProgressDialog();
if (!isAdded()) // never mind then
return;
tvAddress.setText(newSubaddress); tvAddress.setText(newSubaddress);
tvAddressLabel.setText(getString(R.string.generate_address_label_sub, tvAddressLabel.setText(getString(R.string.generate_address_label_sub,
wallet.getNumSubaddresses() - 1)); wallet.getNumSubaddresses() - 1));

View File

@@ -16,8 +16,11 @@
package com.m2049r.xmrwallet; package com.m2049r.xmrwallet;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Paint;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.InputType; import android.text.InputType;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -25,6 +28,7 @@ import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
@@ -77,6 +81,9 @@ public class TxFragment extends Fragment {
private TextView tvTxXmrToKey; private TextView tvTxXmrToKey;
private TextView tvDestinationBtc; private TextView tvDestinationBtc;
private TextView tvTxAmountBtc; private TextView tvTxAmountBtc;
private TextView tvXmrToSupport;
private TextView tvXmrToKeyLabel;
private ImageView tvXmrToLogo;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
@@ -88,6 +95,9 @@ public class TxFragment extends Fragment {
tvTxXmrToKey = view.findViewById(R.id.tvTxXmrToKey); tvTxXmrToKey = view.findViewById(R.id.tvTxXmrToKey);
tvDestinationBtc = view.findViewById(R.id.tvDestinationBtc); tvDestinationBtc = view.findViewById(R.id.tvDestinationBtc);
tvTxAmountBtc = view.findViewById(R.id.tvTxAmountBtc); tvTxAmountBtc = view.findViewById(R.id.tvTxAmountBtc);
tvXmrToSupport = view.findViewById(R.id.tvXmrToSupport);
tvXmrToKeyLabel = view.findViewById(R.id.tvXmrToKeyLabel);
tvXmrToLogo = view.findViewById(R.id.tvXmrToLogo);
tvAccount = view.findViewById(R.id.tvAccount); tvAccount = view.findViewById(R.id.tvAccount);
tvAddress = view.findViewById(R.id.tvAddress); tvAddress = view.findViewById(R.id.tvAddress);
@@ -104,12 +114,9 @@ public class TxFragment extends Fragment {
etTxNotes.setRawInputType(InputType.TYPE_CLASS_TEXT); etTxNotes.setRawInputType(InputType.TYPE_CLASS_TEXT);
tvTxXmrToKey.setOnClickListener(new View.OnClickListener() { tvTxXmrToKey.setOnClickListener(v -> {
@Override
public void onClick(View v) {
Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_xmrtokey), tvTxXmrToKey.getText().toString()); 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(); Toast.makeText(getActivity(), getString(R.string.message_copy_xmrtokey), Toast.LENGTH_SHORT).show();
}
}); });
Bundle args = getArguments(); Bundle args = getArguments();
@@ -283,12 +290,36 @@ public class TxFragment extends Fragment {
showBtcInfo(); showBtcInfo();
} }
@SuppressLint("SetTextI18n")
void showBtcInfo() { void showBtcInfo() {
if (userNotes.xmrtoKey != null) { if (userNotes.xmrtoKey != null) {
cvXmrTo.setVisibility(View.VISIBLE); cvXmrTo.setVisibility(View.VISIBLE);
tvTxXmrToKey.setText(userNotes.xmrtoKey); String key = userNotes.xmrtoKey;
if ("xmrto".equals(userNotes.xmrtoTag)) { // legacy xmr.to service :(
key = "xmrto-" + key;
}
tvTxXmrToKey.setText(key);
tvDestinationBtc.setText(userNotes.xmrtoDestination); tvDestinationBtc.setText(userNotes.xmrtoDestination);
tvTxAmountBtc.setText(userNotes.xmrtoAmount + " BTC"); tvTxAmountBtc.setText(userNotes.xmrtoAmount + " BTC");
switch (userNotes.xmrtoTag) {
case "xmrto":
tvXmrToSupport.setVisibility(View.GONE);
tvXmrToKeyLabel.setVisibility(View.INVISIBLE);
tvXmrToLogo.setImageResource(R.drawable.ic_xmrto_logo);
break;
case "side": // defaults in layout - just add underline
tvXmrToSupport.setPaintFlags(tvXmrToSupport.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
tvXmrToSupport.setOnClickListener(v -> {
Uri uri = Uri.parse("https://sideshift.ai/orders/" + userNotes.xmrtoKey);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
});
break;
default:
tvXmrToSupport.setVisibility(View.GONE);
tvXmrToKeyLabel.setVisibility(View.INVISIBLE);
tvXmrToLogo.setVisibility(View.GONE);
}
} else { } else {
cvXmrTo.setVisibility(View.GONE); cvXmrTo.setVisibility(View.GONE);
} }

View File

@@ -579,11 +579,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
}); });
if (numAccounts != wallet.getNumAccounts()) { if (numAccounts != wallet.getNumAccounts()) {
numAccounts = wallet.getNumAccounts(); numAccounts = wallet.getNumAccounts();
runOnUiThread(new Runnable() { runOnUiThread(this::updateAccountsList);
public void run() {
updateAccountsList();
}
});
} }
try { try {
final WalletFragment walletFragment = (WalletFragment) final WalletFragment walletFragment = (WalletFragment)
@@ -1055,9 +1051,10 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
} }
void updateAccountsList() { void updateAccountsList() {
final Wallet wallet = getWallet();
Menu menu = accountsView.getMenu(); Menu menu = accountsView.getMenu();
menu.removeGroup(R.id.accounts_list); menu.removeGroup(R.id.accounts_list);
final Wallet wallet = getWallet();
if (wallet != null) {
final int n = wallet.getNumAccounts(); final int n = wallet.getNumAccounts();
final boolean showBalances = (n > 1) && !isStreetMode(); final boolean showBalances = (n > 1) && !isStreetMode();
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
@@ -1071,6 +1068,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
} }
menu.setGroupCheckable(R.id.accounts_list, true, true); menu.setGroupCheckable(R.id.accounts_list, true, true);
} }
}
@Override @Override
public void setDrawerEnabled(boolean enabled) { public void setDrawerEnabled(boolean enabled) {

View File

@@ -16,14 +16,13 @@
package com.m2049r.xmrwallet; package com.m2049r.xmrwallet;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Build; import android.os.Build;
import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.model.NetworkType; import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.util.DayNightMode;
import com.m2049r.xmrwallet.util.LocaleHelper; import com.m2049r.xmrwallet.util.LocaleHelper;
import com.m2049r.xmrwallet.util.NightmodeHelper; import com.m2049r.xmrwallet.util.NightmodeHelper;

View File

@@ -21,7 +21,6 @@ import android.net.Uri;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.BitcoinAddressValidator; import com.m2049r.xmrwallet.util.BitcoinAddressValidator;
import com.m2049r.xmrwallet.util.OpenAliasHelper; import com.m2049r.xmrwallet.util.OpenAliasHelper;
import com.m2049r.xmrwallet.util.PaymentProtocolHelper;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
@@ -43,7 +42,6 @@ public class BarcodeData {
static final String BTC_SCHEME = "bitcoin"; static final String BTC_SCHEME = "bitcoin";
static final String BTC_DESCRIPTION = "message"; static final String BTC_DESCRIPTION = "message";
static final String BTC_AMOUNT = "amount"; static final String BTC_AMOUNT = "amount";
static final String BTC_BIP70_PARM = "r";
public enum Asset { public enum Asset {
XMR, BTC XMR, BTC
@@ -52,8 +50,7 @@ public class BarcodeData {
public enum Security { public enum Security {
NORMAL, NORMAL,
OA_NO_DNSSEC, OA_NO_DNSSEC,
OA_DNSSEC, OA_DNSSEC
BIP70
} }
final public Asset asset; final public Asset asset;
@@ -62,7 +59,6 @@ public class BarcodeData {
final public String amount; final public String amount;
final public String description; final public String description;
final public Security security; final public Security security;
final public String bip70;
public BarcodeData(Asset asset, String address) { public BarcodeData(Asset asset, String address) {
this(asset, address, null, null, null, Security.NORMAL); this(asset, address, null, null, null, Security.NORMAL);
@@ -80,21 +76,19 @@ public class BarcodeData {
this(asset, address, null, description, amount, Security.NORMAL); this(asset, address, null, description, amount, Security.NORMAL);
} }
public BarcodeData(Asset asset, String address, String addressName, String description, String amount, Security security) { public BarcodeData(Asset asset, String address, String description, String amount) {
this(asset, address, addressName, null, description, amount, security); this(asset, address, null, description, amount, Security.NORMAL);
} }
public BarcodeData(Asset asset, String address, String addressName, String bip70, String description, String amount, Security security) { public BarcodeData(Asset asset, String address, String addressName, String description, String amount, Security security) {
this.asset = asset; this.asset = asset;
this.address = address; this.address = address;
this.bip70 = bip70;
this.addressName = addressName; this.addressName = addressName;
this.description = description; this.description = description;
this.amount = amount; this.amount = amount;
this.security = security; this.security = security;
} }
public Uri getUri() { public Uri getUri() {
return Uri.parse(getUriString()); return Uri.parse(getUriString());
} }
@@ -127,10 +121,6 @@ public class BarcodeData {
if (bcData == null) { if (bcData == null) {
bcData = parseBitcoinUri(qrCode); bcData = parseBitcoinUri(qrCode);
} }
// check for btc payment uri (like bitpay)
if (bcData == null) {
bcData = parseBitcoinPaymentUrl(qrCode);
}
// check for naked btc address // check for naked btc address
if (bcData == null) { if (bcData == null) {
bcData = parseBitcoinNaked(qrCode); bcData = parseBitcoinNaked(qrCode);
@@ -191,11 +181,11 @@ public class BarcodeData {
} }
} }
if (!Wallet.isAddressValid(address)) { if ((address == null) || !Wallet.isAddressValid(address)) {
Timber.d("address invalid"); Timber.d("address invalid");
return null; return null;
} }
return new BarcodeData(Asset.XMR, address, paymentId, description, amount); return new BarcodeData(Asset.XMR, address, description, amount);
} }
static public BarcodeData parseMoneroNaked(String address) { static public BarcodeData parseMoneroNaked(String address) {
@@ -245,18 +235,10 @@ public class BarcodeData {
} }
String description = parms.get(BTC_DESCRIPTION); String description = parms.get(BTC_DESCRIPTION);
String address = parts[0]; // no need to decode as there can bo no special characters String address = parts[0]; // no need to decode as there can bo no special characters
if (address.isEmpty()) { // possibly a BIP72 uri if (address.isEmpty()) {
String bip70 = parms.get(BTC_BIP70_PARM); Timber.d("no address");
if (bip70 == null) {
Timber.d("no address and can't find pp url");
return null; 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, description, null, Security.NORMAL);
}
if (!BitcoinAddressValidator.validate(address)) { if (!BitcoinAddressValidator.validate(address)) {
Timber.d("BTC address (%s) invalid", address); Timber.d("BTC address (%s) invalid", address);
return null; return null;
@@ -270,22 +252,7 @@ public class BarcodeData {
return null; // we have an amount but its not a number! return null; // we have an amount but its not a number!
} }
} }
return new BarcodeData(BarcodeData.Asset.BTC, address, null, description, amount); return new BarcodeData(BarcodeData.Asset.BTC, address, 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(Asset.BTC, url);
} }
static public BarcodeData parseBitcoinNaked(String address) { static public BarcodeData parseBitcoinNaked(String address) {
@@ -333,6 +300,10 @@ public class BarcodeData {
} }
String paymentId = oaAttrs.get(OpenAliasHelper.OA1_PAYMENTID); String paymentId = oaAttrs.get(OpenAliasHelper.OA1_PAYMENTID);
if (paymentId != null) {
Timber.e("paymentId not supported");
return null;
}
String description = oaAttrs.get(OpenAliasHelper.OA1_DESCRIPTION); String description = oaAttrs.get(OpenAliasHelper.OA1_DESCRIPTION);
if (description == null) { if (description == null) {
description = oaAttrs.get(OpenAliasHelper.OA1_NAME); description = oaAttrs.get(OpenAliasHelper.OA1_NAME);
@@ -348,13 +319,9 @@ public class BarcodeData {
return null; // we have an amount but its not a number! return null; // we have an amount but its not a number!
} }
} }
if ((paymentId != null) && !Wallet.isPaymentIdValid(paymentId)) {
Timber.d("paymentId invalid");
return null;
}
Security sec = dnssec ? BarcodeData.Security.OA_DNSSEC : BarcodeData.Security.OA_NO_DNSSEC; Security sec = dnssec ? BarcodeData.Security.OA_DNSSEC : BarcodeData.Security.OA_NO_DNSSEC;
return new BarcodeData(asset, address, addressName, paymentId, description, amount, sec); return new BarcodeData(asset, address, addressName, description, amount, sec);
} }
} }

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2020 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 lombok.AllArgsConstructor;
import lombok.Getter;
// Nodes stolen from https://moneroworld.com/#nodes
@AllArgsConstructor
public enum DefaultNodes {
MONERUJO("nodex.monerujo.io:18081"),
XMRTO("node.xmr.to:18081"),
SUPPORTXMR("node.supportxmr.com:18081"),
HASHVAULT("nodes.hashvault.pro:18081"),
MONEROWORLD("node.moneroworld.com:18089"),
XMRTW("opennode.xmr-tw.org:18089");
@Getter
private final String uri;
}

View File

@@ -26,6 +26,8 @@ import java.net.URLDecoder;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import lombok.Getter;
import lombok.Setter;
import timber.log.Timber; import timber.log.Timber;
public class Node { public class Node {
@@ -33,15 +35,29 @@ public class Node {
static public final String STAGENET = "stagenet"; static public final String STAGENET = "stagenet";
static public final String TESTNET = "testnet"; static public final String TESTNET = "testnet";
@Getter
private String name = null; private String name = null;
@Getter
final private NetworkType networkType; final private NetworkType networkType;
InetAddress hostAddress; InetAddress hostAddress;
@Getter
private String host; private String host;
@Getter
@Setter
int rpcPort = 0; int rpcPort = 0;
private int levinPort = 0; private int levinPort = 0;
@Getter
@Setter
private String username = ""; private String username = "";
@Getter
@Setter
private String password = ""; private String password = "";
@Getter
@Setter
private boolean favourite = false; private boolean favourite = false;
@Getter
@Setter
private boolean selected = false;
@Override @Override
public int hashCode() { public int hashCode() {
@@ -193,7 +209,6 @@ public class Node {
this.levinPort = socketAddress.getPort(); this.levinPort = socketAddress.getPort();
this.username = ""; this.username = "";
this.password = ""; this.password = "";
//this.name = socketAddress.getHostName(); // triggers DNS so we don't do it by default
} }
public String getAddress() { public String getAddress() {
@@ -204,14 +219,6 @@ public class Node {
return hostAddress.getHostAddress(); return hostAddress.getHostAddress();
} }
public String getHost() {
return host;
}
public int getRpcPort() {
return rpcPort;
}
public void setHost(String host) throws UnknownHostException { public void setHost(String host) throws UnknownHostException {
if ((host == null) || (host.isEmpty())) if ((host == null) || (host.isEmpty()))
throw new UnknownHostException("loopback not supported (yet?)"); throw new UnknownHostException("loopback not supported (yet?)");
@@ -219,18 +226,6 @@ public class Node {
this.hostAddress = InetAddress.getByName(host); this.hostAddress = InetAddress.getByName(host);
} }
public void setUsername(String user) {
username = user;
}
public void setPassword(String pass) {
password = pass;
}
public void setRpcPort(int port) {
this.rpcPort = port;
}
public void setName() { public void setName() {
if (name == null) if (name == null)
this.name = hostAddress.getHostName(); this.name = hostAddress.getHostName();
@@ -243,30 +238,6 @@ public class Node {
this.name = name; this.name = name;
} }
public String getName() {
return name;
}
public NetworkType getNetworkType() {
return networkType;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public boolean isFavourite() {
return favourite;
}
public void setFavourite(boolean favourite) {
this.favourite = favourite;
}
public void toggleFavourite() { public void toggleFavourite() {
favourite = !favourite; favourite = !favourite;
} }

View File

@@ -21,8 +21,8 @@ import com.burgstaller.okhttp.CachingAuthenticatorDecorator;
import com.burgstaller.okhttp.digest.CachingAuthenticator; import com.burgstaller.okhttp.digest.CachingAuthenticator;
import com.burgstaller.okhttp.digest.Credentials; import com.burgstaller.okhttp.digest.Credentials;
import com.burgstaller.okhttp.digest.DigestAuthenticator; import com.burgstaller.okhttp.digest.DigestAuthenticator;
import com.m2049r.levin.scanner.Dispatcher;
import com.m2049r.levin.scanner.LevinPeer; import com.m2049r.levin.scanner.LevinPeer;
import com.m2049r.xmrwallet.util.NodePinger;
import com.m2049r.xmrwallet.util.OkHttpHelper; import com.m2049r.xmrwallet.util.OkHttpHelper;
import org.json.JSONException; import org.json.JSONException;
@@ -36,6 +36,8 @@ import java.util.Comparator;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import lombok.Getter;
import lombok.Setter;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.MediaType; import okhttp3.MediaType;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
@@ -46,14 +48,24 @@ import okhttp3.ResponseBody;
import timber.log.Timber; import timber.log.Timber;
public class NodeInfo extends Node { public class NodeInfo extends Node {
final static public int MIN_MAJOR_VERSION = 11; final static public int MIN_MAJOR_VERSION = 14;
final static public String RPC_VERSION = "2.0"; final static public String RPC_VERSION = "2.0";
@Getter
private long height = 0; private long height = 0;
@Getter
private long timestamp = 0; private long timestamp = 0;
@Getter
private int majorVersion = 0; private int majorVersion = 0;
@Getter
private double responseTime = Double.MAX_VALUE; private double responseTime = Double.MAX_VALUE;
@Getter
private int responseCode = 0; private int responseCode = 0;
@Getter
private boolean tested = false;
@Getter
@Setter
private boolean selecting = false;
public void clear() { public void clear() {
height = 0; height = 0;
@@ -61,13 +73,13 @@ public class NodeInfo extends Node {
responseTime = Double.MAX_VALUE; responseTime = Double.MAX_VALUE;
responseCode = 0; responseCode = 0;
timestamp = 0; timestamp = 0;
tested = false;
} }
static public NodeInfo fromString(String nodeString) { static public NodeInfo fromString(String nodeString) {
try { try {
return new NodeInfo(nodeString); return new NodeInfo(nodeString);
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
Timber.w(ex);
return null; return null;
} }
} }
@@ -113,26 +125,6 @@ public class NodeInfo extends Node {
super(); super();
} }
public long getHeight() {
return height;
}
public long getTimestamp() {
return timestamp;
}
public int getMajorVersion() {
return majorVersion;
}
public double getResponseTime() {
return responseTime;
}
public int getResponseCode() {
return responseCode;
}
public boolean isSuccessful() { public boolean isSuccessful() {
return (responseCode >= 200) && (responseCode < 300); return (responseCode >= 200) && (responseCode < 300);
} }
@@ -145,16 +137,14 @@ public class NodeInfo extends Node {
return isSuccessful() && (majorVersion >= MIN_MAJOR_VERSION) && (responseTime < Double.MAX_VALUE); return isSuccessful() && (majorVersion >= MIN_MAJOR_VERSION) && (responseTime < Double.MAX_VALUE);
} }
static public Comparator<NodeInfo> BestNodeComparator = new Comparator<NodeInfo>() { static public Comparator<NodeInfo> BestNodeComparator = (o1, o2) -> {
@Override
public int compare(NodeInfo o1, NodeInfo o2) {
if (o1.isValid()) { if (o1.isValid()) {
if (o2.isValid()) { // both are valid if (o2.isValid()) { // both are valid
// higher node wins // higher node wins
int heightDiff = (int) (o2.height - o1.height); int heightDiff = (int) (o2.height - o1.height);
if (Math.abs(heightDiff) > Dispatcher.HEIGHT_WINDOW) if (heightDiff != 0)
return heightDiff; return heightDiff;
// if they are (nearly) equal, faster node wins // if they are equal, faster node wins
return (int) Math.signum(o1.responseTime - o2.responseTime); return (int) Math.signum(o1.responseTime - o2.responseTime);
} else { } else {
return -1; return -1;
@@ -162,7 +152,6 @@ public class NodeInfo extends Node {
} else { } else {
return 1; return 1;
} }
}
}; };
public void overwriteWith(NodeInfo anotherNode) { public void overwriteWith(NodeInfo anotherNode) {
@@ -192,7 +181,7 @@ public class NodeInfo extends Node {
} }
private static final int HTTP_TIMEOUT = OkHttpHelper.HTTP_TIMEOUT; private static final int HTTP_TIMEOUT = OkHttpHelper.HTTP_TIMEOUT;
public static final double PING_GOOD = HTTP_TIMEOUT / 3; //ms public static final double PING_GOOD = HTTP_TIMEOUT / 3.0; //ms
public static final double PING_MEDIUM = 2 * PING_GOOD; //ms public static final double PING_MEDIUM = 2 * PING_GOOD; //ms
public static final double PING_BAD = HTTP_TIMEOUT; public static final double PING_BAD = HTTP_TIMEOUT;
@@ -200,7 +189,15 @@ public class NodeInfo extends Node {
return testRpcService(rpcPort); return testRpcService(rpcPort);
} }
public boolean testRpcService(NodePinger.Listener listener) {
boolean result = testRpcService(rpcPort);
if (listener != null)
listener.publish(this);
return result;
}
private boolean testRpcService(int port) { private boolean testRpcService(int port) {
Timber.d("Testing %s", toNodeString());
clear(); clear();
try { try {
OkHttpClient client = OkHttpHelper.getEagerClient(); OkHttpClient client = OkHttpHelper.getEagerClient();
@@ -247,8 +244,9 @@ public class NodeInfo extends Node {
} }
} }
} catch (IOException | JSONException ex) { } catch (IOException | JSONException ex) {
// failure Timber.d(ex);
Timber.d(ex.getMessage()); } finally {
tested = true;
} }
return false; return false;
} }

View File

@@ -20,6 +20,7 @@ import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import com.m2049r.xmrwallet.model.PendingTransaction; import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.model.Wallet;
// https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents // https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents
public class TxData implements Parcelable { public class TxData implements Parcelable {
@@ -52,6 +53,10 @@ public class TxData implements Parcelable {
return amount; return amount;
} }
public double getAmountAsDouble() {
return 1.0 * amount / 1000000000000L;
}
public int getMixin() { public int getMixin() {
return mixin; return mixin;
} }
@@ -68,6 +73,10 @@ public class TxData implements Parcelable {
this.amount = amount; this.amount = amount;
} }
public void setAmount(double amount) {
this.amount = Wallet.getAmountFromDouble(amount);
}
public void setMixin(int mixin) { public void setMixin(int mixin) {
this.mixin = mixin; this.mixin = mixin;
} }

View File

@@ -18,11 +18,20 @@ package com.m2049r.xmrwallet.data;
import android.os.Parcel; import android.os.Parcel;
public class TxDataBtc extends TxData { import androidx.annotation.NonNull;
private String xmrtoUuid; import lombok.Getter;
import lombok.Setter;
public class TxDataBtc extends TxData {
@Getter
@Setter
private String xmrtoOrderId; // shown in success screen
@Getter
@Setter
private String btcAddress; private String btcAddress;
private String bip70; @Getter
@Setter
private double btcAmount; private double btcAmount;
public TxDataBtc() { public TxDataBtc() {
@@ -33,44 +42,11 @@ public class TxDataBtc extends TxData {
super(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 String getBip70() {
return bip70;
}
public void setBip70(String bip70) {
this.bip70 = bip70;
}
public double getBtcAmount() {
return btcAmount;
}
public void setBtcAmount(double btcAmount) {
this.btcAmount = btcAmount;
}
@Override @Override
public void writeToParcel(Parcel out, int flags) { public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags); super.writeToParcel(out, flags);
out.writeString(xmrtoUuid); out.writeString(xmrtoOrderId);
out.writeString(btcAddress); out.writeString(btcAddress);
out.writeString(bip70);
out.writeDouble(btcAmount); out.writeDouble(btcAmount);
} }
@@ -87,21 +63,19 @@ public class TxDataBtc extends TxData {
protected TxDataBtc(Parcel in) { protected TxDataBtc(Parcel in) {
super(in); super(in);
xmrtoUuid = in.readString(); xmrtoOrderId = in.readString();
btcAddress = in.readString(); btcAddress = in.readString();
bip70 = in.readString();
btcAmount = in.readDouble(); btcAmount = in.readDouble();
} }
@NonNull
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(",xmrtoUuid:"); sb.append("xmrtoOrderId:");
sb.append(xmrtoUuid); sb.append(xmrtoOrderId);
sb.append(",btcAddress:"); sb.append(",btcAddress:");
sb.append(btcAddress); sb.append(btcAddress);
sb.append(",bip70:");
sb.append(bip70);
sb.append(",btcAmount:"); sb.append(",btcAmount:");
sb.append(btcAmount); sb.append(btcAmount);
return sb.toString(); return sb.toString();

View File

@@ -16,16 +16,16 @@
package com.m2049r.xmrwallet.data; package com.m2049r.xmrwallet.data;
import com.m2049r.xmrwallet.xmrto.api.QueryOrderStatus; import com.m2049r.xmrwallet.service.shift.sideshift.api.CreateOrder;
import com.m2049r.xmrwallet.util.Helper;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import timber.log.Timber;
public class UserNotes { public class UserNotes {
public String txNotes = ""; public String txNotes = "";
public String note = ""; public String note = "";
public String xmrtoTag = null;
public String xmrtoKey = null; public String xmrtoKey = null;
public String xmrtoAmount = null; // could be a double - but we are not doing any calculations public String xmrtoAmount = null; // could be a double - but we are not doing any calculations
public String xmrtoDestination = null; public String xmrtoDestination = null;
@@ -35,13 +35,14 @@ public class UserNotes {
return; return;
} }
this.txNotes = txNotes; this.txNotes = txNotes;
Pattern p = Pattern.compile("^\\{(xmrto-\\w{6}),([0-9.]*)BTC,(\\w*)\\} ?(.*)"); Pattern p = Pattern.compile("^\\{([a-z]+)-(\\w{6,}),([0-9.]*)BTC,(\\w*)\\} ?(.*)");
Matcher m = p.matcher(txNotes); Matcher m = p.matcher(txNotes);
if (m.find()) { if (m.find()) {
xmrtoKey = m.group(1); xmrtoTag = m.group(1);
xmrtoAmount = m.group(2); xmrtoKey = m.group(2);
xmrtoDestination = m.group(3); xmrtoAmount = m.group(3);
note = m.group(4); xmrtoDestination = m.group(4);
note = m.group(5);
} else { } else {
note = txNotes; note = txNotes;
} }
@@ -56,12 +57,14 @@ public class UserNotes {
txNotes = buildTxNote(); txNotes = buildTxNote();
} }
public void setXmrtoStatus(QueryOrderStatus xmrtoStatus) { public void setXmrtoOrder(CreateOrder order) {
if (xmrtoStatus != null) { if (order != null) {
xmrtoKey = xmrtoStatus.getUuid(); xmrtoTag = order.TAG;
xmrtoAmount = String.valueOf(xmrtoStatus.getBtcAmount()); xmrtoKey = order.getOrderId();
xmrtoDestination = xmrtoStatus.getBtcDestAddress(); xmrtoAmount = Helper.getDisplayAmount(order.getBtcAmount());
xmrtoDestination = order.getBtcAddress();
} else { } else {
xmrtoTag = null;
xmrtoKey = null; xmrtoKey = null;
xmrtoAmount = null; xmrtoAmount = null;
xmrtoDestination = null; xmrtoDestination = null;
@@ -70,11 +73,13 @@ public class UserNotes {
} }
private String buildTxNote() { private String buildTxNote() {
StringBuffer sb = new StringBuffer(); StringBuilder sb = new StringBuilder();
if (xmrtoKey != null) { if (xmrtoKey != null) {
if ((xmrtoAmount == null) || (xmrtoDestination == null)) if ((xmrtoAmount == null) || (xmrtoDestination == null))
throw new IllegalArgumentException("Broken notes"); throw new IllegalArgumentException("Broken notes");
sb.append("{"); sb.append("{");
sb.append(xmrtoTag);
sb.append("-");
sb.append(xmrtoKey); sb.append(xmrtoKey);
sb.append(","); sb.append(",");
sb.append(xmrtoAmount); sb.append(xmrtoAmount);

View File

@@ -27,7 +27,6 @@ import android.widget.TextView;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;

View File

@@ -34,6 +34,8 @@ import android.widget.ImageButton;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.textfield.TextInputLayout;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.BarcodeData; import com.m2049r.xmrwallet.data.BarcodeData;
@@ -45,9 +47,6 @@ import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.BitcoinAddressValidator; import com.m2049r.xmrwallet.util.BitcoinAddressValidator;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.OpenAliasHelper; import com.m2049r.xmrwallet.util.OpenAliasHelper;
import com.m2049r.xmrwallet.util.PaymentProtocolHelper;
import com.m2049r.xmrwallet.xmrto.XmrToError;
import com.m2049r.xmrwallet.xmrto.XmrToException;
import java.util.Map; import java.util.Map;
@@ -92,8 +91,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
private ImageButton bPasteAddress; private ImageButton bPasteAddress;
private boolean resolvingOA = false; private boolean resolvingOA = false;
private boolean resolvingPP = false;
private String resolvedPP = null;
OnScanListener onScanListener; OnScanListener onScanListener;
@@ -125,20 +122,11 @@ public class SendAddressWizardFragment extends SendWizardFragment {
@Override @Override
public void onFocusChange(View v, boolean hasFocus) { public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) { if (!hasFocus) {
View next = etAddress;
String enteredAddress = etAddress.getEditText().getText().toString().trim(); String enteredAddress = etAddress.getEditText().getText().toString().trim();
String dnsOA = dnsFromOpenAlias(enteredAddress); String dnsOA = dnsFromOpenAlias(enteredAddress);
Timber.d("OpenAlias is %s", dnsOA); Timber.d("OpenAlias is %s", dnsOA);
if (dnsOA != null) { if (dnsOA != null) {
processOpenAlias(dnsOA); processOpenAlias(dnsOA);
next = null;
} else {
// maybe a bip72 or 70 URI
final String bip70 = PaymentProtocolHelper.getBip70(enteredAddress);
if (bip70 != null) {
// looks good - resolve through xmr.to
processBip70(bip70);
}
} }
} }
} }
@@ -147,8 +135,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
@Override @Override
public void afterTextChanged(Editable editable) { public void afterTextChanged(Editable editable) {
Timber.d("AFTER: %s", editable.toString()); Timber.d("AFTER: %s", editable.toString());
if (editable.toString().equals(resolvedPP)) return; // no change required
resolvedPP = null;
etAddress.setError(null); etAddress.setError(null);
if (isIntegratedAddress()) { if (isIntegratedAddress()) {
Timber.d("isIntegratedAddress"); Timber.d("isIntegratedAddress");
@@ -156,7 +142,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
tvPaymentIdIntegrated.setVisibility(View.VISIBLE); tvPaymentIdIntegrated.setVisibility(View.VISIBLE);
llXmrTo.setVisibility(View.INVISIBLE); llXmrTo.setVisibility(View.INVISIBLE);
sendListener.setMode(SendFragment.Mode.XMR); sendListener.setMode(SendFragment.Mode.XMR);
} else if (isBitcoinAddress() || (resolvedPP != null)) { } else if (isBitcoinAddress()) {
Timber.d("isBitcoinAddress"); Timber.d("isBitcoinAddress");
setBtcMode(); setBtcMode();
} else { } else {
@@ -190,13 +176,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
et.setSelection(et.getText().length()); et.setSelection(et.getText().length());
etAddress.requestFocus(); etAddress.requestFocus();
} else { } else {
final String bip70 = PaymentProtocolHelper.getBip70(clip);
if (bip70 != null) {
final EditText et = etAddress.getEditText();
et.setText(clip);
et.setSelection(et.getText().length());
processBip70(bip70);
} else
Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show(); Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show();
} }
} }
@@ -273,62 +252,10 @@ public class SendAddressWizardFragment extends SendWizardFragment {
} // else ignore } // else ignore
} }
private void processBip70(final String bip70) {
Timber.d("RESOLVED PP: %s", resolvedPP);
if (resolvingPP) return; // already resolving - just wait
resolvingPP = true;
sendListener.popBarcodeData();
etAddress.setError(getString(R.string.send_address_resolve_bip70));
PaymentProtocolHelper.resolve(bip70, new PaymentProtocolHelper.OnResolvedListener() {
@Override
public void onResolved(BarcodeData.Asset asset, String address, double amount, String resolvedBip70) {
resolvingPP = false;
if (asset != BarcodeData.Asset.BTC)
throw new IllegalArgumentException("only BTC here");
if (resolvedBip70 == null)
throw new IllegalArgumentException("success means we have a pp_url - else die");
final BarcodeData barcodeData =
new BarcodeData(BarcodeData.Asset.BTC, address, null,
resolvedBip70, null, String.valueOf(amount),
BarcodeData.Security.BIP70);
etNotes.post(new Runnable() {
@Override
public void run() {
Timber.d("security is %s", barcodeData.security);
processScannedData(barcodeData);
etNotes.requestFocus();
}
});
}
@Override
public void onFailure(final Exception ex) {
resolvingPP = false;
etAddress.post(new Runnable() {
@Override
public void run() {
int errorMsgId = R.string.send_address_not_bip70;
if (ex instanceof XmrToException) {
XmrToError error = ((XmrToException) ex).getError();
if (error != null) {
errorMsgId = error.getErrorMsgId();
}
}
etAddress.setError(getString(errorMsgId));
}
});
Timber.d("PP FAILED");
}
});
}
private boolean checkAddressNoError() { private boolean checkAddressNoError() {
String address = etAddress.getEditText().getText().toString(); String address = etAddress.getEditText().getText().toString();
return Wallet.isAddressValid(address) return Wallet.isAddressValid(address)
|| BitcoinAddressValidator.validate(address) || BitcoinAddressValidator.validate(address);
|| (resolvedPP != null);
} }
private boolean checkAddress() { private boolean checkAddress() {
@@ -365,11 +292,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
Timber.d("OpenAlias is %s", dnsOA); Timber.d("OpenAlias is %s", dnsOA);
if (dnsOA != null) { if (dnsOA != null) {
processOpenAlias(dnsOA); processOpenAlias(dnsOA);
} else {
String bip70 = PaymentProtocolHelper.getBip70(enteredAddress);
if (bip70 != null) {
processBip70(bip70);
}
} }
return false; return false;
} }
@@ -377,15 +299,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
if (sendListener != null) { if (sendListener != null) {
TxData txData = sendListener.getTxData(); TxData txData = sendListener.getTxData();
if (txData instanceof TxDataBtc) { if (txData instanceof TxDataBtc) {
if (resolvedPP != null) {
// take the value from the field nonetheless as this is what the user sees
// (in case we have a bug somewhere)
((TxDataBtc) txData).setBip70(etAddress.getEditText().getText().toString());
((TxDataBtc) txData).setBtcAddress(null);
} else {
((TxDataBtc) txData).setBtcAddress(etAddress.getEditText().getText().toString()); ((TxDataBtc) txData).setBtcAddress(etAddress.getEditText().getText().toString());
((TxDataBtc) txData).setBip70(null);
}
txData.setDestinationAddress(null); txData.setDestinationAddress(null);
} else { } else {
txData.setDestinationAddress(etAddress.getEditText().getText().toString()); txData.setDestinationAddress(etAddress.getEditText().getText().toString());
@@ -398,7 +312,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
} }
@Override @Override
public void onAttach(Context context) { public void onAttach(@NonNull Context context) {
super.onAttach(context); super.onAttach(context);
if (context instanceof OnScanListener) { if (context instanceof OnScanListener) {
onScanListener = (OnScanListener) context; onScanListener = (OnScanListener) context;
@@ -424,21 +338,11 @@ public class SendAddressWizardFragment extends SendWizardFragment {
} }
public void processScannedData() { public void processScannedData() {
resolvedPP = null;
BarcodeData barcodeData = sendListener.getBarcodeData(); BarcodeData barcodeData = sendListener.getBarcodeData();
if (barcodeData != null) { if (barcodeData != null) {
Timber.d("GOT DATA"); Timber.d("GOT DATA");
if (barcodeData.bip70 != null) { if (barcodeData.address != null) {
setBtcMode();
if (barcodeData.security == BarcodeData.Security.BIP70) {
resolvedPP = barcodeData.bip70;
etAddress.setError(getString(R.string.send_address_bip70));
} else {
processBip70(barcodeData.bip70);
}
etAddress.getEditText().setText(barcodeData.bip70);
} else if (barcodeData.address != null) {
etAddress.getEditText().setText(barcodeData.address); etAddress.getEditText().setText(barcodeData.address);
if (checkAddress()) { if (checkAddress()) {
if (barcodeData.security == BarcodeData.Security.OA_NO_DNSSEC) if (barcodeData.security == BarcodeData.Security.OA_NO_DNSSEC)

View File

@@ -19,7 +19,6 @@ package com.m2049r.xmrwallet.fragment.send;
import android.os.Bundle; import android.os.Bundle;
import android.text.Html; import android.text.Html;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -31,15 +30,14 @@ import com.m2049r.xmrwallet.data.TxDataBtc;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.OkHttpHelper; import com.m2049r.xmrwallet.util.OkHttpHelper;
import com.m2049r.xmrwallet.widget.ExchangeEditText;
import com.m2049r.xmrwallet.widget.ExchangeOtherEditText; import com.m2049r.xmrwallet.widget.ExchangeOtherEditText;
import com.m2049r.xmrwallet.widget.SendProgressView; import com.m2049r.xmrwallet.widget.SendProgressView;
import com.m2049r.xmrwallet.xmrto.XmrToError; import com.m2049r.xmrwallet.service.shift.ShiftError;
import com.m2049r.xmrwallet.xmrto.XmrToException; import com.m2049r.xmrwallet.service.shift.ShiftException;
import com.m2049r.xmrwallet.xmrto.api.QueryOrderParameters; import com.m2049r.xmrwallet.service.shift.sideshift.api.QueryOrderParameters;
import com.m2049r.xmrwallet.xmrto.api.XmrToApi; import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi;
import com.m2049r.xmrwallet.xmrto.api.XmrToCallback; import com.m2049r.xmrwallet.service.shift.ShiftCallback;
import com.m2049r.xmrwallet.xmrto.network.XmrToApiImpl; import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.Locale; import java.util.Locale;
@@ -93,17 +91,22 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
@Override @Override
public boolean onValidateFields() { public boolean onValidateFields() {
Timber.i(maxBtc + "/" + minBtc);
if (!etAmount.validate(maxBtc, minBtc)) { if (!etAmount.validate(maxBtc, minBtc)) {
return false; return false;
} }
if (orderParameters == null) {
return false; // this should never happen
}
if (sendListener != null) { if (sendListener != null) {
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData(); TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
String btcString = etAmount.getNativeAmount(); String btcString = etAmount.getNativeAmount();
if (btcString != null) { if (btcString != null) {
try { try {
double btc = Double.parseDouble(btcString); double btc = Double.parseDouble(btcString);
Timber.d("setAmount %f", btc); Timber.d("setBtcAmount %f", btc);
txDataBtc.setBtcAmount(btc); txDataBtc.setBtcAmount(btc);
txDataBtc.setAmount(btc / orderParameters.getPrice());
} catch (NumberFormatException ex) { } catch (NumberFormatException ex) {
Timber.d(ex.getLocalizedMessage()); Timber.d(ex.getLocalizedMessage());
txDataBtc.setBtcAmount(0); txDataBtc.setBtcAmount(0);
@@ -115,17 +118,6 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
return true; return true;
} }
private void setBip70Mode() {
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
if (txDataBtc.getBip70() == null) {
etAmount.setEditable(true);
Helper.showKeyboard(getActivity());
} else {
etAmount.setEditable(false);
Helper.hideKeyboard(getActivity());
}
}
double maxBtc = 0; double maxBtc = 0;
double minBtc = 0; double minBtc = 0;
@@ -152,7 +144,6 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
etAmount.setAmount(data.amount); etAmount.setAmount(data.amount);
} }
} }
setBip70Mode();
callXmrTo(); callXmrTo();
} }
@@ -164,34 +155,27 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
private void processOrderParms(final QueryOrderParameters orderParameters) { private void processOrderParms(final QueryOrderParameters orderParameters) {
this.orderParameters = orderParameters; this.orderParameters = orderParameters;
getView().post(new Runnable() { getView().post(() -> {
@Override final double price = orderParameters.getPrice();
public void run() { etAmount.setExchangeRate(1 / price);
etAmount.setExchangeRate(1.0d / orderParameters.getPrice()); maxBtc = price * orderParameters.getUpperLimit();
minBtc = price * orderParameters.getLowerLimit();
Timber.d("minBtc=%f / maxBtc=%f", minBtc, maxBtc);
NumberFormat df = NumberFormat.getInstance(Locale.US); NumberFormat df = NumberFormat.getInstance(Locale.US);
df.setMaximumFractionDigits(6); df.setMaximumFractionDigits(6);
String min = df.format(orderParameters.getLowerLimit()); String min = df.format(minBtc);
String max = df.format(orderParameters.getUpperLimit()); String max = df.format(maxBtc);
String rate = df.format(orderParameters.getPrice()); String rate = df.format(price);
Spanned xmrParmText = Html.fromHtml(getString(R.string.info_send_xmrto_parms, min, max, rate)); 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); tvXmrToParms.setText(xmrParmText);
maxBtc = orderParameters.getUpperLimit();
minBtc = orderParameters.getLowerLimit();
Timber.d("minBtc=%f / maxBtc=%f", minBtc, maxBtc);
final long funds = getTotalFunds(); final long funds = getTotalFunds();
double availableXmr = 1.0 * funds / 1000000000000L; double availableXmr = 1.0 * funds / 1000000000000L;
maxBtc = Math.min(maxBtc, availableXmr * orderParameters.getPrice());
String availBtcString; String availBtcString;
String availXmrString; String availXmrString;
if (!sendListener.getActivityCallback().isStreetMode()) { if (!sendListener.getActivityCallback().isStreetMode()) {
availBtcString = df.format(availableXmr * orderParameters.getPrice()); availBtcString = df.format(availableXmr * price);
availXmrString = df.format(availableXmr); availXmrString = df.format(availableXmr);
} else { } else {
availBtcString = getString(R.string.unknown_amount); availBtcString = getString(R.string.unknown_amount);
@@ -202,7 +186,6 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
availBtcString)); availBtcString));
llXmrToParms.setVisibility(View.VISIBLE); llXmrToParms.setVisibility(View.VISIBLE);
evParams.hideProgress(); evParams.hideProgress();
}
}); });
} }
@@ -212,25 +195,20 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
maxBtc = 0; maxBtc = 0;
minBtc = 0; minBtc = 0;
Timber.e(ex); Timber.e(ex);
getView().post(new Runnable() { getView().post(() -> {
@Override if (ex instanceof ShiftException) {
public void run() { ShiftException xmrEx = (ShiftException) ex;
if (ex instanceof XmrToException) { ShiftError xmrErr = xmrEx.getError();
XmrToException xmrEx = (XmrToException) ex;
XmrToError xmrErr = xmrEx.getError();
if (xmrErr != null) { if (xmrErr != null) {
if (xmrErr.isRetryable()) { if (xmrErr.isRetryable()) {
evParams.showMessage(xmrErr.getErrorId().toString(), xmrErr.getErrorMsg(), evParams.showMessage(xmrErr.getErrorType().toString(), xmrErr.getErrorMsg(),
getString(R.string.text_retry)); getString(R.string.text_retry));
evParams.setOnClickListener(new View.OnClickListener() { evParams.setOnClickListener(v -> {
@Override
public void onClick(View v) {
evParams.setOnClickListener(null); evParams.setOnClickListener(null);
callXmrTo(); callXmrTo();
}
}); });
} else { } else {
evParams.showMessage(xmrErr.getErrorId().toString(), xmrErr.getErrorMsg(), evParams.showMessage(xmrErr.getErrorType().toString(), xmrErr.getErrorMsg(),
getString(R.string.text_noretry)); getString(R.string.text_noretry));
} }
} else { } else {
@@ -243,13 +221,12 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
ex.getLocalizedMessage(), ex.getLocalizedMessage(),
getString(R.string.text_noretry)); getString(R.string.text_noretry));
} }
}
}); });
} }
private void callXmrTo() { private void callXmrTo() {
evParams.showProgress(getString(R.string.label_send_progress_queryparms)); evParams.showProgress(getString(R.string.label_send_progress_queryparms));
getXmrToApi().queryOrderParameters(new XmrToCallback<QueryOrderParameters>() { getXmrToApi().queryOrderParameters(new ShiftCallback<QueryOrderParameters>() {
@Override @Override
public void onSuccess(final QueryOrderParameters orderParameters) { public void onSuccess(final QueryOrderParameters orderParameters) {
processOrderParms(orderParameters); processOrderParms(orderParameters);
@@ -262,13 +239,13 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
}); });
} }
private XmrToApi xmrToApi = null; private SideShiftApi xmrToApi = null;
private XmrToApi getXmrToApi() { private SideShiftApi getXmrToApi() {
if (xmrToApi == null) { if (xmrToApi == null) {
synchronized (this) { synchronized (this) {
if (xmrToApi == null) { if (xmrToApi == null) {
xmrToApi = new XmrToApiImpl(OkHttpHelper.getOkHttpClient(), xmrToApi = new SideShiftApiImpl(OkHttpHelper.getOkHttpClient(),
Helper.getXmrToBaseUrl()); Helper.getXmrToBaseUrl());
} }
} }

View File

@@ -16,6 +16,9 @@
package com.m2049r.xmrwallet.fragment.send; package com.m2049r.xmrwallet.fragment.send;
import android.content.Intent;
import android.graphics.Paint;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -29,13 +32,13 @@ import android.widget.Toast;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.PendingTx; import com.m2049r.xmrwallet.data.PendingTx;
import com.m2049r.xmrwallet.data.TxDataBtc; import com.m2049r.xmrwallet.data.TxDataBtc;
import com.m2049r.xmrwallet.service.shift.ShiftCallback;
import com.m2049r.xmrwallet.service.shift.ShiftException;
import com.m2049r.xmrwallet.service.shift.sideshift.api.QueryOrderStatus;
import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi;
import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.OkHttpHelper; import com.m2049r.xmrwallet.util.OkHttpHelper;
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.text.NumberFormat;
import java.util.Locale; import java.util.Locale;
@@ -52,9 +55,8 @@ public class SendBtcSuccessWizardFragment extends SendWizardFragment {
SendSuccessWizardFragment.Listener sendListener; SendSuccessWizardFragment.Listener sendListener;
public SendBtcSuccessWizardFragment setSendListener(SendSuccessWizardFragment.Listener listener) { public void setSendListener(SendSuccessWizardFragment.Listener listener) {
this.sendListener = listener; this.sendListener = listener;
return this;
} }
ImageButton bCopyTxId; ImageButton bCopyTxId;
@@ -69,6 +71,7 @@ public class SendBtcSuccessWizardFragment extends SendWizardFragment {
private ImageView ivXmrToStatusBig; private ImageView ivXmrToStatusBig;
private ProgressBar pbXmrto; private ProgressBar pbXmrto;
private TextView tvTxXmrToKey; private TextView tvTxXmrToKey;
private TextView tvXmrToSupport;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
@@ -101,14 +104,14 @@ public class SendBtcSuccessWizardFragment extends SendWizardFragment {
pbXmrto.getIndeterminateDrawable().setColorFilter(0x61000000, android.graphics.PorterDuff.Mode.MULTIPLY); pbXmrto.getIndeterminateDrawable().setColorFilter(0x61000000, android.graphics.PorterDuff.Mode.MULTIPLY);
tvTxXmrToKey = view.findViewById(R.id.tvTxXmrToKey); tvTxXmrToKey = view.findViewById(R.id.tvTxXmrToKey);
tvTxXmrToKey.setOnClickListener(new View.OnClickListener() { tvTxXmrToKey.setOnClickListener(v -> {
@Override
public void onClick(View v) {
Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_xmrtokey), tvTxXmrToKey.getText().toString()); 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(); Toast.makeText(getActivity(), getString(R.string.message_copy_xmrtokey), Toast.LENGTH_SHORT).show();
}
}); });
tvXmrToSupport = view.findViewById(R.id.tvXmrToSupport);
tvXmrToSupport.setPaintFlags(tvXmrToSupport.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
return view; return view;
} }
@@ -149,7 +152,12 @@ public class SendBtcSuccessWizardFragment extends SendWizardFragment {
String btcAmount = df.format(btcData.getBtcAmount()); String btcAmount = df.format(btcData.getBtcAmount());
tvXmrToAmount.setText(getString(R.string.info_send_xmrto_success_btc, btcAmount)); tvXmrToAmount.setText(getString(R.string.info_send_xmrto_success_btc, btcAmount));
//TODO btcData.getBtcAddress(); //TODO btcData.getBtcAddress();
tvTxXmrToKey.setText(btcData.getXmrtoUuid()); tvTxXmrToKey.setText(btcData.getXmrtoOrderId());
tvXmrToSupport.setOnClickListener(v -> {
Uri orderUri = getXmrToApi().getQueryOrderUri(btcData.getXmrtoOrderId());
Intent intent = new Intent(Intent.ACTION_VIEW, orderUri);
startActivity(intent);
});
queryOrder(); queryOrder();
} else { } else {
throw new IllegalStateException("btcData is null"); throw new IllegalStateException("btcData is null");
@@ -158,33 +166,23 @@ public class SendBtcSuccessWizardFragment extends SendWizardFragment {
sendListener.enableDone(); sendListener.enableDone();
} }
private final int QUERY_INTERVAL = 1000; // ms
private void processQueryOrder(final QueryOrderStatus status) { private void processQueryOrder(final QueryOrderStatus status) {
Timber.d("processQueryOrder %s for %s", status.getState().toString(), status.getUuid()); Timber.d("processQueryOrder %s for %s", status.getState().toString(), status.getOrderId());
if (!btcData.getXmrtoUuid().equals(status.getUuid())) if (!btcData.getXmrtoOrderId().equals(status.getOrderId()))
throw new IllegalStateException("UUIDs do not match!"); throw new IllegalStateException("UUIDs do not match!");
if (isResumed && (getView() != null)) if (isResumed && (getView() != null))
getView().post(new Runnable() { getView().post(() -> {
@Override
public void run() {
showXmrToStatus(status); showXmrToStatus(status);
if (!status.isTerminal()) { if (!status.isTerminal()) {
getView().postDelayed(new Runnable() { getView().postDelayed(this::queryOrder, SideShiftApi.QUERY_INTERVAL);
@Override
public void run() {
queryOrder();
}
}, QUERY_INTERVAL);
}
} }
}); });
} }
private void queryOrder() { private void queryOrder() {
Timber.d("queryOrder(%s)", btcData.getXmrtoUuid()); Timber.d("queryOrder(%s)", btcData.getXmrtoOrderId());
if (!isResumed) return; if (!isResumed) return;
getXmrToApi().queryOrderStatus(btcData.getXmrtoUuid(), new XmrToCallback<QueryOrderStatus>() { getXmrToApi().queryOrderStatus(btcData.getXmrtoOrderId(), new ShiftCallback<QueryOrderStatus>() {
@Override @Override
public void onSuccess(QueryOrderStatus status) { public void onSuccess(QueryOrderStatus status) {
if (!isAdded()) return; if (!isAdded()) return;
@@ -194,38 +192,34 @@ public class SendBtcSuccessWizardFragment extends SendWizardFragment {
@Override @Override
public void onError(final Exception ex) { public void onError(final Exception ex) {
if (!isResumed) return; if (!isResumed) return;
Timber.e(ex); Timber.w(ex);
getActivity().runOnUiThread(new Runnable() { getActivity().runOnUiThread(() -> {
@Override if (ex instanceof ShiftException) {
public void run() { Toast.makeText(getActivity(), ((ShiftException) ex).getError().getErrorMsg(), Toast.LENGTH_LONG).show();
if (ex instanceof XmrToException) {
Toast.makeText(getActivity(), ((XmrToException) ex).getError().getErrorMsg(), Toast.LENGTH_LONG).show();
} else { } else {
Toast.makeText(getActivity(), ex.getLocalizedMessage(), Toast.LENGTH_LONG).show(); Toast.makeText(getActivity(), ex.getLocalizedMessage(), Toast.LENGTH_LONG).show();
} }
}
}); });
} }
}); });
} }
private int statusResource = 0;
void showXmrToStatus(final QueryOrderStatus status) { void showXmrToStatus(final QueryOrderStatus status) {
int statusResource = 0;
if (status.isError()) { if (status.isError()) {
tvXmrToStatus.setText(getString(R.string.info_send_xmrto_error, status.toString())); tvXmrToStatus.setText(getString(R.string.info_send_xmrto_error, status.toString()));
statusResource = R.drawable.ic_error_red_24dp; statusResource = R.drawable.ic_error_red_24dp;
pbXmrto.getIndeterminateDrawable().setColorFilter(0xff8b0000, android.graphics.PorterDuff.Mode.MULTIPLY); pbXmrto.getIndeterminateDrawable().setColorFilter(0xff8b0000, android.graphics.PorterDuff.Mode.MULTIPLY);
} else if (status.isSent()) { } else if (status.isSent() || status.isPaid()) {
tvXmrToStatus.setText(getString(R.string.info_send_xmrto_sent)); tvXmrToStatus.setText(getString(R.string.info_send_xmrto_sent));
statusResource = R.drawable.ic_success_green_24dp; statusResource = R.drawable.ic_success_green_24dp;
pbXmrto.getIndeterminateDrawable().setColorFilter(0xFF417505, android.graphics.PorterDuff.Mode.MULTIPLY); pbXmrto.getIndeterminateDrawable().setColorFilter(0xFF417505, android.graphics.PorterDuff.Mode.MULTIPLY);
} else if (status.isPending()) { } else if (status.isWaiting()) {
if (status.isPaid()) {
tvXmrToStatus.setText(getString(R.string.info_send_xmrto_paid));
} else {
tvXmrToStatus.setText(getString(R.string.info_send_xmrto_unpaid)); 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 if (status.isPending()) {
tvXmrToStatus.setText(getString(R.string.info_send_xmrto_paid));
statusResource = R.drawable.ic_pending_orange_24dp; statusResource = R.drawable.ic_pending_orange_24dp;
pbXmrto.getIndeterminateDrawable().setColorFilter(0xFFFF6105, android.graphics.PorterDuff.Mode.MULTIPLY); pbXmrto.getIndeterminateDrawable().setColorFilter(0xFFFF6105, android.graphics.PorterDuff.Mode.MULTIPLY);
} else { } else {
@@ -240,13 +234,13 @@ public class SendBtcSuccessWizardFragment extends SendWizardFragment {
} }
} }
private XmrToApi xmrToApi = null; private SideShiftApi xmrToApi = null;
private final XmrToApi getXmrToApi() { private SideShiftApi getXmrToApi() {
if (xmrToApi == null) { if (xmrToApi == null) {
synchronized (this) { synchronized (this) {
if (xmrToApi == null) { if (xmrToApi == null) {
xmrToApi = new XmrToApiImpl(OkHttpHelper.getOkHttpClient(), xmrToApi = new SideShiftApiImpl(OkHttpHelper.getOkHttpClient(),
Helper.getXmrToBaseUrl()); Helper.getXmrToBaseUrl());
} }
} }

View File

@@ -563,22 +563,4 @@ public class SendFragment extends Fragment
inflater.inflate(R.menu.send_menu, menu); inflater.inflate(R.menu.send_menu, menu);
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
} }
// xmr.to info box
private static final String PREF_SHOW_XMRTO_ENABLED = "info_xmrto_enabled_send";
boolean showXmrtoEnabled = true;
void loadPrefs() {
SharedPreferences sharedPref = activityCallback.getPrefs();
showXmrtoEnabled = sharedPref.getBoolean(PREF_SHOW_XMRTO_ENABLED, true);
}
void saveXmrToPrefs() {
SharedPreferences sharedPref = activityCallback.getPrefs();
SharedPreferences.Editor editor = sharedPref.edit();
editor.putBoolean(PREF_SHOW_XMRTO_ENABLED, showXmrtoEnabled);
editor.apply();
}
} }

View File

@@ -17,8 +17,6 @@
package com.m2049r.xmrwallet.layout; package com.m2049r.xmrwallet.layout;
import android.content.Context; import android.content.Context;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -26,8 +24,13 @@ import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.NodeInfo; import com.m2049r.xmrwallet.data.NodeInfo;
import com.m2049r.xmrwallet.util.ColorHelper;
import com.m2049r.xmrwallet.util.Helper;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@@ -35,7 +38,6 @@ import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.TimeZone; import java.util.TimeZone;
@@ -44,6 +46,8 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
public interface OnInteractionListener { public interface OnInteractionListener {
void onInteraction(View view, NodeInfo item); void onInteraction(View view, NodeInfo item);
void onLongInteraction(View view, NodeInfo item);
} }
private final List<NodeInfo> nodeItems = new ArrayList<>(); private final List<NodeInfo> nodeItems = new ArrayList<>();
@@ -106,8 +110,9 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
notifyDataSetChanged(); notifyDataSetChanged();
} }
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
final ImageButton ibBookmark; final ImageButton ibBookmark;
final View pbBookmark;
final TextView tvName; final TextView tvName;
final TextView tvIp; final TextView tvIp;
final ImageView ivPing; final ImageView ivPing;
@@ -116,16 +121,20 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
ViewHolder(View itemView) { ViewHolder(View itemView) {
super(itemView); super(itemView);
ibBookmark = itemView.findViewById(R.id.ibBookmark); ibBookmark = itemView.findViewById(R.id.ibBookmark);
pbBookmark = itemView.findViewById(R.id.pbBookmark);
tvName = itemView.findViewById(R.id.tvName); tvName = itemView.findViewById(R.id.tvName);
tvIp = itemView.findViewById(R.id.tvAddress); tvIp = itemView.findViewById(R.id.tvAddress);
ivPing = itemView.findViewById(R.id.ivPing); ivPing = itemView.findViewById(R.id.ivPing);
ibBookmark.setOnClickListener(new View.OnClickListener() { ibBookmark.setOnClickListener(v -> {
@Override
public void onClick(View v) {
nodeItem.toggleFavourite(); nodeItem.toggleFavourite();
showStar(); showStar();
if (!nodeItem.isFavourite()) {
nodeItem.setSelected(false);
notifyDataSetChanged();
} }
}); });
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
} }
private void showStar() { private void showStar() {
@@ -139,16 +148,22 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
void bind(int position) { void bind(int position) {
nodeItem = nodeItems.get(position); nodeItem = nodeItems.get(position);
tvName.setText(nodeItem.getName()); tvName.setText(nodeItem.getName());
final String ts = TS_FORMATTER.format(new Date(nodeItem.getTimestamp() * 1000));
ivPing.setImageResource(getPingIcon(nodeItem)); ivPing.setImageResource(getPingIcon(nodeItem));
if (nodeItem.isTested()) {
if (nodeItem.isValid()) { if (nodeItem.isValid()) {
tvIp.setText(context.getString(R.string.node_height, ts)); Helper.showTimeDifference(tvIp, nodeItem.getTimestamp());
} else { } else {
tvIp.setText(getResponseErrorText(context, nodeItem.getResponseCode())); tvIp.setText(getResponseErrorText(context, nodeItem.getResponseCode()));
tvIp.setTextColor(ColorHelper.getThemedColor(tvIp.getContext(), R.attr.colorError));
} }
itemView.setOnClickListener(this); } else {
tvIp.setText(context.getResources().getString(R.string.node_testing, nodeItem.getHostAddress()));
}
itemView.setSelected(nodeItem.isSelected());
itemView.setClickable(itemsClickable); itemView.setClickable(itemsClickable);
itemView.setEnabled(itemsClickable);
ibBookmark.setClickable(itemsClickable); ibBookmark.setClickable(itemsClickable);
pbBookmark.setVisibility(nodeItem.isSelecting() ? View.VISIBLE : View.INVISIBLE);
showStar(); showStar();
} }
@@ -157,10 +172,24 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
if (listener != null) { if (listener != null) {
int position = getAdapterPosition(); // gets item position int position = getAdapterPosition(); // gets item position
if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it
listener.onInteraction(view, nodeItems.get(position)); final NodeInfo node = nodeItems.get(position);
node.setSelecting(true);
allowClick(false);
listener.onInteraction(view, node);
} }
} }
} }
@Override
public boolean onLongClick(View view) {
if (listener != null) {
int position = getAdapterPosition(); // gets item position
if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it
listener.onLongInteraction(view, nodeItems.get(position));
}
}
return true;
}
} }
static public int getPingIcon(NodeInfo nodeInfo) { static public int getPingIcon(NodeInfo nodeInfo) {

View File

@@ -2,7 +2,7 @@
******************************************************************************* *******************************************************************************
* BTChip Bitcoin Hardware Wallet Java API * BTChip Bitcoin Hardware Wallet Java API
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn * (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
* (c) m2049r * Copyright (c) 2018 m2049r
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@@ -1,3 +1,19 @@
/*
* 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.
*/
package com.m2049r.xmrwallet.ledger; package com.m2049r.xmrwallet.ledger;
import android.content.Context; import android.content.Context;

View File

@@ -275,6 +275,7 @@ public class WalletManager {
return networkType; return networkType;
} }
// this should not be called on the main thread as it connects to the node (and takes a long time)
public void setDaemon(Node node) { public void setDaemon(Node node) {
if (node != null) { if (node != null) {
this.daemonAddress = node.getAddress(); this.daemonAddress = node.getAddress();

View File

@@ -14,11 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
package com.m2049r.xmrwallet.xmrto.network; package com.m2049r.xmrwallet.service.shift;
import org.json.JSONObject; import org.json.JSONObject;
interface NetworkCallback { public interface NetworkCallback {
void onSuccess(JSONObject jsonObject); void onSuccess(JSONObject jsonObject);

View File

@@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
package com.m2049r.xmrwallet.xmrto.network; package com.m2049r.xmrwallet.service.shift;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.json.JSONObject; import org.json.JSONObject;
interface XmrToApiCall { public interface ShiftApiCall {
void call(@NonNull final String path, @NonNull final NetworkCallback callback); void call(@NonNull final String path, @NonNull final NetworkCallback callback);

View File

@@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package com.m2049r.xmrwallet.xmrto.api; package com.m2049r.xmrwallet.service.shift;
public interface XmrToCallback<T> { public interface ShiftCallback<T> {
void onSuccess(T t); void onSuccess(T t);

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