diff --git a/app/build.gradle b/app/build.gradle index e446c9a..b6c7b11 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "net.mynero.wallet" minSdkVersion 21 targetSdkVersion 34 - versionCode 40500 - versionName "0.4.5 'Fluorine Fermi'" + versionCode 40701 + versionName "0.4.7.1 'Fluorine Fermi'" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { @@ -25,26 +25,13 @@ android { } } - flavorDimensions 'type', 'net' + flavorDimensions 'type' productFlavors { - mainnet { - dimension 'net' - } beta { - dimension 'net' + dimension 'type' applicationIdSuffix '.beta' versionNameSuffix ' (beta)' } - devnet { - dimension 'net' - applicationIdSuffix '.test' - versionNameSuffix ' (test)' - } - alpha { - dimension 'type' - applicationIdSuffix '.alpha' - versionNameSuffix ' (alpha)' - } prod { dimension 'type' } diff --git a/app/src/main/java/net/mynero/wallet/MainActivity.java b/app/src/main/java/net/mynero/wallet/MainActivity.java index 0735a03..700a255 100644 --- a/app/src/main/java/net/mynero/wallet/MainActivity.java +++ b/app/src/main/java/net/mynero/wallet/MainActivity.java @@ -108,8 +108,10 @@ public class MainActivity extends AppCompatActivity implements MoneroHandlerThre } @Override - public void onRefresh() { - this.utxoService.refreshUtxos(); + public void onRefresh(boolean walletSynced) { + if(walletSynced) + this.utxoService.refreshUtxos(); + this.historyService.refreshHistory(); this.balanceService.refreshBalance(); this.blockchainService.refreshBlockchain(); diff --git a/app/src/main/java/net/mynero/wallet/adapter/SubaddressAdapter.java b/app/src/main/java/net/mynero/wallet/adapter/SubaddressAdapter.java index 377c0ee..eab52fa 100644 --- a/app/src/main/java/net/mynero/wallet/adapter/SubaddressAdapter.java +++ b/app/src/main/java/net/mynero/wallet/adapter/SubaddressAdapter.java @@ -78,6 +78,8 @@ public class SubaddressAdapter extends RecyclerView.Adapter listener.onSubaddressSelected(subaddress)); + itemView.setOnLongClickListener(v -> { + listener.onSubaddressEditLabel(subaddress); + return true; + }); } } } diff --git a/app/src/main/java/net/mynero/wallet/data/Node.java b/app/src/main/java/net/mynero/wallet/data/Node.java index 328a3e5..43f7070 100644 --- a/app/src/main/java/net/mynero/wallet/data/Node.java +++ b/app/src/main/java/net/mynero/wallet/data/Node.java @@ -351,13 +351,17 @@ public class Node { } public String getAddress() { - return getHost() + ":" + rpcPort; + return getHost() + ":" + getRpcPort(); } public String getHost() { return host; } + public int getRpcPort() { + return rpcPort; + } + public void setHost(String host) throws UnknownHostException { if ((host == null) || (host.isEmpty())) throw new UnknownHostException("loopback not supported (yet?)"); diff --git a/app/src/main/java/net/mynero/wallet/fragment/dialog/AddNodeBottomSheetDialog.java b/app/src/main/java/net/mynero/wallet/fragment/dialog/AddNodeBottomSheetDialog.java index 470d75c..a460d6f 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/dialog/AddNodeBottomSheetDialog.java +++ b/app/src/main/java/net/mynero/wallet/fragment/dialog/AddNodeBottomSheetDialog.java @@ -41,6 +41,7 @@ public class AddNodeBottomSheetDialog extends BottomSheetDialogFragment { super.onViewCreated(view, savedInstanceState); Button addNodeButton = view.findViewById(R.id.add_node_button); 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 usernameEditText = view.findViewById(R.id.username_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); addNodeButton.setOnClickListener(view1 -> { - String node = addressEditText.getText().toString(); + String nodeAddr = addressEditText.getText().toString(); + String portString = portEditText.getText().toString(); String name = nodeNameEditText.getText().toString(); String user = usernameEditText.getText().toString(); String pass = passwordEditText.getText().toString(); if(name.isEmpty()) { Toast.makeText(getContext(), "Enter node name", Toast.LENGTH_SHORT).show(); return; - } else if(node.isEmpty()) { + } else if(nodeAddr.isEmpty() || portString.isEmpty()) { Toast.makeText(getContext(), "Enter node address", Toast.LENGTH_SHORT).show(); return; } else if(!user.isEmpty() && pass.isEmpty()) { @@ -84,14 +86,16 @@ public class AddNodeBottomSheetDialog extends BottomSheetDialogFragment { JSONObject jsonObject = new JSONObject(); try { - if (!user.isEmpty() && !pass.isEmpty()) { + if (!user.isEmpty()) { jsonObject.put("username", user); jsonObject.put("password", pass); } - String[] nodeParts = node.split(":"); - jsonObject.put("host", nodeParts[0]); - jsonObject.put("rpcPort", nodeParts[1]); + if (nodeAddr.contains(":") && !nodeAddr.startsWith("[") && !nodeAddr.endsWith("]")) + nodeAddr = "[" + nodeAddr + "]"; + + jsonObject.put("host", nodeAddr); + jsonObject.put("rpcPort", Integer.parseInt(portString)); jsonObject.put("network", "mainnet"); jsonObject.put("name", name); diff --git a/app/src/main/java/net/mynero/wallet/fragment/dialog/EditAddressLabelBottomSheetDialog.java b/app/src/main/java/net/mynero/wallet/fragment/dialog/EditAddressLabelBottomSheetDialog.java new file mode 100644 index 0000000..f99ae20 --- /dev/null +++ b/app/src/main/java/net/mynero/wallet/fragment/dialog/EditAddressLabelBottomSheetDialog.java @@ -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(); + } +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/fragment/dialog/EditNodeBottomSheetDialog.java b/app/src/main/java/net/mynero/wallet/fragment/dialog/EditNodeBottomSheetDialog.java index b9c7593..88db903 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/dialog/EditNodeBottomSheetDialog.java +++ b/app/src/main/java/net/mynero/wallet/fragment/dialog/EditNodeBottomSheetDialog.java @@ -35,7 +35,7 @@ import timber.log.Timber; public class EditNodeBottomSheetDialog extends BottomSheetDialogFragment { public EditNodeListener listener = null; - public JSONObject nodeJson = null; + public Node node = null; @Override 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 doneEditingButton = view.findViewById(R.id.done_editing_button); 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 usernameEditText = view.findViewById(R.id.username_edittext); EditText passwordEditText = view.findViewById(R.id.password_edittext); ImageButton pastePasswordImageButton = view.findViewById(R.id.paste_password_imagebutton); - Node node = Node.fromJson(nodeJson); - addressEditText.setText(node.getAddress()); + if(node == null) return; + addressEditText.setText(node.getHost()); + portEditText.setText(""+node.getRpcPort()); nodeNameEditText.setText(node.getName()); usernameEditText.setText(node.getUsername()); if(!node.getPassword().isEmpty()) { @@ -84,11 +86,12 @@ public class EditNodeBottomSheetDialog extends BottomSheetDialogFragment { addPasteListener(view, passwordEditText, R.id.paste_password_imagebutton); deleteNodeButton.setOnClickListener(view1 -> { - listener.onNodeDeleted(Node.fromJson(nodeJson)); + listener.onNodeDeleted(node); dismiss(); }); doneEditingButton.setOnClickListener(view1 -> { - String nodeAddress = addressEditText.getText().toString(); + String nodeAddr = addressEditText.getText().toString(); + String portString = portEditText.getText().toString(); String nodeName = nodeNameEditText.getText().toString(); String user = usernameEditText.getText().toString(); String pass = passwordEditText.getText().toString(); @@ -96,7 +99,7 @@ public class EditNodeBottomSheetDialog extends BottomSheetDialogFragment { if(nodeName.isEmpty()) { Toast.makeText(getContext(), "Enter node name", Toast.LENGTH_SHORT).show(); return; - } else if(nodeAddress.isEmpty()) { + } else if(nodeAddr.isEmpty() || portString.isEmpty()) { Toast.makeText(getContext(), "Enter node address", Toast.LENGTH_SHORT).show(); return; } else if(!user.isEmpty() && pass.isEmpty()) { @@ -105,18 +108,20 @@ public class EditNodeBottomSheetDialog extends BottomSheetDialogFragment { } JSONObject jsonObject = new JSONObject(); try { - if (!user.isEmpty() && !pass.isEmpty()) { + if (!user.isEmpty()) { jsonObject.put("username", user); jsonObject.put("password", pass); } - String[] nodeParts = nodeAddress.split(":"); - jsonObject.put("host", nodeParts[0]); - jsonObject.put("rpcPort", nodeParts[1]); + if (nodeAddr.contains(":") && !nodeAddr.startsWith("[") && !nodeAddr.endsWith("]")) + nodeAddr = "[" + nodeAddr + "]"; + + jsonObject.put("host", nodeAddr); + jsonObject.put("rpcPort", Integer.parseInt(portString)); jsonObject.put("network", "mainnet"); jsonObject.put("name", nodeName); - listener.onNodeEdited(Node.fromJson(nodeJson), Node.fromJson(jsonObject)); + listener.onNodeEdited(node, Node.fromJson(jsonObject)); dismiss(); } catch (JSONException e) { throw new RuntimeException(e); diff --git a/app/src/main/java/net/mynero/wallet/fragment/dialog/NodeSelectionBottomSheetDialog.java b/app/src/main/java/net/mynero/wallet/fragment/dialog/NodeSelectionBottomSheetDialog.java index eee0573..4d8df07 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/dialog/NodeSelectionBottomSheetDialog.java +++ b/app/src/main/java/net/mynero/wallet/fragment/dialog/NodeSelectionBottomSheetDialog.java @@ -114,7 +114,7 @@ public class NodeSelectionBottomSheetDialog extends BottomSheetDialogFragment im @Override public boolean onSelectEditNode(Node node) { if (listener != null) { - listener.onClickedEditNode(node.toJson()); + listener.onClickedEditNode(node); } dismiss(); return true; @@ -122,7 +122,7 @@ public class NodeSelectionBottomSheetDialog extends BottomSheetDialogFragment im public interface NodeSelectionDialogListener { void onNodeSelected(); - void onClickedEditNode(JSONObject nodeJson); + void onClickedEditNode(Node node); void onClickedAddNode(); } } \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/fragment/home/HomeFragment.java b/app/src/main/java/net/mynero/wallet/fragment/home/HomeFragment.java index 50606f7..edfa4fb 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/home/HomeFragment.java +++ b/app/src/main/java/net/mynero/wallet/fragment/home/HomeFragment.java @@ -120,7 +120,7 @@ public class HomeFragment extends Fragment implements TransactionInfoAdapter.TxI if (height > 1 && daemonHeight > 1) { progressBar.setProgress(x); progressBarText.setVisibility(View.VISIBLE); - progressBarText.setText("Syncing... " + height + " / " + daemonHeight); + progressBarText.setText("Syncing... " + n + " blocks remaining"); } else { progressBarText.setVisibility(View.GONE); } diff --git a/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingFragment.java b/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingFragment.java index 66e9878..f7a8c33 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingFragment.java +++ b/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingFragment.java @@ -285,7 +285,7 @@ public class OnboardingFragment extends Fragment implements NodeSelectionBottomS } @Override - public void onClickedEditNode(JSONObject nodeJson) { } + public void onClickedEditNode(Node node) { } @Override public void onClickedAddNode() { diff --git a/app/src/main/java/net/mynero/wallet/fragment/receive/ReceiveFragment.java b/app/src/main/java/net/mynero/wallet/fragment/receive/ReceiveFragment.java index 05202d5..479e9e5 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/receive/ReceiveFragment.java +++ b/app/src/main/java/net/mynero/wallet/fragment/receive/ReceiveFragment.java @@ -26,6 +26,7 @@ import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import net.mynero.wallet.R; import net.mynero.wallet.adapter.SubaddressAdapter; import net.mynero.wallet.data.Subaddress; +import net.mynero.wallet.fragment.dialog.EditAddressLabelBottomSheetDialog; import net.mynero.wallet.util.DayNightMode; import net.mynero.wallet.util.Helper; import net.mynero.wallet.util.NightmodeHelper; @@ -70,7 +71,17 @@ public class ReceiveFragment extends Fragment { } 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.setLayoutManager(new LinearLayoutManager(getActivity())); recyclerView.setAdapter(adapter); @@ -78,6 +89,13 @@ public class ReceiveFragment extends Fragment { 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) { final String label = subaddress.getDisplayLabel(); final String address = getContext().getString(R.string.subbaddress_info_subtitle, @@ -86,6 +104,14 @@ public class ReceiveFragment extends Fragment { addressTextView.setText(subaddress.getAddress()); addressImageView.setImageBitmap(generate(subaddress.getAddress(), 256, 256)); 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) { diff --git a/app/src/main/java/net/mynero/wallet/fragment/send/SendFragment.java b/app/src/main/java/net/mynero/wallet/fragment/send/SendFragment.java index 81976ab..fb5e370 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/send/SendFragment.java +++ b/app/src/main/java/net/mynero/wallet/fragment/send/SendFragment.java @@ -434,21 +434,23 @@ public class SendFragment extends Fragment { mViewModel.setPendingTransaction(pendingTx); } else { Activity activity = getActivity(); - if (activity != null) { + if (activity != null && pendingTx != null) { activity.runOnUiThread(() -> { createButton.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) { + e.printStackTrace(); Activity activity = getActivity(); if (activity != null) { activity.runOnUiThread(() -> { createButton.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(); }); } } diff --git a/app/src/main/java/net/mynero/wallet/fragment/settings/SettingsFragment.java b/app/src/main/java/net/mynero/wallet/fragment/settings/SettingsFragment.java index 376ad44..2148ad7 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/settings/SettingsFragment.java +++ b/app/src/main/java/net/mynero/wallet/fragment/settings/SettingsFragment.java @@ -248,10 +248,10 @@ public class SettingsFragment extends Fragment implements PasswordBottomSheetDia } @Override - public void onClickedEditNode(JSONObject nodeJson) { + public void onClickedEditNode(Node node) { EditNodeBottomSheetDialog editNodeDialog = new EditNodeBottomSheetDialog(); editNodeDialog.listener = this; - editNodeDialog.nodeJson = nodeJson; + editNodeDialog.node = node; editNodeDialog.show(getActivity().getSupportFragmentManager(), "edit_node_dialog"); } diff --git a/app/src/main/java/net/mynero/wallet/fragment/transaction/TransactionFragment.java b/app/src/main/java/net/mynero/wallet/fragment/transaction/TransactionFragment.java index 8a7c6b0..3e1f9f8 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/transaction/TransactionFragment.java +++ b/app/src/main/java/net/mynero/wallet/fragment/transaction/TransactionFragment.java @@ -19,18 +19,22 @@ import androidx.lifecycle.ViewModelProvider; import net.mynero.wallet.R; import net.mynero.wallet.model.TransactionInfo; 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.util.Constants; import net.mynero.wallet.util.Helper; import java.util.Calendar; import java.util.Date; +import java.util.List; import java.util.Objects; import java.util.TimeZone; public class TransactionFragment extends Fragment { private TransactionViewModel mViewModel; + private TransactionInfo transactionInfo = null; @Override 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); Bundle args = getArguments(); if (args != null) { - TransactionInfo txInfo = getArguments().getParcelable(Constants.NAV_ARG_TXINFO); - mViewModel.init(txInfo); + this.transactionInfo = getArguments().getParcelable(Constants.NAV_ARG_TXINFO); } bindObservers(view); @@ -59,25 +62,26 @@ public class TransactionFragment extends Fragment { private void bindListeners(View view) { ImageButton copyTxHashImageButton = view.findViewById(R.id.copy_txhash_imagebutton); copyTxHashImageButton.setOnClickListener(view1 -> { - TransactionInfo txInfo = mViewModel.transaction.getValue(); + TransactionInfo txInfo = this.transactionInfo; if (txInfo != null) { Helper.clipBoardCopy(getContext(), "transaction_hash", txInfo.hash); } }); ImageButton copyTxAddressImageButton = view.findViewById(R.id.copy_txaddress_imagebutton); + TextView addressTextView = view.findViewById(R.id.transaction_address_textview); copyTxAddressImageButton.setOnClickListener(view1 -> { - TransactionInfo txInfo = mViewModel.transaction.getValue(); + TransactionInfo txInfo = this.transactionInfo; if (txInfo != null) { - String destination = mViewModel.destination.getValue(); - if (destination != null) { - Helper.clipBoardCopy(getContext(), "transaction_address", destination); - } + String destination = addressTextView.getText().toString(); + Helper.clipBoardCopy(getContext(), "transaction_address", destination); } }); } 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 txConfTextView = view.findViewById(R.id.transaction_conf_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 txAmountTextView = view.findViewById(R.id.transaction_amount_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 -> { - txHashTextView.setText(transactionInfo.hash); - txConfTextView.setText("" + transactionInfo.confirmations); - txDateTextView.setText(getDateTime(transactionInfo.timestamp)); - if(transactionInfo.confirmations > 0) { - blockHeightTextView.setText("" + transactionInfo.blockheight); + HistoryService.getInstance().history.observe(getViewLifecycleOwner(), transactionInfos -> { + TransactionInfo newTransactionInfo = findNewestVersionOfTransaction(this.transactionInfo, transactionInfos); + if(newTransactionInfo == null) return; + + txHashTextView.setText(newTransactionInfo.hash); + 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); - blockHeightLabelTextView.setVisibility(View.VISIBLE); } else { blockHeightTextView.setVisibility(View.GONE); - blockHeightLabelTextView.setVisibility(View.GONE); + confLabel2.setText(getString(R.string.transaction_conf_desc2_unconfirmed)); } Context ctx = getContext(); if(ctx != null) { boolean streetModeEnabled = PrefService.getInstance().getBoolean(Constants.PREF_STREET_MODE, false); - String balanceString = streetModeEnabled ? Constants.STREET_MODE_BALANCE : Wallet.getDisplayAmount(transactionInfo.amount); - if (transactionInfo.direction == TransactionInfo.Direction.Direction_In) { + String balanceString = streetModeEnabled ? Constants.STREET_MODE_BALANCE : Helper.getDisplayAmount(newTransactionInfo.amount, 12); + 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.setText(getString(R.string.tx_list_amount_positive, balanceString)); } else { + txActionTextView.setText(getString(R.string.transaction_action_sent)); 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(s, "-")); - if (s == null) { + txAddressTextView.setText(Objects.requireNonNullElse(destination, "-")); + if (destination == null) { copyTxAddressImageButton.setVisibility(View.INVISIBLE); } }); } + private TransactionInfo findNewestVersionOfTransaction(TransactionInfo oldTransactionInfo, List transactionInfoList) { + for(TransactionInfo transactionInfo : transactionInfoList) { + if(transactionInfo.hash.equals(oldTransactionInfo.hash)) { + this.transactionInfo = transactionInfo; + return this.transactionInfo; + } + } + return null; + } + private String getDateTime(long time) { return DATETIME_FORMATTER.format(new Date(time * 1000)); } diff --git a/app/src/main/java/net/mynero/wallet/fragment/transaction/TransactionViewModel.java b/app/src/main/java/net/mynero/wallet/fragment/transaction/TransactionViewModel.java index 9f35b33..e86ded1 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/transaction/TransactionViewModel.java +++ b/app/src/main/java/net/mynero/wallet/fragment/transaction/TransactionViewModel.java @@ -9,25 +9,4 @@ import net.mynero.wallet.model.Wallet; import net.mynero.wallet.model.WalletManager; public class TransactionViewModel extends ViewModel { - private final MutableLiveData _transaction = new MutableLiveData<>(null); - private final MutableLiveData _destination = new MutableLiveData<>(null); - public LiveData transaction = _transaction; - public LiveData 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); - } } \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/service/HistoryService.java b/app/src/main/java/net/mynero/wallet/service/HistoryService.java index b874e7a..c02d940 100644 --- a/app/src/main/java/net/mynero/wallet/service/HistoryService.java +++ b/app/src/main/java/net/mynero/wallet/service/HistoryService.java @@ -9,7 +9,7 @@ import net.mynero.wallet.model.WalletManager; import java.util.List; public class HistoryService extends ServiceBase { - public static HistoryService instance = null; + private static HistoryService instance = null; private final MutableLiveData> _history = new MutableLiveData<>(); public LiveData> history = _history; @@ -26,7 +26,7 @@ public class HistoryService extends ServiceBase { _history.postValue(getHistory()); } - public List getHistory() { + private List getHistory() { return WalletManager.getInstance().getWallet().getHistory().getAll(); } } diff --git a/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.java b/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.java index 85dd5a7..dcb322a 100644 --- a/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.java +++ b/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.java @@ -17,9 +17,7 @@ package net.mynero.wallet.service; -import net.mynero.wallet.data.DefaultNodes; import net.mynero.wallet.data.Node; -import net.mynero.wallet.data.TxData; import net.mynero.wallet.model.CoinsInfo; import net.mynero.wallet.model.PendingTransaction; import net.mynero.wallet.model.TransactionOutput; @@ -57,7 +55,7 @@ public class MoneroHandlerThread extends Thread implements WalletListener { @Override public synchronized void start() { super.start(); - this.listener.onRefresh(); + this.listener.onRefresh(false); } @Override @@ -120,12 +118,12 @@ public class MoneroHandlerThread extends Thread implements WalletListener { BlockchainService.getInstance().setConnectionStatus(status); } - private void refresh(boolean refreshCoins) { + private void refresh(boolean walletSynced) { wallet.refreshHistory(); - if(refreshCoins) { + if(walletSynced) { wallet.refreshCoins(); } - listener.onRefresh(); + listener.onRefresh(walletSynced); } public PendingTransaction createTx(String address, String amountStr, boolean sendAll, PendingTransaction.Priority feePriority, ArrayList selectedUtxos) throws Exception { @@ -146,7 +144,7 @@ public class MoneroHandlerThread extends Thread implements WalletListener { ArrayList preferredInputs; if (selectedUtxos.isEmpty()) { // 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 { preferredInputs = selectedUtxos; checkSelectedAmounts(preferredInputs, totalAmount, sendAll); @@ -233,7 +231,7 @@ public class MoneroHandlerThread extends Thread implements WalletListener { } public interface Listener { - void onRefresh(); + void onRefresh(boolean walletSynced); void onConnectionFail(); } } diff --git a/app/src/main/java/net/mynero/wallet/service/UTXOService.java b/app/src/main/java/net/mynero/wallet/service/UTXOService.java index 1215b71..84d0ed3 100644 --- a/app/src/main/java/net/mynero/wallet/service/UTXOService.java +++ b/app/src/main/java/net/mynero/wallet/service/UTXOService.java @@ -5,7 +5,6 @@ import android.util.Pair; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; -import net.mynero.wallet.model.Coins; import net.mynero.wallet.model.CoinsInfo; import net.mynero.wallet.model.PendingTransaction; import net.mynero.wallet.model.Wallet; @@ -22,6 +21,7 @@ import java.util.List; public class UTXOService extends ServiceBase { public static UTXOService instance = null; + private List internalCachedUtxos = new ArrayList<>(); private final MutableLiveData> _utxos = new MutableLiveData<>(); public LiveData> utxos = _utxos; private ArrayList frozenCoins = new ArrayList<>(); @@ -41,12 +41,13 @@ public class UTXOService extends ServiceBase { } public void refreshUtxos() { - _utxos.postValue(getUtxosInternal()); + List coinsInfos = getUtxosInternal(); + _utxos.postValue(coinsInfos); + internalCachedUtxos = coinsInfos; } public List getUtxos() { - List value = utxos.getValue(); - return value != null ? value : List.of(); + return Collections.unmodifiableList(internalCachedUtxos); } private List getUtxosInternal() { @@ -93,12 +94,12 @@ public class UTXOService extends ServiceBase { prefService.edit().putString(Constants.PREF_FROZEN_COINS, jsonArray.toString()).apply(); } - public ArrayList selectUtxos(long amount, boolean sendAll) throws Exception { - final long basicFeeEstimate = calculateBasicFee(amount); + public ArrayList selectUtxos(long amount, boolean sendAll, PendingTransaction.Priority feePriority) throws Exception { + final long basicFeeEstimate = calculateBasicFee(amount, feePriority); final long amountWithBasicFee = amount + basicFeeEstimate; ArrayList selectedUtxos = new ArrayList<>(); ArrayList seenTxs = new ArrayList<>(); - List utxos = getUtxos(); + List utxos = new ArrayList<>(getUtxos()); long amountSelected = 0; Collections.sort(utxos); //loop through each utxo @@ -127,11 +128,10 @@ public class UTXOService extends ServiceBase { return selectedUtxos; } - private long calculateBasicFee(long amount) { + private long calculateBasicFee(long amount, PendingTransaction.Priority feePriority) { ArrayList> destinations = new ArrayList<>(); 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 - // 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, PendingTransaction.Priority.Priority_Low); + return WalletManager.getInstance().getWallet().estimateTransactionFee(destinations, feePriority); } } diff --git a/app/src/main/res/layout/add_node_bottom_sheet_dialog.xml b/app/src/main/res/layout/add_node_bottom_sheet_dialog.xml index f5a8eb3..85c853d 100644 --- a/app/src/main/res/layout/add_node_bottom_sheet_dialog.xml +++ b/app/src/main/res/layout/add_node_bottom_sheet_dialog.xml @@ -45,15 +45,13 @@ 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="48dp" android:minHeight="48dp" 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_constraintStart_toEndOf="@id/node_port_edittext" app:layout_constraintTop_toTopOf="@id/address_edittext" tools:ignore="SpeakableTextPresentCheck" /> @@ -61,16 +59,30 @@ android:id="@+id/address_edittext" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginEnd="8dp" android:layout_marginBottom="16dp" android:background="@drawable/edittext_bg" android:hint="@string/node_address_hint" android:inputType="text" - android:digits="-QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890.:" + android:digits="QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890.:-[]" 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" /> + +