Improved transaction detail UI and behavior.
Differentiate pending transactions, show address when we can parse it from the memo, allow address copy.
This commit is contained in:
parent
8371f9c53a
commit
5a956a55d3
|
@ -2,11 +2,15 @@ package cash.z.ecc.android.ui.detail
|
|||
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import cash.z.ecc.android.R
|
||||
import cash.z.ecc.android.ext.goneIf
|
||||
import cash.z.ecc.android.ext.toAppColor
|
||||
import cash.z.ecc.android.ui.MainActivity
|
||||
import cash.z.ecc.android.ui.send.SendViewModel
|
||||
import cash.z.ecc.android.ui.util.INCLUDE_MEMO_PREFIX
|
||||
import cash.z.ecc.android.ui.util.toUtf8Memo
|
||||
import cash.z.wallet.sdk.entity.ConfirmedTransaction
|
||||
import cash.z.wallet.sdk.ext.*
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
@ -21,6 +25,7 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
|||
private val bottomText = itemView.findViewById<TextView>(R.id.text_transaction_bottom)
|
||||
private val shieldIcon = itemView.findViewById<View>(R.id.image_shield)
|
||||
private val formatter = SimpleDateFormat("M/d h:mma", Locale.getDefault())
|
||||
private val addressRegex = """zs\d\w{65,}""".toRegex()
|
||||
|
||||
fun bindTo(transaction: T?) {
|
||||
|
||||
|
@ -29,13 +34,19 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
|||
var lineTwo: String = ""
|
||||
var amountZec: String = ""
|
||||
var amountDisplay: String = ""
|
||||
var amountColor: Int = 0
|
||||
var indicatorBackground: Int = 0
|
||||
var amountColor: Int = R.color.text_light_dimmed
|
||||
var lineOneColor: Int = R.color.text_light
|
||||
var lineTwoColor: Int = R.color.text_light_dimmed
|
||||
var indicatorBackground: Int = R.drawable.background_indicator_unknown
|
||||
|
||||
transaction?.apply {
|
||||
itemView.setOnClickListener {
|
||||
onTransactionClicked(this)
|
||||
}
|
||||
itemView.setOnLongClickListener {
|
||||
onTransactionLongPressed(this)
|
||||
true
|
||||
}
|
||||
amountZec = value.convertZatoshiToZecString()
|
||||
// TODO: these might be good extension functions
|
||||
val timestamp = formatter.format(blockTimeInSeconds * 1000L)
|
||||
|
@ -45,11 +56,16 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
|||
lineOne = "You paid ${toAddress?.toAbbreviatedAddress()}"
|
||||
lineTwo = if (isMined) "Sent $timestamp" else "Pending confirmation"
|
||||
amountDisplay = "- $amountZec"
|
||||
amountColor = R.color.zcashRed
|
||||
indicatorBackground = R.drawable.background_indicator_outbound
|
||||
if (isMined) {
|
||||
amountColor = R.color.zcashRed
|
||||
indicatorBackground = R.drawable.background_indicator_outbound
|
||||
} else {
|
||||
lineOneColor = R.color.text_light_dimmed
|
||||
lineTwoColor = R.color.text_light
|
||||
}
|
||||
}
|
||||
raw == null || raw?.isEmpty() == true -> {
|
||||
lineOne = "Unknown paid you"
|
||||
toAddress.isNullOrEmpty() && value > 0L && minedHeight > 0 -> {
|
||||
lineOne = getSender(transaction)
|
||||
lineTwo = "Received $timestamp"
|
||||
amountDisplay = "+ $amountZec"
|
||||
amountColor = R.color.zcashGreen
|
||||
|
@ -58,9 +74,10 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
|||
else -> {
|
||||
lineOne = "Unknown"
|
||||
lineTwo = "Unknown"
|
||||
amountDisplay = "$amountZec"
|
||||
amountColor = R.color.text_light
|
||||
}
|
||||
}
|
||||
|
||||
// sanitize amount
|
||||
if (value < ZcashSdk.MINERS_FEE_ZATOSHI) amountDisplay = "< 0.001"
|
||||
else if (amountZec.length > 10) { // 10 allows 3 digits to the left and 6 to the right of the decimal
|
||||
|
@ -73,17 +90,41 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
|||
bottomText.text = lineTwo
|
||||
amountText.text = amountDisplay
|
||||
amountText.setTextColor(amountColor.toAppColor())
|
||||
topText.setTextColor(lineOneColor.toAppColor())
|
||||
bottomText.setTextColor(lineTwoColor.toAppColor())
|
||||
val context = itemView.context
|
||||
indicator.background = context.resources.getDrawable(indicatorBackground)
|
||||
shieldIcon.goneIf((transaction?.raw != null || transaction?.expiryHeight != null) && !transaction?.toAddress.isShielded())
|
||||
}
|
||||
|
||||
private fun getSender(transaction: ConfirmedTransaction): String {
|
||||
val memo = transaction.memo.toUtf8Memo()
|
||||
return when {
|
||||
memo.contains(INCLUDE_MEMO_PREFIX) -> {
|
||||
val address = memo.split(INCLUDE_MEMO_PREFIX)[1].trim()
|
||||
"${address.toAbbreviatedAddress()} paid you"
|
||||
}
|
||||
memo.contains("eply to:") -> {
|
||||
val address = memo.split("eply to:")[1].trim()
|
||||
"${address.toAbbreviatedAddress()} paid you"
|
||||
}
|
||||
memo.contains("zs") -> {
|
||||
val who = extractAddress(memo)?.toAbbreviatedAddress() ?: "Unknown"
|
||||
"$who paid you"
|
||||
}
|
||||
else -> "Unknown paid you"
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractAddress(memo: String?) =
|
||||
addressRegex.findAll(memo ?: "").lastOrNull()?.value
|
||||
|
||||
private fun onTransactionClicked(transaction: ConfirmedTransaction) {
|
||||
val txId = transaction.rawTransactionId.toTxId()
|
||||
val detailsMessage: String = "Zatoshi amount: ${transaction.value}\n\n" +
|
||||
"Transaction: $txId" +
|
||||
"${if (transaction.toAddress != null) "\n\nto: ${transaction.toAddress}" else ""}" +
|
||||
"${if (transaction.memo != null) "\n\nmemo: \n${String(transaction.memo!!, Charset.forName("UTF-8"))}" else ""}"
|
||||
"${if (transaction.toAddress != null) "\n\nTo: ${transaction.toAddress}" else ""}" +
|
||||
"${if (transaction.memo != null) "\n\nMemo: \n${String(transaction.memo!!, Charset.forName("UTF-8"))}" else ""}"
|
||||
|
||||
MaterialAlertDialogBuilder(itemView.context)
|
||||
.setMessage(detailsMessage)
|
||||
|
@ -98,6 +139,12 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
|||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun onTransactionLongPressed(transaction: ConfirmedTransaction) {
|
||||
(transaction.toAddress ?: extractAddress(transaction.memo.toUtf8Memo()))?.let {
|
||||
(itemView.context as MainActivity).copyText(it, "Transaction Address")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ByteArray.toTxId(): String {
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package cash.z.ecc.android.ui.util
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
|
||||
const val INCLUDE_MEMO_PREFIX = "sent from"
|
||||
|
||||
inline fun ByteArray?.toUtf8Memo(): String {
|
||||
// TODO: make this more official but for now, this will do
|
||||
return if (this == null || this[0] >= 0xF5) "" else try {
|
||||
String(this, StandardCharsets.UTF_8).trim('\u0000')
|
||||
} catch (t: Throwable) {
|
||||
"unable to parse memo"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
if self.0[0] < 0xF5 {
|
||||
// Check if it is valid UTF8
|
||||
Some(str::from_utf8(&self.0).map(|memo| {
|
||||
// Drop trailing zeroes
|
||||
memo.trim_end_matches(char::from(0)).to_owned()
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
*/
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="4dp" />
|
||||
<gradient
|
||||
android:angle="270"
|
||||
android:endColor="@color/zcashWhite_12"
|
||||
android:startColor="@color/zcashWhite_60" />
|
||||
</shape>
|
Loading…
Reference in New Issue