From 480cbd11f168ef0c893aa8f017f28618f3c9a392 Mon Sep 17 00:00:00 2001 From: pokkst Date: Thu, 15 Dec 2022 11:01:23 -0600 Subject: [PATCH] Add user/pass to node settings, cleanup node parsing, fix quite a few bugs --- .../wallet/adapter/NodeSelectionAdapter.java | 5 +- .../java/net/mynero/wallet/data/Node.java | 4 +- .../dialog/AddNodeBottomSheetDialog.java | 130 +++++++++++------- .../dialog/EditNodeBottomSheetDialog.java | 87 ++++++++++-- .../fragment/receive/ReceiveViewModel.java | 9 +- .../wallet/service/MoneroHandlerThread.java | 16 +-- .../layout/add_node_bottom_sheet_dialog.xml | 73 +++++++++- app/src/main/res/layout/address_item.xml | 4 +- .../layout/edit_node_bottom_sheet_dialog.xml | 77 ++++++++++- app/src/main/res/layout/fragment_receive.xml | 2 - .../node_selection_bottom_sheet_dialog.xml | 3 + .../wallet_keys_bottom_sheet_dialog.xml | 6 + app/src/main/res/values/strings.xml | 2 + 13 files changed, 330 insertions(+), 88 deletions(-) diff --git a/app/src/main/java/net/mynero/wallet/adapter/NodeSelectionAdapter.java b/app/src/main/java/net/mynero/wallet/adapter/NodeSelectionAdapter.java index fe59a6e..6fb2bf4 100644 --- a/app/src/main/java/net/mynero/wallet/adapter/NodeSelectionAdapter.java +++ b/app/src/main/java/net/mynero/wallet/adapter/NodeSelectionAdapter.java @@ -109,7 +109,6 @@ public class NodeSelectionAdapter extends RecyclerView.Adapter { if(match) { Toast.makeText(itemView.getContext(), itemView.getResources().getString(R.string.cant_edit_current_node), Toast.LENGTH_SHORT).show(); - return false; + return true; } else if(isDefaultNode(node)) { Toast.makeText(itemView.getContext(), itemView.getResources().getString(R.string.cant_edit_default_nodes), Toast.LENGTH_SHORT).show(); - return false; + return true; } else { return listener.onSelectEditNode(node); } diff --git a/app/src/main/java/net/mynero/wallet/data/Node.java b/app/src/main/java/net/mynero/wallet/data/Node.java index bf5d93b..5a718ff 100644 --- a/app/src/main/java/net/mynero/wallet/data/Node.java +++ b/app/src/main/java/net/mynero/wallet/data/Node.java @@ -223,7 +223,9 @@ public class Node { return (host.equals(anotherNode.host) && (getAddress().equals(anotherNode.getAddress())) && (rpcPort == anotherNode.rpcPort) - && (networkType == anotherNode.networkType)); + && (networkType == anotherNode.networkType)) + && (username.equals(anotherNode.getUsername())) + && (password.equals(anotherNode.getPassword())); } public boolean isOnion() { diff --git a/app/src/main/java/net/mynero/wallet/fragment/dialog/AddNodeBottomSheetDialog.java b/app/src/main/java/net/mynero/wallet/fragment/dialog/AddNodeBottomSheetDialog.java index 69b3129..ed2cf3f 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/dialog/AddNodeBottomSheetDialog.java +++ b/app/src/main/java/net/mynero/wallet/fragment/dialog/AddNodeBottomSheetDialog.java @@ -2,6 +2,8 @@ package net.mynero.wallet.fragment.dialog; import android.content.Context; import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; import android.util.Patterns; import android.view.LayoutInflater; import android.view.View; @@ -9,6 +11,7 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; import android.widget.ImageButton; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -16,6 +19,7 @@ import androidx.annotation.Nullable; import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import net.mynero.wallet.R; +import net.mynero.wallet.data.Node; import net.mynero.wallet.service.PrefService; import net.mynero.wallet.util.Constants; import net.mynero.wallet.util.Helper; @@ -37,67 +41,89 @@ public class AddNodeBottomSheetDialog extends BottomSheetDialogFragment { 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); - ImageButton pasteAddressImageButton = view.findViewById(R.id.paste_address_imagebutton); - pasteAddressImageButton.setOnClickListener(view1 -> { - Context ctx = getContext(); - if (ctx != null) { - addressEditText.setText(Helper.getClipBoardText(ctx)); + EditText usernameEditText = view.findViewById(R.id.username_edittext); + EditText passwordEditText = view.findViewById(R.id.password_edittext); + ImageButton pastePasswordImageButton = view.findViewById(R.id.paste_password_imagebutton); + + usernameEditText.addTextChangedListener(new TextWatcher() { + @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {} + @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {} + @Override + public void afterTextChanged(Editable editable) { + if(editable.toString().isEmpty()) { + passwordEditText.setText(null); + passwordEditText.setVisibility(View.GONE); + pastePasswordImageButton.setVisibility(View.GONE); + } else { + passwordEditText.setVisibility(View.VISIBLE); + pastePasswordImageButton.setVisibility(View.VISIBLE); + } } }); + + addPasteListener(view, addressEditText, R.id.paste_address_imagebutton); + addPasteListener(view, usernameEditText, R.id.paste_username_imagebutton); + addPasteListener(view, passwordEditText, R.id.paste_password_imagebutton); + addNodeButton.setOnClickListener(view1 -> { String node = addressEditText.getText().toString(); String name = nodeNameEditText.getText().toString(); + String user = usernameEditText.getText().toString(); + String pass = passwordEditText.getText().toString(); try { + String auth = ""; + if(!user.isEmpty() && !pass.isEmpty()) { + auth = user + ":" + pass + "@"; + } else if(!user.isEmpty()) { + Toast.makeText(getContext(), "Enter password", Toast.LENGTH_SHORT).show(); + return; + } - if (node.contains(":") && !name.isEmpty()) { - String[] nodeParts = node.split(":"); - if (nodeParts.length == 2) { - String address = nodeParts[0]; - int port = Integer.parseInt(nodeParts[1]); - String newNodeString = address + ":" + port + "/mainnet/" + name; - boolean validAddress = Patterns.IP_ADDRESS.matcher(address).matches() || Patterns.DOMAIN_NAME.matcher(address).matches() || address.endsWith(".b32.i2p"); - if (validAddress) { - 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; - } + StringBuilder nodeStringBuilder = new StringBuilder(); + nodeStringBuilder.append(auth); - if (!exists) { - jsonArray.put(newNodeString); + if(!name.isEmpty()) { + if(!node.isEmpty()) { + if (node.contains(":")) { + String[] nodeParts = node.split(":"); + if (nodeParts.length == 2) { + String address = nodeParts[0]; + int port = Integer.parseInt(nodeParts[1]); + nodeStringBuilder.append(address).append(":").append(port); } - - PrefService.getInstance().edit().putString(Constants.PREF_CUSTOM_NODES, jsonArray.toString()).apply(); - if (listener != null) { - listener.onNodeAdded(); - } - dismiss(); + } else { + nodeStringBuilder.append(node); } + nodeStringBuilder.append("/mainnet/").append(name); + } else { + Toast.makeText(getContext(), "Enter node address", Toast.LENGTH_SHORT).show(); + return; } - } else if(node.endsWith(".b32.i2p")) { - String newNodeString = node + "/mainnet/" + name; - 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; - } + } else { + Toast.makeText(getContext(), "Enter node name", Toast.LENGTH_SHORT).show(); + return; + } - if (!exists) { - jsonArray.put(newNodeString); - } + 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(nodeStringBuilder.toString())) + exists = true; + } - PrefService.getInstance().edit().putString(Constants.PREF_CUSTOM_NODES, jsonArray.toString()).apply(); - if (listener != null) { - listener.onNodeAdded(); - } - dismiss(); + if (!exists) { + jsonArray.put(nodeStringBuilder.toString()); + } else { + Toast.makeText(getContext(), "Node already exists", Toast.LENGTH_SHORT).show(); + return; + } + + PrefService.getInstance().edit().putString(Constants.PREF_CUSTOM_NODES, jsonArray.toString()).apply(); + if (listener != null) { + listener.onNodeAdded(); } } catch (NumberFormatException | JSONException e) { e.printStackTrace(); @@ -105,6 +131,16 @@ public class AddNodeBottomSheetDialog extends BottomSheetDialogFragment { }); } + private void addPasteListener(View root, EditText editText, int layoutId) { + ImageButton pasteImageButton = root.findViewById(layoutId); + pasteImageButton.setOnClickListener(view1 -> { + Context ctx = getContext(); + if (ctx != null) { + editText.setText(Helper.getClipBoardText(ctx)); + } + }); + } + public interface AddNodeListener { void onNodeAdded(); } diff --git a/app/src/main/java/net/mynero/wallet/fragment/dialog/EditNodeBottomSheetDialog.java b/app/src/main/java/net/mynero/wallet/fragment/dialog/EditNodeBottomSheetDialog.java index 53abf74..2aac51b 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/dialog/EditNodeBottomSheetDialog.java +++ b/app/src/main/java/net/mynero/wallet/fragment/dialog/EditNodeBottomSheetDialog.java @@ -2,6 +2,8 @@ package net.mynero.wallet.fragment.dialog; import android.content.Context; import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; import android.util.Patterns; import android.view.LayoutInflater; import android.view.View; @@ -9,6 +11,7 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; import android.widget.ImageButton; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -40,18 +43,40 @@ public class EditNodeBottomSheetDialog extends BottomSheetDialogFragment { Button doneEditingButton = view.findViewById(R.id.done_editing_button); EditText addressEditText = view.findViewById(R.id.address_edittext); EditText nodeNameEditText = view.findViewById(R.id.node_name_edittext); - ImageButton pasteAddressImageButton = view.findViewById(R.id.paste_address_imagebutton); + EditText usernameEditText = view.findViewById(R.id.username_edittext); + EditText passwordEditText = view.findViewById(R.id.password_edittext); + ImageButton pastePasswordImageButton = view.findViewById(R.id.paste_password_imagebutton); Node node = Node.fromString(nodeString); addressEditText.setText(node.getAddress()); nodeNameEditText.setText(node.getName()); + usernameEditText.setText(node.getUsername()); + if(!node.getPassword().isEmpty()) { + passwordEditText.setText(node.getPassword()); + passwordEditText.setVisibility(View.VISIBLE); + pastePasswordImageButton.setVisibility(View.VISIBLE); + } - pasteAddressImageButton.setOnClickListener(view1 -> { - Context ctx = getContext(); - if (ctx != null) { - addressEditText.setText(Helper.getClipBoardText(ctx)); + usernameEditText.addTextChangedListener(new TextWatcher() { + @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {} + @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {} + @Override + public void afterTextChanged(Editable editable) { + if(editable.toString().isEmpty()) { + passwordEditText.setText(null); + passwordEditText.setVisibility(View.GONE); + pastePasswordImageButton.setVisibility(View.GONE); + } else { + passwordEditText.setVisibility(View.VISIBLE); + pastePasswordImageButton.setVisibility(View.VISIBLE); + } } }); + + addPasteListener(view, addressEditText, R.id.paste_address_imagebutton); + addPasteListener(view, usernameEditText, R.id.paste_username_imagebutton); + addPasteListener(view, passwordEditText, R.id.paste_password_imagebutton); + deleteNodeButton.setOnClickListener(view1 -> { listener.onNodeDeleted(Node.fromString(nodeString)); dismiss(); @@ -59,19 +84,57 @@ public class EditNodeBottomSheetDialog extends BottomSheetDialogFragment { doneEditingButton.setOnClickListener(view1 -> { String nodeAddress = addressEditText.getText().toString(); String nodeName = nodeNameEditText.getText().toString(); - if (nodeAddress.contains(":") && !nodeName.isEmpty()) { - String[] nodeParts = nodeAddress.split(":"); - if (nodeParts.length == 2) { - String address = nodeParts[0]; - int port = Integer.parseInt(nodeParts[1]); - String newNodeString = address + ":" + port + "/mainnet/" + nodeName; - listener.onNodeEdited(Node.fromString(nodeString), Node.fromString(newNodeString)); + String user = usernameEditText.getText().toString(); + String pass = passwordEditText.getText().toString(); + + String auth = ""; + if(!user.isEmpty() && !pass.isEmpty()) { + auth = user + ":" + pass + "@"; + } else if(!user.isEmpty()) { + Toast.makeText(getContext(), "Enter password", Toast.LENGTH_SHORT).show(); + return; + } + + StringBuilder nodeStringBuilder = new StringBuilder(); + nodeStringBuilder.append(auth); + + if(!nodeName.isEmpty()) { + if(!nodeAddress.isEmpty()) { + if (nodeAddress.contains(":")) { + String[] nodeParts = nodeAddress.split(":"); + if (nodeParts.length == 2) { + String address = nodeParts[0]; + int port = Integer.parseInt(nodeParts[1]); + nodeStringBuilder.append(address).append(":").append(port); + } + } else { + nodeStringBuilder.append(nodeAddress); + } + + nodeStringBuilder.append("/mainnet/").append(nodeName); + listener.onNodeEdited(Node.fromString(nodeString), Node.fromString(nodeStringBuilder.toString())); + } else { + Toast.makeText(getContext(), "Enter node address", Toast.LENGTH_SHORT).show(); + return; } + } else { + Toast.makeText(getContext(), "Enter node name", Toast.LENGTH_SHORT).show(); + return; } dismiss(); }); } + private void addPasteListener(View root, EditText editText, int layoutId) { + ImageButton pasteImageButton = root.findViewById(layoutId); + pasteImageButton.setOnClickListener(view1 -> { + Context ctx = getContext(); + if (ctx != null) { + editText.setText(Helper.getClipBoardText(ctx)); + } + }); + } + public interface EditNodeListener { void onNodeDeleted(Node node); void onNodeEdited(Node oldNode, Node newNode); diff --git a/app/src/main/java/net/mynero/wallet/fragment/receive/ReceiveViewModel.java b/app/src/main/java/net/mynero/wallet/fragment/receive/ReceiveViewModel.java index 63d313a..d39405f 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/receive/ReceiveViewModel.java +++ b/app/src/main/java/net/mynero/wallet/fragment/receive/ReceiveViewModel.java @@ -5,10 +5,12 @@ import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; import net.mynero.wallet.data.Subaddress; +import net.mynero.wallet.model.Wallet; import net.mynero.wallet.model.WalletManager; import net.mynero.wallet.service.AddressService; import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class ReceiveViewModel extends ViewModel { @@ -23,12 +25,13 @@ public class ReceiveViewModel extends ViewModel { } private List getSubaddresses() { + Wallet wallet = WalletManager.getInstance().getWallet(); ArrayList subaddresses = new ArrayList<>(); int addressesSize = AddressService.getInstance().getLatestAddressIndex(); - for(int i = 0; i < addressesSize; i++) { - subaddresses.add(WalletManager.getInstance().getWallet().getSubaddressObject(i)); + for(int i = addressesSize - 1; i >= 0; i--) { + subaddresses.add(wallet.getSubaddressObject(i)); } - return subaddresses; + return Collections.unmodifiableList(subaddresses); } public void getFreshSubaddress() { diff --git a/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.java b/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.java index 35cd69c..30f7f5d 100644 --- a/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.java +++ b/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.java @@ -90,9 +90,9 @@ public class MoneroHandlerThread extends Thread implements WalletListener { @Override public void newBlock(long height) { - if(isEveryNthBlock(height, 100)) { // refresh services every 100 blocks downloaded - refresh(false, height); - } else if(isEveryNthBlock(height, 2160)) { // save wallet every 2160 blocks (~3 days) + if(isEveryNthBlock(height, 5)) { // refresh services every 5 blocks downloaded + refresh(height); + } else if(isEveryNthBlock(height, 720)) { // save wallet every 720 blocks (~1 day), we also save upon finishing sync (in refreshed()) wallet.store(); } BlockchainService.getInstance().setDaemonHeight(wallet.isSynchronized() ? height : 0); @@ -100,7 +100,7 @@ public class MoneroHandlerThread extends Thread implements WalletListener { @Override public void updated() { - refresh(false, BlockchainService.GETHEIGHT_FETCH); + refresh(BlockchainService.GETHEIGHT_FETCH); } @Override @@ -118,17 +118,15 @@ public class MoneroHandlerThread extends Thread implements WalletListener { BlockchainService.getInstance().setDaemonHeight(height); wallet.setSynchronized(); wallet.store(); - refresh(true, height); + refresh(height); } BlockchainService.getInstance().setConnectionStatus(status); } - private void refresh(boolean refreshCoins, long height) { + private void refresh(long height) { wallet.refreshHistory(); - if (refreshCoins) { - wallet.refreshCoins(); - } + wallet.refreshCoins(); listener.onRefresh(height); } 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 index 88a8349..b61560f 100644 --- a/app/src/main/res/layout/add_node_bottom_sheet_dialog.xml +++ b/app/src/main/res/layout/add_node_bottom_sheet_dialog.xml @@ -31,7 +31,7 @@ android:id="@+id/node_name_edittext" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginBottom="32dp" + android:layout_marginBottom="16dp" android:background="@drawable/edittext_bg" android:ellipsize="middle" android:hint="@string/node_name_hint" @@ -63,11 +63,12 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="8dp" + android:layout_marginBottom="16dp" android:background="@drawable/edittext_bg" android:hint="@string/node_address_hint" android:inputType="text" android:digits="-QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890.:" - app:layout_constraintBottom_toTopOf="@id/add_node_button" + app:layout_constraintBottom_toTopOf="@id/username_edittext" app:layout_constraintEnd_toStartOf="@id/paste_address_imagebutton" app:layout_constraintStart_toStartOf="parent" /> @@ -75,11 +76,75 @@ android:id="@+id/add_node_button" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="32dp" + android:layout_marginTop="16dp" android:background="@drawable/button_bg" android:text="@string/add_node" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/address_edittext" /> + app:layout_constraintTop_toBottomOf="@id/password_edittext" /> + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/address_item.xml b/app/src/main/res/layout/address_item.xml index 0b00f57..8c1b679 100644 --- a/app/src/main/res/layout/address_item.xml +++ b/app/src/main/res/layout/address_item.xml @@ -4,7 +4,9 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="8dp"> + android:layout_marginBottom="8dp" + android:paddingEnd="8dp" + android:paddingStart="8dp"> + + + + + + + +