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 import java.io.File
class MainActivity : AppCompatActivity(), MoneroHandlerThread.Listener, PasswordListener { class MainActivity : AppCompatActivity(), MoneroHandlerThread.Listener, PasswordListener {
@JvmField
val restartEvents: SingleLiveEvent<*> = SingleLiveEvent<Any?>() val restartEvents: SingleLiveEvent<*> = SingleLiveEvent<Any?>()
var thread: MoneroHandlerThread? = null var thread: MoneroHandlerThread? = null
private set private set

View File

@ -21,6 +21,7 @@ import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import net.mynero.wallet.R import net.mynero.wallet.R
import net.mynero.wallet.data.DefaultNodes import net.mynero.wallet.data.DefaultNodes
@ -83,9 +84,19 @@ class NodeSelectionAdapter(val listener: NodeSelectionAdapterListener?) :
val currentNode = PrefService.instance?.node val currentNode = PrefService.instance?.node
val match = node == currentNode val match = node == currentNode
if (match) { if (match) {
itemView.setBackgroundColor(itemView.resources.getColor(R.color.oled_colorSecondary)) itemView.setBackgroundColor(
ContextCompat.getColor(
itemView.context,
R.color.oled_colorSecondary
)
)
} else { } 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 nodeNameTextView = itemView.findViewById<TextView>(R.id.node_name_textview)
val nodeAddressTextView = itemView.findViewById<TextView>(R.id.node_uri_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.service.PrefService
import net.mynero.wallet.util.Constants import net.mynero.wallet.util.Constants
import net.mynero.wallet.util.Helper import net.mynero.wallet.util.Helper
import net.mynero.wallet.util.Helper.getDisplayAmount
class SubaddressAdapter(val listener: SubaddressAdapterListener?) : class SubaddressAdapter(val listener: SubaddressAdapterListener?) :
RecyclerView.Adapter<SubaddressAdapter.ViewHolder>() { RecyclerView.Adapter<SubaddressAdapter.ViewHolder>() {
@ -101,7 +100,7 @@ class SubaddressAdapter(val listener: SubaddressAdapterListener?) :
} else { } else {
addressAmountTextView.text = itemView.context.getString( addressAmountTextView.text = itemView.context.getString(
R.string.tx_list_amount_positive, R.string.tx_list_amount_positive,
getDisplayAmount(amount, Helper.DISPLAY_DIGITS_INFO) Helper.getDisplayAmount(amount, Helper.DISPLAY_DIGITS_INFO)
) )
} }
} else addressAmountTextView.text = "" } 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.Constants
import net.mynero.wallet.util.DateHelper.DATETIME_FORMATTER import net.mynero.wallet.util.DateHelper.DATETIME_FORMATTER
import net.mynero.wallet.util.Helper import net.mynero.wallet.util.Helper
import net.mynero.wallet.util.Helper.getDisplayAmount import net.mynero.wallet.util.ThemeHelper
import net.mynero.wallet.util.ThemeHelper.getThemedColor
import java.util.Calendar import java.util.Calendar
import java.util.Date import java.util.Date
@ -84,10 +83,10 @@ class TransactionInfoAdapter(val listener: TxInfoAdapterListener?) :
private var amountTextView: TextView? = null private var amountTextView: TextView? = null
init { init {
inboundColour = getThemedColor(view.context, R.attr.positiveColor) inboundColour = ThemeHelper.getThemedColor(view.context, R.attr.positiveColor)
outboundColour = getThemedColor(view.context, R.attr.negativeColor) outboundColour = ThemeHelper.getThemedColor(view.context, R.attr.negativeColor)
pendingColour = getThemedColor(view.context, R.attr.neutralColor) pendingColour = ThemeHelper.getThemedColor(view.context, R.attr.neutralColor)
failedColour = getThemedColor(view.context, R.attr.neutralColor) failedColour = ThemeHelper.getThemedColor(view.context, R.attr.neutralColor)
val cal = Calendar.getInstance() val cal = Calendar.getInstance()
val tz = cal.timeZone //get the local time zone. val tz = cal.timeZone //get the local time zone.
DATETIME_FORMATTER.timeZone = tz DATETIME_FORMATTER.timeZone = tz
@ -97,7 +96,7 @@ class TransactionInfoAdapter(val listener: TxInfoAdapterListener?) :
val streetModeEnabled = val streetModeEnabled =
PrefService.instance?.getBoolean(Constants.PREF_STREET_MODE, false) PrefService.instance?.getBoolean(Constants.PREF_STREET_MODE, false)
val displayAmount = val displayAmount =
if (streetModeEnabled == true) Constants.STREET_MODE_BALANCE else getDisplayAmount( if (streetModeEnabled == true) Constants.STREET_MODE_BALANCE else Helper.getDisplayAmount(
txInfo.amount, txInfo.amount,
Helper.DISPLAY_DIGITS_INFO Helper.DISPLAY_DIGITS_INFO
) )
@ -108,7 +107,7 @@ class TransactionInfoAdapter(val listener: TxInfoAdapterListener?) :
amountTextView = itemView.findViewById(R.id.tx_amount) amountTextView = itemView.findViewById(R.id.tx_amount)
itemView.findViewById<View>(R.id.tx_failed).visibility = View.GONE itemView.findViewById<View>(R.id.tx_failed).visibility = View.GONE
if (txInfo.isFailed) { 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.context.getString(R.string.tx_list_amount_negative, displayAmount)
itemView.findViewById<View>(R.id.tx_failed).visibility = View.VISIBLE itemView.findViewById<View>(R.id.tx_failed).visibility = View.VISIBLE
setTxColour(failedColour) setTxColour(failedColour)
@ -140,10 +139,10 @@ class TransactionInfoAdapter(val listener: TxInfoAdapterListener?) :
confirmationsTextView.visibility = View.GONE confirmationsTextView.visibility = View.GONE
} }
if (txInfo.direction == TransactionInfo.Direction.Direction_Out) { 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) itemView.context.getString(R.string.tx_list_amount_negative, displayAmount)
} else { } else {
(itemView.findViewById<View>(R.id.tx_amount) as TextView).text = amountTextView?.text =
itemView.context.getString(R.string.tx_list_amount_positive, displayAmount) itemView.context.getString(R.string.tx_list_amount_positive, displayAmount)
} }
(itemView.findViewById<View>(R.id.tx_datetime) as TextView).text = (itemView.findViewById<View>(R.id.tx_datetime) as TextView).text =

View File

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

View File

@ -38,7 +38,6 @@ class Subaddress(
) "#$addressIndex" else label ) "#$addressIndex" else label
companion object { companion object {
@JvmField
val DEFAULT_LABEL_FORMATTER: Pattern = 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}$") 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() Toast.makeText(context, "Node already exists", Toast.LENGTH_SHORT).show()
return 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) { private fun addPasteListener(root: View, editText: EditText, layoutId: Int) {

View File

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

View File

@ -21,6 +21,7 @@ import org.json.JSONObject
class EditNodeBottomSheetDialog : BottomSheetDialogFragment() { class EditNodeBottomSheetDialog : BottomSheetDialogFragment() {
@JvmField @JvmField
var listener: EditNodeListener? = null var listener: EditNodeListener? = null
@JvmField @JvmField
var node: Node? = null var node: Node? = null
override fun onCreateView( override fun onCreateView(
@ -95,8 +96,9 @@ class EditNodeBottomSheetDialog : BottomSheetDialogFragment() {
jsonObject.put("username", user) jsonObject.put("username", user)
jsonObject.put("password", pass) jsonObject.put("password", pass)
} }
if (nodeAddr.contains(":") && !nodeAddr.startsWith("[") && !nodeAddr.endsWith("]")) nodeAddr = if (nodeAddr.contains(":") && !nodeAddr.startsWith("[") && !nodeAddr.endsWith("]"))
"[$nodeAddr]" nodeAddr = "[$nodeAddr]"
jsonObject.put("host", nodeAddr) jsonObject.put("host", nodeAddr)
jsonObject.put("rpcPort", portString.toInt()) jsonObject.put("rpcPort", portString.toInt())
jsonObject.put("network", "mainnet") 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()) { for (defaultNode in DefaultNodes.values()) {
fromJson(defaultNode.json)?.let { nodes.add(it) } fromJson(defaultNode.json)?.let { nodes.add(it) }
} }
@ -98,7 +99,8 @@ class NodeSelectionBottomSheetDialog : BottomSheetDialogFragment(), NodeSelectio
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).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) WalletManager.instance?.setDaemon(node)
adapter?.updateSelectedNode() adapter?.updateSelectedNode()
listener?.onNodeSelected() listener?.onNodeSelected()

View File

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

View File

@ -14,7 +14,6 @@ import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.zxing.client.android.Intents import com.google.zxing.client.android.Intents
import com.journeyapps.barcodescanner.ScanContract import com.journeyapps.barcodescanner.ScanContract
@ -22,7 +21,6 @@ import com.journeyapps.barcodescanner.ScanIntentResult
import com.journeyapps.barcodescanner.ScanOptions import com.journeyapps.barcodescanner.ScanOptions
import net.mynero.wallet.MoneroApplication import net.mynero.wallet.MoneroApplication
import net.mynero.wallet.R import net.mynero.wallet.R
import net.mynero.wallet.model.BalanceInfo
import net.mynero.wallet.model.PendingTransaction import net.mynero.wallet.model.PendingTransaction
import net.mynero.wallet.model.Wallet import net.mynero.wallet.model.Wallet
import net.mynero.wallet.model.Wallet.Companion.getAmountFromString import net.mynero.wallet.model.Wallet.Companion.getAmountFromString
@ -40,10 +38,12 @@ import net.mynero.wallet.util.UriData.Companion.parse
class SendBottomSheetDialog : BottomSheetDialogFragment() { class SendBottomSheetDialog : BottomSheetDialogFragment() {
private val _sendingMax = MutableLiveData(false) private val _sendingMax = MutableLiveData(false)
private val _pendingTransaction = MutableLiveData<PendingTransaction?>(null) private val _pendingTransaction = MutableLiveData<PendingTransaction?>(null)
@JvmField @JvmField
var selectedUtxos = ArrayList<String>() var selectedUtxos = ArrayList<String>()
private var sendingMax: LiveData<Boolean?> = _sendingMax private var sendingMax: LiveData<Boolean?> = _sendingMax
private var pendingTransaction: LiveData<PendingTransaction?> = _pendingTransaction private var pendingTransaction: LiveData<PendingTransaction?> = _pendingTransaction
@JvmField @JvmField
var uriData: UriData? = null var uriData: UriData? = null
private val cameraPermissionsLauncher = registerForActivityResult( private val cameraPermissionsLauncher = registerForActivityResult(
@ -56,8 +56,10 @@ class SendBottomSheetDialog : BottomSheetDialogFragment() {
.show() .show()
} }
} }
@JvmField @JvmField
var isChurning = false var isChurning = false
@JvmField @JvmField
var listener: Listener? = null var listener: Listener? = null
var priority: PendingTransaction.Priority = PendingTransaction.Priority.Priority_Low 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.HistoryService
import net.mynero.wallet.service.PrefService import net.mynero.wallet.service.PrefService
import net.mynero.wallet.util.Constants import net.mynero.wallet.util.Constants
import kotlin.math.roundToInt
class HomeFragment : Fragment(), TxInfoAdapterListener { class HomeFragment : Fragment(), TxInfoAdapterListener {
private var startHeight: Long = 0 private var startHeight: Long = 0
@ -124,11 +123,11 @@ class HomeFragment : Fragment(), TxInfoAdapterListener {
displayEmptyHistory(true, view, textResId, botImgResId) displayEmptyHistory(true, view, textResId, botImgResId)
} else { } else {
// POPULATED WALLET HISTORY // POPULATED WALLET HISTORY
history.sorted() val sortedHistory = history.sortedByDescending { it.timestamp }
if (history.size > 100) { if (sortedHistory.size > 100) {
adapter.submitList(history.subList(0, 99)) adapter.submitList(sortedHistory.subList(0, 99))
} else { } else {
adapter.submitList(history) adapter.submitList(sortedHistory)
} }
txHistoryRecyclerView.visibility = View.VISIBLE txHistoryRecyclerView.visibility = View.VISIBLE
displayEmptyHistory( displayEmptyHistory(

View File

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

View File

@ -13,177 +13,188 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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; class TransactionInfo : Parcelable, Comparable<TransactionInfo> {
import android.os.Parcelable; @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 @JvmField
// this is a POJO for the TransactionInfoAdapter var timestamp: Long
public class TransactionInfo implements Parcelable, Comparable<TransactionInfo> { var paymentId: String?
public static final int CONFIRMATION = 10; // blocks
public static final Parcelable.Creator<TransactionInfo> CREATOR = new Parcelable.Creator<TransactionInfo>() { @JvmField
public TransactionInfo createFromParcel(Parcel in) { var accountIndex: Int
return new TransactionInfo(in);
@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
}
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 {
`in`.readList(transfers, Transfer::class.java.classLoader)
}
} }
public TransactionInfo[] newArray(int size) { txKey = `in`.readString()
return new TransactionInfo[size]; notes = `in`.readString()
address = `in`.readString()
}
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"
}
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)
public Direction direction; out.writeString(notes)
public boolean isPending; out.writeString(address)
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) { override fun describeContents(): Int {
direction = Direction.fromInteger(in.readInt()); return 0
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() { override fun compareTo(other: TransactionInfo): Int {
return confirmations >= CONFIRMATION; val b1 = timestamp
} val b2 = other.timestamp
return if (b1 > b2) {
public String getDisplayLabel() { -1
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) { } else if (b1 < b2) {
return 1; 1
} else { } else {
return this.hash.compareTo(another.hash); hash?.let { other.hash?.compareTo(it) } ?: 0
} }
} }
public enum Direction { enum class Direction(val value: Int) {
Direction_In(0), Direction_In(0), Direction_Out(1);
Direction_Out(1);
private final int value; companion object {
fun fromInteger(n: Int): Direction {
Direction(int value) { return when (n) {
this.value = value; 0 -> Direction_In
else -> Direction_Out
}
}
} }
}
public static Direction fromInteger(int n) { companion object {
return switch (n) { const val CONFIRMATION = 10 // blocks
case 0 -> Direction_In;
case 1 -> Direction_Out;
default -> null;
};
}
public int getValue() { @JvmField
return value; 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) { class Status internal constructor(status: Int, @JvmField val errorString: String) {
val status: StatusEnum val status: StatusEnum
@JvmField
var connectionStatus: ConnectionStatus? = null // optional var connectionStatus: ConnectionStatus? = null // optional
init { init {
@ -470,7 +468,6 @@ class Wallet {
@JvmStatic @JvmStatic
external fun isPaymentIdValid(payment_id: String): Boolean external fun isPaymentIdValid(payment_id: String): Boolean
@JvmStatic
fun isAddressValid(address: String): Boolean { fun isAddressValid(address: String): Boolean {
return WalletManager.instance?.networkType?.value?.let { return WalletManager.instance?.networkType?.value?.let {
isAddressValid( isAddressValid(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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