diff --git a/app/build.gradle b/app/build.gradle index a8a1102..2867738 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,14 +1,14 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 28 + compileSdkVersion 30 buildToolsVersion '29.0.2' defaultConfig { applicationId "com.m2049r.xmrwallet" minSdkVersion 21 - targetSdkVersion 28 - versionCode 503 - versionName "1.15.3 'Dark Fork'" + targetSdkVersion 30 + versionCode 600 + versionName "1.16.0 'Karmic Nodes'" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { @@ -117,7 +117,7 @@ dependencies { implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.cardview:cardview:1.0.0' 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 "com.squareup.okhttp3:okhttp:4.9.0" @@ -138,4 +138,7 @@ dependencies { testImplementation "com.squareup.okhttp3:mockwebserver:4.9.0" testImplementation 'org.json:json:20180813' testImplementation 'net.jodah:concurrentunit:0.4.4' + + compileOnly 'org.projectlombok:lombok:1.18.16' + annotationProcessor 'org.projectlombok:lombok:1.18.16' } diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java index 8fb5b4d..2355038 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java +++ b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java @@ -46,6 +46,7 @@ import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.m2049r.xmrwallet.data.DefaultNodes; import com.m2049r.xmrwallet.data.Node; import com.m2049r.xmrwallet.data.NodeInfo; import com.m2049r.xmrwallet.dialog.AboutFragment; @@ -74,6 +75,7 @@ import java.io.IOException; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -90,6 +92,7 @@ public class LoginActivity extends BaseActivity private static final String GENERATE_STACK = "gen"; 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_STAGENET = "daemon_stagenet"; private static final String PREF_DAEMON_MAINNET = "daemon_mainnet"; @@ -105,10 +108,21 @@ public class LoginActivity extends BaseActivity @Override public void setNode(NodeInfo node) { - if ((node != null) && (node.getNetworkType() != WalletManager.getInstance().getNetworkType())) - throw new IllegalArgumentException("network type does not match"); - this.node = node; - WalletManager.getInstance().setDaemon(node); + setNode(node, true); + } + + 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 @@ -117,7 +131,22 @@ public class LoginActivity extends BaseActivity } @Override - public void setFavouriteNodes(Set nodes) { + public Set 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 nodes) { Timber.d("adding %d nodes", nodes.size()); favouriteNodes.clear(); for (NodeInfo node : nodes) { @@ -125,38 +154,31 @@ public class LoginActivity extends BaseActivity if (node.isFavourite()) favouriteNodes.add(node); } - if (favouriteNodes.isEmpty() && (!nodes.isEmpty())) { // no favourites - pick best ones - List 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(); } private void loadFavouritesWithNetwork() { - Helper.runWithNetwork(new Helper.Action() { - @Override - public boolean run() { - loadFavourites(); - return true; - } + Helper.runWithNetwork(() -> { + loadFavourites(); + return true; }); } private void loadFavourites() { Timber.d("loadFavourites"); favouriteNodes.clear(); + final String selectedNodeId = getSelectedNodeId(); Map storedNodes = getSharedPreferences(NODES_PREFS_NAME, Context.MODE_PRIVATE).getAll(); for (Map.Entry nodeEntry : storedNodes.entrySet()) { - if (nodeEntry != null) // just in case, ignore possible future errors - addFavourite((String) nodeEntry.getValue()); + if (nodeEntry != null) { // just in case, ignore possible future errors + 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) SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE); @@ -180,13 +202,12 @@ public class LoginActivity extends BaseActivity } private void saveFavourites() { - List favourites = new ArrayList<>(); Timber.d("SAVE"); SharedPreferences.Editor editor = getSharedPreferences(NODES_PREFS_NAME, Context.MODE_PRIVATE).edit(); editor.clear(); int i = 1; for (Node info : favouriteNodes) { - String nodeString = info.toNodeString(); + final String nodeString = info.toNodeString(); editor.putString(Integer.toString(i), nodeString); Timber.d("saved %d:%s", i, nodeString); i++; @@ -194,13 +215,14 @@ public class LoginActivity extends BaseActivity editor.apply(); } - private void addFavourite(String nodeString) { - NodeInfo nodeInfo = NodeInfo.fromString(nodeString); + private NodeInfo addFavourite(String nodeString) { + final NodeInfo nodeInfo = NodeInfo.fromString(nodeString); if (nodeInfo != null) { nodeInfo.setFavourite(true); favouriteNodes.add(nodeInfo); } else Timber.w("nodeString invalid: %s", nodeString); + return nodeInfo; } 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; @Override @@ -1244,6 +1294,14 @@ public class LoginActivity extends BaseActivity case R.id.action_help_node: HelpFragment.display(getSupportFragmentManager(), R.string.help_node); 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: PrivacyFragment.display(getSupportFragmentManager()); return true; @@ -1253,12 +1311,13 @@ public class LoginActivity extends BaseActivity case R.id.action_theme: onChangeTheme(); return true; - case R.id.action_ledger_seed: + case R.id.action_ledger_seed: { Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container); if (f instanceof GenerateFragment) { ((GenerateFragment) f).convertLedgerSeed(); } return true; + } default: return super.onOptionsItemSelected(item); } @@ -1310,6 +1369,7 @@ public class LoginActivity extends BaseActivity } } } + } boolean checkDevice(String walletName, String password) { diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java b/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java index bce0f09..183f1cb 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java @@ -45,6 +45,7 @@ import com.m2049r.xmrwallet.layout.WalletInfoAdapter; import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.KeyStoreHelper; +import com.m2049r.xmrwallet.util.NodePinger; import com.m2049r.xmrwallet.util.Notice; import com.m2049r.xmrwallet.widget.Toolbar; @@ -105,6 +106,8 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter Set getFavouriteNodes(); + Set getOrPopulateFavourites(); + boolean hasLedger(); } @@ -132,11 +135,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter activityCallback.setTitle(null); activityCallback.setToolbarButton(Toolbar.BUTTON_CREDITS); activityCallback.showNet(); - NodeInfo node = activityCallback.getNode(); - if (node == null) - findBestNode(); - else - showNode(node); + pingSelectedNode(); } @Override @@ -186,23 +185,10 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter pbNode = view.findViewById(R.id.pbNode); llNode = view.findViewById(R.id.llNode); - llNode.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (activityCallback.getFavouriteNodes().isEmpty()) - startNodePrefs(); - else - findBestNode(); - } - }); + llNode.setOnClickListener(v -> startNodePrefs()); tvNodeName = view.findViewById(R.id.tvNodeName); tvNodeAddress = view.findViewById(R.id.tvNodeAddress); - view.findViewById(R.id.ibOption).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - startNodePrefs(); - } - }); + view.findViewById(R.id.ibRenew).setOnClickListener(v -> findBestNode()); Helper.hideKeyboard(getActivity()); @@ -421,32 +407,57 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter } public void findBestNode() { - new AsyncFindBestNode().execute(); + new AsyncFindBestNode().execute(AsyncFindBestNode.FIND_BEST); } - private class AsyncFindBestNode extends AsyncTask { + public void pingSelectedNode() { + new AsyncFindBestNode().execute(AsyncFindBestNode.PING_SELECTED); + } + + private NodeInfo autoselect(Set nodes) { + if (nodes.isEmpty()) return null; + NodePinger.execute(nodes, null); + List nodeList = new ArrayList<>(nodes); + Collections.sort(nodeList, NodeInfo.BestNodeComparator); + return nodeList.get(0); + } + + private class AsyncFindBestNode extends AsyncTask { + final static int PING_SELECTED = 0; + final static int FIND_BEST = 1; + @Override protected void onPreExecute() { super.onPreExecute(); pbNode.setVisibility(View.VISIBLE); llNode.setVisibility(View.INVISIBLE); - activityCallback.setNode(null); } @Override - protected NodeInfo doInBackground(Void... params) { - List nodesToTest = new ArrayList<>(activityCallback.getFavouriteNodes()); - Timber.d("testing best node from %d", nodesToTest.size()); - if (nodesToTest.isEmpty()) return null; - for (NodeInfo node : nodesToTest) { - node.testRpcService(); // TODO: do this in parallel? - // no: it's better if it looks like it's doing something - } - Collections.sort(nodesToTest, NodeInfo.BestNodeComparator); - NodeInfo bestNode = nodesToTest.get(0); - if (bestNode.isValid()) { - activityCallback.setNode(bestNode); - return bestNode; + protected NodeInfo doInBackground(Integer... params) { + Set favourites = activityCallback.getOrPopulateFavourites(); + NodeInfo selectedNode; + if (params[0] == FIND_BEST) { + selectedNode = autoselect(favourites); + } else if (params[0] == PING_SELECTED) { + 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; + } + } + 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 { activityCallback.setNode(null); return null; @@ -462,17 +473,10 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter Timber.d("found a good node %s", result.toString()); 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 { - tvNodeName.setText(getResources().getText(R.string.node_create_hint)); - tvNodeName.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); - tvNodeAddress.setText(null); - tvNodeAddress.setVisibility(View.GONE); - } + 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) { tvNodeName.setText(nodeInfo.getName()); tvNodeName.setCompoundDrawablesWithIntrinsicBounds(NodeInfoAdapter.getPingIcon(nodeInfo), 0, 0, 0); - tvNodeAddress.setText(nodeInfo.getAddress()); + Helper.showTimeDifference(tvNodeAddress, nodeInfo.getTimestamp()); tvNodeAddress.setVisibility(View.VISIBLE); } private void startNodePrefs() { - activityCallback.setNode(null); activityCallback.onNodePrefs(); } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/NodeFragment.java b/app/src/main/java/com/m2049r/xmrwallet/NodeFragment.java index bc99c2f..b601de3 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/NodeFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/NodeFragment.java @@ -20,15 +20,6 @@ import android.content.Context; import android.content.DialogInterface; import android.os.AsyncTask; 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.LayoutInflater; import android.view.Menu; @@ -41,13 +32,23 @@ import android.widget.Button; import android.widget.TextView; 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.xmrwallet.data.DefaultNodes; import com.m2049r.xmrwallet.data.Node; import com.m2049r.xmrwallet.data.NodeInfo; import com.m2049r.xmrwallet.layout.NodeInfoAdapter; import com.m2049r.xmrwallet.model.NetworkType; import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.util.Helper; +import com.m2049r.xmrwallet.util.NodePinger; import com.m2049r.xmrwallet.util.Notice; import com.m2049r.xmrwallet.widget.Toolbar; @@ -55,6 +56,7 @@ import java.io.File; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.text.NumberFormat; +import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Set; @@ -87,7 +89,11 @@ public class NodeFragment extends Fragment Set getFavouriteNodes(); - void setFavouriteNodes(Set favouriteNodes); + Set getOrPopulateFavourites(); + + void setFavouriteNodes(Collection favouriteNodes); + + void setNode(NodeInfo node); } void filterFavourites() { @@ -156,15 +162,12 @@ public class NodeFragment extends Fragment tvPull = view.findViewById(R.id.tvPull); pullToRefresh = view.findViewById(R.id.pullToRefresh); - pullToRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { - @Override - public void onRefresh() { - if (WalletManager.getInstance().getNetworkType() == NetworkType.NetworkType_Mainnet) { - refresh(); - } else { - Toast.makeText(getActivity(), getString(R.string.node_wrong_net), Toast.LENGTH_LONG).show(); - pullToRefresh.setRefreshing(false); - } + pullToRefresh.setOnRefreshListener(() -> { + if (WalletManager.getInstance().getNetworkType() == NetworkType.NetworkType_Mainnet) { + refresh(AsyncFindNodes.SCAN); + } else { + 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); Notice.showAll(llNotice, ".*_nodes"); + refresh(AsyncFindNodes.PING); // start connection tests + return view; } private AsyncFindNodes asyncFindNodes = null; - private void refresh() { - if (asyncFindNodes != null) return; // ignore refresh request as one is ongoing + private boolean refresh(int type) { + if (asyncFindNodes != null) return false; // ignore refresh request as one is ongoing asyncFindNodes = new AsyncFindNodes(); updateRefreshElements(); - asyncFindNodes.execute(); + asyncFindNodes.execute(type); + return true; } @Override @@ -204,6 +210,19 @@ public class NodeFragment extends Fragment @Override public void onInteraction(final View view, final NodeInfo nodeItem) { 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); if (diag != null) { diag.show(); @@ -223,7 +242,12 @@ public class NodeFragment extends Fragment } } - private class AsyncFindNodes extends AsyncTask { + private class AsyncFindNodes extends AsyncTask + implements NodePinger.Listener { + final static int SCAN = 0; + final static int RESTORE_DEFAULTS = 1; + final static int PING = 2; + @Override protected void onPreExecute() { super.onPreExecute(); @@ -234,48 +258,60 @@ public class NodeFragment extends Fragment } @Override - protected Boolean doInBackground(Void... params) { - Timber.d("scanning"); - Set seedList = new HashSet<>(); - seedList.addAll(nodeList); - nodeList.clear(); - Timber.d("seed %d", seedList.size()); - Dispatcher d = new Dispatcher(new Dispatcher.Listener() { - @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); + 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); } - }); - // 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))); + } + 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"); + Set seedList = new HashSet<>(); + seedList.addAll(nodeList); + nodeList.clear(); + Timber.d("seed %d", seedList.size()); + Dispatcher d = new Dispatcher(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) + 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 - nodeList.addAll(d.getRpcNodes()); - return true; + return false; } @Override @@ -310,6 +346,10 @@ public class NodeFragment extends Fragment nodesAdapter.allowClick(true); updateRefreshElements(); } + + public void publish(NodeInfo nodeInfo) { + publishProgress(nodeInfo); + } } @Override @@ -410,11 +450,6 @@ public class NodeFragment extends Fragment NodeFragment.this.editDialog = null; } - private void undoChanges() { - if (nodeBackup != null) - nodeInfo.overwriteWith(nodeBackup); - } - private void show() { editDialog.show(); } @@ -480,12 +515,9 @@ public class NodeFragment extends Fragment .setPositiveButton(getString(R.string.label_ok), null) .setNeutralButton(getString(R.string.label_test), null) .setNegativeButton(getString(R.string.label_cancel), - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - undoChanges(); - closeDialog(); - nodesAdapter.dataSetChanged(); // to refresh test results - } + (dialog, id) -> { + closeDialog(); + nodesAdapter.dataSetChanged(); // to refresh test results }); 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(); + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java index 5af5778..87de0d8 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java +++ b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java @@ -579,10 +579,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste }); if (numAccounts != wallet.getNumAccounts()) { numAccounts = wallet.getNumAccounts(); - runOnUiThread(() -> { - if (getWallet() != null) - updateAccountsList(); - }); + runOnUiThread(this::updateAccountsList); } try { final WalletFragment walletFragment = (WalletFragment) @@ -1054,21 +1051,23 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste } void updateAccountsList() { - final Wallet wallet = getWallet(); Menu menu = accountsView.getMenu(); menu.removeGroup(R.id.accounts_list); - final int n = wallet.getNumAccounts(); - final boolean showBalances = (n > 1) && !isStreetMode(); - for (int i = 0; i < n; i++) { - final String label = (showBalances ? - getString(R.string.label_account, wallet.getAccountLabel(i), Helper.getDisplayAmount(wallet.getBalance(i), 2)) - : wallet.getAccountLabel(i)); - final MenuItem item = menu.add(R.id.accounts_list, getAccountId(i), 2 * i, label); - item.setIcon(R.drawable.ic_account_balance_wallet_black_24dp); - if (i == wallet.getAccountIndex()) - item.setChecked(true); + final Wallet wallet = getWallet(); + if (wallet != null) { + final int n = wallet.getNumAccounts(); + final boolean showBalances = (n > 1) && !isStreetMode(); + for (int i = 0; i < n; i++) { + final String label = (showBalances ? + getString(R.string.label_account, wallet.getAccountLabel(i), Helper.getDisplayAmount(wallet.getBalance(i), 2)) + : wallet.getAccountLabel(i)); + final MenuItem item = menu.add(R.id.accounts_list, getAccountId(i), 2 * i, label); + 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 diff --git a/app/src/main/java/com/m2049r/xmrwallet/data/DefaultNodes.java b/app/src/main/java/com/m2049r/xmrwallet/data/DefaultNodes.java new file mode 100644 index 0000000..42dd75d --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/data/DefaultNodes.java @@ -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; +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/data/Node.java b/app/src/main/java/com/m2049r/xmrwallet/data/Node.java index 4eb1d33..133c49c 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/data/Node.java +++ b/app/src/main/java/com/m2049r/xmrwallet/data/Node.java @@ -26,6 +26,8 @@ import java.net.URLDecoder; import java.net.URLEncoder; import java.net.UnknownHostException; +import lombok.Getter; +import lombok.Setter; import timber.log.Timber; public class Node { @@ -33,15 +35,29 @@ public class Node { static public final String STAGENET = "stagenet"; static public final String TESTNET = "testnet"; + @Getter private String name = null; + @Getter final private NetworkType networkType; InetAddress hostAddress; + @Getter private String host; + @Getter + @Setter int rpcPort = 0; private int levinPort = 0; + @Getter + @Setter private String username = ""; + @Getter + @Setter private String password = ""; + @Getter + @Setter private boolean favourite = false; + @Getter + @Setter + private boolean selected = false; @Override public int hashCode() { @@ -193,7 +209,6 @@ public class Node { this.levinPort = socketAddress.getPort(); this.username = ""; this.password = ""; - //this.name = socketAddress.getHostName(); // triggers DNS so we don't do it by default } public String getAddress() { @@ -204,14 +219,6 @@ public class Node { return hostAddress.getHostAddress(); } - public String getHost() { - return host; - } - - public int getRpcPort() { - return rpcPort; - } - public void setHost(String host) throws UnknownHostException { if ((host == null) || (host.isEmpty())) throw new UnknownHostException("loopback not supported (yet?)"); @@ -219,18 +226,6 @@ public class Node { 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() { if (name == null) this.name = hostAddress.getHostName(); @@ -243,30 +238,6 @@ public class Node { 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() { favourite = !favourite; } diff --git a/app/src/main/java/com/m2049r/xmrwallet/data/NodeInfo.java b/app/src/main/java/com/m2049r/xmrwallet/data/NodeInfo.java index 614f709..26a0e4c 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/data/NodeInfo.java +++ b/app/src/main/java/com/m2049r/xmrwallet/data/NodeInfo.java @@ -21,8 +21,8 @@ import com.burgstaller.okhttp.CachingAuthenticatorDecorator; import com.burgstaller.okhttp.digest.CachingAuthenticator; import com.burgstaller.okhttp.digest.Credentials; import com.burgstaller.okhttp.digest.DigestAuthenticator; -import com.m2049r.levin.scanner.Dispatcher; import com.m2049r.levin.scanner.LevinPeer; +import com.m2049r.xmrwallet.util.NodePinger; import com.m2049r.xmrwallet.util.OkHttpHelper; import org.json.JSONException; @@ -67,7 +67,6 @@ public class NodeInfo extends Node { try { return new NodeInfo(nodeString); } catch (IllegalArgumentException ex) { - Timber.w(ex); return null; } } @@ -145,23 +144,20 @@ public class NodeInfo extends Node { return isSuccessful() && (majorVersion >= MIN_MAJOR_VERSION) && (responseTime < Double.MAX_VALUE); } - static public Comparator BestNodeComparator = new Comparator() { - @Override - public int compare(NodeInfo o1, NodeInfo o2) { - if (o1.isValid()) { - if (o2.isValid()) { // both are valid - // higher node wins - int heightDiff = (int) (o2.height - o1.height); - if (Math.abs(heightDiff) > Dispatcher.HEIGHT_WINDOW) - return heightDiff; - // if they are (nearly) equal, faster node wins - return (int) Math.signum(o1.responseTime - o2.responseTime); - } else { - return -1; - } + static public Comparator BestNodeComparator = (o1, o2) -> { + if (o1.isValid()) { + if (o2.isValid()) { // both are valid + // higher node wins + int heightDiff = (int) (o2.height - o1.height); + if (heightDiff != 0) + return heightDiff; + // if they are equal, faster node wins + return (int) Math.signum(o1.responseTime - o2.responseTime); } else { - return 1; + return -1; } + } else { + return 1; } }; @@ -200,7 +196,15 @@ public class NodeInfo extends Node { 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) { + Timber.d("Testing %s", toNodeString()); clear(); try { OkHttpClient client = OkHttpHelper.getEagerClient(); @@ -248,7 +252,7 @@ public class NodeInfo extends Node { } } catch (IOException | JSONException ex) { // failure - Timber.d(ex.getMessage()); + Timber.d(ex); } return false; } diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/NodeInfoAdapter.java b/app/src/main/java/com/m2049r/xmrwallet/layout/NodeInfoAdapter.java index 0157464..2aeed11 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/layout/NodeInfoAdapter.java +++ b/app/src/main/java/com/m2049r/xmrwallet/layout/NodeInfoAdapter.java @@ -17,8 +17,6 @@ package com.m2049r.xmrwallet.layout; import android.content.Context; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -26,8 +24,13 @@ import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.data.NodeInfo; +import com.m2049r.xmrwallet.util.ColorHelper; +import com.m2049r.xmrwallet.util.Helper; import java.net.HttpURLConnection; import java.text.SimpleDateFormat; @@ -35,7 +38,6 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; -import java.util.Date; import java.util.List; import java.util.TimeZone; @@ -44,6 +46,8 @@ public class NodeInfoAdapter extends RecyclerView.Adapter nodeItems = new ArrayList<>(); @@ -106,7 +110,7 @@ public class NodeInfoAdapter extends RecyclerView.Adapter { + nodeItem.toggleFavourite(); + showStar(); + if (!nodeItem.isFavourite()) { + nodeItem.setSelected(false); + notifyDataSetChanged(); } }); + itemView.setOnClickListener(this); + itemView.setOnLongClickListener(this); } private void showStar() { @@ -139,15 +146,16 @@ public class NodeInfoAdapter extends RecyclerView.Adapter= STALE_NODE_HOURS) + view.setTextColor(ColorHelper.getThemedColor(view.getContext(), R.attr.colorError)); + else + view.setTextColor(ColorHelper.getThemedColor(view.getContext(), android.R.attr.textColorPrimary)); + } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/NodePinger.java b/app/src/main/java/com/m2049r/xmrwallet/util/NodePinger.java new file mode 100644 index 0000000..831f359 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/util/NodePinger.java @@ -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 nodes, final Listener listener) { + final ExecutorService exeService = Executors.newFixedThreadPool(NUM_THREADS); + List> 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(); + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/RestoreHeight.java b/app/src/main/java/com/m2049r/xmrwallet/util/RestoreHeight.java index d0d238e..0ca4582 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/RestoreHeight.java +++ b/app/src/main/java/com/m2049r/xmrwallet/util/RestoreHeight.java @@ -119,6 +119,8 @@ public class RestoreHeight { blockheight.put("2020-07-01", 2132318L); blockheight.put("2020-08-01", 2154590L); blockheight.put("2020-09-01", 2176790L); + blockheight.put("2020-10-01", 2198370L); + blockheight.put("2020-11-01", 2220670L); } public long getHeight(String date) { diff --git a/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeView.java b/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeView.java index c3ed1c4..dceb376 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeView.java +++ b/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeView.java @@ -179,7 +179,7 @@ public class ExchangeView extends LinearLayout { // make progress circle gray pbExchange.getIndeterminateDrawable(). - setColorFilter(getResources().getColor(ColorHelper.getThemedResourceId(getContext(), R.attr.colorPrimaryVariant)), + setColorFilter(ColorHelper.getThemedColor(getContext(), R.attr.colorPrimaryVariant), android.graphics.PorterDuff.Mode.MULTIPLY); sCurrencyA.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { diff --git a/app/src/main/res/drawable/ic_renew_24dp.xml b/app/src/main/res/drawable/ic_renew_24dp.xml new file mode 100644 index 0000000..adae305 --- /dev/null +++ b/app/src/main/res/drawable/ic_renew_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_search_24dp.xml b/app/src/main/res/drawable/ic_search_24dp.xml deleted file mode 100644 index 283d073..0000000 --- a/app/src/main/res/drawable/ic_search_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/selector_login.xml b/app/src/main/res/drawable/selector_login.xml index 5f81c3c..668fd24 100644 --- a/app/src/main/res/drawable/selector_login.xml +++ b/app/src/main/res/drawable/selector_login.xml @@ -6,6 +6,12 @@ + + + + + + diff --git a/app/src/main/res/layout/fragment_login.xml b/app/src/main/res/layout/fragment_login.xml index c668aaa..7e65c36 100644 --- a/app/src/main/res/layout/fragment_login.xml +++ b/app/src/main/res/layout/fragment_login.xml @@ -37,7 +37,7 @@ android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_marginStart="16dp" - android:layout_toStartOf="@+id/ibOption"> + android:layout_toStartOf="@+id/ibRenew"> @@ -73,7 +73,7 @@ + android:src="@drawable/ic_renew_24dp" /> + + \ No newline at end of file diff --git a/app/src/main/res/values-cat/strings.xml b/app/src/main/res/values-cat/strings.xml index ebce1cf..580a34c 100644 --- a/app/src/main/res/values-cat/strings.xml +++ b/app/src/main/res/values-cat/strings.xml @@ -399,4 +399,11 @@ There is nothing here\nPlease create or restore a wallet + Restore default nodes + Restoring already in progress… + + Last Block: %1$d seconds ago + Last Block: %1$d minutes ago + Last Block: %1$d hours ago + Last Block: %1$d days ago diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index dfad342..704bfb1 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -402,4 +402,11 @@ There is nothing here\nPlease create or restore a wallet + Restore default nodes + Restoring already in progress… + + Last Block: %1$d seconds ago + Last Block: %1$d minutes ago + Last Block: %1$d hours ago + Last Block: %1$d days ago diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 6e7117c..871cc0a 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -401,4 +401,11 @@ There is nothing here\nPlease create or restore a wallet + Restore default nodes + Restoring already in progress… + + Last Block: %1$d seconds ago + Last Block: %1$d minutes ago + Last Block: %1$d hours ago + Last Block: %1$d days ago diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index 0b71a9c..8c064d2 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -401,4 +401,11 @@ There is nothing here\nPlease create or restore a wallet + Restore default nodes + Restoring already in progress… + + Last Block: %1$d seconds ago + Last Block: %1$d minutes ago + Last Block: %1$d hours ago + Last Block: %1$d days ago diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 66cfe35..bb30b71 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -392,4 +392,11 @@ There is nothing here\nPlease create or restore a wallet + Restore default nodes + Restoring already in progress… + + Last Block: %1$d seconds ago + Last Block: %1$d minutes ago + Last Block: %1$d hours ago + Last Block: %1$d days ago diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 70fbdec..1cd2f8e 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -399,4 +399,11 @@ There is nothing here\nPlease create or restore a wallet + Restore default nodes + Restoring already in progress… + + Last Block: %1$d seconds ago + Last Block: %1$d minutes ago + Last Block: %1$d hours ago + Last Block: %1$d days ago diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 0a5ab4a..e9c4582 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -405,4 +405,11 @@ There is nothing here\nPlease create or restore a wallet + Restore default nodes + Restoring already in progress… + + Last Block: %1$d seconds ago + Last Block: %1$d minutes ago + Last Block: %1$d hours ago + Last Block: %1$d days ago diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index df20451..ab70199 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -403,4 +403,11 @@ There is nothing here\nPlease create or restore a wallet + Restore default nodes + Restoring already in progress… + + Last Block: %1$d seconds ago + Last Block: %1$d minutes ago + Last Block: %1$d hours ago + Last Block: %1$d days ago diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 45cbc4a..576b1b4 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -404,4 +404,11 @@ There is nothing here\nPlease create or restore a wallet + Restore default nodes + Restoring already in progress… + + Last Block: %1$d seconds ago + Last Block: %1$d minutes ago + Last Block: %1$d hours ago + Last Block: %1$d days ago diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 22d1478..c299282 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -410,4 +410,11 @@ There is nothing here\nPlease create or restore a wallet + Restore default nodes + Restoring already in progress… + + Last Block: %1$d seconds ago + Last Block: %1$d minutes ago + Last Block: %1$d hours ago + Last Block: %1$d days ago diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index ffe0c96..50739f6 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -401,4 +401,11 @@ There is nothing here\nPlease create or restore a wallet + Restore default nodes + Restoring already in progress… + + Last Block: %1$d seconds ago + Last Block: %1$d minutes ago + Last Block: %1$d hours ago + Last Block: %1$d days ago diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 938c664..54d5cbc 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -11,6 +11,7 @@ #FF6252 #27C79C #FF1A80 + #20FF1A80 #2D1A2E diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 1da1103..0a43347 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -401,4 +401,11 @@ There is nothing here\nPlease create or restore a wallet + Restore default nodes + Restoring already in progress… + + Last Block: %1$d seconds ago + Last Block: %1$d minutes ago + Last Block: %1$d hours ago + Last Block: %1$d days ago diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 73c8fbd..d582dc2 100755 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -393,4 +393,11 @@ There is nothing here\nPlease create or restore a wallet + Restore default nodes + Restoring already in progress… + + Last Block: %1$d seconds ago + Last Block: %1$d minutes ago + Last Block: %1$d hours ago + Last Block: %1$d days ago diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 341284f..238ade9 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -405,4 +405,11 @@ There is nothing here\nPlease create or restore a wallet + Restore default nodes + Restoring already in progress… + + Last Block: %1$d seconds ago + Last Block: %1$d minutes ago + Last Block: %1$d hours ago + Last Block: %1$d days ago diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 7f1ea26..4f083dd 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -401,4 +401,11 @@ There is nothing here\nPlease create or restore a wallet + Restore default nodes + Restoring already in progress… + + Last Block: %1$d seconds ago + Last Block: %1$d minutes ago + Last Block: %1$d hours ago + Last Block: %1$d days ago diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 94a08d2..e15a71f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -405,4 +405,11 @@ There is nothing here\nPlease create or restore a wallet + Restore default nodes + Restoring already in progress… + + Last Block: %1$d seconds ago + Last Block: %1$d minutes ago + Last Block: %1$d hours ago + Last Block: %1$d days ago diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 653e260..18040c5 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -402,4 +402,11 @@ There is nothing here\nPlease create or restore a wallet + Restore default nodes + Restoring already in progress… + + Last Block: %1$d seconds ago + Last Block: %1$d minutes ago + Last Block: %1$d hours ago + Last Block: %1$d days ago diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 912a95a..1d91687 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -400,4 +400,11 @@ There is nothing here\nPlease create or restore a wallet + Restore default nodes + Restoring already in progress… + + Last Block: %1$d seconds ago + Last Block: %1$d minutes ago + Last Block: %1$d hours ago + Last Block: %1$d days ago diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 072e5d1..46da770 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -385,4 +385,11 @@ There is nothing here\nPlease create or restore a wallet + Restore default nodes + Restoring already in progress… + + Last Block: %1$d seconds ago + Last Block: %1$d minutes ago + Last Block: %1$d hours ago + Last Block: %1$d days ago diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 84ed96f..58cc46d 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -405,4 +405,11 @@ There is nothing here\nPlease create or restore a wallet + Restore default nodes + Restoring already in progress… + + Last Block: %1$d seconds ago + Last Block: %1$d minutes ago + Last Block: %1$d hours ago + Last Block: %1$d days ago diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 9e8ae5e..5f3c3d8 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -324,4 +324,11 @@ There is nothing here\nPlease create or restore a wallet + Restore default nodes + Restoring already in progress… + + Last Block: %1$d seconds ago + Last Block: %1$d minutes ago + Last Block: %1$d hours ago + Last Block: %1$d days ago diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 8d5a6d2..dd41ad2 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -400,4 +400,11 @@ There is nothing here\nPlease create or restore a wallet + Restore default nodes + Restoring already in progress… + + Last Block: %1$d seconds ago + Last Block: %1$d minutes ago + Last Block: %1$d hours ago + Last Block: %1$d days ago diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 6617d74..bb315d0 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -14,6 +14,7 @@ #FA5544 #00C691 #F0006B + #20F0006B #FFFDFB @color/monerujoGreen diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4c1fd87..7f58bf4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -452,4 +452,11 @@ There is nothing here\nPlease create or restore a wallet + Restore default nodes + Restoring already in progress… + + Last Block: %1$d seconds ago + Last Block: %1$d minutes ago + Last Block: %1$d hours ago + Last Block: %1$d days ago diff --git a/build.gradle b/build.gradle index c90efbe..dfd7a36 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' + classpath 'com.android.tools.build:gradle:4.1.1' } } diff --git a/external-libs/script/monero-fetch.sh b/external-libs/script/monero-fetch.sh index 1cb2ed7..fbe4bc2 100755 --- a/external-libs/script/monero-fetch.sh +++ b/external-libs/script/monero-fetch.sh @@ -7,7 +7,7 @@ source script/env.sh cd $EXTERNAL_LIBS_BUILD_ROOT url="https://github.com/m2049r/monero" -version="release-v0.17.1.1-monerujo" +version="release-v0.17.1.3-monerujo" if [ ! -d "monero" ]; then git clone ${url} -b ${version}