From 465713206736b898d31ad8e8199a90a11572c2b7 Mon Sep 17 00:00:00 2001 From: pokkst Date: Sat, 17 Sep 2022 04:14:15 -0500 Subject: [PATCH] Add ability to add custom nodes --- .../adapter/NodeSelectionAdapter.java | 115 ++++++++ .../m2049r/xmrwallet/data/DefaultNodes.java | 31 +- .../java/com/m2049r/xmrwallet/data/Node.java | 41 +-- .../dialog/AddNodeBottomSheetDialog.java | 80 +++++ .../NodeSelectionBottomSheetDialog.java | 89 ++++++ .../fragment/settings/SettingsFragment.java | 43 ++- .../fragment/settings/SettingsViewModel.java | 7 +- .../xmrwallet/service/BlockchainService.java | 7 + .../service/MoneroHandlerThread.java | 11 +- .../com/m2049r/xmrwallet/util/Constants.java | 2 + .../layout/add_node_bottom_sheet_dialog.xml | 84 ++++++ app/src/main/res/layout/fragment_settings.xml | 278 ++++++++++-------- .../node_selection_bottom_sheet_dialog.xml | 54 ++++ .../main/res/layout/node_selection_item.xml | 31 ++ app/src/main/res/values/strings.xml | 8 + 15 files changed, 702 insertions(+), 179 deletions(-) create mode 100644 app/src/main/java/com/m2049r/xmrwallet/adapter/NodeSelectionAdapter.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/AddNodeBottomSheetDialog.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/NodeSelectionBottomSheetDialog.java create mode 100644 app/src/main/res/layout/add_node_bottom_sheet_dialog.xml create mode 100644 app/src/main/res/layout/node_selection_bottom_sheet_dialog.xml create mode 100644 app/src/main/res/layout/node_selection_item.xml diff --git a/app/src/main/java/com/m2049r/xmrwallet/adapter/NodeSelectionAdapter.java b/app/src/main/java/com/m2049r/xmrwallet/adapter/NodeSelectionAdapter.java new file mode 100644 index 0000000..87ff106 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/adapter/NodeSelectionAdapter.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2017 m2049r + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet.adapter; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import com.m2049r.xmrwallet.R; +import com.m2049r.xmrwallet.data.DefaultNodes; +import com.m2049r.xmrwallet.data.Node; +import com.m2049r.xmrwallet.model.TransactionInfo; +import com.m2049r.xmrwallet.service.PrefService; +import com.m2049r.xmrwallet.util.Constants; + +import java.util.ArrayList; +import java.util.List; + +public class NodeSelectionAdapter extends RecyclerView.Adapter { + + private List localDataSet; + private NodeSelectionAdapterListener listener = null; + + /** + * Initialize the dataset of the Adapter. + */ + public NodeSelectionAdapter(NodeSelectionAdapterListener listener) { + this.listener = listener; + this.localDataSet = new ArrayList<>(); + } + + public void submitList(List dataSet) { + this.localDataSet = dataSet; + notifyDataSetChanged(); + } + + public void updateSelectedNode() { + notifyDataSetChanged(); + } + + // Create new views (invoked by the layout manager) + @Override + public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { + // Create a new view, which defines the UI of the list item + View view = LayoutInflater.from(viewGroup.getContext()) + .inflate(R.layout.node_selection_item, viewGroup, false); + + return new ViewHolder(listener, view); + } + + // Replace the contents of a view (invoked by the layout manager) + @Override + public void onBindViewHolder(ViewHolder viewHolder, final int position) { + Node node = localDataSet.get(position); + viewHolder.bind(node); + } + + // Return the size of your dataset (invoked by the layout manager) + @Override + public int getItemCount() { + return localDataSet.size(); + } + + public interface NodeSelectionAdapterListener { + void onSelectNode(Node node); + } + + /** + * Provide a reference to the type of views that you are using + * (custom ViewHolder). + */ + public static class ViewHolder extends RecyclerView.ViewHolder { + private final NodeSelectionAdapterListener listener; + public ViewHolder(NodeSelectionAdapterListener listener, View view) { + super(view); + this.listener = listener; + } + + public void bind(Node node) { + String currentNodeString = PrefService.getInstance().getString(Constants.PREF_NODE, DefaultNodes.XMRTW.getAddress()); + Node currentNode = Node.fromString(currentNodeString); + boolean match = node.equals(currentNode); + if(match) { + itemView.setBackgroundColor(itemView.getResources().getColor(R.color.oled_colorSecondary)); + } else { + itemView.setBackgroundColor(itemView.getResources().getColor(android.R.color.transparent)); + + } + TextView nodeNameTextView = itemView.findViewById(R.id.node_name_textview); + TextView nodeAddressTextView = itemView.findViewById(R.id.node_uri_textview); + nodeNameTextView.setText(node.getName()); + nodeAddressTextView.setText(node.getAddress()); + + itemView.setOnClickListener(view -> listener.onSelectNode(node)); + } + } +} + diff --git a/app/src/main/java/com/m2049r/xmrwallet/data/DefaultNodes.java b/app/src/main/java/com/m2049r/xmrwallet/data/DefaultNodes.java index 3fe1306..1e1d639 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/data/DefaultNodes.java +++ b/app/src/main/java/com/m2049r/xmrwallet/data/DefaultNodes.java @@ -16,25 +16,34 @@ 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"), + MONERUJO("nodex.monerujo.io:18081/mainnet/monerujo"), + SUPPORTXMR("node.supportxmr.com:18081/mainnet/SupportXMR"), + HASHVAULT("nodes.hashvault.pro:18081/mainnet/Hashvault"), + MONEROWORLD("node.moneroworld.com:18089/mainnet/MoneroWorld"), + XMRTW("opennode.xmr-tw.org:18089/mainnet/XMRTW"), MONERUJO_ONION("monerujods7mbghwe6cobdr6ujih6c22zu5rl7zshmizz2udf7v7fsad.onion:18081/mainnet/monerujo.onion"), Criminales78("56wl7y2ebhamkkiza4b7il4mrzwtyvpdym7bm2bkg3jrei2je646k3qd.onion:18089/mainnet/Criminales78.onion"), xmrfail("mxcd4577fldb3ppzy7obmmhnu3tf57gbcbd4qhwr2kxyjj2qi3dnbfqd.onion:18081/mainnet/xmrfail.onion"), boldsuck("6dsdenp6vjkvqzy4wzsnzn6wixkdzihx3khiumyzieauxuxslmcaeiad.onion:18081/mainnet/boldsuck.onion"), SAMOURAI("446unwib5vc7pfbzflosy6m6vtyuhddnalr3hutyavwe4esfuu5g6ryd.onion:18089/mainnet/samourai.onion"); - @Getter private final String uri; + DefaultNodes(String uri) { + this.uri = uri; + } + + public String getUri() { + return uri; + } + + public String getAddress() { + return uri.split("/")[0]; + } + + public String getName() { + return uri.split("/")[2]; + } } 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 e0eca15..22dcea8 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/data/Node.java +++ b/app/src/main/java/com/m2049r/xmrwallet/data/Node.java @@ -42,7 +42,6 @@ public class Node { @Getter @Setter private final boolean selected = false; - Address hostAddress; @Getter @Setter int rpcPort = 0; @@ -151,7 +150,6 @@ public class Node { // constructor used for created nodes from retrieved peer lists public Node(InetSocketAddress socketAddress) { this(); - this.hostAddress = Address.of(socketAddress.getAddress()); this.host = socketAddress.getHostString(); this.rpcPort = 0; // unknown this.levinPort = socketAddress.getPort(); @@ -213,7 +211,7 @@ public class Node { @Override public int hashCode() { - return hostAddress.hashCode(); + return host.hashCode(); } // Nodes are equal if they are the same host address:port & are on the same network @@ -221,13 +219,14 @@ public class Node { public boolean equals(Object other) { if (!(other instanceof Node)) return false; final Node anotherNode = (Node) other; - return (hostAddress.equals(anotherNode.hostAddress) + return (host.equals(anotherNode.host) + && (getAddress().equals(anotherNode.getAddress())) && (rpcPort == anotherNode.rpcPort) && (networkType == anotherNode.networkType)); } public boolean isOnion() { - return hostAddress.isOnion(); + return OnionHelper.isOnionHost(host); } public String toNodeString() { @@ -263,49 +262,23 @@ public class Node { } public String getAddress() { - return getHostAddress() + ":" + rpcPort; + return getHost() + ":" + rpcPort; } - public String getHostAddress() { - return hostAddress.getHostAddress(); + public String getHost() { + return host; } public void setHost(String host) throws UnknownHostException { if ((host == null) || (host.isEmpty())) throw new UnknownHostException("loopback not supported (yet?)"); this.host = host; - this.hostAddress = Address.of(host); - } - - public void setDefaultName() { - if (name != null) return; - String nodeName = hostAddress.getHostName(); - if (hostAddress.isOnion()) { - nodeName = nodeName.substring(0, nodeName.length() - ".onion".length()); - if (nodeName.length() > 16) { - nodeName = nodeName.substring(0, 8) + "…" + nodeName.substring(nodeName.length() - 6); - } - nodeName = nodeName + ".onion"; - } - this.name = nodeName; - } - - public void setName(String name) { - if ((name == null) || (name.isEmpty())) - setDefaultName(); - else - this.name = name; - } - - public void toggleFavourite() { - favourite = !favourite; } public void overwriteWith(Node anotherNode) { if (networkType != anotherNode.networkType) throw new IllegalStateException("network types do not match"); name = anotherNode.name; - hostAddress = anotherNode.hostAddress; host = anotherNode.host; rpcPort = anotherNode.rpcPort; levinPort = anotherNode.levinPort; diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/AddNodeBottomSheetDialog.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/AddNodeBottomSheetDialog.java new file mode 100644 index 0000000..75d2838 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/AddNodeBottomSheetDialog.java @@ -0,0 +1,80 @@ +package com.m2049r.xmrwallet.fragment.dialog; + +import android.os.Bundle; +import android.util.Patterns; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.m2049r.xmrwallet.R; +import com.m2049r.xmrwallet.service.PrefService; +import com.m2049r.xmrwallet.util.Constants; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +public class AddNodeBottomSheetDialog extends BottomSheetDialogFragment { + public AddNodeListener listener = null; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.add_node_bottom_sheet_dialog, null); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + Button addNodeButton = view.findViewById(R.id.add_node_button); + EditText addressEditText = view.findViewById(R.id.address_edittext); + EditText nodeNameEditText = view.findViewById(R.id.node_name_edittext); + + addNodeButton.setOnClickListener(view1 -> { + String node = addressEditText.getText().toString(); + String name = nodeNameEditText.getText().toString(); + if(node.contains(":") && !name.isEmpty()) { + String[] nodeParts = node.split(":"); + if(nodeParts.length == 2) { + try { + String address = nodeParts[0]; + int port = Integer.parseInt(nodeParts[1]); + String newNodeString = address + ":" + port + "/mainnet/" + name; + boolean validIp = Patterns.IP_ADDRESS.matcher(address).matches(); + if(validIp) { + String nodesArray = PrefService.getInstance().getString(Constants.PREF_CUSTOM_NODES, "[]"); + JSONArray jsonArray = new JSONArray(nodesArray); + boolean exists = false; + for(int i = 0; i < jsonArray.length(); i++) { + String nodeString = jsonArray.getString(i); + if(nodeString.equals(newNodeString)) + exists = true; + } + + if(!exists) { + jsonArray.put(newNodeString); + } + + PrefService.getInstance().edit().putString(Constants.PREF_CUSTOM_NODES, jsonArray.toString()).apply(); + if(listener != null) { + listener.onNodeAdded(); + } + dismiss(); + } + } catch(NumberFormatException | JSONException e) { + e.printStackTrace(); + } + } + } + }); + } + + public interface AddNodeListener { + void onNodeAdded(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/NodeSelectionBottomSheetDialog.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/NodeSelectionBottomSheetDialog.java new file mode 100644 index 0000000..f0ad53e --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/NodeSelectionBottomSheetDialog.java @@ -0,0 +1,89 @@ +package com.m2049r.xmrwallet.fragment.dialog; + +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageButton; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.m2049r.xmrwallet.R; +import com.m2049r.xmrwallet.adapter.NodeSelectionAdapter; +import com.m2049r.xmrwallet.data.DefaultNodes; +import com.m2049r.xmrwallet.data.Node; +import com.m2049r.xmrwallet.model.TransactionInfo; +import com.m2049r.xmrwallet.model.WalletManager; +import com.m2049r.xmrwallet.service.PrefService; +import com.m2049r.xmrwallet.util.Constants; +import com.m2049r.xmrwallet.util.Helper; + +import org.json.JSONArray; +import org.json.JSONException; + +import java.util.ArrayList; +import java.util.Collections; + +public class NodeSelectionBottomSheetDialog extends BottomSheetDialogFragment implements NodeSelectionAdapter.NodeSelectionAdapterListener { + private NodeSelectionAdapter adapter = null; + public NodeSelectionDialogListener listener = null; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.node_selection_bottom_sheet_dialog, null); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + ArrayList nodes = new ArrayList<>(); + adapter = new NodeSelectionAdapter(this); + + RecyclerView recyclerView = view.findViewById(R.id.node_selection_recyclerview); + recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); + recyclerView.setAdapter(adapter); + + Button addNodeButton = view.findViewById(R.id.add_node_button); + addNodeButton.setOnClickListener(view1 -> { + if(listener != null) { + listener.onClickedAddNode(); + } + dismiss(); + }); + + for(DefaultNodes defaultNode : DefaultNodes.values()) { + nodes.add(Node.fromString(defaultNode.getUri())); + } + try { + String nodesArray = PrefService.getInstance().getString(Constants.PREF_CUSTOM_NODES, "[]"); + JSONArray jsonArray = new JSONArray(nodesArray); + for(int i = 0; i < jsonArray.length(); i++) { + String nodeString = jsonArray.getString(i); + Node node = Node.fromString(nodeString); + nodes.add(node); + } + } catch (JSONException e) { + e.printStackTrace(); + } + + adapter.submitList(nodes); + } + + @Override + public void onSelectNode(Node node) { + PrefService.getInstance().edit().putString(Constants.PREF_NODE, node.getAddress()).apply(); + WalletManager.getInstance().setDaemon(node); + adapter.updateSelectedNode(); + } + + public interface NodeSelectionDialogListener { + void onClickedAddNode(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/settings/SettingsFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/settings/SettingsFragment.java index 32f681d..22605b9 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/settings/SettingsFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/settings/SettingsFragment.java @@ -9,6 +9,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; +import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; @@ -16,18 +17,25 @@ import androidx.annotation.Nullable; import androidx.appcompat.widget.SwitchCompat; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; import com.m2049r.xmrwallet.R; +import com.m2049r.xmrwallet.data.DefaultNodes; +import com.m2049r.xmrwallet.data.Node; +import com.m2049r.xmrwallet.fragment.dialog.AddNodeBottomSheetDialog; import com.m2049r.xmrwallet.fragment.dialog.InformationBottomSheetDialog; +import com.m2049r.xmrwallet.fragment.dialog.NodeSelectionBottomSheetDialog; import com.m2049r.xmrwallet.fragment.dialog.PasswordBottomSheetDialog; +import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.WalletManager; +import com.m2049r.xmrwallet.service.BlockchainService; import com.m2049r.xmrwallet.service.PrefService; import com.m2049r.xmrwallet.util.Constants; import com.m2049r.xmrwallet.util.DayNightMode; import com.m2049r.xmrwallet.util.NightmodeHelper; -public class SettingsFragment extends Fragment implements PasswordBottomSheetDialog.PasswordListener { +public class SettingsFragment extends Fragment implements PasswordBottomSheetDialog.PasswordListener, NodeSelectionBottomSheetDialog.NodeSelectionDialogListener, AddNodeBottomSheetDialog.AddNodeListener { private SettingsViewModel mViewModel; TextWatcher proxyAddressListener = new TextWatcher() { @@ -64,6 +72,7 @@ public class SettingsFragment extends Fragment implements PasswordBottomSheetDia super.onViewCreated(view, savedInstanceState); mViewModel = new ViewModelProvider(this).get(SettingsViewModel.class); Button displaySeedButton = view.findViewById(R.id.display_seed_button); + Button selectNodeButton = view.findViewById(R.id.select_node_button); SwitchCompat nightModeSwitch = view.findViewById(R.id.day_night_switch); SwitchCompat torSwitch = view.findViewById(R.id.tor_switch); ConstraintLayout proxySettingsLayout = view.findViewById(R.id.wallet_proxy_settings_layout); @@ -126,6 +135,24 @@ public class SettingsFragment extends Fragment implements PasswordBottomSheetDia displaySeedDialog(); } }); + + TextView statusTextView = view.findViewById(R.id.status_textview); + BlockchainService.getInstance().connectionStatus.observe(getViewLifecycleOwner(), connectionStatus -> { + if(connectionStatus == Wallet.ConnectionStatus.ConnectionStatus_Connected) { + statusTextView.setText(getResources().getText(R.string.connected)); + } else if(connectionStatus == Wallet.ConnectionStatus.ConnectionStatus_Disconnected) { + statusTextView.setText(getResources().getText(R.string.disconnected)); + } else if(connectionStatus == Wallet.ConnectionStatus.ConnectionStatus_WrongVersion) { + statusTextView.setText(getResources().getText(R.string.version_mismatch)); + } + }); + Node node = Node.fromString(PrefService.getInstance().getString(Constants.PREF_NODE, DefaultNodes.XMRTW.getAddress())); + selectNodeButton.setText(getString(R.string.node_button_text, node.getAddress())); + selectNodeButton.setOnClickListener(view1 -> { + NodeSelectionBottomSheetDialog dialog = new NodeSelectionBottomSheetDialog(); + dialog.listener = this; + dialog.show(getActivity().getSupportFragmentManager(), "node_selection_dialog"); + }); } private void displaySeedDialog() { @@ -164,4 +191,18 @@ public class SettingsFragment extends Fragment implements PasswordBottomSheetDia walletProxyAddressEditText.addTextChangedListener(proxyAddressListener); walletProxyPortEditText.addTextChangedListener(proxyPortListener); } + + @Override + public void onClickedAddNode() { + AddNodeBottomSheetDialog addNodeDialog = new AddNodeBottomSheetDialog(); + addNodeDialog.listener = this; + addNodeDialog.show(getActivity().getSupportFragmentManager(), "add_node_dialog"); + } + + @Override + public void onNodeAdded() { + NodeSelectionBottomSheetDialog dialog = new NodeSelectionBottomSheetDialog(); + dialog.listener = this; + dialog.show(getActivity().getSupportFragmentManager(), "node_selection_dialog"); + } } \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/settings/SettingsViewModel.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/settings/SettingsViewModel.java index 729206a..8a657a6 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/settings/SettingsViewModel.java +++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/settings/SettingsViewModel.java @@ -8,6 +8,7 @@ import android.widget.Toast; import androidx.lifecycle.ViewModel; import com.m2049r.xmrwallet.R; +import com.m2049r.xmrwallet.data.DefaultNodes; import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.service.PrefService; import com.m2049r.xmrwallet.service.TxService; @@ -20,7 +21,10 @@ public class SettingsViewModel extends ViewModel { public void updateProxy() { AsyncTask.execute(() -> { boolean usesProxy = PrefService.getInstance().getBoolean(Constants.PREF_USES_TOR, false); - if(!usesProxy) { + String currentNodeString = PrefService.getInstance().getString(Constants.PREF_NODE, DefaultNodes.XMRTW.getAddress()); + boolean isNodeLocalIp = currentNodeString.startsWith("10.") || currentNodeString.startsWith("192.168.") || currentNodeString.equals("localhost") || currentNodeString.equals("127.0.0.1"); + + if(!usesProxy || isNodeLocalIp) { WalletManager.getInstance().setProxy(""); WalletManager.getInstance().getWallet().setProxy(""); return; @@ -29,6 +33,7 @@ public class SettingsViewModel extends ViewModel { if(proxyAddress.isEmpty()) proxyAddress = "127.0.0.1"; if(proxyPort.isEmpty()) proxyPort = "9050"; boolean validIpAddress = Patterns.IP_ADDRESS.matcher(proxyAddress).matches(); + if(validIpAddress) { String proxy = proxyAddress + ":" + proxyPort; PrefService.getInstance().edit().putString(Constants.PREF_PROXY, proxy).apply(); diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/BlockchainService.java b/app/src/main/java/com/m2049r/xmrwallet/service/BlockchainService.java index ccf8680..0a6bc20 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/service/BlockchainService.java +++ b/app/src/main/java/com/m2049r/xmrwallet/service/BlockchainService.java @@ -3,12 +3,15 @@ package com.m2049r.xmrwallet.service; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; +import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.WalletManager; public class BlockchainService extends ServiceBase { public static BlockchainService instance = null; private final MutableLiveData _currentHeight = new MutableLiveData<>(0L); public LiveData height = _currentHeight; + private final MutableLiveData _connectionStatus = new MutableLiveData<>(Wallet.ConnectionStatus.ConnectionStatus_Disconnected); + public LiveData connectionStatus = _connectionStatus; private long daemonHeight = 0; private long lastDaemonHeightUpdateTimeMs = 0; public BlockchainService(MoneroHandlerThread thread) { @@ -44,4 +47,8 @@ public class BlockchainService extends ServiceBase { } } } + + public void setConnectionStatus(Wallet.ConnectionStatus status) { + _connectionStatus.postValue(status); + } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/MoneroHandlerThread.java b/app/src/main/java/com/m2049r/xmrwallet/service/MoneroHandlerThread.java index 8be796d..ce53dcb 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/service/MoneroHandlerThread.java +++ b/app/src/main/java/com/m2049r/xmrwallet/service/MoneroHandlerThread.java @@ -55,15 +55,16 @@ public class MoneroHandlerThread extends Thread implements WalletListener { @Override public void run() { + String currentNodeString = PrefService.getInstance().getString(Constants.PREF_NODE, DefaultNodes.XMRTW.getAddress()); + Node selectedNode = Node.fromString(currentNodeString); boolean usesTor = PrefService.getInstance().getBoolean(Constants.PREF_USES_TOR, false); - if (usesTor) { + boolean isLocalIp = currentNodeString.startsWith("10.") || currentNodeString.startsWith("192.168.") || currentNodeString.equals("localhost") || currentNodeString.equals("127.0.0.1"); + if (usesTor && !isLocalIp) { String proxy = PrefService.getInstance().getString(Constants.PREF_PROXY, ""); WalletManager.getInstance().setProxy(proxy); - WalletManager.getInstance().setDaemon(Node.fromString(DefaultNodes.SAMOURAI.getUri())); wallet.setProxy(proxy); - } else { - WalletManager.getInstance().setDaemon(Node.fromString(DefaultNodes.XMRTW.getUri())); } + WalletManager.getInstance().setDaemon(selectedNode); wallet.init(0); wallet.setListener(this); wallet.startRefresh(); @@ -108,6 +109,8 @@ public class MoneroHandlerThread extends Thread implements WalletListener { wallet.store(); refresh(); } + + BlockchainService.getInstance().setConnectionStatus(status); } private void refresh() { diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/Constants.java b/app/src/main/java/com/m2049r/xmrwallet/util/Constants.java index 114cd4e..7295ad9 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/Constants.java +++ b/app/src/main/java/com/m2049r/xmrwallet/util/Constants.java @@ -7,6 +7,8 @@ public class Constants { public static final String PREF_USES_TOR = "pref_uses_tor"; public static final String PREF_NIGHT_MODE = "pref_night_mode"; public static final String PREF_PROXY = "pref_proxy"; + public static final String PREF_NODE = "pref_node"; + public static final String PREF_CUSTOM_NODES = "pref_custom_nodes"; public static final String URI_PREFIX = "monero:"; public static final String URI_ARG_AMOUNT = "tx_amount"; diff --git a/app/src/main/res/layout/add_node_bottom_sheet_dialog.xml b/app/src/main/res/layout/add_node_bottom_sheet_dialog.xml new file mode 100644 index 0000000..5d83a3b --- /dev/null +++ b/app/src/main/res/layout/add_node_bottom_sheet_dialog.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + +