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:
Kevin Gorham 2020-03-27 16:46:38 -04:00
parent 8371f9c53a
commit 5a956a55d3
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
3 changed files with 93 additions and 9 deletions

View File

@ -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 {

View File

@ -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
}
*/

View File

@ -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>