Cleanup more Kotlin code, and convert SendFragment

This commit is contained in:
pokkst 2023-12-06 17:13:44 -06:00
parent 3c52bd3a55
commit d4cb982b94
No known key found for this signature in database
GPG key ID: EC4FAAA66859FAA4
28 changed files with 647 additions and 1009 deletions

View file

@ -23,7 +23,6 @@ import net.mynero.wallet.util.UriData
import java.io.File
class MainActivity : AppCompatActivity(), MoneroHandlerThread.Listener, PasswordListener {
@JvmField
val restartEvents: SingleLiveEvent<*> = SingleLiveEvent<Any?>()
var thread: MoneroHandlerThread? = null
private set

View file

@ -21,6 +21,7 @@ import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import net.mynero.wallet.R
import net.mynero.wallet.data.DefaultNodes
@ -83,9 +84,19 @@ class NodeSelectionAdapter(val listener: NodeSelectionAdapterListener?) :
val currentNode = PrefService.instance?.node
val match = node == currentNode
if (match) {
itemView.setBackgroundColor(itemView.resources.getColor(R.color.oled_colorSecondary))
itemView.setBackgroundColor(
ContextCompat.getColor(
itemView.context,
R.color.oled_colorSecondary
)
)
} else {
itemView.setBackgroundColor(itemView.resources.getColor(android.R.color.transparent))
itemView.setBackgroundColor(
ContextCompat.getColor(
itemView.context,
android.R.color.transparent
)
)
}
val nodeNameTextView = itemView.findViewById<TextView>(R.id.node_name_textview)
val nodeAddressTextView = itemView.findViewById<TextView>(R.id.node_uri_textview)

View file

@ -25,7 +25,6 @@ import net.mynero.wallet.data.Subaddress
import net.mynero.wallet.service.PrefService
import net.mynero.wallet.util.Constants
import net.mynero.wallet.util.Helper
import net.mynero.wallet.util.Helper.getDisplayAmount
class SubaddressAdapter(val listener: SubaddressAdapterListener?) :
RecyclerView.Adapter<SubaddressAdapter.ViewHolder>() {
@ -101,7 +100,7 @@ class SubaddressAdapter(val listener: SubaddressAdapterListener?) :
} else {
addressAmountTextView.text = itemView.context.getString(
R.string.tx_list_amount_positive,
getDisplayAmount(amount, Helper.DISPLAY_DIGITS_INFO)
Helper.getDisplayAmount(amount, Helper.DISPLAY_DIGITS_INFO)
)
}
} else addressAmountTextView.text = ""

View file

@ -27,8 +27,7 @@ import net.mynero.wallet.service.PrefService
import net.mynero.wallet.util.Constants
import net.mynero.wallet.util.DateHelper.DATETIME_FORMATTER
import net.mynero.wallet.util.Helper
import net.mynero.wallet.util.Helper.getDisplayAmount
import net.mynero.wallet.util.ThemeHelper.getThemedColor
import net.mynero.wallet.util.ThemeHelper
import java.util.Calendar
import java.util.Date
@ -84,10 +83,10 @@ class TransactionInfoAdapter(val listener: TxInfoAdapterListener?) :
private var amountTextView: TextView? = null
init {
inboundColour = getThemedColor(view.context, R.attr.positiveColor)
outboundColour = getThemedColor(view.context, R.attr.negativeColor)
pendingColour = getThemedColor(view.context, R.attr.neutralColor)
failedColour = getThemedColor(view.context, R.attr.neutralColor)
inboundColour = ThemeHelper.getThemedColor(view.context, R.attr.positiveColor)
outboundColour = ThemeHelper.getThemedColor(view.context, R.attr.negativeColor)
pendingColour = ThemeHelper.getThemedColor(view.context, R.attr.neutralColor)
failedColour = ThemeHelper.getThemedColor(view.context, R.attr.neutralColor)
val cal = Calendar.getInstance()
val tz = cal.timeZone //get the local time zone.
DATETIME_FORMATTER.timeZone = tz
@ -97,7 +96,7 @@ class TransactionInfoAdapter(val listener: TxInfoAdapterListener?) :
val streetModeEnabled =
PrefService.instance?.getBoolean(Constants.PREF_STREET_MODE, false)
val displayAmount =
if (streetModeEnabled == true) Constants.STREET_MODE_BALANCE else getDisplayAmount(
if (streetModeEnabled == true) Constants.STREET_MODE_BALANCE else Helper.getDisplayAmount(
txInfo.amount,
Helper.DISPLAY_DIGITS_INFO
)
@ -108,7 +107,7 @@ class TransactionInfoAdapter(val listener: TxInfoAdapterListener?) :
amountTextView = itemView.findViewById(R.id.tx_amount)
itemView.findViewById<View>(R.id.tx_failed).visibility = View.GONE
if (txInfo.isFailed) {
(itemView.findViewById<View>(R.id.tx_amount) as TextView).text =
amountTextView?.text =
itemView.context.getString(R.string.tx_list_amount_negative, displayAmount)
itemView.findViewById<View>(R.id.tx_failed).visibility = View.VISIBLE
setTxColour(failedColour)
@ -140,10 +139,10 @@ class TransactionInfoAdapter(val listener: TxInfoAdapterListener?) :
confirmationsTextView.visibility = View.GONE
}
if (txInfo.direction == TransactionInfo.Direction.Direction_Out) {
(itemView.findViewById<View>(R.id.tx_amount) as TextView).text =
amountTextView?.text =
itemView.context.getString(R.string.tx_list_amount_negative, displayAmount)
} else {
(itemView.findViewById<View>(R.id.tx_amount) as TextView).text =
amountTextView?.text =
itemView.context.getString(R.string.tx_list_amount_positive, displayAmount)
}
(itemView.findViewById<View>(R.id.tx_datetime) as TextView).text =

View file

@ -85,13 +85,13 @@ enum class DefaultNodes(
"mainnet",
"Criminales78.onion"
),
xmrfail(
Xmrfail(
"mxcd4577fldb3ppzy7obmmhnu3tf57gbcbd4qhwr2kxyjj2qi3dnbfqd.onion",
18081,
"mainnet",
"xmrfail.onion"
),
boldsuck(
Boldsuck(
"6dsdenp6vjkvqzy4wzsnzn6wixkdzihx3khiumyzieauxuxslmcaeiad.onion",
18081,
"mainnet",

View file

@ -38,7 +38,6 @@ class Subaddress(
) "#$addressIndex" else label
companion object {
@JvmField
val DEFAULT_LABEL_FORMATTER: Pattern =
Pattern.compile("^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{2}:[0-9]{2}:[0-9]{2}$")
}

View file

@ -1,79 +0,0 @@
/*
* Copyright (c) 2017 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.mynero.wallet.data
import java.util.regex.Pattern
class UserNotes(txNotes: String?) {
var txNotes: String? = ""
@JvmField
var note: String? = ""
@JvmField
var xmrtoTag: String? = null
@JvmField
var xmrtoKey: String? = null
@JvmField
var xmrtoAmount: String? = null // could be a double - but we are not doing any calculations
var xmrtoCurrency: String? = null
@JvmField
var xmrtoDestination: String? = null
init {
this.txNotes = txNotes
val p = Pattern.compile("^\\{([a-z]+)-(\\w{6,}),([0-9.]*)([A-Z]+),(\\w*)\\} ?(.*)")
val m = p.matcher(txNotes)
if (m.find()) {
xmrtoTag = m.group(1)
xmrtoKey = m.group(2)
xmrtoAmount = m.group(3)
xmrtoCurrency = m.group(4)
xmrtoDestination = m.group(5)
note = m.group(6)
} else {
note = txNotes
}
}
fun setNote(newNote: String?) {
note = newNote ?: ""
txNotes = buildTxNote()
}
private fun buildTxNote(): String {
val sb = StringBuilder()
if (xmrtoKey != null) {
require(!(xmrtoAmount == null || xmrtoDestination == null)) { "Broken notes" }
sb.append("{")
sb.append(xmrtoTag)
sb.append("-")
sb.append(xmrtoKey)
sb.append(",")
sb.append(xmrtoAmount)
sb.append(xmrtoCurrency)
sb.append(",")
sb.append(xmrtoDestination)
sb.append("}")
if (note != null && note?.isNotEmpty() == true) sb.append(" ")
}
sb.append(note)
return sb.toString()
}
}

View file

@ -114,7 +114,8 @@ class AddNodeBottomSheetDialog : BottomSheetDialogFragment() {
Toast.makeText(context, "Node already exists", Toast.LENGTH_SHORT).show()
return
}
PrefService.instance?.edit()?.putString(Constants.PREF_CUSTOM_NODES, jsonArray.toString())?.apply()
PrefService.instance?.edit()?.putString(Constants.PREF_CUSTOM_NODES, jsonArray.toString())
?.apply()
}
private fun addPasteListener(root: View, editText: EditText, layoutId: Int) {

View file

@ -17,9 +17,7 @@ import java.text.SimpleDateFormat
import java.util.Locale
class EditAddressLabelBottomSheetDialog : BottomSheetDialogFragment() {
@JvmField
var listener: LabelListener? = null
@JvmField
var addressIndex = 0
override fun onCreateView(
inflater: LayoutInflater,

View file

@ -21,6 +21,7 @@ import org.json.JSONObject
class EditNodeBottomSheetDialog : BottomSheetDialogFragment() {
@JvmField
var listener: EditNodeListener? = null
@JvmField
var node: Node? = null
override fun onCreateView(
@ -95,8 +96,9 @@ class EditNodeBottomSheetDialog : BottomSheetDialogFragment() {
jsonObject.put("username", user)
jsonObject.put("password", pass)
}
if (nodeAddr.contains(":") && !nodeAddr.startsWith("[") && !nodeAddr.endsWith("]")) nodeAddr =
"[$nodeAddr]"
if (nodeAddr.contains(":") && !nodeAddr.startsWith("[") && !nodeAddr.endsWith("]"))
nodeAddr = "[$nodeAddr]"
jsonObject.put("host", nodeAddr)
jsonObject.put("rpcPort", portString.toInt())
jsonObject.put("network", "mainnet")

View file

@ -82,7 +82,8 @@ class NodeSelectionBottomSheetDialog : BottomSheetDialogFragment(), NodeSelectio
}
}
}
PrefService.instance?.edit()?.putString(Constants.PREF_CUSTOM_NODES, jsonArray.toString())?.apply()
PrefService.instance?.edit()?.putString(Constants.PREF_CUSTOM_NODES, jsonArray.toString())
?.apply()
for (defaultNode in DefaultNodes.values()) {
fromJson(defaultNode.json)?.let { nodes.add(it) }
}
@ -98,7 +99,8 @@ class NodeSelectionBottomSheetDialog : BottomSheetDialogFragment(), NodeSelectio
Toast.LENGTH_SHORT
).show()
}
PrefService.instance?.edit()?.putString(Constants.PREF_NODE_2, node?.toJson().toString())?.apply()
PrefService.instance?.edit()?.putString(Constants.PREF_NODE_2, node?.toJson().toString())
?.apply()
WalletManager.instance?.setDaemon(node)
adapter?.updateSelectedNode()
listener?.onNodeSelected()

View file

@ -17,6 +17,7 @@ import java.io.File
class PasswordBottomSheetDialog : BottomSheetDialogFragment() {
@JvmField
var listener: PasswordListener? = null
@JvmField
var cancelable = false
override fun onCreateView(

View file

@ -14,7 +14,6 @@ import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.zxing.client.android.Intents
import com.journeyapps.barcodescanner.ScanContract
@ -22,7 +21,6 @@ import com.journeyapps.barcodescanner.ScanIntentResult
import com.journeyapps.barcodescanner.ScanOptions
import net.mynero.wallet.MoneroApplication
import net.mynero.wallet.R
import net.mynero.wallet.model.BalanceInfo
import net.mynero.wallet.model.PendingTransaction
import net.mynero.wallet.model.Wallet
import net.mynero.wallet.model.Wallet.Companion.getAmountFromString
@ -40,10 +38,12 @@ import net.mynero.wallet.util.UriData.Companion.parse
class SendBottomSheetDialog : BottomSheetDialogFragment() {
private val _sendingMax = MutableLiveData(false)
private val _pendingTransaction = MutableLiveData<PendingTransaction?>(null)
@JvmField
var selectedUtxos = ArrayList<String>()
private var sendingMax: LiveData<Boolean?> = _sendingMax
private var pendingTransaction: LiveData<PendingTransaction?> = _pendingTransaction
@JvmField
var uriData: UriData? = null
private val cameraPermissionsLauncher = registerForActivityResult(
@ -56,8 +56,10 @@ class SendBottomSheetDialog : BottomSheetDialogFragment() {
.show()
}
}
@JvmField
var isChurning = false
@JvmField
var listener: Listener? = null
var priority: PendingTransaction.Priority = PendingTransaction.Priority.Priority_Low

View file

@ -25,7 +25,6 @@ import net.mynero.wallet.service.BlockchainService
import net.mynero.wallet.service.HistoryService
import net.mynero.wallet.service.PrefService
import net.mynero.wallet.util.Constants
import kotlin.math.roundToInt
class HomeFragment : Fragment(), TxInfoAdapterListener {
private var startHeight: Long = 0
@ -124,11 +123,11 @@ class HomeFragment : Fragment(), TxInfoAdapterListener {
displayEmptyHistory(true, view, textResId, botImgResId)
} else {
// POPULATED WALLET HISTORY
history.sorted()
if (history.size > 100) {
adapter.submitList(history.subList(0, 99))
val sortedHistory = history.sortedByDescending { it.timestamp }
if (sortedHistory.size > 100) {
adapter.submitList(sortedHistory.subList(0, 99))
} else {
adapter.submitList(history)
adapter.submitList(sortedHistory)
}
txHistoryRecyclerView.visibility = View.VISIBLE
displayEmptyHistory(

View file

@ -8,6 +8,7 @@ import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
@ -120,7 +121,8 @@ class ReceiveFragment : Fragment() {
private fun generate(text: String, width: Int, height: Int): Bitmap? {
if (width <= 0 || height <= 0) return null
val hints: MutableMap<EncodeHintType, Any?> = EnumMap(com.google.zxing.EncodeHintType::class.java)
val hints: MutableMap<EncodeHintType, Any?> =
EnumMap(com.google.zxing.EncodeHintType::class.java)
hints[EncodeHintType.CHARACTER_SET] = StandardCharsets.UTF_8
hints[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.M
try {
@ -131,7 +133,10 @@ class ReceiveFragment : Fragment() {
if (bitMatrix[j, i]) {
pixels[i * width + j] = -0x1
} else {
pixels[i * height + j] = resources.getColor(R.color.oled_colorBackground)
context?.let { ctx ->
pixels[i * height + j] =
ContextCompat.getColor(ctx, R.color.oled_colorBackground)
}
}
}
}

View file

@ -1,485 +1,520 @@
package net.mynero.wallet.fragment.send;
package net.mynero.wallet.fragment.send
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
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 android.widget.LinearLayout;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;
import android.app.Activity
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
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 android.widget.LinearLayout
import android.widget.RadioGroup
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.google.zxing.client.android.Intents
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanIntentResult
import com.journeyapps.barcodescanner.ScanOptions
import com.ncorti.slidetoact.SlideToActView
import com.ncorti.slidetoact.SlideToActView.OnSlideCompleteListener
import net.mynero.wallet.MoneroApplication
import net.mynero.wallet.R
import net.mynero.wallet.model.PendingTransaction
import net.mynero.wallet.model.Wallet
import net.mynero.wallet.service.BalanceService
import net.mynero.wallet.service.TxService
import net.mynero.wallet.util.Helper
import net.mynero.wallet.util.UriData
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
import com.google.zxing.client.android.Intents;
import com.journeyapps.barcodescanner.ScanContract;
import com.journeyapps.barcodescanner.ScanOptions;
import com.ncorti.slidetoact.SlideToActView;
import net.mynero.wallet.MoneroApplication;
import net.mynero.wallet.R;
import net.mynero.wallet.model.PendingTransaction;
import net.mynero.wallet.model.Wallet;
import net.mynero.wallet.service.BalanceService;
import net.mynero.wallet.service.TxService;
import net.mynero.wallet.util.Helper;
import net.mynero.wallet.util.UriData;
import java.util.ArrayList;
import java.util.List;
import kotlin.Pair;
public class SendFragment extends Fragment {
public PendingTransaction.Priority priority;
private SendViewModel mViewModel;
private Button sendMaxButton;
private ImageButton addOutputImageView;
private LinearLayout destList;
private LayoutInflater inflater;
private Button createButton;
private SlideToActView sendTxSlider;
private RadioGroup feeRadioGroup;
private TextView feeRadioGroupLabelTextView;
private TextView feeTextView;
private TextView addressTextView;
private TextView amountTextView;
private int currentEntryIndex = -1;
private final ActivityResultLauncher<ScanOptions> qrCodeLauncher = registerForActivityResult(new ScanContract(), result -> {
if (result.getContents() != null) {
class SendFragment : Fragment() {
var priority: PendingTransaction.Priority = PendingTransaction.Priority.Priority_Low
private var mViewModel: SendViewModel? = null
private var sendMaxButton: Button? = null
private var addOutputImageView: ImageButton? = null
private var destList: LinearLayout? = null
private var inflater: LayoutInflater? = null
private var createButton: Button? = null
private var sendTxSlider: SlideToActView? = null
private var feeRadioGroup: RadioGroup? = null
private var feeRadioGroupLabelTextView: TextView? = null
private var feeTextView: TextView? = null
private var addressTextView: TextView? = null
private var amountTextView: TextView? = null
private var currentEntryIndex = -1
private val qrCodeLauncher =
registerForActivityResult(ScanContract()) { result: ScanIntentResult ->
if (result.contents != null) {
if (currentEntryIndex != -1) {
pasteAddress(getDestView(currentEntryIndex), result.getContents(), false);
currentEntryIndex = -1;
pasteAddress(getDestView(currentEntryIndex), result.contents, false)
currentEntryIndex = -1
}
}
});
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_send, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mViewModel = new ViewModelProvider(this).get(SendViewModel.class);
sendMaxButton = view.findViewById(R.id.send_max_button);
addOutputImageView = view.findViewById(R.id.add_output_button);
destList = view.findViewById(R.id.transaction_destination_list);
createButton = view.findViewById(R.id.create_tx_button);
feeRadioGroup = view.findViewById(R.id.tx_fee_radiogroup);
feeTextView = view.findViewById(R.id.fee_textview);
sendTxSlider = view.findViewById(R.id.send_tx_slider);
addressTextView = view.findViewById(R.id.address_pending_textview);
amountTextView = view.findViewById(R.id.amount_pending_textview);
feeRadioGroup = view.findViewById(R.id.tx_fee_radiogroup);
feeRadioGroupLabelTextView = view.findViewById(R.id.tx_fee_radiogroup_label_textview);
FragmentActivity activity = getActivity();
if (activity != null) {
inflater = activity.getLayoutInflater();
}
bindListeners();
bindObservers();
init();
} private final ActivityResultLauncher<String> cameraPermissionsLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(),
granted -> {
private val cameraPermissionsLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted: Boolean ->
if (granted) {
onScan(currentEntryIndex);
onScan(currentEntryIndex)
} else {
Toast.makeText(getActivity(), getString(R.string.no_camera_permission), Toast.LENGTH_SHORT).show();
currentEntryIndex = -1;
Toast.makeText(activity, getString(R.string.no_camera_permission), Toast.LENGTH_SHORT)
.show()
currentEntryIndex = -1
}
});
private void init() {
addOutput(true);
}
private void bindListeners() {
feeRadioGroup.check(R.id.low_fee_radiobutton);
priority = PendingTransaction.Priority.Priority_Low;
feeRadioGroup.setOnCheckedChangeListener((radioGroup, i) -> {
if (i == R.id.low_fee_radiobutton) {
priority = PendingTransaction.Priority.Priority_Low;
} else if (i == R.id.med_fee_radiobutton) {
priority = PendingTransaction.Priority.Priority_Medium;
} else if (i == R.id.high_fee_radiobutton) {
priority = PendingTransaction.Priority.Priority_High;
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_send, container, false)
}
});
addOutputImageView.setOnClickListener(view1 -> {
sendMaxButton.setVisibility(View.GONE);
int outputCount = getDestCount();
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mViewModel = ViewModelProvider(this)[SendViewModel::class.java]
sendMaxButton = view.findViewById(R.id.send_max_button)
addOutputImageView = view.findViewById(R.id.add_output_button)
destList = view.findViewById(R.id.transaction_destination_list)
createButton = view.findViewById(R.id.create_tx_button)
feeRadioGroup = view.findViewById(R.id.tx_fee_radiogroup)
feeTextView = view.findViewById(R.id.fee_textview)
sendTxSlider = view.findViewById(R.id.send_tx_slider)
addressTextView = view.findViewById(R.id.address_pending_textview)
amountTextView = view.findViewById(R.id.amount_pending_textview)
feeRadioGroup = view.findViewById(R.id.tx_fee_radiogroup)
feeRadioGroupLabelTextView = view.findViewById(R.id.tx_fee_radiogroup_label_textview)
inflater = activity?.layoutInflater
bindListeners()
bindObservers()
init()
}
private fun init() {
addOutput(true)
}
private fun bindListeners() {
feeRadioGroup?.check(R.id.low_fee_radiobutton)
priority = PendingTransaction.Priority.Priority_Low
feeRadioGroup?.setOnCheckedChangeListener { _: RadioGroup?, i: Int ->
when (i) {
R.id.low_fee_radiobutton -> priority = PendingTransaction.Priority.Priority_Low
R.id.med_fee_radiobutton -> priority = PendingTransaction.Priority.Priority_Medium
R.id.high_fee_radiobutton -> priority = PendingTransaction.Priority.Priority_High
}
}
addOutputImageView?.setOnClickListener {
sendMaxButton?.visibility = View.GONE
val outputCount = destCount
if (outputCount < 8) {
addOutput(false);
addOutput(false)
} else {
Toast.makeText(getActivity(), getString(R.string.max_outputs_allowed), Toast.LENGTH_SHORT).show();
Toast.makeText(
activity,
getString(R.string.max_outputs_allowed),
Toast.LENGTH_SHORT
).show()
}
});
sendMaxButton.setOnClickListener(view1 -> {
mViewModel.setSendingMax(!isSendAll());
});
createButton.setOnClickListener(view1 -> {
boolean outputsValid = checkDestsValidity(isSendAll());
}
sendMaxButton?.setOnClickListener { mViewModel?.setSendingMax(!isSendAll) }
createButton?.setOnClickListener {
val outputsValid = checkDestsValidity(isSendAll)
if (outputsValid) {
Toast.makeText(getActivity(), getString(R.string.creating_tx), Toast.LENGTH_SHORT).show();
createButton.setEnabled(false);
sendMaxButton.setEnabled(false);
createTx(getRawDests(), isSendAll(), priority);
Toast.makeText(activity, getString(R.string.creating_tx), Toast.LENGTH_SHORT).show()
createButton?.isEnabled = false
sendMaxButton?.isEnabled = false
createTx(rawDests, isSendAll, priority)
} else {
Toast.makeText(getActivity(), getString(R.string.creating_tx_failed_invalid_outputs), Toast.LENGTH_SHORT).show();
Toast.makeText(
activity,
getString(R.string.creating_tx_failed_invalid_outputs),
Toast.LENGTH_SHORT
).show()
}
}
sendTxSlider?.onSlideCompleteListener =
object : OnSlideCompleteListener {
override fun onSlideComplete(view: SlideToActView) {
val pendingTx = mViewModel?.pendingTransaction?.value ?: return
Toast.makeText(activity, getString(R.string.sending_tx), Toast.LENGTH_SHORT)
.show()
sendTx(pendingTx)
}
});
sendTxSlider.setOnSlideCompleteListener(slideToActView -> {
PendingTransaction pendingTx = mViewModel.pendingTransaction.getValue();
if (pendingTx != null) {
Toast.makeText(getActivity(), getString(R.string.sending_tx), Toast.LENGTH_SHORT).show();
sendTx(pendingTx);
}
});
}
private boolean checkDestsValidity(boolean sendAll) {
List<Pair<String, String>> dests = getRawDests();
for (Pair<String, String> dest : dests) {
String address = dest.component1();
String amount = dest.component2();
private fun checkDestsValidity(sendAll: Boolean): Boolean {
val dests = rawDests
for (dest in dests) {
val address = dest.component1()
val amount = dest.component2()
if (!sendAll) {
if (amount.isEmpty()) {
Toast.makeText(getActivity(), getString(R.string.send_amount_empty), Toast.LENGTH_SHORT).show();
return false;
Toast.makeText(
activity,
getString(R.string.send_amount_empty),
Toast.LENGTH_SHORT
).show()
return false
}
long amountRaw = Wallet.getAmountFromString(amount);
long balance = BalanceService.instance.getUnlockedBalanceRaw();
val amountRaw = Wallet.getAmountFromString(amount)
val balance = BalanceService.instance?.unlockedBalanceRaw ?: 0
if (amountRaw >= balance || amountRaw <= 0) {
Toast.makeText(getActivity(), getString(R.string.send_amount_invalid), Toast.LENGTH_SHORT).show();
return false;
Toast.makeText(
activity,
getString(R.string.send_amount_invalid),
Toast.LENGTH_SHORT
).show()
return false
}
} else if (dests.size() > 1) {
Toast.makeText(getActivity(), getString(R.string.send_amount_invalid_sendall_paytomany), Toast.LENGTH_SHORT).show();
return false;
} else if (dests.size > 1) {
Toast.makeText(
activity,
getString(R.string.send_amount_invalid_sendall_paytomany),
Toast.LENGTH_SHORT
).show()
return false
}
UriData uriData = UriData.parse(address);
boolean isValidAddress = uriData != null;
val uriData = UriData.parse(address)
val isValidAddress = uriData != null
if (!isValidAddress) {
Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show();
return false;
Toast.makeText(
activity,
getString(R.string.send_address_invalid),
Toast.LENGTH_SHORT
).show()
return false
}
if (dests.size > 1 && uriData?.hasPaymentId() == true) {
Toast.makeText(
activity,
getString(R.string.paymentid_paytomany),
Toast.LENGTH_SHORT
).show()
return false
}
}
return true
}
if (dests.size() > 1 && uriData.hasPaymentId()) {
Toast.makeText(getActivity(), getString(R.string.paymentid_paytomany), Toast.LENGTH_SHORT).show();
return false;
private fun destsHasPaymentId(): Boolean {
val dests = rawDests
for (dest in dests) {
val address = dest.component1()
val uriData = UriData.parse(address) ?: return false
if (uriData.hasPaymentId()) return true
}
return false
}
return true;
}
private boolean destsHasPaymentId() {
List<Pair<String, String>> dests = getRawDests();
for (Pair<String, String> dest : dests) {
String address = dest.component1();
UriData uriData = UriData.parse(address);
if (uriData == null) return false;
if (uriData.hasPaymentId()) return true;
}
return false;
}
private void bindObservers() {
mViewModel.sendingMax.observe(getViewLifecycleOwner(), sendingMax -> {
if (mViewModel.pendingTransaction.getValue() == null) {
if (sendingMax) {
prepareOutputsForMaxSend();
sendMaxButton.setText(getText(R.string.undo));
private fun bindObservers() {
mViewModel?.sendingMax?.observe(viewLifecycleOwner) { sendingMax: Boolean? ->
if (mViewModel?.pendingTransaction?.value == null) {
if (sendingMax == true) {
prepareOutputsForMaxSend()
sendMaxButton?.text = getText(R.string.undo)
} else {
unprepareMaxSend();
sendMaxButton.setText(getText(R.string.send_max));
unprepareMaxSend()
sendMaxButton?.text = getText(R.string.send_max)
}
}
});
mViewModel.showAddOutputButton.observe(getViewLifecycleOwner(), show -> {
setAddOutputButtonVisibility((show && !destsHasPaymentId()) ? View.VISIBLE : View.INVISIBLE);
});
mViewModel.pendingTransaction.observe(getViewLifecycleOwner(), pendingTx -> {
showConfirmationLayout(pendingTx != null);
}
mViewModel?.showAddOutputButton?.observe(viewLifecycleOwner) { show: Boolean? ->
setAddOutputButtonVisibility(
if (show == true && !destsHasPaymentId()) View.VISIBLE else View.INVISIBLE
)
}
mViewModel?.pendingTransaction?.observe(viewLifecycleOwner) { pendingTx: PendingTransaction? ->
showConfirmationLayout(pendingTx != null)
if (pendingTx != null) {
String address = getDestCount() == 1 ? getAddressField(0).getText().toString() : "Multiple";
addressTextView.setText(getString(R.string.tx_address_text, address));
amountTextView.setText(getString(R.string.tx_amount_text, Helper.getDisplayAmount(pendingTx.getAmount())));
feeTextView.setText(getString(R.string.tx_fee_text, Helper.getDisplayAmount(pendingTx.getFee())));
val address = if (destCount == 1) getAddressField(0).text.toString() else "Multiple"
addressTextView?.text = getString(R.string.tx_address_text, address)
amountTextView?.text =
getString(R.string.tx_amount_text, Helper.getDisplayAmount(pendingTx.getAmount()))
feeTextView?.text =
getString(R.string.tx_fee_text, Helper.getDisplayAmount(pendingTx.getFee()))
}
}
});
}
private void addOutput(boolean initial) {
private fun addOutput(initial: Boolean) {
if (inflater != null) {
int index = getDestCount();
ConstraintLayout entryView = (ConstraintLayout) inflater.inflate(R.layout.transaction_output_item, null);
ImageButton removeOutputImageButton = entryView.findViewById(R.id.remove_output_imagebutton);
EditText addressField = entryView.findViewById(R.id.address_edittext);
addressField.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
val index = destCount
val entryView =
inflater?.inflate(R.layout.transaction_output_item, null) as ConstraintLayout
val removeOutputImageButton =
entryView.findViewById<ImageButton>(R.id.remove_output_imagebutton)
val addressField = entryView.findViewById<EditText>(R.id.address_edittext)
addressField.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(
charSequence: CharSequence,
i: Int,
i1: Int,
i2: Int
) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
int currentOutputs = getDestCount();
UriData uriData = UriData.parse(editable.toString());
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) {
val currentOutputs: Int = destCount
val uriData = UriData.parse(editable.toString())
if (uriData != null) {
// we have valid address
boolean hasPaymentId = uriData.hasPaymentId();
val hasPaymentId = uriData.hasPaymentId()
if (currentOutputs > 1 && hasPaymentId) {
// multiple outputs when pasting/editing in integrated address. this is not allowed
Toast.makeText(getActivity(), getString(R.string.paymentid_paytomany), Toast.LENGTH_SHORT).show();
addressField.setText(null);
Toast.makeText(
activity,
getString(R.string.paymentid_paytomany),
Toast.LENGTH_SHORT
).show()
addressField.text = null
} else if (currentOutputs == 1 && hasPaymentId) {
// show add output button: we are sending to integrated address
mViewModel.setShowAddOutputButton(false);
mViewModel?.setShowAddOutputButton(false)
}
} else if (currentOutputs == 1 && !isSendAll()) {
} else if (currentOutputs == 1 && !isSendAll) {
// when send-all is false and this is our only dest and address is invalid, then show add output button
mViewModel.setShowAddOutputButton(true);
mViewModel?.setShowAddOutputButton(true)
}
}
});
entryView.findViewById(R.id.paste_amount_imagebutton).setOnClickListener(view1 -> {
Context ctx = getContext();
})
entryView.findViewById<View>(R.id.paste_amount_imagebutton)
.setOnClickListener {
val ctx = context
if (ctx != null) {
String clipboard = Helper.getClipBoardText(ctx);
val clipboard = Helper.getClipBoardText(ctx)
if (clipboard != null) {
pasteAddress(entryView, clipboard, true);
pasteAddress(entryView, clipboard, true)
}
}
});
entryView.findViewById(R.id.paste_address_imagebutton).setOnClickListener(view1 -> {
Context ctx = getContext();
}
entryView.findViewById<View>(R.id.paste_address_imagebutton)
.setOnClickListener {
val ctx = context
if (ctx != null) {
String clipboard = Helper.getClipBoardText(ctx);
val clipboard = Helper.getClipBoardText(ctx)
if (clipboard != null) {
pasteAddress(entryView, clipboard, false);
pasteAddress(entryView, clipboard, false)
}
}
});
entryView.findViewById(R.id.scan_address_imagebutton).setOnClickListener(view -> onScan(index));
}
entryView.findViewById<View>(R.id.scan_address_imagebutton)
.setOnClickListener { onScan(index) }
if (initial) {
removeOutputImageButton.setVisibility(View.INVISIBLE);
removeOutputImageButton.visibility = View.INVISIBLE
} else {
removeOutputImageButton.setOnClickListener(view -> {
int currentCount = getDestCount();
removeOutputImageButton.setOnClickListener {
val currentCount = destCount
if (currentCount > 1) {
if (currentCount == 2) {
sendMaxButton.setVisibility(View.VISIBLE);
sendMaxButton?.visibility = View.VISIBLE
}
destList.removeView(entryView);
destList?.removeView(entryView)
}
});
}
destList.addView(entryView);
}
destList?.addView(entryView)
}
}
private int getDestCount() {
return destList.getChildCount();
private val destCount: Int
get() = destList?.childCount ?: -1
private val rawDests: List<Pair<String, String>>
get() {
val dests = ArrayList<Pair<String, String>>()
for (i in 0 until destCount) {
val entryView = getDestView(i)
val amountField = entryView.findViewById<EditText>(R.id.amount_edittext)
val addressField = entryView.findViewById<EditText>(R.id.address_edittext)
val amount = amountField.text.toString().trim { it <= ' ' }
val address = addressField.text.toString().trim { it <= ' ' }
dests.add(Pair(address, amount))
}
return dests
}
private val isSendAll: Boolean
get() = mViewModel?.sendingMax?.value ?: false
private fun getDestView(pos: Int): ConstraintLayout {
return destList?.getChildAt(pos) as ConstraintLayout
}
private List<Pair<String, String>> getRawDests() {
ArrayList<Pair<String, String>> dests = new ArrayList<>();
for (int i = 0; i < getDestCount(); i++) {
ConstraintLayout entryView = getDestView(i);
EditText amountField = entryView.findViewById(R.id.amount_edittext);
EditText addressField = entryView.findViewById(R.id.address_edittext);
String amount = amountField.getText().toString().trim();
String address = addressField.getText().toString().trim();
dests.add(new Pair<>(address, amount));
private fun getAddressField(pos: Int): EditText {
return getDestView(pos).findViewById<View>(R.id.address_edittext) as EditText
}
return dests;
private fun unprepareMaxSend() {
val entryView = getDestView(0)
entryView.findViewById<View>(R.id.sending_all_textview).visibility = View.INVISIBLE
entryView.findViewById<View>(R.id.amount_edittext).visibility =
View.VISIBLE
}
private boolean isSendAll() {
return mViewModel.sendingMax.getValue() != null ? mViewModel.sendingMax.getValue() : false;
private fun prepareOutputsForMaxSend() {
val entryView = getDestView(0)
entryView.findViewById<View>(R.id.sending_all_textview).visibility = View.VISIBLE
entryView.findViewById<View>(R.id.amount_edittext).visibility =
View.INVISIBLE
}
private ConstraintLayout getDestView(int pos) {
return (ConstraintLayout) destList.getChildAt(pos);
}
private EditText getAddressField(int pos) {
return (EditText) getDestView(pos).findViewById(R.id.address_edittext);
}
private void unprepareMaxSend() {
ConstraintLayout entryView = getDestView(0);
entryView.findViewById(R.id.sending_all_textview).setVisibility(View.INVISIBLE);
entryView.findViewById(R.id.amount_edittext).setVisibility(View.VISIBLE);
}
private void prepareOutputsForMaxSend() {
ConstraintLayout entryView = getDestView(0);
entryView.findViewById(R.id.sending_all_textview).setVisibility(View.VISIBLE);
entryView.findViewById(R.id.amount_edittext).setVisibility(View.INVISIBLE);
}
private void showConfirmationLayout(boolean show) {
private fun showConfirmationLayout(show: Boolean) {
if (show) {
destList.setVisibility(View.GONE);
setAddOutputButtonVisibility(View.GONE);
sendMaxButton.setVisibility(View.GONE);
createButton.setVisibility(View.GONE);
feeRadioGroup.setVisibility(View.GONE);
feeRadioGroupLabelTextView.setVisibility(View.GONE);
sendTxSlider.setVisibility(View.VISIBLE);
feeTextView.setVisibility(View.VISIBLE);
addressTextView.setVisibility(View.VISIBLE);
amountTextView.setVisibility(View.VISIBLE);
destList?.visibility = View.GONE
setAddOutputButtonVisibility(View.GONE)
sendMaxButton?.visibility = View.GONE
createButton?.visibility = View.GONE
feeRadioGroup?.visibility = View.GONE
feeRadioGroupLabelTextView?.visibility = View.GONE
sendTxSlider?.visibility = View.VISIBLE
feeTextView?.visibility = View.VISIBLE
addressTextView?.visibility = View.VISIBLE
amountTextView?.visibility = View.VISIBLE
} else {
destList.setVisibility(View.VISIBLE);
setAddOutputButtonVisibility(View.VISIBLE);
sendMaxButton.setVisibility(View.VISIBLE);
createButton.setVisibility(View.VISIBLE);
feeRadioGroup.setVisibility(View.VISIBLE);
feeRadioGroupLabelTextView.setVisibility(View.VISIBLE);
sendTxSlider.setVisibility(View.GONE);
feeTextView.setVisibility(View.GONE);
addressTextView.setVisibility(View.GONE);
amountTextView.setVisibility(View.GONE);
destList?.visibility = View.VISIBLE
setAddOutputButtonVisibility(View.VISIBLE)
sendMaxButton?.visibility = View.VISIBLE
createButton?.visibility = View.VISIBLE
feeRadioGroup?.visibility = View.VISIBLE
feeRadioGroupLabelTextView?.visibility = View.VISIBLE
sendTxSlider?.visibility = View.GONE
feeTextView?.visibility = View.GONE
addressTextView?.visibility = View.GONE
amountTextView?.visibility = View.GONE
}
}
private void onScan(int index) {
currentEntryIndex = index;
if (Helper.getCameraPermission(getActivity(), cameraPermissionsLauncher)) {
ScanOptions options = new ScanOptions();
options.setBeepEnabled(false);
options.setOrientationLocked(true);
options.setDesiredBarcodeFormats(List.of(Intents.Scan.QR_CODE_MODE));
options.addExtra(Intents.Scan.SCAN_TYPE, Intents.Scan.MIXED_SCAN);
qrCodeLauncher.launch(options);
private fun onScan(index: Int) {
currentEntryIndex = index
if (activity?.let { Helper.getCameraPermission(it, cameraPermissionsLauncher) } == true) {
val options = ScanOptions()
options.setBeepEnabled(false)
options.setOrientationLocked(true)
options.setDesiredBarcodeFormats(listOf(Intents.Scan.QR_CODE_MODE))
options.addExtra(Intents.Scan.SCAN_TYPE, Intents.Scan.MIXED_SCAN)
qrCodeLauncher.launch(options)
}
}
private void pasteAddress(ConstraintLayout entryView, String clipboard, boolean pastingAmount) {
private fun pasteAddress(
entryView: ConstraintLayout,
clipboard: String,
pastingAmount: Boolean
) {
if (pastingAmount) {
try {
Double.parseDouble(clipboard);
setAmount(entryView, clipboard);
} catch (Exception e) {
Toast.makeText(getActivity(), getString(R.string.send_amount_invalid), Toast.LENGTH_SHORT).show();
return;
clipboard.toDouble()
setAmount(entryView, clipboard)
} catch (e: Exception) {
Toast.makeText(
activity,
getString(R.string.send_amount_invalid),
Toast.LENGTH_SHORT
).show()
return
}
}
UriData uriData = UriData.parse(clipboard);
val uriData = UriData.parse(clipboard)
if (uriData != null) {
int currentOutputs = getDestCount();
val currentOutputs = destCount
if (currentOutputs > 1 && uriData.hasPaymentId()) {
Toast.makeText(getActivity(), getString(R.string.paymentid_paytomany), Toast.LENGTH_SHORT).show();
return;
Toast.makeText(
activity,
getString(R.string.paymentid_paytomany),
Toast.LENGTH_SHORT
).show()
return
} else if (currentOutputs == 1 && uriData.hasPaymentId()) {
mViewModel.setShowAddOutputButton(false);
mViewModel?.setShowAddOutputButton(false)
}
EditText addressField = entryView.findViewById(R.id.address_edittext);
addressField.setText(uriData.address);
val addressField = entryView.findViewById<EditText>(R.id.address_edittext)
addressField.setText(uriData.address)
if (uriData.hasAmount()) {
setAmount(entryView, uriData.getAmount());
setAmount(entryView, uriData.amount)
}
} else {
Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show();
Toast.makeText(activity, getString(R.string.send_address_invalid), Toast.LENGTH_SHORT)
.show()
}
}
private void setAmount(ConstraintLayout entryView, String amount) {
sendMaxButton.setEnabled(false);
EditText amountField = entryView.findViewById(R.id.amount_edittext);
amountField.setText(amount);
private fun setAmount(entryView: ConstraintLayout, amount: String?) {
sendMaxButton?.isEnabled = false
val amountField = entryView.findViewById<EditText>(R.id.amount_edittext)
amountField.setText(amount)
}
private void createTx(List<Pair<String, String>> dests, boolean sendAll, PendingTransaction.Priority feePriority) {
((MoneroApplication) getActivity().getApplication()).getExecutor().execute(() -> {
private fun createTx(
dests: List<Pair<String, String>>,
sendAll: Boolean,
feePriority: PendingTransaction.Priority
) {
(activity?.application as MoneroApplication).executor?.execute {
try {
PendingTransaction pendingTx = TxService.instance.createTx(dests, sendAll, feePriority, new ArrayList<>());
if (pendingTx != null && pendingTx.getStatus() == PendingTransaction.Status.Status_Ok) {
mViewModel.setPendingTransaction(pendingTx);
val pendingTx =
TxService.instance?.createTx(dests, sendAll, feePriority, ArrayList())
if (pendingTx != null && pendingTx.status === PendingTransaction.Status.Status_Ok) {
mViewModel?.setPendingTransaction(pendingTx)
} else {
Activity activity = getActivity();
val activity: Activity? = activity
if (activity != null && pendingTx != null) {
activity.runOnUiThread(() -> {
createButton.setEnabled(true);
sendMaxButton.setEnabled(true);
if (pendingTx.getErrorString() != null)
Toast.makeText(activity, getString(R.string.error_creating_tx, pendingTx.getErrorString()), Toast.LENGTH_SHORT).show();
});
activity.runOnUiThread(Runnable {
createButton?.isEnabled = true
sendMaxButton?.isEnabled = true
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(activity, getString(R.string.error_creating_tx, e.getMessage()), Toast.LENGTH_SHORT).show();
});
} catch (e: Exception) {
e.printStackTrace()
val activity: Activity? = activity
activity?.runOnUiThread {
createButton?.isEnabled = true
sendMaxButton?.isEnabled = true
Toast.makeText(
activity,
getString(R.string.error_creating_tx, e.message),
Toast.LENGTH_SHORT
).show()
}
}
}
});
}
private void sendTx(PendingTransaction pendingTx) {
((MoneroApplication) getActivity().getApplication()).getExecutor().execute(() -> {
boolean success = TxService.instance.sendTx(pendingTx);
Activity activity = getActivity();
if (activity != null) {
activity.runOnUiThread(() -> {
if (success) {
Toast.makeText(getActivity(), getString(R.string.sent_tx), Toast.LENGTH_SHORT).show();
getActivity().onBackPressed();
private fun sendTx(pendingTx: PendingTransaction) {
(activity?.application as MoneroApplication).executor?.execute {
val success = TxService.instance?.sendTx(pendingTx)
val activity: Activity? = activity
activity?.runOnUiThread {
if (success == true) {
Toast.makeText(getActivity(), getString(R.string.sent_tx), Toast.LENGTH_SHORT)
.show()
activity.onBackPressed()
} else {
sendTxSlider.resetSlider();
Toast.makeText(getActivity(), getString(R.string.error_sending_tx), Toast.LENGTH_SHORT).show();
sendTxSlider?.resetSlider()
Toast.makeText(
getActivity(),
getString(R.string.error_sending_tx),
Toast.LENGTH_SHORT
)
.show()
}
}
});
}
});
}
private void setAddOutputButtonVisibility(int visibility) {
addOutputImageView.setVisibility(visibility);
private fun setAddOutputButtonVisibility(visibility: Int) {
addOutputImageView?.visibility = visibility
}
}

View file

@ -1,29 +1,27 @@
package net.mynero.wallet.fragment.send;
package net.mynero.wallet.fragment.send
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import net.mynero.wallet.model.PendingTransaction
import net.mynero.wallet.model.PendingTransaction;
public class SendViewModel extends ViewModel {
private final MutableLiveData<Boolean> _sendingMax = new MutableLiveData<>(false);
private final MutableLiveData<Boolean> _showAddOutputButton = new MutableLiveData<>(true);
private final MutableLiveData<PendingTransaction> _pendingTransaction = new MutableLiveData<>(null);
public LiveData<Boolean> sendingMax = _sendingMax;
public LiveData<Boolean> showAddOutputButton = _showAddOutputButton;
public LiveData<PendingTransaction> pendingTransaction = _pendingTransaction;
public void setSendingMax(boolean value) {
_sendingMax.setValue(value);
setShowAddOutputButton(!value);
class SendViewModel : ViewModel() {
private val _sendingMax = MutableLiveData(false)
private val _showAddOutputButton = MutableLiveData(true)
private val _pendingTransaction = MutableLiveData<PendingTransaction?>(null)
var sendingMax: LiveData<Boolean?> = _sendingMax
var showAddOutputButton: LiveData<Boolean?> = _showAddOutputButton
var pendingTransaction: LiveData<PendingTransaction?> = _pendingTransaction
fun setSendingMax(value: Boolean) {
_sendingMax.value = value
setShowAddOutputButton(!value)
}
public void setShowAddOutputButton(boolean value) {
_showAddOutputButton.setValue(value);
fun setShowAddOutputButton(value: Boolean) {
_showAddOutputButton.value = value
}
public void setPendingTransaction(PendingTransaction pendingTx) {
_pendingTransaction.postValue(pendingTx);
fun setPendingTransaction(pendingTx: PendingTransaction?) {
_pendingTransaction.postValue(pendingTx)
}
}

View file

@ -13,177 +13,188 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.mynero.wallet.model
package net.mynero.wallet.model;
import android.os.Build
import android.os.Parcel
import android.os.Parcelable
import android.os.Parcelable.Creator
import net.mynero.wallet.data.Subaddress
import android.os.Parcel;
import android.os.Parcelable;
class TransactionInfo : Parcelable, Comparable<TransactionInfo> {
@JvmField
var direction: Direction
var isPending: Boolean
var isFailed: Boolean
import androidx.annotation.NonNull;
@JvmField
var amount: Long
var fee: Long
import net.mynero.wallet.data.Subaddress;
@JvmField
var blockheight: Long
import java.util.List;
@JvmField
var hash: String?
// this is not the TransactionInfo from the API as that is owned by the TransactionHistory
// this is a POJO for the TransactionInfoAdapter
public class TransactionInfo implements Parcelable, Comparable<TransactionInfo> {
public static final int CONFIRMATION = 10; // blocks
public static final Parcelable.Creator<TransactionInfo> CREATOR = new Parcelable.Creator<TransactionInfo>() {
public TransactionInfo createFromParcel(Parcel in) {
return new TransactionInfo(in);
@JvmField
var timestamp: Long
var paymentId: String?
@JvmField
var accountIndex: Int
@JvmField
var addressIndex: Int
@JvmField
var confirmations: Long
var subaddressLabel: String?
@JvmField
var transfers: List<Transfer>? = listOf()
@JvmField
var txKey: String? = null
var notes: String? = null
@JvmField
var address: String? = null
constructor(
direction: Int,
isPending: Boolean,
isFailed: Boolean,
amount: Long,
fee: Long,
blockheight: Long,
hash: String?,
timestamp: Long,
paymentId: String?,
accountIndex: Int,
addressIndex: Int,
confirmations: Long,
subaddressLabel: String?,
transfers: List<Transfer>?
) {
this.direction = Direction.values()[direction]
this.isPending = isPending
this.isFailed = isFailed
this.amount = amount
this.fee = fee
this.blockheight = blockheight
this.hash = hash
this.timestamp = timestamp
this.paymentId = paymentId
this.accountIndex = accountIndex
this.addressIndex = addressIndex
this.confirmations = confirmations
this.subaddressLabel = subaddressLabel
this.transfers = transfers
}
public TransactionInfo[] newArray(int size) {
return new TransactionInfo[size];
}
};
public Direction direction;
public boolean isPending;
public boolean isFailed;
public long amount;
public long fee;
public long blockheight;
public String hash;
public long timestamp;
public String paymentId;
public int accountIndex;
public int addressIndex;
public long confirmations;
public String subaddressLabel;
public List<Transfer> transfers;
public String txKey = null;
public String notes = null;
public String address = null;
public TransactionInfo(
int direction,
boolean isPending,
boolean isFailed,
long amount,
long fee,
long blockheight,
String hash,
long timestamp,
String paymentId,
int accountIndex,
int addressIndex,
long confirmations,
String subaddressLabel,
List<Transfer> transfers) {
this.direction = Direction.values()[direction];
this.isPending = isPending;
this.isFailed = isFailed;
this.amount = amount;
this.fee = fee;
this.blockheight = blockheight;
this.hash = hash;
this.timestamp = timestamp;
this.paymentId = paymentId;
this.accountIndex = accountIndex;
this.addressIndex = addressIndex;
this.confirmations = confirmations;
this.subaddressLabel = subaddressLabel;
this.transfers = transfers;
}
private TransactionInfo(Parcel in) {
direction = Direction.fromInteger(in.readInt());
isPending = in.readByte() != 0;
isFailed = in.readByte() != 0;
amount = in.readLong();
fee = in.readLong();
blockheight = in.readLong();
hash = in.readString();
timestamp = in.readLong();
paymentId = in.readString();
accountIndex = in.readInt();
addressIndex = in.readInt();
confirmations = in.readLong();
subaddressLabel = in.readString();
in.readList(transfers, Transfer.class.getClassLoader());
txKey = in.readString();
notes = in.readString();
address = in.readString();
}
public boolean isConfirmed() {
return confirmations >= CONFIRMATION;
}
public String getDisplayLabel() {
if (subaddressLabel.isEmpty() || (Subaddress.DEFAULT_LABEL_FORMATTER.matcher(subaddressLabel).matches()))
return ("#" + addressIndex);
else
return subaddressLabel;
}
@Override
@NonNull
public String toString() {
return direction + "@" + blockheight + " " + amount;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(direction.getValue());
out.writeByte((byte) (isPending ? 1 : 0));
out.writeByte((byte) (isFailed ? 1 : 0));
out.writeLong(amount);
out.writeLong(fee);
out.writeLong(blockheight);
out.writeString(hash);
out.writeLong(timestamp);
out.writeString(paymentId);
out.writeInt(accountIndex);
out.writeInt(addressIndex);
out.writeLong(confirmations);
out.writeString(subaddressLabel);
out.writeList(transfers);
out.writeString(txKey);
out.writeString(notes);
out.writeString(address);
}
@Override
public int describeContents() {
return 0;
}
@Override
public int compareTo(TransactionInfo another) {
long b1 = this.timestamp;
long b2 = another.timestamp;
if (b1 > b2) {
return -1;
} else if (b1 < b2) {
return 1;
private constructor(`in`: Parcel) {
direction = Direction.fromInteger(`in`.readInt())
isPending = `in`.readByte().toInt() != 0
isFailed = `in`.readByte().toInt() != 0
amount = `in`.readLong()
fee = `in`.readLong()
blockheight = `in`.readLong()
hash = `in`.readString()
timestamp = `in`.readLong()
paymentId = `in`.readString()
accountIndex = `in`.readInt()
addressIndex = `in`.readInt()
confirmations = `in`.readLong()
subaddressLabel = `in`.readString()
transfers?.toMutableList()?.let { transfers ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
`in`.readList(transfers, Transfer::class.java.classLoader, Transfer::class.java)
} else {
return this.hash.compareTo(another.hash);
`in`.readList(transfers, Transfer::class.java.classLoader)
}
}
public enum Direction {
Direction_In(0),
Direction_Out(1);
private final int value;
Direction(int value) {
this.value = value;
txKey = `in`.readString()
notes = `in`.readString()
address = `in`.readString()
}
public static Direction fromInteger(int n) {
return switch (n) {
case 0 -> Direction_In;
case 1 -> Direction_Out;
default -> null;
};
val isConfirmed: Boolean
get() = confirmations >= CONFIRMATION
val displayLabel: String?
get() = if (subaddressLabel?.isEmpty() == true || Subaddress.DEFAULT_LABEL_FORMATTER.matcher(
subaddressLabel.toString()
).matches()
) "#$addressIndex" else subaddressLabel
override fun toString(): String {
return "$direction@$blockheight $amount"
}
public int getValue() {
return value;
override fun writeToParcel(out: Parcel, flags: Int) {
out.writeInt(direction.value)
out.writeByte((if (isPending) 1 else 0).toByte())
out.writeByte((if (isFailed) 1 else 0).toByte())
out.writeLong(amount)
out.writeLong(fee)
out.writeLong(blockheight)
out.writeString(hash)
out.writeLong(timestamp)
out.writeString(paymentId)
out.writeInt(accountIndex)
out.writeInt(addressIndex)
out.writeLong(confirmations)
out.writeString(subaddressLabel)
transfers?.let {
out.writeList(transfers)
}
out.writeString(txKey)
out.writeString(notes)
out.writeString(address)
}
override fun describeContents(): Int {
return 0
}
override fun compareTo(other: TransactionInfo): Int {
val b1 = timestamp
val b2 = other.timestamp
return if (b1 > b2) {
-1
} else if (b1 < b2) {
1
} else {
hash?.let { other.hash?.compareTo(it) } ?: 0
}
}
enum class Direction(val value: Int) {
Direction_In(0), Direction_Out(1);
companion object {
fun fromInteger(n: Int): Direction {
return when (n) {
0 -> Direction_In
else -> Direction_Out
}
}
}
}
companion object {
const val CONFIRMATION = 10 // blocks
@JvmField
val CREATOR: Creator<TransactionInfo> = object : Creator<TransactionInfo> {
override fun createFromParcel(`in`: Parcel): TransactionInfo {
return TransactionInfo(`in`)
}
override fun newArray(size: Int): Array<TransactionInfo?> {
return arrayOfNulls(size)
}
}
}
}

View file

@ -430,8 +430,6 @@ class Wallet {
class Status internal constructor(status: Int, @JvmField val errorString: String) {
val status: StatusEnum
@JvmField
var connectionStatus: ConnectionStatus? = null // optional
init {
@ -470,7 +468,6 @@ class Wallet {
@JvmStatic
external fun isPaymentIdValid(payment_id: String): Boolean
@JvmStatic
fun isAddressValid(address: String): Boolean {
return WalletManager.instance?.networkType?.value?.let {
isAddressValid(

View file

@ -6,8 +6,6 @@ import net.mynero.wallet.model.BalanceInfo
class BalanceService(thread: MoneroHandlerThread) : ServiceBase(thread) {
private val _balanceInfo = MutableLiveData<BalanceInfo?>(null)
@JvmField
var balanceInfo: LiveData<BalanceInfo?> = _balanceInfo
init {

View file

@ -9,6 +9,7 @@ class BlockchainService(thread: MoneroHandlerThread) : ServiceBase(thread) {
private val _currentHeight = MutableLiveData(0L)
private val _connectionStatus = MutableLiveData(ConnectionStatus.ConnectionStatus_Disconnected)
var height: LiveData<Long> = _currentHeight
@JvmField
var connectionStatus: LiveData<ConnectionStatus> = _connectionStatus
var daemonHeight: Long = 0

View file

@ -33,7 +33,6 @@ class TxService(thread: MoneroHandlerThread) : ServiceBase(thread) {
}
companion object {
@JvmField
var instance: TxService? = null
}
}

View file

@ -96,9 +96,9 @@ class UTXOService(thread: MoneroHandlerThread?) : ServiceBase(thread) {
val seenTxs = ArrayList<String>()
val utxos: List<CoinsInfo> = ArrayList(getUtxos())
var amountSelected: Long = 0
utxos.sorted()
val sortedUtxos = utxos.sorted()
//loop through each utxo
for (coinsInfo in utxos) {
for (coinsInfo in sortedUtxos) {
if (!coinsInfo.isSpent && coinsInfo.isUnlocked && !coinsInfo.isFrozen && !frozenCoins.contains(
coinsInfo.pubKey
)

View file

@ -38,8 +38,6 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.core.content.ContextCompat
import net.mynero.wallet.R
import net.mynero.wallet.model.WalletManager
import net.mynero.wallet.model.WalletManager.Companion.initLogger
import net.mynero.wallet.model.WalletManager.Companion.setLogLevel
import timber.log.Timber
import java.io.File
import java.io.IOException
@ -62,8 +60,6 @@ object Helper {
private const val MONERO_DIR = "monero"
private val HexArray = "0123456789ABCDEF".toCharArray()
var ALLOW_SHIFT = false
@JvmField
var DISPLAY_DIGITS_INFO = 5
private var ShakeAnimation: Animation? = null
fun getWalletRoot(context: Context): File {
@ -104,7 +100,6 @@ object Helper {
}
}
@JvmStatic
fun getCameraPermission(context: Activity, launcher: ActivityResultLauncher<String?>): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (context.checkSelfPermission(Manifest.permission.CAMERA)
@ -161,7 +156,6 @@ object Helper {
return BigDecimal(amount).scaleByPowerOfTen(-XMR_DECIMALS)
}
@JvmStatic
fun getDisplayAmount(amount: Long): String {
return getDisplayAmount(amount, XMR_DECIMALS)
}
@ -203,13 +197,10 @@ object Helper {
}
fun getBitmap(context: Context, drawableId: Int): Bitmap {
val drawable = ContextCompat.getDrawable(context, drawableId)
return if (drawable is BitmapDrawable) {
BitmapFactory.decodeResource(context.resources, drawableId)
} else if (drawable is VectorDrawable) {
getBitmap(drawable)
} else {
throw IllegalArgumentException("unsupported drawable type")
return when (val drawable = ContextCompat.getDrawable(context, drawableId)) {
is BitmapDrawable -> BitmapFactory.decodeResource(context.resources, drawableId)
is VectorDrawable -> getBitmap(drawable)
else -> throw IllegalArgumentException("unsupported drawable type")
}
}
@ -268,7 +259,6 @@ object Helper {
}
}
@JvmStatic
fun getClipBoardText(context: Context): String? {
val clipboardManager =
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
@ -300,7 +290,7 @@ object Helper {
// TODO make the log levels refer to the WalletManagerFactory::LogLevel enum ?
fun initLogger(context: Context, level: Int) {
val home = getStorage(context, MONERO_DIR).absolutePath
initLogger("$home/monerujo", "monerujo.log")
if (level >= WalletManager.LOGLEVEL_SILENT) setLogLevel(level)
WalletManager.initLogger("$home/monerujo", "monerujo.log")
if (level >= WalletManager.LOGLEVEL_SILENT) WalletManager.setLogLevel(level)
}
}

View file

@ -34,7 +34,6 @@ object ThemeHelper {
) typedValue.resourceId else 0
}
@JvmStatic
@ColorInt
fun getThemedColor(ctx: Context, attrId: Int): Int {
val typedValue = TypedValue()

View file

@ -1,97 +0,0 @@
/*
* Copyright (c) 2017 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.mynero.wallet.util;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
public class HelperTest {
@Test
public void testMinus() {
long l = -1000000000000L;
String s = Helper.getDisplayAmount(l, 5);
System.out.println(s);
assertTrue(s.equals("-1.00"));
}
@Test
public void testTen() {
long l = 10L;
String s = Helper.getDisplayAmount(l);
System.out.println(s);
assertTrue(s.equals("0.00000000001"));
}
@Test
public void testZero() {
long l = 0L;
String s = Helper.getDisplayAmount(l);
System.out.println(s);
assertTrue(s.equals("0.00"));
}
@Test
public void testG() {
long l = 1234567891234L;
String s = Helper.getDisplayAmount(l);
System.out.println(s);
assertTrue(s.equals("1.234567891234"));
}
@Test
public void testG2() {
long l = 1000000000000L;
String s = Helper.getDisplayAmount(l);
System.out.println(s);
assertTrue(s.equals("1.00"));
}
@Test
public void testE() {
long l = 1234567891234L;
String s = Helper.getDisplayAmount(l, 4);
System.out.println(s);
assertTrue(s.equals("1.2346"));
}
@Test
public void testF() {
long l = 1234567891234L;
String s = Helper.getDisplayAmount(l, 12);
System.out.println(s);
assertTrue(s.equals("1.234567891234"));
}
@Test
public void testH() {
long l = 1004567891234L;
String s = Helper.getDisplayAmount(l, 2);
System.out.println(s);
assertTrue(s.equals("1.00"));
}
@Test
public void testGetDisplayAmount() {
assertTrue("0.000000051".equals(Helper.getDisplayAmount(0.000000051)));
assertTrue("1.000000051".equals(Helper.getDisplayAmount(1.000000051)));
assertTrue("1.0".equals(Helper.getDisplayAmount(1d)));
assertTrue("0.0".equals(Helper.getDisplayAmount(0d)));
}
}

View file

@ -1,133 +0,0 @@
/*
* Copyright (c) 2017 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.mynero.wallet.util;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.junit.Assert.assertTrue;
// all ranges go back 5 days
public class RestoreHeightTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void pre2014() {
assertTrue(getHeight("2013-12-01") == 0);
assertTrue(getHeight("1958-12-01") == 0);
}
@Test
public void zero() {
assertTrue(getHeight("2014-04-27") == 0);
}
@Test
public void notZero() {
assertTrue(getHeight("2014-05-07") > 0);
}
@Test(expected = IllegalArgumentException.class)
public void notDateA() {
getHeight("2013-13-04");
}
@Test(expected = IllegalArgumentException.class)
public void notDateB() {
getHeight("2013-13-01-");
}
@Test(expected = IllegalArgumentException.class)
public void notDateC() {
getHeight("x013-13-01");
}
@Test(expected = IllegalArgumentException.class)
public void notDateD() {
getHeight("2013-12-41");
}
@Test
public void test201709() {
// getHeight() returns blockheight of < two days ago
assertTrue(isInRange(getHeight("2017-09-01"), 1383957, 1387716));
assertTrue(isInRange(getHeight("2017-09-05"), 1386967, 1390583));
assertTrue(isInRange(getHeight("2017-09-21"), 1398492, 1402068));
}
@Test
public void test20160324() { // blocktime changed from 1 minute to 2 minutes on this day
assertTrue(isInRange(getHeight("2016-03-23"), 998955, 1006105));
assertTrue(isInRange(getHeight("2016-03-24"), 1000414, 1007486));
assertTrue(isInRange(getHeight("2016-03-25"), 1001800, 1008900));
assertTrue(isInRange(getHeight("2016-03-26"), 1003243, 1009985));
assertTrue(isInRange(getHeight("2016-03-27"), 1004694, 1010746));
}
@Test
public void test2014() {
assertTrue(isInRange(getHeight("2014-04-26"), 0, 8501));
assertTrue(isInRange(getHeight("2014-05-09"), 20289, 28311));
assertTrue(isInRange(getHeight("2014-05-17"), 32608, 40075));
assertTrue(isInRange(getHeight("2014-05-30"), 52139, 59548));
}
@Test
public void test2015() {
assertTrue(isInRange(getHeight("2015-01-26"), 397914, 405055));
assertTrue(isInRange(getHeight("2015-08-13"), 682595, 689748));
}
@Test
public void test2016() {
assertTrue(isInRange(getHeight("2016-01-26"), 918313, 925424));
assertTrue(isInRange(getHeight("2016-08-13"), 1107244, 1110793));
}
@Test
public void test2017() {
assertTrue(isInRange(getHeight("2017-01-26"), 1226806, 1230402));
assertTrue(isInRange(getHeight("2017-08-13"), 1370264, 1373854));
assertTrue(isInRange(getHeight("2017-08-31"), 1383254, 1386967));
assertTrue(isInRange(getHeight("2017-06-09"), 1323288, 1326884));
}
@Test
public void post201802() {
assertTrue(isInRange(getHeight("2018-02-19"), 1507579, 1511127));
}
@Test
public void postFuture() {
long b_20180701 = 1606715L;
long b_20190108 = b_20180701 + 720 * (31 + 31 + 30 + 31 + 30 + 31 + 7);
assertTrue(isInRange(getHeight("2019-01-08"), b_20190108 - 720 * 5, b_20190108));
}
private boolean isInRange(long n, long min, long max) {
if (n > max) return false;
return n >= min;
}
private long getHeight(String date) {
return RestoreHeight.getInstance().getHeight(date);
}
}

View file

@ -1,98 +0,0 @@
/*
* Copyright (c) 2017 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.mynero.wallet.util;
import net.mynero.wallet.data.UserNotes;
import org.junit.Test;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class UserNoteTest {
@Test
public void createFromTxNote_noNote() {
UserNotes userNotes = new UserNotes("{xmrto-iyrpxU,0.009BTC,mjn127C5wRQCULksMYMFHLp9UTdQuCfbZ9}");
assertTrue("xmrto".equals(userNotes.xmrtoTag));
assertTrue("iyrpxU".equals(userNotes.xmrtoKey));
assertTrue("0.009".equals(userNotes.xmrtoAmount));
assertTrue("mjn127C5wRQCULksMYMFHLp9UTdQuCfbZ9".equals(userNotes.xmrtoDestination));
assertTrue(userNotes.note.isEmpty());
}
@Test
public void createFromTxNote_withNote() {
UserNotes userNotes = new UserNotes("{xmrto-iyrpxU,0.009BTC,mjn127C5wRQCULksMYMFHLp9UTdQuCfbZ9} aNote");
assertTrue("xmrto".equals(userNotes.xmrtoTag));
assertTrue("iyrpxU".equals(userNotes.xmrtoKey));
assertTrue("0.009".equals(userNotes.xmrtoAmount));
assertTrue("mjn127C5wRQCULksMYMFHLp9UTdQuCfbZ9".equals(userNotes.xmrtoDestination));
assertTrue("aNote".equals(userNotes.note));
}
@Test
public void createFromTxNote_withNoteNoSpace() {
UserNotes userNotes = new UserNotes("{xmrto-iyrpxU,0.009BTC,mjn127C5wRQCULksMYMFHLp9UTdQuCfbZ9}aNote");
assertTrue("xmrto".equals(userNotes.xmrtoTag));
assertTrue("iyrpxU".equals(userNotes.xmrtoKey));
assertTrue("0.009".equals(userNotes.xmrtoAmount));
assertTrue("mjn127C5wRQCULksMYMFHLp9UTdQuCfbZ9".equals(userNotes.xmrtoDestination));
assertTrue("aNote".equals(userNotes.note));
}
@Test
public void createFromTxNote_brokenB() {
String brokenNote = "{xmrto-iyrpxU,0.009BTC,mjn127C5wRQCULksMYMFHLp9UTdQuCfbZ9";
UserNotes userNotes = new UserNotes(brokenNote);
assertNull(userNotes.xmrtoKey);
assertNull(userNotes.xmrtoAmount);
assertNull(userNotes.xmrtoDestination);
assertTrue(brokenNote.equals(userNotes.note));
}
@Test
public void createFromTxNote_normal() {
String aNote = "aNote";
UserNotes userNotes = new UserNotes(aNote);
assertNull(userNotes.xmrtoKey);
assertNull(userNotes.xmrtoAmount);
assertNull(userNotes.xmrtoDestination);
assertTrue(aNote.equals(userNotes.note));
}
@Test
public void createFromTxNote_empty() {
String aNote = "";
UserNotes userNotes = new UserNotes(aNote);
assertNull(userNotes.xmrtoKey);
assertNull(userNotes.xmrtoAmount);
assertNull(userNotes.xmrtoDestination);
assertTrue(aNote.equals(userNotes.note));
}
@Test
public void createFromTxNote_null() {
UserNotes userNotes = new UserNotes(null);
assertNull(userNotes.xmrtoKey);
assertNull(userNotes.xmrtoAmount);
assertNull(userNotes.xmrtoDestination);
assertNotNull(userNotes.note);
assertTrue(userNotes.note.isEmpty());
}
}