mirror of
https://codeberg.org/anoncontributorxmr/mysu.git
synced 2024-12-22 21:17:47 +00:00
Add ability to add custom nodes
This commit is contained in:
parent
1e4a91d046
commit
4657132067
15 changed files with 702 additions and 179 deletions
|
@ -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<NodeSelectionAdapter.ViewHolder> {
|
||||||
|
|
||||||
|
private List<Node> 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<Node> 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -16,25 +16,34 @@
|
||||||
|
|
||||||
package com.m2049r.xmrwallet.data;
|
package com.m2049r.xmrwallet.data;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
// Nodes stolen from https://moneroworld.com/#nodes
|
// Nodes stolen from https://moneroworld.com/#nodes
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
public enum DefaultNodes {
|
public enum DefaultNodes {
|
||||||
MONERUJO("nodex.monerujo.io:18081"),
|
MONERUJO("nodex.monerujo.io:18081/mainnet/monerujo"),
|
||||||
XMRTO("node.xmr.to:18081"),
|
SUPPORTXMR("node.supportxmr.com:18081/mainnet/SupportXMR"),
|
||||||
SUPPORTXMR("node.supportxmr.com:18081"),
|
HASHVAULT("nodes.hashvault.pro:18081/mainnet/Hashvault"),
|
||||||
HASHVAULT("nodes.hashvault.pro:18081"),
|
MONEROWORLD("node.moneroworld.com:18089/mainnet/MoneroWorld"),
|
||||||
MONEROWORLD("node.moneroworld.com:18089"),
|
XMRTW("opennode.xmr-tw.org:18089/mainnet/XMRTW"),
|
||||||
XMRTW("opennode.xmr-tw.org:18089"),
|
|
||||||
MONERUJO_ONION("monerujods7mbghwe6cobdr6ujih6c22zu5rl7zshmizz2udf7v7fsad.onion:18081/mainnet/monerujo.onion"),
|
MONERUJO_ONION("monerujods7mbghwe6cobdr6ujih6c22zu5rl7zshmizz2udf7v7fsad.onion:18081/mainnet/monerujo.onion"),
|
||||||
Criminales78("56wl7y2ebhamkkiza4b7il4mrzwtyvpdym7bm2bkg3jrei2je646k3qd.onion:18089/mainnet/Criminales78.onion"),
|
Criminales78("56wl7y2ebhamkkiza4b7il4mrzwtyvpdym7bm2bkg3jrei2je646k3qd.onion:18089/mainnet/Criminales78.onion"),
|
||||||
xmrfail("mxcd4577fldb3ppzy7obmmhnu3tf57gbcbd4qhwr2kxyjj2qi3dnbfqd.onion:18081/mainnet/xmrfail.onion"),
|
xmrfail("mxcd4577fldb3ppzy7obmmhnu3tf57gbcbd4qhwr2kxyjj2qi3dnbfqd.onion:18081/mainnet/xmrfail.onion"),
|
||||||
boldsuck("6dsdenp6vjkvqzy4wzsnzn6wixkdzihx3khiumyzieauxuxslmcaeiad.onion:18081/mainnet/boldsuck.onion"),
|
boldsuck("6dsdenp6vjkvqzy4wzsnzn6wixkdzihx3khiumyzieauxuxslmcaeiad.onion:18081/mainnet/boldsuck.onion"),
|
||||||
SAMOURAI("446unwib5vc7pfbzflosy6m6vtyuhddnalr3hutyavwe4esfuu5g6ryd.onion:18089/mainnet/samourai.onion");
|
SAMOURAI("446unwib5vc7pfbzflosy6m6vtyuhddnalr3hutyavwe4esfuu5g6ryd.onion:18089/mainnet/samourai.onion");
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final String uri;
|
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];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,6 @@ public class Node {
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
private final boolean selected = false;
|
private final boolean selected = false;
|
||||||
Address hostAddress;
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
int rpcPort = 0;
|
int rpcPort = 0;
|
||||||
|
@ -151,7 +150,6 @@ public class Node {
|
||||||
// constructor used for created nodes from retrieved peer lists
|
// constructor used for created nodes from retrieved peer lists
|
||||||
public Node(InetSocketAddress socketAddress) {
|
public Node(InetSocketAddress socketAddress) {
|
||||||
this();
|
this();
|
||||||
this.hostAddress = Address.of(socketAddress.getAddress());
|
|
||||||
this.host = socketAddress.getHostString();
|
this.host = socketAddress.getHostString();
|
||||||
this.rpcPort = 0; // unknown
|
this.rpcPort = 0; // unknown
|
||||||
this.levinPort = socketAddress.getPort();
|
this.levinPort = socketAddress.getPort();
|
||||||
|
@ -213,7 +211,7 @@ public class Node {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
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
|
// 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) {
|
public boolean equals(Object other) {
|
||||||
if (!(other instanceof Node)) return false;
|
if (!(other instanceof Node)) return false;
|
||||||
final Node anotherNode = (Node) other;
|
final Node anotherNode = (Node) other;
|
||||||
return (hostAddress.equals(anotherNode.hostAddress)
|
return (host.equals(anotherNode.host)
|
||||||
|
&& (getAddress().equals(anotherNode.getAddress()))
|
||||||
&& (rpcPort == anotherNode.rpcPort)
|
&& (rpcPort == anotherNode.rpcPort)
|
||||||
&& (networkType == anotherNode.networkType));
|
&& (networkType == anotherNode.networkType));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOnion() {
|
public boolean isOnion() {
|
||||||
return hostAddress.isOnion();
|
return OnionHelper.isOnionHost(host);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toNodeString() {
|
public String toNodeString() {
|
||||||
|
@ -263,49 +262,23 @@ public class Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAddress() {
|
public String getAddress() {
|
||||||
return getHostAddress() + ":" + rpcPort;
|
return getHost() + ":" + rpcPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getHostAddress() {
|
public String getHost() {
|
||||||
return hostAddress.getHostAddress();
|
return host;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHost(String host) throws UnknownHostException {
|
public void setHost(String host) throws UnknownHostException {
|
||||||
if ((host == null) || (host.isEmpty()))
|
if ((host == null) || (host.isEmpty()))
|
||||||
throw new UnknownHostException("loopback not supported (yet?)");
|
throw new UnknownHostException("loopback not supported (yet?)");
|
||||||
this.host = host;
|
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) {
|
public void overwriteWith(Node anotherNode) {
|
||||||
if (networkType != anotherNode.networkType)
|
if (networkType != anotherNode.networkType)
|
||||||
throw new IllegalStateException("network types do not match");
|
throw new IllegalStateException("network types do not match");
|
||||||
name = anotherNode.name;
|
name = anotherNode.name;
|
||||||
hostAddress = anotherNode.hostAddress;
|
|
||||||
host = anotherNode.host;
|
host = anotherNode.host;
|
||||||
rpcPort = anotherNode.rpcPort;
|
rpcPort = anotherNode.rpcPort;
|
||||||
levinPort = anotherNode.levinPort;
|
levinPort = anotherNode.levinPort;
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Node> 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
@ -16,18 +17,25 @@ import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.widget.SwitchCompat;
|
import androidx.appcompat.widget.SwitchCompat;
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
import com.m2049r.xmrwallet.R;
|
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.InformationBottomSheetDialog;
|
||||||
|
import com.m2049r.xmrwallet.fragment.dialog.NodeSelectionBottomSheetDialog;
|
||||||
import com.m2049r.xmrwallet.fragment.dialog.PasswordBottomSheetDialog;
|
import com.m2049r.xmrwallet.fragment.dialog.PasswordBottomSheetDialog;
|
||||||
|
import com.m2049r.xmrwallet.model.Wallet;
|
||||||
import com.m2049r.xmrwallet.model.WalletManager;
|
import com.m2049r.xmrwallet.model.WalletManager;
|
||||||
|
import com.m2049r.xmrwallet.service.BlockchainService;
|
||||||
import com.m2049r.xmrwallet.service.PrefService;
|
import com.m2049r.xmrwallet.service.PrefService;
|
||||||
import com.m2049r.xmrwallet.util.Constants;
|
import com.m2049r.xmrwallet.util.Constants;
|
||||||
import com.m2049r.xmrwallet.util.DayNightMode;
|
import com.m2049r.xmrwallet.util.DayNightMode;
|
||||||
import com.m2049r.xmrwallet.util.NightmodeHelper;
|
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;
|
private SettingsViewModel mViewModel;
|
||||||
TextWatcher proxyAddressListener = new TextWatcher() {
|
TextWatcher proxyAddressListener = new TextWatcher() {
|
||||||
|
@ -64,6 +72,7 @@ public class SettingsFragment extends Fragment implements PasswordBottomSheetDia
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
mViewModel = new ViewModelProvider(this).get(SettingsViewModel.class);
|
mViewModel = new ViewModelProvider(this).get(SettingsViewModel.class);
|
||||||
Button displaySeedButton = view.findViewById(R.id.display_seed_button);
|
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 nightModeSwitch = view.findViewById(R.id.day_night_switch);
|
||||||
SwitchCompat torSwitch = view.findViewById(R.id.tor_switch);
|
SwitchCompat torSwitch = view.findViewById(R.id.tor_switch);
|
||||||
ConstraintLayout proxySettingsLayout = view.findViewById(R.id.wallet_proxy_settings_layout);
|
ConstraintLayout proxySettingsLayout = view.findViewById(R.id.wallet_proxy_settings_layout);
|
||||||
|
@ -126,6 +135,24 @@ public class SettingsFragment extends Fragment implements PasswordBottomSheetDia
|
||||||
displaySeedDialog();
|
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() {
|
private void displaySeedDialog() {
|
||||||
|
@ -164,4 +191,18 @@ public class SettingsFragment extends Fragment implements PasswordBottomSheetDia
|
||||||
walletProxyAddressEditText.addTextChangedListener(proxyAddressListener);
|
walletProxyAddressEditText.addTextChangedListener(proxyAddressListener);
|
||||||
walletProxyPortEditText.addTextChangedListener(proxyPortListener);
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -8,6 +8,7 @@ import android.widget.Toast;
|
||||||
import androidx.lifecycle.ViewModel;
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
import com.m2049r.xmrwallet.R;
|
import com.m2049r.xmrwallet.R;
|
||||||
|
import com.m2049r.xmrwallet.data.DefaultNodes;
|
||||||
import com.m2049r.xmrwallet.model.WalletManager;
|
import com.m2049r.xmrwallet.model.WalletManager;
|
||||||
import com.m2049r.xmrwallet.service.PrefService;
|
import com.m2049r.xmrwallet.service.PrefService;
|
||||||
import com.m2049r.xmrwallet.service.TxService;
|
import com.m2049r.xmrwallet.service.TxService;
|
||||||
|
@ -20,7 +21,10 @@ public class SettingsViewModel extends ViewModel {
|
||||||
public void updateProxy() {
|
public void updateProxy() {
|
||||||
AsyncTask.execute(() -> {
|
AsyncTask.execute(() -> {
|
||||||
boolean usesProxy = PrefService.getInstance().getBoolean(Constants.PREF_USES_TOR, false);
|
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().setProxy("");
|
||||||
WalletManager.getInstance().getWallet().setProxy("");
|
WalletManager.getInstance().getWallet().setProxy("");
|
||||||
return;
|
return;
|
||||||
|
@ -29,6 +33,7 @@ public class SettingsViewModel extends ViewModel {
|
||||||
if(proxyAddress.isEmpty()) proxyAddress = "127.0.0.1";
|
if(proxyAddress.isEmpty()) proxyAddress = "127.0.0.1";
|
||||||
if(proxyPort.isEmpty()) proxyPort = "9050";
|
if(proxyPort.isEmpty()) proxyPort = "9050";
|
||||||
boolean validIpAddress = Patterns.IP_ADDRESS.matcher(proxyAddress).matches();
|
boolean validIpAddress = Patterns.IP_ADDRESS.matcher(proxyAddress).matches();
|
||||||
|
|
||||||
if(validIpAddress) {
|
if(validIpAddress) {
|
||||||
String proxy = proxyAddress + ":" + proxyPort;
|
String proxy = proxyAddress + ":" + proxyPort;
|
||||||
PrefService.getInstance().edit().putString(Constants.PREF_PROXY, proxy).apply();
|
PrefService.getInstance().edit().putString(Constants.PREF_PROXY, proxy).apply();
|
||||||
|
|
|
@ -3,12 +3,15 @@ package com.m2049r.xmrwallet.service;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import com.m2049r.xmrwallet.model.Wallet;
|
||||||
import com.m2049r.xmrwallet.model.WalletManager;
|
import com.m2049r.xmrwallet.model.WalletManager;
|
||||||
|
|
||||||
public class BlockchainService extends ServiceBase {
|
public class BlockchainService extends ServiceBase {
|
||||||
public static BlockchainService instance = null;
|
public static BlockchainService instance = null;
|
||||||
private final MutableLiveData<Long> _currentHeight = new MutableLiveData<>(0L);
|
private final MutableLiveData<Long> _currentHeight = new MutableLiveData<>(0L);
|
||||||
public LiveData<Long> height = _currentHeight;
|
public LiveData<Long> height = _currentHeight;
|
||||||
|
private final MutableLiveData<Wallet.ConnectionStatus> _connectionStatus = new MutableLiveData<>(Wallet.ConnectionStatus.ConnectionStatus_Disconnected);
|
||||||
|
public LiveData<Wallet.ConnectionStatus> connectionStatus = _connectionStatus;
|
||||||
private long daemonHeight = 0;
|
private long daemonHeight = 0;
|
||||||
private long lastDaemonHeightUpdateTimeMs = 0;
|
private long lastDaemonHeightUpdateTimeMs = 0;
|
||||||
public BlockchainService(MoneroHandlerThread thread) {
|
public BlockchainService(MoneroHandlerThread thread) {
|
||||||
|
@ -44,4 +47,8 @@ public class BlockchainService extends ServiceBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setConnectionStatus(Wallet.ConnectionStatus status) {
|
||||||
|
_connectionStatus.postValue(status);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,15 +55,16 @@ public class MoneroHandlerThread extends Thread implements WalletListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
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);
|
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, "");
|
String proxy = PrefService.getInstance().getString(Constants.PREF_PROXY, "");
|
||||||
WalletManager.getInstance().setProxy(proxy);
|
WalletManager.getInstance().setProxy(proxy);
|
||||||
WalletManager.getInstance().setDaemon(Node.fromString(DefaultNodes.SAMOURAI.getUri()));
|
|
||||||
wallet.setProxy(proxy);
|
wallet.setProxy(proxy);
|
||||||
} else {
|
|
||||||
WalletManager.getInstance().setDaemon(Node.fromString(DefaultNodes.XMRTW.getUri()));
|
|
||||||
}
|
}
|
||||||
|
WalletManager.getInstance().setDaemon(selectedNode);
|
||||||
wallet.init(0);
|
wallet.init(0);
|
||||||
wallet.setListener(this);
|
wallet.setListener(this);
|
||||||
wallet.startRefresh();
|
wallet.startRefresh();
|
||||||
|
@ -108,6 +109,8 @@ public class MoneroHandlerThread extends Thread implements WalletListener {
|
||||||
wallet.store();
|
wallet.store();
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BlockchainService.getInstance().setConnectionStatus(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refresh() {
|
private void refresh() {
|
||||||
|
|
|
@ -7,6 +7,8 @@ public class Constants {
|
||||||
public static final String PREF_USES_TOR = "pref_uses_tor";
|
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_NIGHT_MODE = "pref_night_mode";
|
||||||
public static final String PREF_PROXY = "pref_proxy";
|
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_PREFIX = "monero:";
|
||||||
public static final String URI_ARG_AMOUNT = "tx_amount";
|
public static final String URI_ARG_AMOUNT = "tx_amount";
|
||||||
|
|
84
app/src/main/res/layout/add_node_bottom_sheet_dialog.xml
Normal file
84
app/src/main/res/layout/add_node_bottom_sheet_dialog.xml
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fillViewport="true"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
android:padding="24dp">
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
<!-- CREATE LAYOUT -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/send_monero_textview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="32dp"
|
||||||
|
android:text="@string/add_node"
|
||||||
|
android:textSize="32sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/node_name_edittext"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/node_name_edittext"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="32dp"
|
||||||
|
android:background="@drawable/edittext_bg"
|
||||||
|
android:ellipsize="middle"
|
||||||
|
android:hint="@string/node_name_hint"
|
||||||
|
android:singleLine="true"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/address_edittext"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/send_monero_textview" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/paste_address_imagebutton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:minWidth="24dp"
|
||||||
|
android:minHeight="24dp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:src="@drawable/ic_content_paste_24dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/address_edittext"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/address_edittext"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/address_edittext"
|
||||||
|
tools:ignore="SpeakableTextPresentCheck" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/address_edittext"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:background="@drawable/edittext_bg"
|
||||||
|
android:hint="@string/node_address_hint"
|
||||||
|
android:inputType="text"
|
||||||
|
android:digits="1234567890.:"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/add_node_button"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/paste_address_imagebutton"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/add_node_button"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:background="@drawable/button_bg"
|
||||||
|
android:text="@string/add_node"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/address_edittext" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
|
@ -1,24 +1,37 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:padding="24dp"
|
android:padding="24dp"
|
||||||
tools:context=".fragment.settings.SettingsFragment">
|
tools:context=".fragment.settings.SettingsFragment">
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/settings_textview"
|
android:id="@+id/settings_textview"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/settings"
|
android:text="@string/settings"
|
||||||
android:textSize="32sp"
|
android:textSize="32sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
app:layout_constraintBottom_toTopOf="@id/wallet_settings_textview"
|
app:layout_constraintEnd_toStartOf="@id/status_textview"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/status_textview"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/disconnected"
|
||||||
|
android:textSize="12sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/settings_textview"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/settings_textview"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/settings_textview" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/wallet_settings_textview"
|
android:id="@+id/wallet_settings_textview"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -27,19 +40,17 @@
|
||||||
android:text="@string/wallet"
|
android:text="@string/wallet"
|
||||||
android:textSize="24sp"
|
android:textSize="24sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
app:layout_constraintBottom_toTopOf="@id/display_seed_button"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/settings_textview" />
|
app:layout_constraintTop_toBottomOf="@id/settings_textview" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/display_seed_button"
|
android:id="@+id/display_seed_button"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:background="@drawable/button_bg"
|
android:background="@drawable/button_bg"
|
||||||
android:text="@string/display_recovery_phrase"
|
android:text="@string/display_recovery_phrase"
|
||||||
app:layout_constraintBottom_toTopOf="@id/appearance_settings_textview"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="0.0"
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
@ -53,7 +64,6 @@
|
||||||
android:text="@string/appearance"
|
android:text="@string/appearance"
|
||||||
android:textSize="24sp"
|
android:textSize="24sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
app:layout_constraintBottom_toTopOf="@id/day_night_switch"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/display_seed_button" />
|
app:layout_constraintTop_toBottomOf="@id/display_seed_button" />
|
||||||
|
@ -74,7 +84,6 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
app:layout_constraintBottom_toTopOf="@id/network_settings_textview"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/appearance_settings_textview" />
|
app:layout_constraintTop_toBottomOf="@id/appearance_settings_textview" />
|
||||||
|
|
||||||
|
@ -86,11 +95,23 @@
|
||||||
android:text="@string/network"
|
android:text="@string/network"
|
||||||
android:textSize="24sp"
|
android:textSize="24sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
app:layout_constraintBottom_toTopOf="@id/tor_switch"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/day_night_switch" />
|
app:layout_constraintTop_toBottomOf="@id/day_night_switch" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/select_node_button"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="middle"
|
||||||
|
android:background="@drawable/button_bg"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/network_settings_textview" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tor_textview"
|
android:id="@+id/tor_textview"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
@ -108,7 +129,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/network_settings_textview" />
|
app:layout_constraintTop_toBottomOf="@id/select_node_button" />
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/wallet_proxy_settings_layout"
|
android:id="@+id/wallet_proxy_settings_layout"
|
||||||
|
@ -144,3 +165,4 @@
|
||||||
app:layout_constraintTop_toBottomOf="@id/wallet_proxy_address_edittext" />
|
app:layout_constraintTop_toBottomOf="@id/wallet_proxy_address_edittext" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fillViewport="true"
|
||||||
|
android:padding="24dp">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/nodes_textview"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:text="@string/nodes"
|
||||||
|
android:textSize="32sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/add_node_button"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/add_node_button"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/add_node_button" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/add_node_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/button_bg"
|
||||||
|
android:text="@string/add_node"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/nodes_textview"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/node_selection_recyclerview"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/node_selection_recyclerview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingBottom="128dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:nestedScrollingEnabled="false"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/add_node_button">
|
||||||
|
|
||||||
|
</androidx.recyclerview.widget.RecyclerView>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
31
app/src/main/res/layout/node_selection_item.xml
Normal file
31
app/src/main/res/layout/node_selection_item.xml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:layout_marginBottom="8dp">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/node_name_textview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Node Name"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/node_uri_textview"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/node_uri_textview"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="NODE::"
|
||||||
|
android:ellipsize="middle"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/node_name_textview"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
android:singleLine="true" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -76,9 +76,17 @@
|
||||||
<string name="wallet">Wallet</string>
|
<string name="wallet">Wallet</string>
|
||||||
<string name="appearance">Appearance</string>
|
<string name="appearance">Appearance</string>
|
||||||
<string name="network">Network</string>
|
<string name="network">Network</string>
|
||||||
|
<string name="add_node">Add Node</string>
|
||||||
|
<string name="nodes">Nodes</string>
|
||||||
|
<string name="node_name_hint">My Monero Node</string>
|
||||||
|
<string name="node_address_hint">127.0.0.1:18081</string>
|
||||||
|
|
||||||
<string name="wallet_proxy_address_hint">127.0.0.1</string>
|
<string name="wallet_proxy_address_hint">127.0.0.1</string>
|
||||||
<string name="wallet_proxy_port_hint">9050</string>
|
<string name="wallet_proxy_port_hint">9050</string>
|
||||||
<string name="invalid_ip">Invalid IP address</string>
|
<string name="invalid_ip">Invalid IP address</string>
|
||||||
<string name="no_history_nget_some_monero_in_here">No history!\nGet some Monero in here!</string>
|
<string name="no_history_nget_some_monero_in_here">No history!\nGet some Monero in here!</string>
|
||||||
|
<string name="node_button_text">Node: %1$s</string>
|
||||||
|
<string name="connected">Connected</string>
|
||||||
|
<string name="disconnected">Disconnected</string>
|
||||||
|
<string name="version_mismatch">Version mismatch</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue