New handling of nodes (#699)
* rework node handling * update block heights
This commit is contained in:
parent
45c5883e11
commit
4e59be2dff
|
@ -1,14 +1,14 @@
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 28
|
compileSdkVersion 30
|
||||||
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 30
|
||||||
versionCode 503
|
versionCode 600
|
||||||
versionName "1.15.3 'Dark Fork'"
|
versionName "1.16.0 'Karmic Nodes'"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
|
@ -117,7 +117,7 @@ dependencies {
|
||||||
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.2'
|
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:4.9.0"
|
implementation "com.squareup.okhttp3:okhttp:4.9.0"
|
||||||
|
@ -138,4 +138,7 @@ dependencies {
|
||||||
testImplementation "com.squareup.okhttp3:mockwebserver:4.9.0"
|
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'
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@ import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
import androidx.fragment.app.FragmentTransaction;
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
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.dialog.AboutFragment;
|
import com.m2049r.xmrwallet.dialog.AboutFragment;
|
||||||
|
@ -74,6 +75,7 @@ import java.io.IOException;
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -90,6 +92,7 @@ public class LoginActivity extends BaseActivity
|
||||||
private static final String GENERATE_STACK = "gen";
|
private static final String GENERATE_STACK = "gen";
|
||||||
|
|
||||||
private static final String NODES_PREFS_NAME = "nodes";
|
private static final String NODES_PREFS_NAME = "nodes";
|
||||||
|
private static final String SELECTED_NODE_PREFS_NAME = "selected_node";
|
||||||
private static final String PREF_DAEMON_TESTNET = "daemon_testnet";
|
private static final String PREF_DAEMON_TESTNET = "daemon_testnet";
|
||||||
private static final String PREF_DAEMON_STAGENET = "daemon_stagenet";
|
private static final String PREF_DAEMON_STAGENET = "daemon_stagenet";
|
||||||
private static final String PREF_DAEMON_MAINNET = "daemon_mainnet";
|
private static final String PREF_DAEMON_MAINNET = "daemon_mainnet";
|
||||||
|
@ -105,10 +108,21 @@ public class LoginActivity extends BaseActivity
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setNode(NodeInfo node) {
|
public void setNode(NodeInfo node) {
|
||||||
if ((node != null) && (node.getNetworkType() != WalletManager.getInstance().getNetworkType()))
|
setNode(node, true);
|
||||||
throw new IllegalArgumentException("network type does not match");
|
}
|
||||||
this.node = node;
|
|
||||||
WalletManager.getInstance().setDaemon(node);
|
private void setNode(NodeInfo node, boolean save) {
|
||||||
|
if (node != this.node) {
|
||||||
|
if ((node != null) && (node.getNetworkType() != WalletManager.getInstance().getNetworkType()))
|
||||||
|
throw new IllegalArgumentException("network type does not match");
|
||||||
|
this.node = node;
|
||||||
|
for (NodeInfo nodeInfo : favouriteNodes) {
|
||||||
|
nodeInfo.setSelected(nodeInfo == node);
|
||||||
|
}
|
||||||
|
WalletManager.getInstance().setDaemon(node);
|
||||||
|
if (save)
|
||||||
|
saveSelectedNode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -117,7 +131,22 @@ public class LoginActivity extends BaseActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setFavouriteNodes(Set<NodeInfo> nodes) {
|
public Set<NodeInfo> getOrPopulateFavourites() {
|
||||||
|
if (favouriteNodes.isEmpty()) {
|
||||||
|
for (DefaultNodes node : DefaultNodes.values()) {
|
||||||
|
NodeInfo nodeInfo = NodeInfo.fromString(node.getUri());
|
||||||
|
if (nodeInfo != null) {
|
||||||
|
nodeInfo.setFavourite(true);
|
||||||
|
favouriteNodes.add(nodeInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
saveFavourites();
|
||||||
|
}
|
||||||
|
return favouriteNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFavouriteNodes(Collection<NodeInfo> nodes) {
|
||||||
Timber.d("adding %d nodes", nodes.size());
|
Timber.d("adding %d nodes", nodes.size());
|
||||||
favouriteNodes.clear();
|
favouriteNodes.clear();
|
||||||
for (NodeInfo node : nodes) {
|
for (NodeInfo node : nodes) {
|
||||||
|
@ -125,38 +154,31 @@ public class LoginActivity extends BaseActivity
|
||||||
if (node.isFavourite())
|
if (node.isFavourite())
|
||||||
favouriteNodes.add(node);
|
favouriteNodes.add(node);
|
||||||
}
|
}
|
||||||
if (favouriteNodes.isEmpty() && (!nodes.isEmpty())) { // no favourites - pick best ones
|
|
||||||
List<NodeInfo> nodeList = new ArrayList<>(nodes);
|
|
||||||
Collections.sort(nodeList, NodeInfo.BestNodeComparator);
|
|
||||||
int i = 0;
|
|
||||||
for (NodeInfo node : nodeList) {
|
|
||||||
Timber.d("adding %s", node);
|
|
||||||
node.setFavourite(true);
|
|
||||||
favouriteNodes.add(node);
|
|
||||||
if (++i >= 3) break; // add max first 3 nodes
|
|
||||||
}
|
|
||||||
Toast.makeText(this, getString(R.string.node_nobookmark, i), Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
saveFavourites();
|
saveFavourites();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadFavouritesWithNetwork() {
|
private void loadFavouritesWithNetwork() {
|
||||||
Helper.runWithNetwork(new Helper.Action() {
|
Helper.runWithNetwork(() -> {
|
||||||
@Override
|
loadFavourites();
|
||||||
public boolean run() {
|
return true;
|
||||||
loadFavourites();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadFavourites() {
|
private void loadFavourites() {
|
||||||
Timber.d("loadFavourites");
|
Timber.d("loadFavourites");
|
||||||
favouriteNodes.clear();
|
favouriteNodes.clear();
|
||||||
|
final String selectedNodeId = getSelectedNodeId();
|
||||||
Map<String, ?> storedNodes = getSharedPreferences(NODES_PREFS_NAME, Context.MODE_PRIVATE).getAll();
|
Map<String, ?> storedNodes = getSharedPreferences(NODES_PREFS_NAME, Context.MODE_PRIVATE).getAll();
|
||||||
for (Map.Entry<String, ?> nodeEntry : storedNodes.entrySet()) {
|
for (Map.Entry<String, ?> nodeEntry : storedNodes.entrySet()) {
|
||||||
if (nodeEntry != null) // just in case, ignore possible future errors
|
if (nodeEntry != null) { // just in case, ignore possible future errors
|
||||||
addFavourite((String) nodeEntry.getValue());
|
final String nodeId = (String) nodeEntry.getValue();
|
||||||
|
final NodeInfo addedNode = addFavourite(nodeId);
|
||||||
|
if (addedNode != null) {
|
||||||
|
if (nodeId.equals(selectedNodeId)) {
|
||||||
|
addedNode.setSelected(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (storedNodes.isEmpty()) { // try to load legacy list & remove it (i.e. migrate the data once)
|
if (storedNodes.isEmpty()) { // try to load legacy list & remove it (i.e. migrate the data once)
|
||||||
SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE);
|
SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE);
|
||||||
|
@ -180,13 +202,12 @@ public class LoginActivity extends BaseActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveFavourites() {
|
private void saveFavourites() {
|
||||||
List<Node> favourites = new ArrayList<>();
|
|
||||||
Timber.d("SAVE");
|
Timber.d("SAVE");
|
||||||
SharedPreferences.Editor editor = getSharedPreferences(NODES_PREFS_NAME, Context.MODE_PRIVATE).edit();
|
SharedPreferences.Editor editor = getSharedPreferences(NODES_PREFS_NAME, Context.MODE_PRIVATE).edit();
|
||||||
editor.clear();
|
editor.clear();
|
||||||
int i = 1;
|
int i = 1;
|
||||||
for (Node info : favouriteNodes) {
|
for (Node info : favouriteNodes) {
|
||||||
String nodeString = info.toNodeString();
|
final String nodeString = info.toNodeString();
|
||||||
editor.putString(Integer.toString(i), nodeString);
|
editor.putString(Integer.toString(i), nodeString);
|
||||||
Timber.d("saved %d:%s", i, nodeString);
|
Timber.d("saved %d:%s", i, nodeString);
|
||||||
i++;
|
i++;
|
||||||
|
@ -194,13 +215,14 @@ public class LoginActivity extends BaseActivity
|
||||||
editor.apply();
|
editor.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addFavourite(String nodeString) {
|
private NodeInfo addFavourite(String nodeString) {
|
||||||
NodeInfo nodeInfo = NodeInfo.fromString(nodeString);
|
final NodeInfo nodeInfo = NodeInfo.fromString(nodeString);
|
||||||
if (nodeInfo != null) {
|
if (nodeInfo != null) {
|
||||||
nodeInfo.setFavourite(true);
|
nodeInfo.setFavourite(true);
|
||||||
favouriteNodes.add(nodeInfo);
|
favouriteNodes.add(nodeInfo);
|
||||||
} else
|
} else
|
||||||
Timber.w("nodeString invalid: %s", nodeString);
|
Timber.w("nodeString invalid: %s", nodeString);
|
||||||
|
return nodeInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadLegacyList(final String legacyListString) {
|
private void loadLegacyList(final String legacyListString) {
|
||||||
|
@ -211,6 +233,34 @@ public class LoginActivity extends BaseActivity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void saveSelectedNode() { // save only if changed
|
||||||
|
final NodeInfo nodeInfo = getNode();
|
||||||
|
final String selectedNodeId = getSelectedNodeId();
|
||||||
|
if (nodeInfo != null) {
|
||||||
|
if (!nodeInfo.toNodeString().equals(selectedNodeId))
|
||||||
|
saveSelectedNode(nodeInfo);
|
||||||
|
} else {
|
||||||
|
if (selectedNodeId != null)
|
||||||
|
saveSelectedNode(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveSelectedNode(NodeInfo nodeInfo) {
|
||||||
|
SharedPreferences.Editor editor = getSharedPreferences(SELECTED_NODE_PREFS_NAME, Context.MODE_PRIVATE).edit();
|
||||||
|
if (nodeInfo == null) {
|
||||||
|
editor.clear();
|
||||||
|
} else {
|
||||||
|
editor.putString("0", getNode().toNodeString());
|
||||||
|
}
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getSelectedNodeId() {
|
||||||
|
return getSharedPreferences(SELECTED_NODE_PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
.getString("0", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private Toolbar toolbar;
|
private Toolbar toolbar;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1244,6 +1294,14 @@ public class LoginActivity extends BaseActivity
|
||||||
case R.id.action_help_node:
|
case R.id.action_help_node:
|
||||||
HelpFragment.display(getSupportFragmentManager(), R.string.help_node);
|
HelpFragment.display(getSupportFragmentManager(), R.string.help_node);
|
||||||
return true;
|
return true;
|
||||||
|
case R.id.action_default_nodes: {
|
||||||
|
Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
||||||
|
if ((WalletManager.getInstance().getNetworkType() == NetworkType.NetworkType_Mainnet) &&
|
||||||
|
(f instanceof NodeFragment)) {
|
||||||
|
((NodeFragment) f).restoreDefaultNodes();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
case R.id.action_privacy_policy:
|
case R.id.action_privacy_policy:
|
||||||
PrivacyFragment.display(getSupportFragmentManager());
|
PrivacyFragment.display(getSupportFragmentManager());
|
||||||
return true;
|
return true;
|
||||||
|
@ -1253,12 +1311,13 @@ public class LoginActivity extends BaseActivity
|
||||||
case R.id.action_theme:
|
case R.id.action_theme:
|
||||||
onChangeTheme();
|
onChangeTheme();
|
||||||
return true;
|
return true;
|
||||||
case R.id.action_ledger_seed:
|
case R.id.action_ledger_seed: {
|
||||||
Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
||||||
if (f instanceof GenerateFragment) {
|
if (f instanceof GenerateFragment) {
|
||||||
((GenerateFragment) f).convertLedgerSeed();
|
((GenerateFragment) f).convertLedgerSeed();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
@ -1310,6 +1369,7 @@ public class LoginActivity extends BaseActivity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean checkDevice(String walletName, String password) {
|
boolean checkDevice(String walletName, String password) {
|
||||||
|
|
|
@ -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))
|
||||||
Collections.sort(nodesToTest, NodeInfo.BestNodeComparator);
|
selectedNode = null; // it's not in the favourites (any longer)
|
||||||
NodeInfo bestNode = nodesToTest.get(0);
|
if (selectedNode == null)
|
||||||
if (bestNode.isValid()) {
|
for (NodeInfo node : favourites) {
|
||||||
activityCallback.setNode(bestNode);
|
if (node.isSelected()) {
|
||||||
return bestNode;
|
selectedNode = node;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (selectedNode == null) { // autoselect
|
||||||
|
selectedNode = autoselect(favourites);
|
||||||
|
} else
|
||||||
|
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;
|
||||||
|
@ -462,17 +473,10 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||||
Timber.d("found a good node %s", result.toString());
|
Timber.d("found a good node %s", result.toString());
|
||||||
showNode(result);
|
showNode(result);
|
||||||
} else {
|
} else {
|
||||||
if (!activityCallback.getFavouriteNodes().isEmpty()) {
|
tvNodeName.setText(getResources().getText(R.string.node_create_hint));
|
||||||
tvNodeName.setText(getResources().getText(R.string.node_refresh_hint));
|
tvNodeName.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||||
tvNodeName.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_refresh_black_24dp, 0, 0, 0);
|
tvNodeAddress.setText(null);
|
||||||
tvNodeAddress.setText(null);
|
tvNodeAddress.setVisibility(View.GONE);
|
||||||
tvNodeAddress.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
tvNodeName.setText(getResources().getText(R.string.node_create_hint));
|
|
||||||
tvNodeName.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
|
||||||
tvNodeAddress.setText(null);
|
|
||||||
tvNodeAddress.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,6 +56,7 @@ 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.Set;
|
import java.util.Set;
|
||||||
|
@ -87,7 +89,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,15 +162,12 @@ 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
|
if (WalletManager.getInstance().getNetworkType() == NetworkType.NetworkType_Mainnet) {
|
||||||
public void onRefresh() {
|
refresh(AsyncFindNodes.SCAN);
|
||||||
if (WalletManager.getInstance().getNetworkType() == NetworkType.NetworkType_Mainnet) {
|
} else {
|
||||||
refresh();
|
Toast.makeText(getActivity(), getString(R.string.node_wrong_net), Toast.LENGTH_LONG).show();
|
||||||
} else {
|
pullToRefresh.setRefreshing(false);
|
||||||
Toast.makeText(getActivity(), getString(R.string.node_wrong_net), Toast.LENGTH_LONG).show();
|
|
||||||
pullToRefresh.setRefreshing(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -176,16 +179,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 +210,19 @@ 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);
|
||||||
|
}
|
||||||
|
nodeItem.setSelected(true);
|
||||||
|
activityCallback.setNode(nodeItem); // this marks it as selected & saves it as well
|
||||||
|
nodesAdapter.dataSetChanged(); // to refresh test results
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 +242,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,48 +258,60 @@ public class NodeFragment extends Fragment
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Boolean doInBackground(Void... params) {
|
protected Boolean doInBackground(Integer... params) {
|
||||||
Timber.d("scanning");
|
if (params[0] == RESTORE_DEFAULTS) { // true = restore defaults
|
||||||
Set<NodeInfo> seedList = new HashSet<>();
|
for (DefaultNodes node : DefaultNodes.values()) {
|
||||||
seedList.addAll(nodeList);
|
NodeInfo nodeInfo = NodeInfo.fromString(node.getUri());
|
||||||
nodeList.clear();
|
if (nodeInfo != null) {
|
||||||
Timber.d("seed %d", seedList.size());
|
nodeInfo.setFavourite(true);
|
||||||
Dispatcher d = new Dispatcher(new Dispatcher.Listener() {
|
nodeList.add(nodeInfo);
|
||||||
@Override
|
|
||||||
public void onGet(NodeInfo info) {
|
|
||||||
publishProgress(info);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
d.seedPeers(seedList);
|
|
||||||
d.awaitTermination(NODES_TO_FIND);
|
|
||||||
|
|
||||||
// we didn't find enough because we didn't ask around enough? ask more!
|
|
||||||
if ((d.getRpcNodes().size() < NODES_TO_FIND) &&
|
|
||||||
(d.getPeerCount() < NODES_TO_FIND + seedList.size())) {
|
|
||||||
// try again
|
|
||||||
publishProgress((NodeInfo[]) null);
|
|
||||||
d = new Dispatcher(new Dispatcher.Listener() {
|
|
||||||
@Override
|
|
||||||
public void onGet(NodeInfo info) {
|
|
||||||
publishProgress(info);
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
// also seed with monero seed nodes (see p2p/net_node.inl:410 in monero src)
|
NodePinger.execute(nodeList, this);
|
||||||
seedList.add(new NodeInfo(new InetSocketAddress("107.152.130.98", 18080)));
|
return true;
|
||||||
seedList.add(new NodeInfo(new InetSocketAddress("212.83.175.67", 18080)));
|
} else if (params[0] == PING) {
|
||||||
seedList.add(new NodeInfo(new InetSocketAddress("5.9.100.248", 18080)));
|
NodePinger.execute(nodeList, this);
|
||||||
seedList.add(new NodeInfo(new InetSocketAddress("163.172.182.165", 18080)));
|
return true;
|
||||||
seedList.add(new NodeInfo(new InetSocketAddress("161.67.132.39", 18080)));
|
} else if (params[0] == SCAN) {
|
||||||
seedList.add(new NodeInfo(new InetSocketAddress("198.74.231.92", 18080)));
|
// otherwise scan the network
|
||||||
seedList.add(new NodeInfo(new InetSocketAddress("195.154.123.123", 18080)));
|
Timber.d("scanning");
|
||||||
seedList.add(new NodeInfo(new InetSocketAddress("212.83.172.165", 18080)));
|
Set<NodeInfo> seedList = new HashSet<>();
|
||||||
seedList.add(new NodeInfo(new InetSocketAddress("192.110.160.146", 18080)));
|
seedList.addAll(nodeList);
|
||||||
|
nodeList.clear();
|
||||||
|
Timber.d("seed %d", seedList.size());
|
||||||
|
Dispatcher d = new Dispatcher(info -> publishProgress(info));
|
||||||
d.seedPeers(seedList);
|
d.seedPeers(seedList);
|
||||||
d.awaitTermination(NODES_TO_FIND);
|
d.awaitTermination(NODES_TO_FIND);
|
||||||
|
|
||||||
|
// we didn't find enough because we didn't ask around enough? ask more!
|
||||||
|
if ((d.getRpcNodes().size() < NODES_TO_FIND) &&
|
||||||
|
(d.getPeerCount() < NODES_TO_FIND + seedList.size())) {
|
||||||
|
// try again
|
||||||
|
publishProgress((NodeInfo[]) null);
|
||||||
|
d = new Dispatcher(new Dispatcher.Listener() {
|
||||||
|
@Override
|
||||||
|
public void onGet(NodeInfo info) {
|
||||||
|
publishProgress(info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// also seed with monero seed nodes (see p2p/net_node.inl:410 in monero src)
|
||||||
|
seedList.add(new NodeInfo(new InetSocketAddress("107.152.130.98", 18080)));
|
||||||
|
seedList.add(new NodeInfo(new InetSocketAddress("212.83.175.67", 18080)));
|
||||||
|
seedList.add(new NodeInfo(new InetSocketAddress("5.9.100.248", 18080)));
|
||||||
|
seedList.add(new NodeInfo(new InetSocketAddress("163.172.182.165", 18080)));
|
||||||
|
seedList.add(new NodeInfo(new InetSocketAddress("161.67.132.39", 18080)));
|
||||||
|
seedList.add(new NodeInfo(new InetSocketAddress("198.74.231.92", 18080)));
|
||||||
|
seedList.add(new NodeInfo(new InetSocketAddress("195.154.123.123", 18080)));
|
||||||
|
seedList.add(new NodeInfo(new InetSocketAddress("212.83.172.165", 18080)));
|
||||||
|
seedList.add(new NodeInfo(new InetSocketAddress("192.110.160.146", 18080)));
|
||||||
|
d.seedPeers(seedList);
|
||||||
|
d.awaitTermination(NODES_TO_FIND);
|
||||||
|
}
|
||||||
|
// final (filtered) result
|
||||||
|
nodeList.addAll(d.getRpcNodes());
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
// final (filtered) result
|
return false;
|
||||||
nodeList.addAll(d.getRpcNodes());
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -310,6 +346,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 +450,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 +515,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) {
|
closeDialog();
|
||||||
undoChanges();
|
nodesAdapter.dataSetChanged(); // to refresh test results
|
||||||
closeDialog();
|
|
||||||
nodesAdapter.dataSetChanged(); // to refresh test results
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
editDialog = alertDialogBuilder.create();
|
editDialog = alertDialogBuilder.create();
|
||||||
|
@ -556,4 +588,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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -579,10 +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(() -> {
|
runOnUiThread(this::updateAccountsList);
|
||||||
if (getWallet() != null)
|
|
||||||
updateAccountsList();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
final WalletFragment walletFragment = (WalletFragment)
|
final WalletFragment walletFragment = (WalletFragment)
|
||||||
|
@ -1054,21 +1051,23 @@ 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 int n = wallet.getNumAccounts();
|
final Wallet wallet = getWallet();
|
||||||
final boolean showBalances = (n > 1) && !isStreetMode();
|
if (wallet != null) {
|
||||||
for (int i = 0; i < n; i++) {
|
final int n = wallet.getNumAccounts();
|
||||||
final String label = (showBalances ?
|
final boolean showBalances = (n > 1) && !isStreetMode();
|
||||||
getString(R.string.label_account, wallet.getAccountLabel(i), Helper.getDisplayAmount(wallet.getBalance(i), 2))
|
for (int i = 0; i < n; i++) {
|
||||||
: wallet.getAccountLabel(i));
|
final String label = (showBalances ?
|
||||||
final MenuItem item = menu.add(R.id.accounts_list, getAccountId(i), 2 * i, label);
|
getString(R.string.label_account, wallet.getAccountLabel(i), Helper.getDisplayAmount(wallet.getBalance(i), 2))
|
||||||
item.setIcon(R.drawable.ic_account_balance_wallet_black_24dp);
|
: wallet.getAccountLabel(i));
|
||||||
if (i == wallet.getAccountIndex())
|
final MenuItem item = menu.add(R.id.accounts_list, getAccountId(i), 2 * i, label);
|
||||||
item.setChecked(true);
|
item.setIcon(R.drawable.ic_account_balance_wallet_black_24dp);
|
||||||
|
if (i == wallet.getAccountIndex())
|
||||||
|
item.setChecked(true);
|
||||||
|
}
|
||||||
|
menu.setGroupCheckable(R.id.accounts_list, true, true);
|
||||||
}
|
}
|
||||||
menu.setGroupCheckable(R.id.accounts_list, true, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
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;
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -67,7 +67,6 @@ public class NodeInfo extends Node {
|
||||||
try {
|
try {
|
||||||
return new NodeInfo(nodeString);
|
return new NodeInfo(nodeString);
|
||||||
} catch (IllegalArgumentException ex) {
|
} catch (IllegalArgumentException ex) {
|
||||||
Timber.w(ex);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,23 +144,20 @@ 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
|
if (o1.isValid()) {
|
||||||
public int compare(NodeInfo o1, NodeInfo o2) {
|
if (o2.isValid()) { // both are valid
|
||||||
if (o1.isValid()) {
|
// higher node wins
|
||||||
if (o2.isValid()) { // both are valid
|
int heightDiff = (int) (o2.height - o1.height);
|
||||||
// higher node wins
|
if (heightDiff != 0)
|
||||||
int heightDiff = (int) (o2.height - o1.height);
|
return heightDiff;
|
||||||
if (Math.abs(heightDiff) > Dispatcher.HEIGHT_WINDOW)
|
// if they are equal, faster node wins
|
||||||
return heightDiff;
|
return (int) Math.signum(o1.responseTime - o2.responseTime);
|
||||||
// if they are (nearly) equal, faster node wins
|
|
||||||
return (int) Math.signum(o1.responseTime - o2.responseTime);
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return 1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -200,7 +196,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();
|
||||||
|
@ -248,7 +252,7 @@ public class NodeInfo extends Node {
|
||||||
}
|
}
|
||||||
} catch (IOException | JSONException ex) {
|
} catch (IOException | JSONException ex) {
|
||||||
// failure
|
// failure
|
||||||
Timber.d(ex.getMessage());
|
Timber.d(ex);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,7 +110,7 @@ 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 TextView tvName;
|
final TextView tvName;
|
||||||
final TextView tvIp;
|
final TextView tvIp;
|
||||||
|
@ -119,13 +123,16 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
|
||||||
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
|
nodeItem.toggleFavourite();
|
||||||
public void onClick(View v) {
|
showStar();
|
||||||
nodeItem.toggleFavourite();
|
if (!nodeItem.isFavourite()) {
|
||||||
showStar();
|
nodeItem.setSelected(false);
|
||||||
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
itemView.setOnClickListener(this);
|
||||||
|
itemView.setOnLongClickListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showStar() {
|
private void showStar() {
|
||||||
|
@ -139,15 +146,16 @@ 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.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);
|
itemView.setSelected(nodeItem.isSelected());
|
||||||
itemView.setClickable(itemsClickable);
|
itemView.setClickable(itemsClickable);
|
||||||
|
itemView.setEnabled(itemsClickable);
|
||||||
ibBookmark.setClickable(itemsClickable);
|
ibBookmark.setClickable(itemsClickable);
|
||||||
showStar();
|
showStar();
|
||||||
}
|
}
|
||||||
|
@ -161,6 +169,17 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.m2049r.xmrwallet.util;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Color;
|
||||||
|
|
||||||
import com.m2049r.xmrwallet.R;
|
import com.m2049r.xmrwallet.R;
|
||||||
|
|
||||||
|
@ -10,4 +11,9 @@ public class ColorHelper {
|
||||||
TypedArray styledAttributes = ctx.getTheme().obtainStyledAttributes(R.style.MyMaterialTheme, new int[]{attrId});
|
TypedArray styledAttributes = ctx.getTheme().obtainStyledAttributes(R.style.MyMaterialTheme, new int[]{attrId});
|
||||||
return styledAttributes.getResourceId(0, 0);
|
return styledAttributes.getResourceId(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static public int getThemedColor(Context ctx, int attrId) {
|
||||||
|
TypedArray styledAttributes = ctx.getTheme().obtainStyledAttributes(R.style.MyMaterialTheme, new int[]{attrId});
|
||||||
|
return styledAttributes.getColor(0, Color.BLACK);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,7 @@ import java.math.BigInteger;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.Calendar;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
@ -662,4 +663,30 @@ public class Helper {
|
||||||
static public boolean preventScreenshot() {
|
static public boolean preventScreenshot() {
|
||||||
return !(BuildConfig.DEBUG || BuildConfig.FLAVOR_type.equals("alpha"));
|
return !(BuildConfig.DEBUG || BuildConfig.FLAVOR_type.equals("alpha"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static public final int STALE_NODE_HOURS = 2;
|
||||||
|
|
||||||
|
static public void showTimeDifference(TextView view, long timeInSeconds) {
|
||||||
|
final Context ctx = view.getContext();
|
||||||
|
final long now = Calendar.getInstance().getTimeInMillis() / 1000;
|
||||||
|
final long secs = (now - timeInSeconds);
|
||||||
|
final long mins = secs / 60; // in minutes
|
||||||
|
final long hours = mins / 60;
|
||||||
|
final long days = hours / 24;
|
||||||
|
String msg;
|
||||||
|
if (mins < 2) {
|
||||||
|
msg = ctx.getString(R.string.node_updated_now, secs);
|
||||||
|
} else if (hours < 2) {
|
||||||
|
msg = ctx.getString(R.string.node_updated_mins, mins);
|
||||||
|
} else if (days < 2) {
|
||||||
|
msg = ctx.getString(R.string.node_updated_hours, hours);
|
||||||
|
} else {
|
||||||
|
msg = ctx.getString(R.string.node_updated_days, days);
|
||||||
|
}
|
||||||
|
view.setText(msg);
|
||||||
|
if (hours >= STALE_NODE_HOURS)
|
||||||
|
view.setTextColor(ColorHelper.getThemedColor(view.getContext(), R.attr.colorError));
|
||||||
|
else
|
||||||
|
view.setTextColor(ColorHelper.getThemedColor(view.getContext(), android.R.attr.textColorPrimary));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* 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.util;
|
||||||
|
|
||||||
|
import com.m2049r.xmrwallet.data.NodeInfo;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public class NodePinger {
|
||||||
|
static final public int NUM_THREADS = 10;
|
||||||
|
static final public long MAX_TIME = 5L; // seconds
|
||||||
|
|
||||||
|
public interface Listener {
|
||||||
|
void publish(NodeInfo node);
|
||||||
|
}
|
||||||
|
|
||||||
|
static public void execute(Collection<NodeInfo> nodes, final Listener listener) {
|
||||||
|
final ExecutorService exeService = Executors.newFixedThreadPool(NUM_THREADS);
|
||||||
|
List<Callable<Boolean>> taskList = new ArrayList<>();
|
||||||
|
for (NodeInfo node : nodes) {
|
||||||
|
taskList.add(() -> {
|
||||||
|
node.clear();
|
||||||
|
return node.testRpcService(listener);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
exeService.invokeAll(taskList, MAX_TIME, TimeUnit.SECONDS);
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
Timber.w(ex);
|
||||||
|
}
|
||||||
|
exeService.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
|
@ -119,6 +119,8 @@ public class RestoreHeight {
|
||||||
blockheight.put("2020-07-01", 2132318L);
|
blockheight.put("2020-07-01", 2132318L);
|
||||||
blockheight.put("2020-08-01", 2154590L);
|
blockheight.put("2020-08-01", 2154590L);
|
||||||
blockheight.put("2020-09-01", 2176790L);
|
blockheight.put("2020-09-01", 2176790L);
|
||||||
|
blockheight.put("2020-10-01", 2198370L);
|
||||||
|
blockheight.put("2020-11-01", 2220670L);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getHeight(String date) {
|
public long getHeight(String date) {
|
||||||
|
|
|
@ -179,7 +179,7 @@ public class ExchangeView extends LinearLayout {
|
||||||
|
|
||||||
// make progress circle gray
|
// make progress circle gray
|
||||||
pbExchange.getIndeterminateDrawable().
|
pbExchange.getIndeterminateDrawable().
|
||||||
setColorFilter(getResources().getColor(ColorHelper.getThemedResourceId(getContext(), R.attr.colorPrimaryVariant)),
|
setColorFilter(ColorHelper.getThemedColor(getContext(), R.attr.colorPrimaryVariant),
|
||||||
android.graphics.PorterDuff.Mode.MULTIPLY);
|
android.graphics.PorterDuff.Mode.MULTIPLY);
|
||||||
|
|
||||||
sCurrencyA.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
sCurrencyA.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@color/btn_color_selector"
|
||||||
|
android:pathData="M12,6v3l4,-4 -4,-4v3c-4.42,0 -8,3.58 -8,8 0,1.57 0.46,3.03 1.24,4.26L6.7,14.8c-0.45,-0.83 -0.7,-1.79 -0.7,-2.8 0,-3.31 2.69,-6 6,-6zM18.76,7.74L17.3,9.2c0.44,0.84 0.7,1.79 0.7,2.8 0,3.31 -2.69,6 -6,6v-3l-4,4 4,4v-3c4.42,0 8,-3.58 8,-8 0,-1.57 -0.46,-3.03 -1.24,-4.26z" />
|
||||||
|
</vector>
|
|
@ -1,9 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24.0"
|
|
||||||
android:viewportHeight="24.0">
|
|
||||||
<path
|
|
||||||
android:fillColor="@color/btn_color_selector"
|
|
||||||
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
|
|
||||||
</vector>
|
|
|
@ -6,6 +6,12 @@
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
|
<item android:state_selected="true">
|
||||||
|
<shape>
|
||||||
|
<solid android:color="@color/monerujoPinkAlpha" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
|
||||||
<item android:state_pressed="false">
|
<item android:state_pressed="false">
|
||||||
<shape>
|
<shape>
|
||||||
<solid android:color="@android:color/transparent" />
|
<solid android:color="@android:color/transparent" />
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentTop="true"
|
android:layout_alignParentTop="true"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_toStartOf="@+id/ibOption">
|
android:layout_toStartOf="@+id/ibRenew">
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/pbNode"
|
android:id="@+id/pbNode"
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
android:id="@+id/llNode"
|
android:id="@+id/llNode"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:gravity="start|center_vertical"
|
android:gravity="start|center_vertical"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/ibOption"
|
android:id="@+id/ibRenew"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
|
@ -81,7 +81,7 @@
|
||||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:padding="12dp"
|
android:padding="12dp"
|
||||||
android:src="@drawable/ic_search_24dp" />
|
android:src="@drawable/ic_renew_24dp" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
|
|
|
@ -8,4 +8,10 @@
|
||||||
android:orderInCategory="500"
|
android:orderInCategory="500"
|
||||||
android:title="@string/menu_help"
|
android:title="@string/menu_help"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_default_nodes"
|
||||||
|
android:orderInCategory="500"
|
||||||
|
android:title="@string/menu_default_nodes"
|
||||||
|
app:showAsAction="never" />
|
||||||
</menu>
|
</menu>
|
|
@ -399,4 +399,11 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||||
|
|
||||||
|
<string name="menu_default_nodes">Restore default nodes</string>
|
||||||
|
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||||
|
|
||||||
|
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||||
|
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||||
|
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||||
|
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -402,4 +402,11 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||||
|
|
||||||
|
<string name="menu_default_nodes">Restore default nodes</string>
|
||||||
|
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||||
|
|
||||||
|
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||||
|
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||||
|
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||||
|
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -401,4 +401,11 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||||
|
|
||||||
|
<string name="menu_default_nodes">Restore default nodes</string>
|
||||||
|
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||||
|
|
||||||
|
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||||
|
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||||
|
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||||
|
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -401,4 +401,11 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||||
|
|
||||||
|
<string name="menu_default_nodes">Restore default nodes</string>
|
||||||
|
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||||
|
|
||||||
|
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||||
|
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||||
|
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||||
|
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -392,4 +392,11 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||||
|
|
||||||
|
<string name="menu_default_nodes">Restore default nodes</string>
|
||||||
|
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||||
|
|
||||||
|
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||||
|
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||||
|
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||||
|
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -399,4 +399,11 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||||
|
|
||||||
|
<string name="menu_default_nodes">Restore default nodes</string>
|
||||||
|
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||||
|
|
||||||
|
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||||
|
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||||
|
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||||
|
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -405,4 +405,11 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||||
|
|
||||||
|
<string name="menu_default_nodes">Restore default nodes</string>
|
||||||
|
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||||
|
|
||||||
|
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||||
|
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||||
|
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||||
|
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -403,4 +403,11 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||||
|
|
||||||
|
<string name="menu_default_nodes">Restore default nodes</string>
|
||||||
|
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||||
|
|
||||||
|
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||||
|
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||||
|
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||||
|
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -404,4 +404,11 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||||
|
|
||||||
|
<string name="menu_default_nodes">Restore default nodes</string>
|
||||||
|
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||||
|
|
||||||
|
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||||
|
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||||
|
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||||
|
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -410,4 +410,11 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||||
|
|
||||||
|
<string name="menu_default_nodes">Restore default nodes</string>
|
||||||
|
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||||
|
|
||||||
|
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||||
|
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||||
|
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||||
|
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -401,4 +401,11 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||||
|
|
||||||
|
<string name="menu_default_nodes">Restore default nodes</string>
|
||||||
|
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||||
|
|
||||||
|
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||||
|
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||||
|
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||||
|
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
<color name="monerujoOrange">#FF6252</color>
|
<color name="monerujoOrange">#FF6252</color>
|
||||||
<color name="monerujoGreen">#27C79C</color>
|
<color name="monerujoGreen">#27C79C</color>
|
||||||
<color name="monerujoPink">#FF1A80</color>
|
<color name="monerujoPink">#FF1A80</color>
|
||||||
|
<color name="monerujoPinkAlpha">#20FF1A80</color>
|
||||||
<color name="monerujoE">#2D1A2E</color>
|
<color name="monerujoE">#2D1A2E</color>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -401,4 +401,11 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||||
|
|
||||||
|
<string name="menu_default_nodes">Restore default nodes</string>
|
||||||
|
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||||
|
|
||||||
|
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||||
|
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||||
|
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||||
|
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -393,4 +393,11 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||||
|
|
||||||
|
<string name="menu_default_nodes">Restore default nodes</string>
|
||||||
|
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||||
|
|
||||||
|
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||||
|
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||||
|
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||||
|
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -405,4 +405,11 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||||
|
|
||||||
|
<string name="menu_default_nodes">Restore default nodes</string>
|
||||||
|
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||||
|
|
||||||
|
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||||
|
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||||
|
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||||
|
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -401,4 +401,11 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||||
|
|
||||||
|
<string name="menu_default_nodes">Restore default nodes</string>
|
||||||
|
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||||
|
|
||||||
|
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||||
|
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||||
|
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||||
|
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -405,4 +405,11 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||||
|
|
||||||
|
<string name="menu_default_nodes">Restore default nodes</string>
|
||||||
|
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||||
|
|
||||||
|
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||||
|
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||||
|
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||||
|
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -402,4 +402,11 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||||
|
|
||||||
|
<string name="menu_default_nodes">Restore default nodes</string>
|
||||||
|
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||||
|
|
||||||
|
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||||
|
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||||
|
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||||
|
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -400,4 +400,11 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||||
|
|
||||||
|
<string name="menu_default_nodes">Restore default nodes</string>
|
||||||
|
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||||
|
|
||||||
|
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||||
|
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||||
|
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||||
|
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -385,4 +385,11 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||||
|
|
||||||
|
<string name="menu_default_nodes">Restore default nodes</string>
|
||||||
|
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||||
|
|
||||||
|
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||||
|
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||||
|
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||||
|
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -405,4 +405,11 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||||
|
|
||||||
|
<string name="menu_default_nodes">Restore default nodes</string>
|
||||||
|
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||||
|
|
||||||
|
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||||
|
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||||
|
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||||
|
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -324,4 +324,11 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||||
|
|
||||||
|
<string name="menu_default_nodes">Restore default nodes</string>
|
||||||
|
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||||
|
|
||||||
|
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||||
|
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||||
|
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||||
|
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -400,4 +400,11 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||||
|
|
||||||
|
<string name="menu_default_nodes">Restore default nodes</string>
|
||||||
|
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||||
|
|
||||||
|
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||||
|
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||||
|
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||||
|
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
<color name="monerujoOrange">#FA5544</color>
|
<color name="monerujoOrange">#FA5544</color>
|
||||||
<color name="monerujoGreen">#00C691</color>
|
<color name="monerujoGreen">#00C691</color>
|
||||||
<color name="monerujoPink">#F0006B</color>
|
<color name="monerujoPink">#F0006B</color>
|
||||||
|
<color name="monerujoPinkAlpha">#20F0006B</color>
|
||||||
<color name="monerujoE">#FFFDFB</color>
|
<color name="monerujoE">#FFFDFB</color>
|
||||||
|
|
||||||
<color name="monerujoReceive">@color/monerujoGreen</color>
|
<color name="monerujoReceive">@color/monerujoGreen</color>
|
||||||
|
|
|
@ -452,4 +452,11 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||||
|
|
||||||
|
<string name="menu_default_nodes">Restore default nodes</string>
|
||||||
|
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||||
|
|
||||||
|
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||||
|
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||||
|
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||||
|
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -6,7 +6,7 @@ buildscript {
|
||||||
google()
|
google()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:4.1.0'
|
classpath 'com.android.tools.build:gradle:4.1.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ source script/env.sh
|
||||||
cd $EXTERNAL_LIBS_BUILD_ROOT
|
cd $EXTERNAL_LIBS_BUILD_ROOT
|
||||||
|
|
||||||
url="https://github.com/m2049r/monero"
|
url="https://github.com/m2049r/monero"
|
||||||
version="release-v0.17.1.1-monerujo"
|
version="release-v0.17.1.3-monerujo"
|
||||||
|
|
||||||
if [ ! -d "monero" ]; then
|
if [ ! -d "monero" ]; then
|
||||||
git clone ${url} -b ${version}
|
git clone ${url} -b ${version}
|
||||||
|
|
Loading…
Reference in New Issue