Merge branch 'main' into update-readme

This commit is contained in:
pokkst 2023-12-03 11:13:21 -06:00
commit be456366db
23 changed files with 474 additions and 251 deletions

View File

@ -9,8 +9,8 @@ android {
applicationId "net.mynero.wallet" applicationId "net.mynero.wallet"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 34 targetSdkVersion 34
versionCode 40500 versionCode 40701
versionName "0.4.5 'Fluorine Fermi'" versionName "0.4.7.1 'Fluorine Fermi'"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild { externalNativeBuild {
cmake { cmake {
@ -25,26 +25,13 @@ android {
} }
} }
flavorDimensions 'type', 'net' flavorDimensions 'type'
productFlavors { productFlavors {
mainnet {
dimension 'net'
}
beta { beta {
dimension 'net' dimension 'type'
applicationIdSuffix '.beta' applicationIdSuffix '.beta'
versionNameSuffix ' (beta)' versionNameSuffix ' (beta)'
} }
devnet {
dimension 'net'
applicationIdSuffix '.test'
versionNameSuffix ' (test)'
}
alpha {
dimension 'type'
applicationIdSuffix '.alpha'
versionNameSuffix ' (alpha)'
}
prod { prod {
dimension 'type' dimension 'type'
} }

View File

@ -108,8 +108,10 @@ public class MainActivity extends AppCompatActivity implements MoneroHandlerThre
} }
@Override @Override
public void onRefresh() { public void onRefresh(boolean walletSynced) {
this.utxoService.refreshUtxos(); if(walletSynced)
this.utxoService.refreshUtxos();
this.historyService.refreshHistory(); this.historyService.refreshHistory();
this.balanceService.refreshBalance(); this.balanceService.refreshBalance();
this.blockchainService.refreshBlockchain(); this.blockchainService.refreshBlockchain();

View File

@ -78,6 +78,8 @@ public class SubaddressAdapter extends RecyclerView.Adapter<SubaddressAdapter.Vi
public interface SubaddressAdapterListener { public interface SubaddressAdapterListener {
void onSubaddressSelected(Subaddress subaddress); void onSubaddressSelected(Subaddress subaddress);
void onSubaddressEditLabel(Subaddress subaddress);
} }
/** /**
@ -119,6 +121,10 @@ public class SubaddressAdapter extends RecyclerView.Adapter<SubaddressAdapter.Vi
addressAmountTextView.setText(""); addressAmountTextView.setText("");
itemView.setOnClickListener(view -> listener.onSubaddressSelected(subaddress)); itemView.setOnClickListener(view -> listener.onSubaddressSelected(subaddress));
itemView.setOnLongClickListener(v -> {
listener.onSubaddressEditLabel(subaddress);
return true;
});
} }
} }
} }

View File

@ -351,13 +351,17 @@ public class Node {
} }
public String getAddress() { public String getAddress() {
return getHost() + ":" + rpcPort; return getHost() + ":" + getRpcPort();
} }
public String getHost() { public String getHost() {
return host; return host;
} }
public int getRpcPort() {
return rpcPort;
}
public void setHost(String host) throws UnknownHostException { public void setHost(String host) throws UnknownHostException {
if ((host == null) || (host.isEmpty())) if ((host == null) || (host.isEmpty()))
throw new UnknownHostException("loopback not supported (yet?)"); throw new UnknownHostException("loopback not supported (yet?)");

View File

@ -41,6 +41,7 @@ public class AddNodeBottomSheetDialog extends BottomSheetDialogFragment {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
Button addNodeButton = view.findViewById(R.id.add_node_button); Button addNodeButton = view.findViewById(R.id.add_node_button);
EditText addressEditText = view.findViewById(R.id.address_edittext); EditText addressEditText = view.findViewById(R.id.address_edittext);
EditText portEditText = view.findViewById(R.id.node_port_edittext);
EditText nodeNameEditText = view.findViewById(R.id.node_name_edittext); EditText nodeNameEditText = view.findViewById(R.id.node_name_edittext);
EditText usernameEditText = view.findViewById(R.id.username_edittext); EditText usernameEditText = view.findViewById(R.id.username_edittext);
EditText passwordEditText = view.findViewById(R.id.password_edittext); EditText passwordEditText = view.findViewById(R.id.password_edittext);
@ -67,14 +68,15 @@ public class AddNodeBottomSheetDialog extends BottomSheetDialogFragment {
addPasteListener(view, passwordEditText, R.id.paste_password_imagebutton); addPasteListener(view, passwordEditText, R.id.paste_password_imagebutton);
addNodeButton.setOnClickListener(view1 -> { addNodeButton.setOnClickListener(view1 -> {
String node = addressEditText.getText().toString(); String nodeAddr = addressEditText.getText().toString();
String portString = portEditText.getText().toString();
String name = nodeNameEditText.getText().toString(); String name = nodeNameEditText.getText().toString();
String user = usernameEditText.getText().toString(); String user = usernameEditText.getText().toString();
String pass = passwordEditText.getText().toString(); String pass = passwordEditText.getText().toString();
if(name.isEmpty()) { if(name.isEmpty()) {
Toast.makeText(getContext(), "Enter node name", Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), "Enter node name", Toast.LENGTH_SHORT).show();
return; return;
} else if(node.isEmpty()) { } else if(nodeAddr.isEmpty() || portString.isEmpty()) {
Toast.makeText(getContext(), "Enter node address", Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), "Enter node address", Toast.LENGTH_SHORT).show();
return; return;
} else if(!user.isEmpty() && pass.isEmpty()) { } else if(!user.isEmpty() && pass.isEmpty()) {
@ -84,14 +86,16 @@ public class AddNodeBottomSheetDialog extends BottomSheetDialogFragment {
JSONObject jsonObject = new JSONObject(); JSONObject jsonObject = new JSONObject();
try { try {
if (!user.isEmpty() && !pass.isEmpty()) { if (!user.isEmpty()) {
jsonObject.put("username", user); jsonObject.put("username", user);
jsonObject.put("password", pass); jsonObject.put("password", pass);
} }
String[] nodeParts = node.split(":");
jsonObject.put("host", nodeParts[0]); if (nodeAddr.contains(":") && !nodeAddr.startsWith("[") && !nodeAddr.endsWith("]"))
jsonObject.put("rpcPort", nodeParts[1]); nodeAddr = "[" + nodeAddr + "]";
jsonObject.put("host", nodeAddr);
jsonObject.put("rpcPort", Integer.parseInt(portString));
jsonObject.put("network", "mainnet"); jsonObject.put("network", "mainnet");
jsonObject.put("name", name); jsonObject.put("name", name);

View File

@ -0,0 +1,71 @@
package net.mynero.wallet.fragment.dialog;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import net.mynero.wallet.R;
import net.mynero.wallet.model.Wallet;
import net.mynero.wallet.model.WalletManager;
import net.mynero.wallet.service.AddressService;
import net.mynero.wallet.util.Helper;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class EditAddressLabelBottomSheetDialog extends BottomSheetDialogFragment {
public LabelListener listener = null;
public int addressIndex = 0;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.bottom_sheet_dialog_edit_address_label, null);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Wallet wallet = WalletManager.getInstance().getWallet();
AddressService addressService = AddressService.getInstance();
ImageButton pasteButton = view.findViewById(R.id.paste_password_imagebutton);
EditText labelEditText = view.findViewById(R.id.wallet_password_edittext);
Button saveLabelButton = view.findViewById(R.id.unlock_wallet_button);
boolean isDate = false;
try {
new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss", Locale.US).parse(wallet.getSubaddressLabel(addressIndex));
isDate = true;
} catch (ParseException ignored) { }
labelEditText.setText(isDate ? null : wallet.getSubaddressLabel(addressIndex));
pasteButton.setOnClickListener(view1 -> labelEditText.setText(Helper.getClipBoardText(view.getContext())));
saveLabelButton.setOnClickListener(view1 -> {
String label = labelEditText.getText().toString();
if(addressService.getLatestAddressIndex() == addressIndex) {
addressService.freshSubaddress();
}
wallet.setSubaddressLabel(addressIndex, label);
wallet.store();
if(listener != null) {
listener.onDismiss();
}
dismiss();
});
}
public interface LabelListener {
void onDismiss();
}
}

View File

@ -35,7 +35,7 @@ import timber.log.Timber;
public class EditNodeBottomSheetDialog extends BottomSheetDialogFragment { public class EditNodeBottomSheetDialog extends BottomSheetDialogFragment {
public EditNodeListener listener = null; public EditNodeListener listener = null;
public JSONObject nodeJson = null; public Node node = null;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@ -48,13 +48,15 @@ public class EditNodeBottomSheetDialog extends BottomSheetDialogFragment {
Button deleteNodeButton = view.findViewById(R.id.delete_node_button); Button deleteNodeButton = view.findViewById(R.id.delete_node_button);
Button doneEditingButton = view.findViewById(R.id.done_editing_button); Button doneEditingButton = view.findViewById(R.id.done_editing_button);
EditText addressEditText = view.findViewById(R.id.address_edittext); EditText addressEditText = view.findViewById(R.id.address_edittext);
EditText portEditText = view.findViewById(R.id.node_port_edittext);
EditText nodeNameEditText = view.findViewById(R.id.node_name_edittext); EditText nodeNameEditText = view.findViewById(R.id.node_name_edittext);
EditText usernameEditText = view.findViewById(R.id.username_edittext); EditText usernameEditText = view.findViewById(R.id.username_edittext);
EditText passwordEditText = view.findViewById(R.id.password_edittext); EditText passwordEditText = view.findViewById(R.id.password_edittext);
ImageButton pastePasswordImageButton = view.findViewById(R.id.paste_password_imagebutton); ImageButton pastePasswordImageButton = view.findViewById(R.id.paste_password_imagebutton);
Node node = Node.fromJson(nodeJson); if(node == null) return;
addressEditText.setText(node.getAddress()); addressEditText.setText(node.getHost());
portEditText.setText(""+node.getRpcPort());
nodeNameEditText.setText(node.getName()); nodeNameEditText.setText(node.getName());
usernameEditText.setText(node.getUsername()); usernameEditText.setText(node.getUsername());
if(!node.getPassword().isEmpty()) { if(!node.getPassword().isEmpty()) {
@ -84,11 +86,12 @@ public class EditNodeBottomSheetDialog extends BottomSheetDialogFragment {
addPasteListener(view, passwordEditText, R.id.paste_password_imagebutton); addPasteListener(view, passwordEditText, R.id.paste_password_imagebutton);
deleteNodeButton.setOnClickListener(view1 -> { deleteNodeButton.setOnClickListener(view1 -> {
listener.onNodeDeleted(Node.fromJson(nodeJson)); listener.onNodeDeleted(node);
dismiss(); dismiss();
}); });
doneEditingButton.setOnClickListener(view1 -> { doneEditingButton.setOnClickListener(view1 -> {
String nodeAddress = addressEditText.getText().toString(); String nodeAddr = addressEditText.getText().toString();
String portString = portEditText.getText().toString();
String nodeName = nodeNameEditText.getText().toString(); String nodeName = nodeNameEditText.getText().toString();
String user = usernameEditText.getText().toString(); String user = usernameEditText.getText().toString();
String pass = passwordEditText.getText().toString(); String pass = passwordEditText.getText().toString();
@ -96,7 +99,7 @@ public class EditNodeBottomSheetDialog extends BottomSheetDialogFragment {
if(nodeName.isEmpty()) { if(nodeName.isEmpty()) {
Toast.makeText(getContext(), "Enter node name", Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), "Enter node name", Toast.LENGTH_SHORT).show();
return; return;
} else if(nodeAddress.isEmpty()) { } else if(nodeAddr.isEmpty() || portString.isEmpty()) {
Toast.makeText(getContext(), "Enter node address", Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), "Enter node address", Toast.LENGTH_SHORT).show();
return; return;
} else if(!user.isEmpty() && pass.isEmpty()) { } else if(!user.isEmpty() && pass.isEmpty()) {
@ -105,18 +108,20 @@ public class EditNodeBottomSheetDialog extends BottomSheetDialogFragment {
} }
JSONObject jsonObject = new JSONObject(); JSONObject jsonObject = new JSONObject();
try { try {
if (!user.isEmpty() && !pass.isEmpty()) { if (!user.isEmpty()) {
jsonObject.put("username", user); jsonObject.put("username", user);
jsonObject.put("password", pass); jsonObject.put("password", pass);
} }
String[] nodeParts = nodeAddress.split(":");
jsonObject.put("host", nodeParts[0]); if (nodeAddr.contains(":") && !nodeAddr.startsWith("[") && !nodeAddr.endsWith("]"))
jsonObject.put("rpcPort", nodeParts[1]); nodeAddr = "[" + nodeAddr + "]";
jsonObject.put("host", nodeAddr);
jsonObject.put("rpcPort", Integer.parseInt(portString));
jsonObject.put("network", "mainnet"); jsonObject.put("network", "mainnet");
jsonObject.put("name", nodeName); jsonObject.put("name", nodeName);
listener.onNodeEdited(Node.fromJson(nodeJson), Node.fromJson(jsonObject)); listener.onNodeEdited(node, Node.fromJson(jsonObject));
dismiss(); dismiss();
} catch (JSONException e) { } catch (JSONException e) {
throw new RuntimeException(e); throw new RuntimeException(e);

View File

@ -114,7 +114,7 @@ public class NodeSelectionBottomSheetDialog extends BottomSheetDialogFragment im
@Override @Override
public boolean onSelectEditNode(Node node) { public boolean onSelectEditNode(Node node) {
if (listener != null) { if (listener != null) {
listener.onClickedEditNode(node.toJson()); listener.onClickedEditNode(node);
} }
dismiss(); dismiss();
return true; return true;
@ -122,7 +122,7 @@ public class NodeSelectionBottomSheetDialog extends BottomSheetDialogFragment im
public interface NodeSelectionDialogListener { public interface NodeSelectionDialogListener {
void onNodeSelected(); void onNodeSelected();
void onClickedEditNode(JSONObject nodeJson); void onClickedEditNode(Node node);
void onClickedAddNode(); void onClickedAddNode();
} }
} }

View File

@ -120,7 +120,7 @@ public class HomeFragment extends Fragment implements TransactionInfoAdapter.TxI
if (height > 1 && daemonHeight > 1) { if (height > 1 && daemonHeight > 1) {
progressBar.setProgress(x); progressBar.setProgress(x);
progressBarText.setVisibility(View.VISIBLE); progressBarText.setVisibility(View.VISIBLE);
progressBarText.setText("Syncing... " + height + " / " + daemonHeight); progressBarText.setText("Syncing... " + n + " blocks remaining");
} else { } else {
progressBarText.setVisibility(View.GONE); progressBarText.setVisibility(View.GONE);
} }

View File

@ -285,7 +285,7 @@ public class OnboardingFragment extends Fragment implements NodeSelectionBottomS
} }
@Override @Override
public void onClickedEditNode(JSONObject nodeJson) { } public void onClickedEditNode(Node node) { }
@Override @Override
public void onClickedAddNode() { public void onClickedAddNode() {

View File

@ -26,6 +26,7 @@ import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import net.mynero.wallet.R; import net.mynero.wallet.R;
import net.mynero.wallet.adapter.SubaddressAdapter; import net.mynero.wallet.adapter.SubaddressAdapter;
import net.mynero.wallet.data.Subaddress; import net.mynero.wallet.data.Subaddress;
import net.mynero.wallet.fragment.dialog.EditAddressLabelBottomSheetDialog;
import net.mynero.wallet.util.DayNightMode; import net.mynero.wallet.util.DayNightMode;
import net.mynero.wallet.util.Helper; import net.mynero.wallet.util.Helper;
import net.mynero.wallet.util.NightmodeHelper; import net.mynero.wallet.util.NightmodeHelper;
@ -70,7 +71,17 @@ public class ReceiveFragment extends Fragment {
} }
private void bindObservers(View view) { private void bindObservers(View view) {
SubaddressAdapter adapter = new SubaddressAdapter(mViewModel::selectAddress); SubaddressAdapter adapter = new SubaddressAdapter(new SubaddressAdapter.SubaddressAdapterListener() {
@Override
public void onSubaddressSelected(Subaddress subaddress) {
mViewModel.selectAddress(subaddress);
}
@Override
public void onSubaddressEditLabel(Subaddress subaddress) {
editAddressLabel(subaddress);
}
});
RecyclerView recyclerView = view.findViewById(R.id.address_list_recyclerview); RecyclerView recyclerView = view.findViewById(R.id.address_list_recyclerview);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerView.setAdapter(adapter); recyclerView.setAdapter(adapter);
@ -78,6 +89,13 @@ public class ReceiveFragment extends Fragment {
mViewModel.addresses.observe(getViewLifecycleOwner(), adapter::submitList); mViewModel.addresses.observe(getViewLifecycleOwner(), adapter::submitList);
} }
private void editAddressLabel(Subaddress subaddress) {
EditAddressLabelBottomSheetDialog dialog = new EditAddressLabelBottomSheetDialog();
dialog.addressIndex = subaddress.getAddressIndex();
dialog.listener = () -> mViewModel.init();
dialog.show(getParentFragmentManager(), "edit_address_dialog");
}
private void setAddress(Subaddress subaddress) { private void setAddress(Subaddress subaddress) {
final String label = subaddress.getDisplayLabel(); final String label = subaddress.getDisplayLabel();
final String address = getContext().getString(R.string.subbaddress_info_subtitle, final String address = getContext().getString(R.string.subbaddress_info_subtitle,
@ -86,6 +104,14 @@ public class ReceiveFragment extends Fragment {
addressTextView.setText(subaddress.getAddress()); addressTextView.setText(subaddress.getAddress());
addressImageView.setImageBitmap(generate(subaddress.getAddress(), 256, 256)); addressImageView.setImageBitmap(generate(subaddress.getAddress(), 256, 256));
copyAddressImageButton.setOnClickListener(view1 -> Helper.clipBoardCopy(getContext(), "address", subaddress.getAddress())); copyAddressImageButton.setOnClickListener(view1 -> Helper.clipBoardCopy(getContext(), "address", subaddress.getAddress()));
addressLabelTextView.setOnLongClickListener(v -> {
editAddressLabel(subaddress);
return true;
});
addressTextView.setOnLongClickListener(v -> {
editAddressLabel(subaddress);
return true;
});
} }
private Bitmap generate(String text, int width, int height) { private Bitmap generate(String text, int width, int height) {

View File

@ -434,21 +434,23 @@ public class SendFragment extends Fragment {
mViewModel.setPendingTransaction(pendingTx); mViewModel.setPendingTransaction(pendingTx);
} else { } else {
Activity activity = getActivity(); Activity activity = getActivity();
if (activity != null) { if (activity != null && pendingTx != null) {
activity.runOnUiThread(() -> { activity.runOnUiThread(() -> {
createButton.setEnabled(true); createButton.setEnabled(true);
sendMaxButton.setEnabled(true); sendMaxButton.setEnabled(true);
Toast.makeText(getActivity(), getString(R.string.error_creating_tx), Toast.LENGTH_SHORT).show(); if(pendingTx.getErrorString() != null)
Toast.makeText(activity, getString(R.string.error_creating_tx, pendingTx.getErrorString()), Toast.LENGTH_SHORT).show();
}); });
} }
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
Activity activity = getActivity(); Activity activity = getActivity();
if (activity != null) { if (activity != null) {
activity.runOnUiThread(() -> { activity.runOnUiThread(() -> {
createButton.setEnabled(true); createButton.setEnabled(true);
sendMaxButton.setEnabled(true); sendMaxButton.setEnabled(true);
Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_SHORT).show(); Toast.makeText(activity, getString(R.string.error_creating_tx, e.getMessage()), Toast.LENGTH_SHORT).show();
}); });
} }
} }

View File

@ -248,10 +248,10 @@ public class SettingsFragment extends Fragment implements PasswordBottomSheetDia
} }
@Override @Override
public void onClickedEditNode(JSONObject nodeJson) { public void onClickedEditNode(Node node) {
EditNodeBottomSheetDialog editNodeDialog = new EditNodeBottomSheetDialog(); EditNodeBottomSheetDialog editNodeDialog = new EditNodeBottomSheetDialog();
editNodeDialog.listener = this; editNodeDialog.listener = this;
editNodeDialog.nodeJson = nodeJson; editNodeDialog.node = node;
editNodeDialog.show(getActivity().getSupportFragmentManager(), "edit_node_dialog"); editNodeDialog.show(getActivity().getSupportFragmentManager(), "edit_node_dialog");
} }

View File

@ -19,18 +19,22 @@ import androidx.lifecycle.ViewModelProvider;
import net.mynero.wallet.R; import net.mynero.wallet.R;
import net.mynero.wallet.model.TransactionInfo; import net.mynero.wallet.model.TransactionInfo;
import net.mynero.wallet.model.Wallet; import net.mynero.wallet.model.Wallet;
import net.mynero.wallet.model.WalletManager;
import net.mynero.wallet.service.HistoryService;
import net.mynero.wallet.service.PrefService; import net.mynero.wallet.service.PrefService;
import net.mynero.wallet.util.Constants; import net.mynero.wallet.util.Constants;
import net.mynero.wallet.util.Helper; import net.mynero.wallet.util.Helper;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.TimeZone; import java.util.TimeZone;
public class TransactionFragment extends Fragment { public class TransactionFragment extends Fragment {
private TransactionViewModel mViewModel; private TransactionViewModel mViewModel;
private TransactionInfo transactionInfo = null;
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@ -48,8 +52,7 @@ public class TransactionFragment extends Fragment {
mViewModel = new ViewModelProvider(this).get(TransactionViewModel.class); mViewModel = new ViewModelProvider(this).get(TransactionViewModel.class);
Bundle args = getArguments(); Bundle args = getArguments();
if (args != null) { if (args != null) {
TransactionInfo txInfo = getArguments().getParcelable(Constants.NAV_ARG_TXINFO); this.transactionInfo = getArguments().getParcelable(Constants.NAV_ARG_TXINFO);
mViewModel.init(txInfo);
} }
bindObservers(view); bindObservers(view);
@ -59,25 +62,26 @@ public class TransactionFragment extends Fragment {
private void bindListeners(View view) { private void bindListeners(View view) {
ImageButton copyTxHashImageButton = view.findViewById(R.id.copy_txhash_imagebutton); ImageButton copyTxHashImageButton = view.findViewById(R.id.copy_txhash_imagebutton);
copyTxHashImageButton.setOnClickListener(view1 -> { copyTxHashImageButton.setOnClickListener(view1 -> {
TransactionInfo txInfo = mViewModel.transaction.getValue(); TransactionInfo txInfo = this.transactionInfo;
if (txInfo != null) { if (txInfo != null) {
Helper.clipBoardCopy(getContext(), "transaction_hash", txInfo.hash); Helper.clipBoardCopy(getContext(), "transaction_hash", txInfo.hash);
} }
}); });
ImageButton copyTxAddressImageButton = view.findViewById(R.id.copy_txaddress_imagebutton); ImageButton copyTxAddressImageButton = view.findViewById(R.id.copy_txaddress_imagebutton);
TextView addressTextView = view.findViewById(R.id.transaction_address_textview);
copyTxAddressImageButton.setOnClickListener(view1 -> { copyTxAddressImageButton.setOnClickListener(view1 -> {
TransactionInfo txInfo = mViewModel.transaction.getValue(); TransactionInfo txInfo = this.transactionInfo;
if (txInfo != null) { if (txInfo != null) {
String destination = mViewModel.destination.getValue(); String destination = addressTextView.getText().toString();
if (destination != null) { Helper.clipBoardCopy(getContext(), "transaction_address", destination);
Helper.clipBoardCopy(getContext(), "transaction_address", destination);
}
} }
}); });
} }
private void bindObservers(View view) { private void bindObservers(View view) {
TextView txActionTextView = view.findViewById(R.id.transaction_action_textview);
TextView confLabel2 = view.findViewById(R.id.transaction_conf_label2_textview);
TextView txHashTextView = view.findViewById(R.id.transaction_hash_textview); TextView txHashTextView = view.findViewById(R.id.transaction_hash_textview);
TextView txConfTextView = view.findViewById(R.id.transaction_conf_textview); TextView txConfTextView = view.findViewById(R.id.transaction_conf_textview);
TextView txAddressTextView = view.findViewById(R.id.transaction_address_textview); TextView txAddressTextView = view.findViewById(R.id.transaction_address_textview);
@ -85,43 +89,73 @@ public class TransactionFragment extends Fragment {
TextView txDateTextView = view.findViewById(R.id.transaction_date_textview); TextView txDateTextView = view.findViewById(R.id.transaction_date_textview);
TextView txAmountTextView = view.findViewById(R.id.transaction_amount_textview); TextView txAmountTextView = view.findViewById(R.id.transaction_amount_textview);
TextView blockHeightTextView = view.findViewById(R.id.tx_block_height_textview); TextView blockHeightTextView = view.findViewById(R.id.tx_block_height_textview);
TextView blockHeightLabelTextView = view.findViewById(R.id.transaction_block_height_label_textview);
mViewModel.transaction.observe(getViewLifecycleOwner(), transactionInfo -> { HistoryService.getInstance().history.observe(getViewLifecycleOwner(), transactionInfos -> {
txHashTextView.setText(transactionInfo.hash); TransactionInfo newTransactionInfo = findNewestVersionOfTransaction(this.transactionInfo, transactionInfos);
txConfTextView.setText("" + transactionInfo.confirmations); if(newTransactionInfo == null) return;
txDateTextView.setText(getDateTime(transactionInfo.timestamp));
if(transactionInfo.confirmations > 0) { txHashTextView.setText(newTransactionInfo.hash);
blockHeightTextView.setText("" + transactionInfo.blockheight); txConfTextView.setText("" + newTransactionInfo.confirmations);
txDateTextView.setText(getDateTime(newTransactionInfo.timestamp));
if(newTransactionInfo.confirmations > 1) {
confLabel2.setText(getString(R.string.transaction_conf_desc2_confirmed));
blockHeightTextView.setText("" + newTransactionInfo.blockheight);
blockHeightTextView.setVisibility(View.VISIBLE);
} else if(newTransactionInfo.confirmations == 1) {
confLabel2.setText(getString(R.string.transaction_conf_1_desc2_confirmed));
blockHeightTextView.setText("" + newTransactionInfo.blockheight);
blockHeightTextView.setVisibility(View.VISIBLE); blockHeightTextView.setVisibility(View.VISIBLE);
blockHeightLabelTextView.setVisibility(View.VISIBLE);
} else { } else {
blockHeightTextView.setVisibility(View.GONE); blockHeightTextView.setVisibility(View.GONE);
blockHeightLabelTextView.setVisibility(View.GONE); confLabel2.setText(getString(R.string.transaction_conf_desc2_unconfirmed));
} }
Context ctx = getContext(); Context ctx = getContext();
if(ctx != null) { if(ctx != null) {
boolean streetModeEnabled = PrefService.getInstance().getBoolean(Constants.PREF_STREET_MODE, false); boolean streetModeEnabled = PrefService.getInstance().getBoolean(Constants.PREF_STREET_MODE, false);
String balanceString = streetModeEnabled ? Constants.STREET_MODE_BALANCE : Wallet.getDisplayAmount(transactionInfo.amount); String balanceString = streetModeEnabled ? Constants.STREET_MODE_BALANCE : Helper.getDisplayAmount(newTransactionInfo.amount, 12);
if (transactionInfo.direction == TransactionInfo.Direction.Direction_In) { if (newTransactionInfo.direction == TransactionInfo.Direction.Direction_In) {
txActionTextView.setText(getString(R.string.transaction_action_recv));
txAmountTextView.setTextColor(ContextCompat.getColor(ctx, R.color.oled_positiveColor)); txAmountTextView.setTextColor(ContextCompat.getColor(ctx, R.color.oled_positiveColor));
txAmountTextView.setText(getString(R.string.tx_list_amount_positive, balanceString));
} else { } else {
txActionTextView.setText(getString(R.string.transaction_action_sent));
txAmountTextView.setTextColor(ContextCompat.getColor(ctx, R.color.oled_negativeColor)); txAmountTextView.setTextColor(ContextCompat.getColor(ctx, R.color.oled_negativeColor));
txAmountTextView.setText(getString(R.string.tx_list_amount_negative, balanceString)); }
txAmountTextView.setText(balanceString);
}
String destination = "-";
Wallet wallet = WalletManager.getInstance().getWallet();
if (newTransactionInfo.txKey == null) {
newTransactionInfo.txKey = wallet.getTxKey(newTransactionInfo.hash);
}
if (newTransactionInfo.address == null && newTransactionInfo.direction == TransactionInfo.Direction.Direction_In) {
destination = wallet.getSubaddress(newTransactionInfo.accountIndex, newTransactionInfo.addressIndex);
} else if (newTransactionInfo.address != null && newTransactionInfo.direction == TransactionInfo.Direction.Direction_In) {
destination = newTransactionInfo.address;
} else if (newTransactionInfo.transfers != null && newTransactionInfo.direction == TransactionInfo.Direction.Direction_Out) {
if (newTransactionInfo.transfers.size() == 1) {
destination = newTransactionInfo.transfers.get(0).address;
} }
} }
});
mViewModel.destination.observe(getViewLifecycleOwner(), s -> { txAddressTextView.setText(Objects.requireNonNullElse(destination, "-"));
txAddressTextView.setText(Objects.requireNonNullElse(s, "-")); if (destination == null) {
if (s == null) {
copyTxAddressImageButton.setVisibility(View.INVISIBLE); copyTxAddressImageButton.setVisibility(View.INVISIBLE);
} }
}); });
} }
private TransactionInfo findNewestVersionOfTransaction(TransactionInfo oldTransactionInfo, List<TransactionInfo> transactionInfoList) {
for(TransactionInfo transactionInfo : transactionInfoList) {
if(transactionInfo.hash.equals(oldTransactionInfo.hash)) {
this.transactionInfo = transactionInfo;
return this.transactionInfo;
}
}
return null;
}
private String getDateTime(long time) { private String getDateTime(long time) {
return DATETIME_FORMATTER.format(new Date(time * 1000)); return DATETIME_FORMATTER.format(new Date(time * 1000));
} }

View File

@ -9,25 +9,4 @@ import net.mynero.wallet.model.Wallet;
import net.mynero.wallet.model.WalletManager; import net.mynero.wallet.model.WalletManager;
public class TransactionViewModel extends ViewModel { public class TransactionViewModel extends ViewModel {
private final MutableLiveData<TransactionInfo> _transaction = new MutableLiveData<>(null);
private final MutableLiveData<String> _destination = new MutableLiveData<>(null);
public LiveData<TransactionInfo> transaction = _transaction;
public LiveData<String> destination = _destination;
public void init(TransactionInfo info) {
Wallet wallet = WalletManager.getInstance().getWallet();
if (info.txKey == null) {
info.txKey = wallet.getTxKey(info.hash);
}
if (info.address == null && info.direction == TransactionInfo.Direction.Direction_In) {
_destination.setValue(wallet.getSubaddress(info.accountIndex, info.addressIndex));
} else if (info.address != null && info.direction == TransactionInfo.Direction.Direction_In) {
_destination.setValue(info.address);
} else if (info.transfers != null && info.direction == TransactionInfo.Direction.Direction_Out) {
if (info.transfers.size() == 1) {
_destination.setValue(info.transfers.get(0).address);
}
}
this._transaction.setValue(info);
}
} }

View File

@ -9,7 +9,7 @@ import net.mynero.wallet.model.WalletManager;
import java.util.List; import java.util.List;
public class HistoryService extends ServiceBase { public class HistoryService extends ServiceBase {
public static HistoryService instance = null; private static HistoryService instance = null;
private final MutableLiveData<List<TransactionInfo>> _history = new MutableLiveData<>(); private final MutableLiveData<List<TransactionInfo>> _history = new MutableLiveData<>();
public LiveData<List<TransactionInfo>> history = _history; public LiveData<List<TransactionInfo>> history = _history;
@ -26,7 +26,7 @@ public class HistoryService extends ServiceBase {
_history.postValue(getHistory()); _history.postValue(getHistory());
} }
public List<TransactionInfo> getHistory() { private List<TransactionInfo> getHistory() {
return WalletManager.getInstance().getWallet().getHistory().getAll(); return WalletManager.getInstance().getWallet().getHistory().getAll();
} }
} }

View File

@ -17,9 +17,7 @@
package net.mynero.wallet.service; package net.mynero.wallet.service;
import net.mynero.wallet.data.DefaultNodes;
import net.mynero.wallet.data.Node; import net.mynero.wallet.data.Node;
import net.mynero.wallet.data.TxData;
import net.mynero.wallet.model.CoinsInfo; import net.mynero.wallet.model.CoinsInfo;
import net.mynero.wallet.model.PendingTransaction; import net.mynero.wallet.model.PendingTransaction;
import net.mynero.wallet.model.TransactionOutput; import net.mynero.wallet.model.TransactionOutput;
@ -57,7 +55,7 @@ public class MoneroHandlerThread extends Thread implements WalletListener {
@Override @Override
public synchronized void start() { public synchronized void start() {
super.start(); super.start();
this.listener.onRefresh(); this.listener.onRefresh(false);
} }
@Override @Override
@ -120,12 +118,12 @@ public class MoneroHandlerThread extends Thread implements WalletListener {
BlockchainService.getInstance().setConnectionStatus(status); BlockchainService.getInstance().setConnectionStatus(status);
} }
private void refresh(boolean refreshCoins) { private void refresh(boolean walletSynced) {
wallet.refreshHistory(); wallet.refreshHistory();
if(refreshCoins) { if(walletSynced) {
wallet.refreshCoins(); wallet.refreshCoins();
} }
listener.onRefresh(); listener.onRefresh(walletSynced);
} }
public PendingTransaction createTx(String address, String amountStr, boolean sendAll, PendingTransaction.Priority feePriority, ArrayList<String> selectedUtxos) throws Exception { public PendingTransaction createTx(String address, String amountStr, boolean sendAll, PendingTransaction.Priority feePriority, ArrayList<String> selectedUtxos) throws Exception {
@ -146,7 +144,7 @@ public class MoneroHandlerThread extends Thread implements WalletListener {
ArrayList<String> preferredInputs; ArrayList<String> preferredInputs;
if (selectedUtxos.isEmpty()) { if (selectedUtxos.isEmpty()) {
// no inputs manually selected, we are sending from home screen most likely, or user somehow broke the app // no inputs manually selected, we are sending from home screen most likely, or user somehow broke the app
preferredInputs = UTXOService.getInstance().selectUtxos(totalAmount, sendAll); preferredInputs = UTXOService.getInstance().selectUtxos(totalAmount, sendAll, feePriority);
} else { } else {
preferredInputs = selectedUtxos; preferredInputs = selectedUtxos;
checkSelectedAmounts(preferredInputs, totalAmount, sendAll); checkSelectedAmounts(preferredInputs, totalAmount, sendAll);
@ -233,7 +231,7 @@ public class MoneroHandlerThread extends Thread implements WalletListener {
} }
public interface Listener { public interface Listener {
void onRefresh(); void onRefresh(boolean walletSynced);
void onConnectionFail(); void onConnectionFail();
} }
} }

View File

@ -5,7 +5,6 @@ import android.util.Pair;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import net.mynero.wallet.model.Coins;
import net.mynero.wallet.model.CoinsInfo; import net.mynero.wallet.model.CoinsInfo;
import net.mynero.wallet.model.PendingTransaction; import net.mynero.wallet.model.PendingTransaction;
import net.mynero.wallet.model.Wallet; import net.mynero.wallet.model.Wallet;
@ -22,6 +21,7 @@ import java.util.List;
public class UTXOService extends ServiceBase { public class UTXOService extends ServiceBase {
public static UTXOService instance = null; public static UTXOService instance = null;
private List<CoinsInfo> internalCachedUtxos = new ArrayList<>();
private final MutableLiveData<List<CoinsInfo>> _utxos = new MutableLiveData<>(); private final MutableLiveData<List<CoinsInfo>> _utxos = new MutableLiveData<>();
public LiveData<List<CoinsInfo>> utxos = _utxos; public LiveData<List<CoinsInfo>> utxos = _utxos;
private ArrayList<String> frozenCoins = new ArrayList<>(); private ArrayList<String> frozenCoins = new ArrayList<>();
@ -41,12 +41,13 @@ public class UTXOService extends ServiceBase {
} }
public void refreshUtxos() { public void refreshUtxos() {
_utxos.postValue(getUtxosInternal()); List<CoinsInfo> coinsInfos = getUtxosInternal();
_utxos.postValue(coinsInfos);
internalCachedUtxos = coinsInfos;
} }
public List<CoinsInfo> getUtxos() { public List<CoinsInfo> getUtxos() {
List<CoinsInfo> value = utxos.getValue(); return Collections.unmodifiableList(internalCachedUtxos);
return value != null ? value : List.of();
} }
private List<CoinsInfo> getUtxosInternal() { private List<CoinsInfo> getUtxosInternal() {
@ -93,12 +94,12 @@ public class UTXOService extends ServiceBase {
prefService.edit().putString(Constants.PREF_FROZEN_COINS, jsonArray.toString()).apply(); prefService.edit().putString(Constants.PREF_FROZEN_COINS, jsonArray.toString()).apply();
} }
public ArrayList<String> selectUtxos(long amount, boolean sendAll) throws Exception { public ArrayList<String> selectUtxos(long amount, boolean sendAll, PendingTransaction.Priority feePriority) throws Exception {
final long basicFeeEstimate = calculateBasicFee(amount); final long basicFeeEstimate = calculateBasicFee(amount, feePriority);
final long amountWithBasicFee = amount + basicFeeEstimate; final long amountWithBasicFee = amount + basicFeeEstimate;
ArrayList<String> selectedUtxos = new ArrayList<>(); ArrayList<String> selectedUtxos = new ArrayList<>();
ArrayList<String> seenTxs = new ArrayList<>(); ArrayList<String> seenTxs = new ArrayList<>();
List<CoinsInfo> utxos = getUtxos(); List<CoinsInfo> utxos = new ArrayList<>(getUtxos());
long amountSelected = 0; long amountSelected = 0;
Collections.sort(utxos); Collections.sort(utxos);
//loop through each utxo //loop through each utxo
@ -127,11 +128,10 @@ public class UTXOService extends ServiceBase {
return selectedUtxos; return selectedUtxos;
} }
private long calculateBasicFee(long amount) { private long calculateBasicFee(long amount, PendingTransaction.Priority feePriority) {
ArrayList<Pair<String, Long>> destinations = new ArrayList<>(); ArrayList<Pair<String, Long>> destinations = new ArrayList<>();
destinations.add(new Pair<>("87MRtZPrWUCVUgcFHdsVb5MoZUcLtqfD3FvQVGwftFb8eSdMnE39JhAJcbuSW8X2vRaRsB9RQfuCpFciybJFHaz3QYPhCLw", amount)); destinations.add(new Pair<>("87MRtZPrWUCVUgcFHdsVb5MoZUcLtqfD3FvQVGwftFb8eSdMnE39JhAJcbuSW8X2vRaRsB9RQfuCpFciybJFHaz3QYPhCLw", amount));
// destination string doesn't actually matter here, so i'm using the donation address. amount also technically doesn't matter // destination string doesn't actually matter here, so i'm using the donation address. amount also technically doesn't matter
// priority also isn't accounted for in the Monero C++ code. maybe this is a bug by the core Monero team, or i'm using an outdated method. return WalletManager.getInstance().getWallet().estimateTransactionFee(destinations, feePriority);
return WalletManager.getInstance().getWallet().estimateTransactionFee(destinations, PendingTransaction.Priority.Priority_Low);
} }
} }

View File

@ -45,15 +45,13 @@
android:id="@+id/paste_address_imagebutton" android:id="@+id/paste_address_imagebutton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:minWidth="48dp" android:minWidth="48dp"
android:minHeight="48dp" android:minHeight="48dp"
android:src="@drawable/ic_content_paste_24dp" android:src="@drawable/ic_content_paste_24dp"
app:layout_constraintBottom_toBottomOf="@id/address_edittext" app:layout_constraintBottom_toBottomOf="@id/address_edittext"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/address_edittext" app:layout_constraintStart_toEndOf="@id/node_port_edittext"
app:layout_constraintTop_toTopOf="@id/address_edittext" app:layout_constraintTop_toTopOf="@id/address_edittext"
tools:ignore="SpeakableTextPresentCheck" /> tools:ignore="SpeakableTextPresentCheck" />
@ -61,16 +59,30 @@
android:id="@+id/address_edittext" android:id="@+id/address_edittext"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:background="@drawable/edittext_bg" android:background="@drawable/edittext_bg"
android:hint="@string/node_address_hint" android:hint="@string/node_address_hint"
android:inputType="text" android:inputType="text"
android:digits="-QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890.:" android:digits="QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890.:-[]"
app:layout_constraintBottom_toTopOf="@id/username_edittext" app:layout_constraintBottom_toTopOf="@id/username_edittext"
app:layout_constraintEnd_toStartOf="@id/paste_address_imagebutton" app:layout_constraintEnd_toStartOf="@id/node_port_edittext"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
<EditText
android:id="@+id/node_port_edittext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:background="@drawable/edittext_bg"
android:hint="@string/node_port_hint"
android:inputType="number"
android:digits="-QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890.:"
app:layout_constraintTop_toTopOf="@id/address_edittext"
app:layout_constraintBottom_toBottomOf="@id/address_edittext"
app:layout_constraintEnd_toStartOf="@id/paste_address_imagebutton"
app:layout_constraintStart_toEndOf="@id/address_edittext" />
<Button <Button
android:id="@+id/add_node_button" android:id="@+id/add_node_button"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -85,7 +97,6 @@
android:id="@+id/username_edittext" android:id="@+id/username_edittext"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:background="@drawable/edittext_bg" android:background="@drawable/edittext_bg"
android:hint="@string/node_username_hint" android:hint="@string/node_username_hint"
@ -98,8 +109,6 @@
android:id="@+id/paste_username_imagebutton" android:id="@+id/paste_username_imagebutton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:minWidth="48dp" android:minWidth="48dp"
android:minHeight="48dp" android:minHeight="48dp"

View File

@ -0,0 +1,73 @@
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/oled_dialogBackgroundColor">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/enter_password_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:text="@string/edit_address_label"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginTop="24dp"
android:textSize="32sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/wallet_password_edittext"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/wallet_password_edittext"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:background="@drawable/edittext_bg"
android:layout_marginStart="24dp"
android:hint="@string/label"
android:inputType="text"
app:layout_constraintBottom_toTopOf="@id/unlock_wallet_button"
app:layout_constraintEnd_toStartOf="@id/paste_password_imagebutton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/enter_password_textview" />
<ImageButton
android:id="@+id/paste_password_imagebutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="24dp"
android:background="@android:color/transparent"
android:minWidth="48dp"
android:minHeight="48dp"
android:src="@drawable/ic_content_paste_24dp"
app:layout_constraintBottom_toBottomOf="@id/wallet_password_edittext"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/wallet_password_edittext"
app:layout_constraintTop_toTopOf="@id/wallet_password_edittext" />
<Button
android:id="@+id/unlock_wallet_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/button_bg"
android:text="@string/save"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/wallet_password_edittext" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@ -45,15 +45,13 @@
android:id="@+id/paste_address_imagebutton" android:id="@+id/paste_address_imagebutton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:minWidth="48dp" android:minWidth="48dp"
android:minHeight="48dp" android:minHeight="48dp"
android:src="@drawable/ic_content_paste_24dp" android:src="@drawable/ic_content_paste_24dp"
app:layout_constraintBottom_toBottomOf="@id/address_edittext" app:layout_constraintBottom_toBottomOf="@id/address_edittext"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/address_edittext" app:layout_constraintStart_toEndOf="@id/node_port_edittext"
app:layout_constraintTop_toTopOf="@id/address_edittext" app:layout_constraintTop_toTopOf="@id/address_edittext"
tools:ignore="SpeakableTextPresentCheck" /> tools:ignore="SpeakableTextPresentCheck" />
@ -61,20 +59,34 @@
android:id="@+id/address_edittext" android:id="@+id/address_edittext"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="8dp" android:layout_marginBottom="16dp"
android:background="@drawable/edittext_bg" android:background="@drawable/edittext_bg"
android:hint="@string/node_address_hint" android:hint="@string/node_address_hint"
android:inputType="text" android:inputType="text"
android:digits="-QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890.:" android:digits="QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890.:-[]"
app:layout_constraintBottom_toTopOf="@id/username_edittext" app:layout_constraintBottom_toTopOf="@id/username_edittext"
app:layout_constraintEnd_toStartOf="@id/paste_address_imagebutton" app:layout_constraintEnd_toStartOf="@id/node_port_edittext"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
<EditText
android:id="@+id/node_port_edittext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:background="@drawable/edittext_bg"
android:hint="@string/node_port_hint"
android:inputType="number"
android:digits="-QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890.:"
app:layout_constraintTop_toTopOf="@id/address_edittext"
app:layout_constraintBottom_toBottomOf="@id/address_edittext"
app:layout_constraintEnd_toStartOf="@id/paste_address_imagebutton"
app:layout_constraintStart_toEndOf="@id/address_edittext" />
<EditText <EditText
android:id="@+id/username_edittext" android:id="@+id/username_edittext"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:background="@drawable/edittext_bg" android:background="@drawable/edittext_bg"
android:hint="@string/node_username_hint" android:hint="@string/node_username_hint"
@ -87,8 +99,6 @@
android:id="@+id/paste_username_imagebutton" android:id="@+id/paste_username_imagebutton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:minWidth="48dp" android:minWidth="48dp"
android:minHeight="48dp" android:minHeight="48dp"
@ -136,7 +146,7 @@
android:id="@+id/delete_node_button" android:id="@+id/delete_node_button"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="32dp"
android:layout_marginEnd="1dp" android:layout_marginEnd="1dp"
android:background="@drawable/button_bg_left" android:background="@drawable/button_bg_left"
android:text="@string/delete" android:text="@string/delete"
@ -147,7 +157,7 @@
android:id="@+id/done_editing_button" android:id="@+id/done_editing_button"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="32dp"
android:layout_marginStart="1dp" android:layout_marginStart="1dp"
android:background="@drawable/button_bg_right" android:background="@drawable/button_bg_right"
android:text="@string/done" android:text="@string/done"

View File

@ -8,143 +8,54 @@
tools:context="net.mynero.wallet.fragment.settings.SettingsFragment"> tools:context="net.mynero.wallet.fragment.settings.SettingsFragment">
<TextView <TextView
android:id="@+id/transaction_title_textview" android:id="@+id/transaction_action_textview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="32dp" android:layout_marginBottom="32dp"
android:text="@string/transaction" android:text="@string/transaction_action_recv"
android:textSize="32sp" android:textSize="42sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/transaction_hash_label_textview" android:textColor="@color/oled_addressListColor"
app:layout_constraintBottom_toTopOf="@id/transaction_amount_textview"
app:layout_constraintEnd_toEndOf="parent" 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 <TextView
android:id="@+id/transaction_hash_label_textview" android:id="@+id/transaction_amount_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/transaction_hash"
android:textSize="18sp"
android:layout_marginTop="16dp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/transaction_title_textview"/>
<TextView
android:id="@+id/transaction_hash_textview"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/transaction_hash"
android:textSize="14sp"
android:layout_marginTop="8dp"
android:textStyle="bold"
android:singleLine="true"
android:ellipsize="middle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/copy_txhash_imagebutton"
app:layout_constraintTop_toTopOf="@id/copy_txhash_imagebutton"
app:layout_constraintBottom_toBottomOf="@id/copy_txhash_imagebutton"/>
<ImageButton
android:id="@+id/copy_txhash_imagebutton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@android:color/transparent" android:text="@string/tx_amount_no_prefix2"
android:minWidth="48dp" android:textSize="24sp"
android:minHeight="48dp"
android:src="@drawable/ic_content_copy_24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/transaction_hash_textview"
app:layout_constraintTop_toBottomOf="@id/transaction_hash_label_textview" />
<TextView
android:id="@+id/transaction_conf_label_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/confirmations"
android:textSize="18sp"
android:layout_marginTop="16dp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/copy_txhash_imagebutton"/>
<TextView
android:id="@+id/transaction_conf_textview"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="0"
android:textSize="14sp"
android:layout_marginTop="8dp"
android:textStyle="bold" android:textStyle="bold"
android:singleLine="true" android:singleLine="true"
android:ellipsize="middle" android:ellipsize="middle"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/transaction_action_textview"/>
app:layout_constraintTop_toBottomOf="@id/transaction_conf_label_textview"/>
<TextView <TextView
android:id="@+id/transaction_block_height_label_textview" android:id="@+id/transaction_amount_units_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/block_height"
android:textSize="18sp"
android:layout_marginTop="16dp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/transaction_conf_textview"/>
<TextView
android:id="@+id/tx_block_height_textview"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="0" android:text="XMR"
android:textSize="14sp" android:layout_marginStart="8dp"
android:layout_marginTop="8dp" android:textSize="24sp"
android:textStyle="bold" android:textStyle="bold"
android:singleLine="true" android:singleLine="true"
android:ellipsize="middle" android:ellipsize="middle"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toEndOf="@id/transaction_amount_textview"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/transaction_block_height_label_textview"/> app:layout_constraintTop_toBottomOf="@id/transaction_action_textview"/>
<TextView
android:id="@+id/transaction_amount_label_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/amount_label"
android:textSize="18sp"
android:layout_marginTop="16dp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/tx_block_height_textview"/>
<TextView
android:id="@+id/transaction_amount_textview"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/tx_amount_no_prefix"
android:textSize="14sp"
android:layout_marginTop="8dp"
android:textStyle="bold"
android:singleLine="true"
android:ellipsize="middle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/transaction_amount_label_textview"/>
<TextView <TextView
android:id="@+id/transaction_address_label_textview" android:id="@+id/transaction_address_label_textview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/transaction_destination" android:text="@string/transaction_destination_desc"
android:textSize="18sp" android:textSize="28sp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:textStyle="bold" android:textStyle="bold"
android:textColor="@color/oled_addressListColor"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/transaction_amount_textview"/> app:layout_constraintTop_toBottomOf="@id/transaction_amount_textview"/>
@ -153,9 +64,8 @@
android:id="@+id/transaction_address_textview" android:id="@+id/transaction_address_textview"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/transaction_hash" android:text="@string/transaction_destination"
android:textSize="14sp" android:textSize="24sp"
android:layout_marginTop="8dp"
android:textStyle="bold" android:textStyle="bold"
android:singleLine="true" android:singleLine="true"
android:ellipsize="middle" android:ellipsize="middle"
@ -177,24 +87,63 @@
app:layout_constraintTop_toBottomOf="@id/transaction_address_label_textview" /> app:layout_constraintTop_toBottomOf="@id/transaction_address_label_textview" />
<TextView <TextView
android:id="@+id/transaction_date_label_textview" android:id="@+id/transaction_hash_label_textview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/date" android:text="@string/transaction_hash_desc"
android:textSize="18sp" android:textSize="28sp"
android:layout_marginTop="16dp" android:layout_marginTop="8dp"
android:textStyle="bold" android:textStyle="bold"
android:textColor="@color/oled_addressListColor"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/copy_txaddress_imagebutton"/> app:layout_constraintTop_toBottomOf="@id/copy_txaddress_imagebutton"/>
<TextView
android:id="@+id/transaction_hash_textview"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/transaction_hash"
android:textSize="24sp"
android:textStyle="bold"
android:singleLine="true"
android:ellipsize="middle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/copy_txhash_imagebutton"
app:layout_constraintTop_toTopOf="@id/copy_txhash_imagebutton"
app:layout_constraintBottom_toBottomOf="@id/copy_txhash_imagebutton"/>
<ImageButton
android:id="@+id/copy_txhash_imagebutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:minWidth="48dp"
android:minHeight="48dp"
android:src="@drawable/ic_content_copy_24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/transaction_hash_textview"
app:layout_constraintTop_toBottomOf="@id/transaction_hash_label_textview" />
<TextView
android:id="@+id/transaction_date_label_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/transaction_on_date_label"
android:textSize="28sp"
android:textStyle="bold"
android:textColor="@color/oled_addressListColor"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/copy_txhash_imagebutton"/>
<TextView <TextView
android:id="@+id/transaction_date_textview" android:id="@+id/transaction_date_textview"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="0" android:text="0"
android:textSize="14sp" android:textSize="24sp"
android:layout_marginTop="8dp"
android:textStyle="bold" android:textStyle="bold"
android:singleLine="true" android:singleLine="true"
android:ellipsize="middle" android:ellipsize="middle"
@ -202,4 +151,55 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/transaction_date_label_textview"/> app:layout_constraintTop_toBottomOf="@id/transaction_date_label_textview"/>
<TextView
android:id="@+id/transaction_conf_label_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/transaction_conf_desc"
android:textSize="28sp"
android:layout_marginTop="16dp"
android:textStyle="bold"
android:textColor="@color/oled_addressListColor"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/transaction_date_textview"/>
<TextView
android:id="@+id/transaction_conf_textview"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="0"
android:textSize="24sp"
android:textStyle="bold"
android:singleLine="true"
android:ellipsize="middle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/transaction_conf_label_textview"/>
<TextView
android:id="@+id/transaction_conf_label2_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/transaction_conf_desc2_confirmed"
android:textSize="28sp"
android:textStyle="bold"
android:textColor="@color/oled_addressListColor"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/transaction_conf_textview"/>
<TextView
android:id="@+id/tx_block_height_textview"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="0"
android:textSize="24sp"
android:textStyle="bold"
android:singleLine="true"
android:ellipsize="middle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/transaction_conf_label2_textview"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -52,8 +52,11 @@
<string name="password_optional">Password (optional)</string> <string name="password_optional">Password (optional)</string>
<string name="password_confirm">Confirm password</string> <string name="password_confirm">Confirm password</string>
<string name="password">Password</string> <string name="password">Password</string>
<string name="label">Label</string>
<string name="unlock">Unlock</string> <string name="unlock">Unlock</string>
<string name="save">Save</string>
<string name="enter_password">Enter password</string> <string name="enter_password">Enter password</string>
<string name="edit_address_label">Set address label</string>
<string name="tx_address_text">Address: %1$s</string> <string name="tx_address_text">Address: %1$s</string>
<string name="tx_amount_text">Amount: %1$s XMR</string> <string name="tx_amount_text">Amount: %1$s XMR</string>
<string name="tx_fee_text">Fee: %1$s XMR</string> <string name="tx_fee_text">Fee: %1$s XMR</string>
@ -70,13 +73,16 @@
<string name="add_node">Add Node</string> <string name="add_node">Add Node</string>
<string name="nodes">Nodes</string> <string name="nodes">Nodes</string>
<string name="node_name_hint">My Monero Node</string> <string name="node_name_hint">My Monero Node</string>
<string name="node_address_hint">127.0.0.1:18081</string> <string name="node_address_hint">127.0.0.1</string>
<string name="node_port_hint">18081</string>
<string name="node_username_hint">Username (optional)</string> <string name="node_username_hint">Username (optional)</string>
<string name="node_password_hint">Password</string> <string name="node_password_hint">Password</string>
<string name="transaction">Transaction</string> <string name="transaction">Transaction</string>
<string name="transaction_action_sent">You sent</string>
<string name="transaction_action_recv">You received</string>
<string name="amount_label">Amount</string> <string name="amount_label">Amount</string>
<string name="tx_amount_no_prefix">%1$s XMR</string> <string name="tx_amount_no_prefix">%1$s XMR</string>
<string name="tx_amount_no_prefix2">999.99999999999</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="no_history_loading">Loading your wallet…</string> <string name="no_history_loading">Loading your wallet…</string>
@ -86,9 +92,16 @@
<string name="disconnected">Disconnected</string> <string name="disconnected">Disconnected</string>
<string name="version_mismatch">Version mismatch</string> <string name="version_mismatch">Version mismatch</string>
<string name="transaction_hash">Transaction Hash</string> <string name="transaction_hash">Transaction Hash</string>
<string name="transaction_hash_desc">in a transaction with id</string>
<string name="transaction_destination">Destination</string> <string name="transaction_destination">Destination</string>
<string name="transaction_destination_desc">to address</string>
<string name="confirmations">Confirmations</string> <string name="confirmations">Confirmations</string>
<string name="transaction_conf_desc">It has been confirmed</string>
<string name="transaction_conf_desc2_confirmed">times, and was mined in block</string>
<string name="transaction_conf_desc2_unconfirmed">times, and is currently in the mempool</string>
<string name="transaction_conf_1_desc2_confirmed">time, and was mined in block</string>
<string name="date">Date</string> <string name="date">Date</string>
<string name="transaction_on_date_label">on</string>
<string name="node_selected">Node has been selected</string> <string name="node_selected">Node has been selected</string>
<string name="fee_priority">Fee priority:</string> <string name="fee_priority">Fee priority:</string>
<string name="low">Low</string> <string name="low">Low</string>