New: Implemented transaction detail view.
First pass complete. What remains is: error state handling, animations and hooking into the send flow.
This commit is contained in:
parent
de84bcbe7c
commit
630e7e773a
|
@ -1,11 +1,10 @@
|
||||||
package cash.z.ecc.android.ext
|
package cash.z.ecc.android.ext
|
||||||
|
|
||||||
import android.text.Spannable
|
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import android.text.style.ForegroundColorSpan
|
import android.text.style.ForegroundColorSpan
|
||||||
import androidx.core.text.toSpannable
|
import androidx.core.text.toSpannable
|
||||||
|
|
||||||
fun CharSequence.toColoredSpan(colorResId: Int, coloredPortion: String): Spannable {
|
fun CharSequence.toColoredSpan(colorResId: Int, coloredPortion: String): CharSequence {
|
||||||
return toSpannable().apply {
|
return toSpannable().apply {
|
||||||
val start = this@toColoredSpan.indexOf(coloredPortion)
|
val start = this@toColoredSpan.indexOf(coloredPortion)
|
||||||
setSpan(ForegroundColorSpan(colorResId.toAppColor()), start, start + coloredPortion.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
setSpan(ForegroundColorSpan(colorResId.toAppColor()), start, start + coloredPortion.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
|
|
@ -89,6 +89,7 @@ object Report {
|
||||||
BACKUP,
|
BACKUP,
|
||||||
HOME,
|
HOME,
|
||||||
HISTORY("wallet.history"),
|
HISTORY("wallet.history"),
|
||||||
|
TRANSACTION("wallet.transaction"),
|
||||||
LANDING,
|
LANDING,
|
||||||
PROFILE,
|
PROFILE,
|
||||||
FEEDBACK,
|
FEEDBACK,
|
||||||
|
@ -124,6 +125,7 @@ object Report {
|
||||||
HOME_FUND_NOW("home.fund.now"),
|
HOME_FUND_NOW("home.fund.now"),
|
||||||
HOME_CLEAR_AMOUNT("home.clear.amount"),
|
HOME_CLEAR_AMOUNT("home.clear.amount"),
|
||||||
HISTORY_BACK("history.back"),
|
HISTORY_BACK("history.back"),
|
||||||
|
TRANSACTION_BACK("transaction.back"),
|
||||||
PROFILE_CLOSE("profile.close"),
|
PROFILE_CLOSE("profile.close"),
|
||||||
PROFILE_BACKUP("profile.backup"),
|
PROFILE_BACKUP("profile.backup"),
|
||||||
PROFILE_VIEW_USER_LOGS("profile.view.user.logs"),
|
PROFILE_VIEW_USER_LOGS("profile.view.user.logs"),
|
||||||
|
|
|
@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
|
||||||
import cash.z.ecc.android.ext.Const
|
import cash.z.ecc.android.ext.Const
|
||||||
import cash.z.ecc.android.lockbox.LockBox
|
import cash.z.ecc.android.lockbox.LockBox
|
||||||
import cash.z.ecc.android.sdk.Synchronizer
|
import cash.z.ecc.android.sdk.Synchronizer
|
||||||
|
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
||||||
import cash.z.ecc.android.sdk.ext.twig
|
import cash.z.ecc.android.sdk.ext.twig
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Named
|
import javax.inject.Named
|
||||||
|
@ -19,6 +20,10 @@ class HistoryViewModel @Inject constructor() : ViewModel() {
|
||||||
|
|
||||||
val transactions get() = synchronizer.clearedTransactions
|
val transactions get() = synchronizer.clearedTransactions
|
||||||
val balance get() = synchronizer.balances
|
val balance get() = synchronizer.balances
|
||||||
|
val latestHeight get() = synchronizer.latestHeight
|
||||||
|
|
||||||
|
var selectedTransaction: ConfirmedTransaction? = null
|
||||||
|
|
||||||
|
|
||||||
suspend fun getAddress() = synchronizer.getAddress()
|
suspend fun getAddress() = synchronizer.getAddress()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,304 @@
|
||||||
|
package cash.z.ecc.android.ui.history
|
||||||
|
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.graphics.ColorMatrix
|
||||||
|
import android.graphics.ColorMatrixColorFilter
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.format.DateUtils
|
||||||
|
import android.text.method.ScrollingMovementMethod
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.transition.*
|
||||||
|
import cash.z.ecc.android.R
|
||||||
|
import cash.z.ecc.android.ZcashWalletApp
|
||||||
|
import cash.z.ecc.android.databinding.FragmentTransactionBinding
|
||||||
|
import cash.z.ecc.android.di.viewmodel.activityViewModel
|
||||||
|
import cash.z.ecc.android.ext.*
|
||||||
|
import cash.z.ecc.android.feedback.Report
|
||||||
|
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
||||||
|
import cash.z.ecc.android.sdk.ext.ZcashSdk
|
||||||
|
import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
|
||||||
|
import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress
|
||||||
|
import cash.z.ecc.android.sdk.ext.twig
|
||||||
|
import cash.z.ecc.android.ui.MainActivity
|
||||||
|
import cash.z.ecc.android.ui.base.BaseFragment
|
||||||
|
import cash.z.ecc.android.ui.util.toUtf8Memo
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.text.NumberFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
class TransactionFragment : BaseFragment<FragmentTransactionBinding>() {
|
||||||
|
override val screen = Report.Screen.TRANSACTION
|
||||||
|
private val viewModel: HistoryViewModel by activityViewModel()
|
||||||
|
|
||||||
|
var isMemoExpanded: Boolean = false
|
||||||
|
|
||||||
|
override fun inflate(inflater: LayoutInflater): FragmentTransactionBinding =
|
||||||
|
FragmentTransactionBinding.inflate(inflater)
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
// val transition = TransitionInflater.from(requireContext()).inflateTransition(android.R.transition.move)
|
||||||
|
// sharedElementEnterTransition = transition
|
||||||
|
// sharedElementReturnTransition = transition
|
||||||
|
|
||||||
|
// sharedElementEnterTransition = createSharedElementTransition()
|
||||||
|
// sharedElementReturnTransition = createSharedElementTransition()
|
||||||
|
|
||||||
|
// sharedElementEnterTransition = ChangeBounds().apply { duration = 1500 }
|
||||||
|
// sharedElementReturnTransition = ChangeBounds().apply { duration = 1500 }
|
||||||
|
// enterTransition = Fade().apply {
|
||||||
|
// duration = 1800
|
||||||
|
//// slideEdge = Gravity.END
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSharedElementTransition(duration: Long = 800L): Transition {
|
||||||
|
return TransitionSet().apply {
|
||||||
|
ordering = TransitionSet.ORDERING_TOGETHER
|
||||||
|
this.duration = duration
|
||||||
|
// interpolator = PathInterpolatorCompat.create(0.4f, 0f, 0.2f, 1f)
|
||||||
|
addTransition(ChangeBounds())
|
||||||
|
addTransition(ChangeClipBounds())
|
||||||
|
addTransition(ChangeTransform())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
binding.apply {
|
||||||
|
ViewCompat.setTransitionName(topBoxValue, "test_amount_anim_${viewModel.selectedTransaction!!.id}")
|
||||||
|
ViewCompat.setTransitionName(topBoxBackground, "test_bg_anim_${viewModel.selectedTransaction!!.id}")
|
||||||
|
backButtonHitArea.onClickNavBack { tapped(Report.Tap.TRANSACTION_BACK) }
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
viewModel.selectedTransaction.toUiModel(viewModel.latestHeight).let { uiModel ->
|
||||||
|
topBoxLabel.text = uiModel.topLabel
|
||||||
|
topBoxValue.text = uiModel.topValue
|
||||||
|
bottomBoxLabel.text = uiModel.bottomLabel
|
||||||
|
bottomBoxValue.text = uiModel.bottomValue
|
||||||
|
textBlockHeight.text = uiModel.minedHeight
|
||||||
|
textTimestamp.text = uiModel.timestamp
|
||||||
|
if (uiModel.iconRotation < 0) {
|
||||||
|
topBoxIcon.gone()
|
||||||
|
} else {
|
||||||
|
topBoxIcon.rotation = uiModel.iconRotation
|
||||||
|
topBoxIcon.visible()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!uiModel.isMined) {
|
||||||
|
textBlockHeight.invisible()
|
||||||
|
textBlockHeightPrefix.invisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
val exploreOnClick = View.OnClickListener {
|
||||||
|
uiModel.txId?.let { txId ->
|
||||||
|
mainActivity?.showFirstUseWarning(
|
||||||
|
Const.Pref.FIRST_USE_VIEW_TX,
|
||||||
|
titleResId = R.string.dialog_first_use_view_tx_title,
|
||||||
|
msgResId = R.string.dialog_first_use_view_tx_message,
|
||||||
|
positiveResId = R.string.dialog_first_use_view_tx_positive,
|
||||||
|
negativeResId = R.string.dialog_first_use_view_tx_negative
|
||||||
|
) {
|
||||||
|
onLaunchUrl(txId.toTransactionUrl())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buttonExplore.setOnClickListener(exploreOnClick)
|
||||||
|
textBlockHeight.setOnClickListener(exploreOnClick)
|
||||||
|
|
||||||
|
uiModel.fee?.let { subwaySpotFee.visible(); subwayLabelFee.visible(); subwayLabelFee.text = it }
|
||||||
|
uiModel.source?.let { subwaySpotSource.visible(); subwayLabelSource.visible(); subwayLabelSource.text = it }
|
||||||
|
uiModel.toAddressLabel()?.let { subwaySpotAddress.visible(); subwayLabelAddress.visible(); subwayLabelAddress.text = it }
|
||||||
|
uiModel.toAddressClickListener()?.let { subwayLabelAddress.setOnClickListener(it) }
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: remove logic from sections below and add more fields or extension functions to UiModel
|
||||||
|
uiModel.confirmation?.let {
|
||||||
|
subwaySpotConfirmations.visible(); subwayLabelConfirmations.visible();
|
||||||
|
subwayLabelConfirmations.text = it
|
||||||
|
if (it.equals("confirmed", true)) {
|
||||||
|
subwayLabelConfirmations.setTextColor(R.color.tx_primary.toAppColor())
|
||||||
|
} else {
|
||||||
|
subwayLabelConfirmations.setTextColor(R.color.tx_text_light_dimmed.toAppColor())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uiModel.memo?.let {
|
||||||
|
hitAreaMemoSubway.setOnClickListener { _ -> onToggleMemo(!isMemoExpanded, it) }
|
||||||
|
hitAreaMemoIcon.setOnClickListener { _ -> onToggleMemo(!isMemoExpanded, it) }
|
||||||
|
subwayLabelMemo.movementMethod = ScrollingMovementMethod()
|
||||||
|
subwaySpotMemoContent.visible()
|
||||||
|
subwayLabelMemo.visible()
|
||||||
|
hitAreaMemoSubway.visible()
|
||||||
|
onToggleMemo(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val invertingMatrix = ColorMatrixColorFilter(ColorMatrix().apply { setSaturation(0f) })
|
||||||
|
private fun onToggleMemo(isExpanded: Boolean, memo: String = "") {
|
||||||
|
twig("onToggleMemo($isExpanded, $memo)")
|
||||||
|
if (isExpanded) {
|
||||||
|
twig("setting memo text to: $memo")
|
||||||
|
binding.subwayLabelMemo.setText(memo)
|
||||||
|
binding.subwayLabelMemo.invalidate()
|
||||||
|
// don't impede the ability to scroll
|
||||||
|
binding.groupMemoIcon.gone()
|
||||||
|
binding.subwayLabelMemo.backgroundTintList = ColorStateList.valueOf(R.color.tx_text_light_dimmed.toAppColor())
|
||||||
|
binding.subwaySpotMemoContent.colorFilter = invertingMatrix
|
||||||
|
binding.subwaySpotMemoContent.rotation = 90.0f
|
||||||
|
} else {
|
||||||
|
binding.subwayLabelMemo.setText(getString(R.string.transaction_with_memo))
|
||||||
|
binding.subwayLabelMemo.invalidate()
|
||||||
|
twig("setting memo text to: with a memo")
|
||||||
|
binding.groupMemoIcon.visible()
|
||||||
|
binding.subwayLabelMemo.backgroundTintList = ColorStateList.valueOf(R.color.tx_primary.toAppColor())
|
||||||
|
binding.subwaySpotMemoContent.colorFilter = null
|
||||||
|
binding.subwaySpotMemoContent.rotation = 0.0f
|
||||||
|
}
|
||||||
|
isMemoExpanded = isExpanded
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.toTransactionUrl(): String {
|
||||||
|
return "https://explorer.z.cash/tx/$this"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun UiModel?.toAddressClickListener(): View.OnClickListener? {
|
||||||
|
return this?.address?.let { addr ->
|
||||||
|
View.OnClickListener { mainActivity?.copyText(addr, "Address") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun UiModel?.toAddressLabel(): CharSequence? {
|
||||||
|
if (this == null || this.address == null || this.isInbound == null) return null
|
||||||
|
val prefix = getString(
|
||||||
|
if (isInbound == true) {
|
||||||
|
R.string.transaction_prefix_from
|
||||||
|
} else {
|
||||||
|
R.string.transaction_prefix_to
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return "$prefix ${address?.toAbbreviatedAddress() ?: "Unknown" }".let {
|
||||||
|
it.toColoredSpan(R.color.tx_text_light_dimmed, if (address == null) it else prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun ConfirmedTransaction?.toUiModel(latestHeight: Int? = null): UiModel = UiModel().apply {
|
||||||
|
this@toUiModel.let { tx ->
|
||||||
|
txId = mainActivity?.toTxId(tx?.rawTransactionId)
|
||||||
|
isInbound = when {
|
||||||
|
!(tx?.toAddress.isNullOrEmpty()) -> false
|
||||||
|
tx != null && tx.toAddress.isNullOrEmpty() && tx.value > 0L && tx.minedHeight > 0 -> true
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
isMined = tx?.minedHeight != null && tx.minedHeight > ZcashSdk.SAPLING_ACTIVATION_HEIGHT
|
||||||
|
topValue = if (tx == null) "" else "\$${tx?.value.convertZatoshiToZecString()}"
|
||||||
|
minedHeight = NumberFormat.getNumberInstance(Locale.getDefault()).format(
|
||||||
|
tx?.minedHeight ?: 0
|
||||||
|
)
|
||||||
|
val flags =
|
||||||
|
DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_YEAR or DateUtils.FORMAT_ABBREV_MONTH
|
||||||
|
timestamp = if (tx == null) "Details Unavailable" else DateUtils.getRelativeDateTimeString(
|
||||||
|
ZcashWalletApp.instance,
|
||||||
|
tx.blockTimeInSeconds * 1000,
|
||||||
|
DateUtils.SECOND_IN_MILLIS,
|
||||||
|
DateUtils.WEEK_IN_MILLIS,
|
||||||
|
flags
|
||||||
|
).toString()
|
||||||
|
|
||||||
|
// memo logic
|
||||||
|
val txMemo = tx?.memo.toUtf8Memo()
|
||||||
|
if (!txMemo.isNullOrEmpty()) {
|
||||||
|
memo = txMemo
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirmation logic
|
||||||
|
// TODO: clean all of this up and remove/improve reliance on `isSufficientlyOld` function. Also, add a constant for the number of confirmations we expect.
|
||||||
|
tx?.let {
|
||||||
|
val isMined = it.blockTimeInSeconds != 0L
|
||||||
|
if (isMined) {
|
||||||
|
val hasLatestHeight = latestHeight != null && latestHeight > ZcashSdk.SAPLING_ACTIVATION_HEIGHT
|
||||||
|
if (it.minedHeight > 0 && hasLatestHeight) {
|
||||||
|
val confirmations = latestHeight!! - it.minedHeight + 1
|
||||||
|
confirmation = if (confirmations > 10) "Confirmed" else "$confirmations of 10 Confirmations"
|
||||||
|
} else {
|
||||||
|
if (!hasLatestHeight && isSufficientlyOld(tx)) {
|
||||||
|
twig("Warning: could not load latestheight from server to determine confirmations but this transaction is mined and old enough to be considered confirmed")
|
||||||
|
confirmation = "Confirmed"
|
||||||
|
} else {
|
||||||
|
twig("Warning: could not determine confirmation text value so it will be left null!")
|
||||||
|
confirmation = "Confirmation count temporarily unavailable"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
confirmation = "Pending"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
val mainActivity = (context as MainActivity)
|
||||||
|
// inbound v. outbound values
|
||||||
|
when (isInbound) {
|
||||||
|
true -> {
|
||||||
|
topLabel = "You Received"
|
||||||
|
bottomLabel = "Total Received"
|
||||||
|
bottomValue = "\$${tx?.value.convertZatoshiToZecString()}"
|
||||||
|
iconRotation = -45f
|
||||||
|
source = "to your shielded wallet"
|
||||||
|
address = mainActivity.extractValidAddress(tx?.memo.toUtf8Memo())
|
||||||
|
}
|
||||||
|
false -> {
|
||||||
|
topLabel = "You Sent"
|
||||||
|
bottomLabel = "Total Sent"
|
||||||
|
bottomValue = "\$${tx?.value?.plus(ZcashSdk.MINERS_FEE_ZATOSHI).convertZatoshiToZecString()}"
|
||||||
|
iconRotation = 135f
|
||||||
|
fee = "+0.0001 network fee"
|
||||||
|
source = "from your shielded wallet"
|
||||||
|
address = tx?.toAddress
|
||||||
|
}
|
||||||
|
null -> {
|
||||||
|
twig("Error: transaction appears to be invalid.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: determine this in a more generic and technically correct way. For now, this is good enough.
|
||||||
|
// the goal is just to improve the edge cases where the latest height isn't known but other
|
||||||
|
// information suggests that the TX is confirmed. We can improve this, later.
|
||||||
|
private fun isSufficientlyOld(tx: ConfirmedTransaction): Boolean {
|
||||||
|
val threshold = 75 * 1000 * 25 //approx 25 blocks
|
||||||
|
val delta = System.currentTimeMillis() / 1000L - tx.blockTimeInSeconds
|
||||||
|
return tx.minedHeight > ZcashSdk.SAPLING_ACTIVATION_HEIGHT
|
||||||
|
&& delta < threshold
|
||||||
|
}
|
||||||
|
|
||||||
|
data class UiModel(
|
||||||
|
var topLabel: String = "",
|
||||||
|
var topValue: String = "",
|
||||||
|
var bottomLabel: String = "",
|
||||||
|
var bottomValue: String = "",
|
||||||
|
var minedHeight: String = "",
|
||||||
|
var timestamp: String = "",
|
||||||
|
var iconRotation: Float = -1f,
|
||||||
|
|
||||||
|
var fee: String? = null,
|
||||||
|
var source: String? = null,
|
||||||
|
var memo: String? = null,
|
||||||
|
var address: String? = null,
|
||||||
|
var isInbound: Boolean? = null,
|
||||||
|
var isMined: Boolean = false,
|
||||||
|
var confirmation: String? = null,
|
||||||
|
var txId: String? = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,17 +8,13 @@ import cash.z.ecc.android.R
|
||||||
import cash.z.ecc.android.ext.goneIf
|
import cash.z.ecc.android.ext.goneIf
|
||||||
import cash.z.ecc.android.ext.toAppColor
|
import cash.z.ecc.android.ext.toAppColor
|
||||||
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
||||||
import cash.z.ecc.android.sdk.db.entity.PendingTransactionEntity
|
|
||||||
import cash.z.ecc.android.sdk.ext.ZcashSdk
|
import cash.z.ecc.android.sdk.ext.ZcashSdk
|
||||||
import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
|
import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
|
||||||
import cash.z.ecc.android.sdk.ext.isShielded
|
import cash.z.ecc.android.sdk.ext.isShielded
|
||||||
import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress
|
import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress
|
||||||
import cash.z.ecc.android.ui.MainActivity
|
import cash.z.ecc.android.ui.MainActivity
|
||||||
import cash.z.ecc.android.ui.util.INCLUDE_MEMO_PREFIXES_RECOGNIZED
|
|
||||||
import cash.z.ecc.android.ui.util.toUtf8Memo
|
import cash.z.ecc.android.ui.util.toUtf8Memo
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.nio.charset.Charset
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@ -29,10 +25,10 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
||||||
private val bottomText = itemView.findViewById<TextView>(R.id.text_transaction_bottom)
|
private val bottomText = itemView.findViewById<TextView>(R.id.text_transaction_bottom)
|
||||||
private val shieldIcon = itemView.findViewById<View>(R.id.image_shield)
|
private val shieldIcon = itemView.findViewById<View>(R.id.image_shield)
|
||||||
private val formatter = SimpleDateFormat("M/d h:mma", Locale.getDefault())
|
private val formatter = SimpleDateFormat("M/d h:mma", Locale.getDefault())
|
||||||
private val addressRegex = """zs\d\w{65,}""".toRegex()
|
|
||||||
|
|
||||||
fun bindTo(transaction: T?) {
|
fun bindTo(transaction: T?) {
|
||||||
(itemView.context as MainActivity).lifecycleScope.launch {
|
val mainActivity = itemView.context as MainActivity
|
||||||
|
mainActivity.lifecycleScope.launch {
|
||||||
// update view
|
// update view
|
||||||
var lineOne: String = ""
|
var lineOne: String = ""
|
||||||
var lineTwo: String = ""
|
var lineTwo: String = ""
|
||||||
|
@ -60,7 +56,7 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
||||||
lineOne = "You paid ${toAddress?.toAbbreviatedAddress()}"
|
lineOne = "You paid ${toAddress?.toAbbreviatedAddress()}"
|
||||||
lineTwo = if (isMined) "Sent $timestamp" else "Pending confirmation"
|
lineTwo = if (isMined) "Sent $timestamp" else "Pending confirmation"
|
||||||
// TODO: this logic works but is sloppy. Find a more robust solution to displaying information about expiration (such as expires in 1 block, etc). Then if it is way beyond expired, remove it entirely. Perhaps give the user a button for that (swipe to dismiss?)
|
// TODO: this logic works but is sloppy. Find a more robust solution to displaying information about expiration (such as expires in 1 block, etc). Then if it is way beyond expired, remove it entirely. Perhaps give the user a button for that (swipe to dismiss?)
|
||||||
if(!isMined && (expiryHeight != null) && (expiryHeight!! < (itemView.context as MainActivity).latestHeight ?: -1)) lineTwo = "Expired"
|
if(!isMined && (expiryHeight != null) && (expiryHeight!! < mainActivity.latestHeight ?: -1)) lineTwo = "Expired"
|
||||||
amountDisplay = "- $amountZec"
|
amountDisplay = "- $amountZec"
|
||||||
if (isMined) {
|
if (isMined) {
|
||||||
amountColor = R.color.zcashRed
|
amountColor = R.color.zcashRed
|
||||||
|
@ -71,7 +67,7 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toAddress.isNullOrEmpty() && value > 0L && minedHeight > 0 -> {
|
toAddress.isNullOrEmpty() && value > 0L && minedHeight > 0 -> {
|
||||||
lineOne = getSender(transaction)
|
lineOne = "${mainActivity.getSender(transaction)} paid you"
|
||||||
lineTwo = "Received $timestamp"
|
lineTwo = "Received $timestamp"
|
||||||
amountDisplay = "+ $amountZec"
|
amountDisplay = "+ $amountZec"
|
||||||
amountColor = R.color.zcashGreen
|
amountColor = R.color.zcashGreen
|
||||||
|
@ -91,7 +87,6 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
topText.text = lineOne
|
topText.text = lineOne
|
||||||
bottomText.text = lineTwo
|
bottomText.text = lineTwo
|
||||||
amountText.text = amountDisplay
|
amountText.text = amountDisplay
|
||||||
|
@ -106,71 +101,20 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getSender(transaction: ConfirmedTransaction): String {
|
|
||||||
val memo = transaction.memo.toUtf8Memo()
|
|
||||||
val who = extractValidAddress(memo)?.toAbbreviatedAddress() ?: "Unknown"
|
|
||||||
return "$who paid you"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun extractAddress(memo: String?) =
|
|
||||||
addressRegex.findAll(memo ?: "").lastOrNull()?.value
|
|
||||||
|
|
||||||
private suspend fun extractValidAddress(memo: String?): String? {
|
|
||||||
if (memo == null || memo.length < 25) return null
|
|
||||||
|
|
||||||
// note: cannot use substringAfterLast because we need to ignore case
|
|
||||||
try {
|
|
||||||
INCLUDE_MEMO_PREFIXES_RECOGNIZED.forEach { prefix ->
|
|
||||||
memo.lastIndexOf(prefix, ignoreCase = true).takeUnless { it == -1 }?.let { lastIndex ->
|
|
||||||
memo.substring(lastIndex + prefix.length).trimStart().validateAddress()?.let { address ->
|
|
||||||
return@extractValidAddress address
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch(t: Throwable) { }
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onTransactionClicked(transaction: ConfirmedTransaction) {
|
private fun onTransactionClicked(transaction: ConfirmedTransaction) {
|
||||||
val txId = transaction.rawTransactionId.toTxId()
|
(itemView.context as MainActivity).apply {
|
||||||
val detailsMessage: String = "Zatoshi amount: ${transaction.value}\n\n" +
|
historyViewModel.selectedTransaction = transaction
|
||||||
"Mined height: ${transaction.minedHeight}\n\n" +
|
safeNavigate(R.id.action_nav_history_to_nav_transaction)
|
||||||
"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 ""}"
|
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(itemView.context)
|
|
||||||
.setMessage(detailsMessage)
|
|
||||||
.setTitle("Transaction Details")
|
|
||||||
.setCancelable(true)
|
|
||||||
.setPositiveButton("Ok") { dialog, _ ->
|
|
||||||
dialog.dismiss()
|
|
||||||
}
|
|
||||||
.setNegativeButton("Copy TX") { dialog, _ ->
|
|
||||||
(itemView.context as MainActivity).copyText(txId, "Transaction Id")
|
|
||||||
dialog.dismiss()
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onTransactionLongPressed(transaction: ConfirmedTransaction) {
|
|
||||||
(transaction.toAddress ?: extractAddress(transaction.memo.toUtf8Memo()))?.let {
|
|
||||||
(itemView.context as MainActivity).copyText(it, "Transaction Address")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun String?.validateAddress(): String? {
|
private fun onTransactionLongPressed(transaction: ConfirmedTransaction) {
|
||||||
if (this == null) return null
|
val mainActivity = itemView.context as MainActivity
|
||||||
return if ((itemView.context as MainActivity).isValidAddress(this)) this else null
|
(transaction.toAddress ?: mainActivity.extractAddress(transaction.memo.toUtf8Memo()))?.let {
|
||||||
|
mainActivity.copyText(it, "Transaction Address")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ByteArray.toTxId(): String {
|
|
||||||
val sb = StringBuilder(size * 2)
|
|
||||||
for(i in (size - 1) downTo 0) {
|
|
||||||
sb.append(String.format("%02x", this[i]))
|
|
||||||
}
|
|
||||||
return sb.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,547 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@drawable/background_home">
|
||||||
|
|
||||||
|
<!-- -->
|
||||||
|
<!-- Guidelines -->
|
||||||
|
<!-- -->
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Guideline
|
||||||
|
android:id="@+id/guideline_content_top"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
app:layout_constraintGuide_percent="0.1812" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Guideline
|
||||||
|
android:id="@+id/guideline_keyline_start"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintGuide_percent="0.054" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Guideline
|
||||||
|
android:id="@+id/guideline_keyline_end"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintGuide_percent="0.946" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Guideline
|
||||||
|
android:id="@+id/guideline_content_bottom"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
app:layout_constraintGuide_percent="0.8447" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Guideline
|
||||||
|
android:id="@+id/guideline_subway_line"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintGuide_percent="0.16" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:id="@+id/space_spots"
|
||||||
|
android:layout_width="12dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/subway_line"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/subway_line"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/subway_line" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:id="@+id/space_spots_memo"
|
||||||
|
android:layout_width="18dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/subway_line"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/subway_line"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/subway_line" />
|
||||||
|
|
||||||
|
<!-- -->
|
||||||
|
<!-- Header -->
|
||||||
|
<!-- -->
|
||||||
|
|
||||||
|
<!-- Close Button -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/close_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:tint="@color/zcashWhite_40"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/text_timestamp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.05"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:srcCompat="@drawable/ic_cancel" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/back_button_hit_area"
|
||||||
|
android:layout_width="56dp"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:clickable="true"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.01"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="0.045" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:text="@string/transaction_title"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/close_button"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/close_button"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/close_button" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_timestamp"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:autoSizeMaxTextSize="18sp"
|
||||||
|
android:autoSizeMinTextSize="6dp"
|
||||||
|
android:autoSizeTextType="uniform"
|
||||||
|
android:gravity="bottom"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="@color/tx_text_light_dimmed"
|
||||||
|
android:textSize="18sp"
|
||||||
|
app:layout_constraintBaseline_toBaselineOf="@id/text_block_height"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/text_block_height_prefix"
|
||||||
|
app:layout_constraintHorizontal_bias="0"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/guideline_keyline_start"
|
||||||
|
tools:text="2020-04-14 5:12am and this is way long" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_block_height_prefix"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/transaction_block_height_prefix"
|
||||||
|
android:textColor="@color/tx_text_light_dimmed"
|
||||||
|
android:textSize="18sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/close_button"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/padding_bottom"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/text_block_height" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_block_height"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:textColor="@color/tx_text_light_dimmed_less"
|
||||||
|
android:textSize="18sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/close_button"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/padding_bottom"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/guideline_keyline_end"
|
||||||
|
tools:text="796,798" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:id="@+id/padding_bottom"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/top_box_border"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/guideline_keyline_end"
|
||||||
|
app:layout_constraintHeight_percent="0.021798" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- -->
|
||||||
|
<!-- Content: Top -->
|
||||||
|
<!-- -->
|
||||||
|
|
||||||
|
<!-- %height: 75/734 -->
|
||||||
|
<View
|
||||||
|
android:id="@+id/top_box_background"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:background="#25272B"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/guideline_keyline_end"
|
||||||
|
app:layout_constraintHeight_percent="0.1022"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/guideline_keyline_start"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/guideline_content_top" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/top_box_border"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="2dp"
|
||||||
|
android:background="@color/tx_primary"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/guideline_keyline_end"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/guideline_keyline_start"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/top_box_background" />
|
||||||
|
|
||||||
|
<!-- Icon: BG -->
|
||||||
|
<!-- %height: 42/734 -->
|
||||||
|
<View
|
||||||
|
android:id="@+id/top_box_icon_background"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:background="@drawable/background_circle_solid"
|
||||||
|
android:backgroundTint="@color/tx_circle_icon_bg"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/top_box_background"
|
||||||
|
app:layout_constraintDimensionRatio="w,1:1"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/top_box_background"
|
||||||
|
app:layout_constraintHeight_percent="0.0572"
|
||||||
|
app:layout_constraintHorizontal_bias="0.9556"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/top_box_background"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/top_box_background" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/top_box_icon"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:src="@drawable/ic_arrow_back_black_24dp"
|
||||||
|
android:tint="@color/text_light"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/top_box_icon_background"
|
||||||
|
app:layout_constraintDimensionRatio="w,1:1"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/top_box_icon_background"
|
||||||
|
app:layout_constraintHeight_percent="0.0408"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/top_box_icon_background"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/top_box_icon_background" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/top_box_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:textColor="@color/tx_primary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/top_box_value"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/top_box_background"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0444"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/top_box_background"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/top_box_border"
|
||||||
|
app:layout_constraintVertical_chainStyle="packed"
|
||||||
|
tools:text="You Sent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/top_box_value"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textAppearance="@style/Zcash.TextAppearance.Zec"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:textSize="36sp"
|
||||||
|
app:autoSizeMaxTextSize="36sp"
|
||||||
|
app:autoSizeMinTextSize="6sp"
|
||||||
|
app:autoSizeTextType="uniform"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/top_box_background"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/top_box_icon_background"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/top_box_label"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/top_box_label"
|
||||||
|
tools:text="$4.32" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- -->
|
||||||
|
<!-- Content: Subway -->
|
||||||
|
<!-- -->
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/subway_line"
|
||||||
|
android:layout_width="2dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:background="@color/tx_primary"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/bottom_box_background"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/guideline_subway_line"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/top_box_background" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/subway_spot_fee"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:background="@drawable/background_circle_solid"
|
||||||
|
android:backgroundTint="@color/tx_primary"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/subway_label_fee"
|
||||||
|
app:layout_constraintDimensionRatio="h,1:1"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/space_spots"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/space_spots"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/subway_label_fee" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/subway_spot_source"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:background="@drawable/background_circle_solid"
|
||||||
|
android:backgroundTint="@color/tx_primary"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/subway_label_source"
|
||||||
|
app:layout_constraintDimensionRatio="h,1:1"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/space_spots"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/space_spots"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/subway_label_source" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/subway_spot_memo_content"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:src="@drawable/ic_expand_memo_enabled"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/subway_label_memo"
|
||||||
|
app:layout_constraintDimensionRatio="h,1:1"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/space_spots_memo"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/space_spots_memo"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/subway_label_memo"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/subway_spot_address"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:background="@drawable/background_circle_solid"
|
||||||
|
android:backgroundTint="@color/tx_primary"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/subway_label_address"
|
||||||
|
app:layout_constraintDimensionRatio="h,1:1"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/space_spots"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/space_spots"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/subway_label_address" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/subway_spot_confirmations"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:background="@drawable/background_circle_solid"
|
||||||
|
android:backgroundTint="@color/tx_primary"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/subway_label_confirmations"
|
||||||
|
app:layout_constraintDimensionRatio="h,1:1"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/space_spots"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/space_spots"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/subway_label_confirmations" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/subway_label_fee"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:textColor="@color/tx_text_light_dimmed"
|
||||||
|
android:textSize="18dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/subway_line"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/subway_line"
|
||||||
|
tools:text="+0.0001 network fee" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/subway_label_source"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:textColor="@color/tx_text_light_dimmed"
|
||||||
|
android:textSize="18dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/subway_line"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/subway_label_fee"
|
||||||
|
tools:text="from your shielded wallet" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/spacer_memo_icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:text="@string/transaction_with_memo"
|
||||||
|
android:textSize="18dp"
|
||||||
|
android:visibility="invisible"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/subway_line"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/subway_label_source"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/subway_label_memo"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:background="@null"
|
||||||
|
android:clickable="false"
|
||||||
|
android:fadeScrollbars="false"
|
||||||
|
android:maxLines="3"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
android:textColor="@color/tx_text_light_dimmed"
|
||||||
|
android:textSize="18dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/guideline_keyline_end"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/subway_line"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/subway_label_source"
|
||||||
|
tools:text="this is a memo with 512 characters Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Intege"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon_memo"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/ic_memo"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/spacer_memo_icon"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/spacer_memo_icon"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/spacer_memo_icon" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/hit_area_memo_subway"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/subway_label_memo"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/subway_label_memo"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/guideline_keyline_start"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/subway_label_source"
|
||||||
|
tools:alpha="0.3"
|
||||||
|
tools:background="@color/zcashRed"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/hit_area_memo_icon"
|
||||||
|
android:layout_width="56dp"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/subway_label_memo"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/icon_memo"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/spacer_memo_icon"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/hit_area_memo_subway"
|
||||||
|
tools:alpha="0.3"
|
||||||
|
tools:background="@color/zcashRed"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/subway_label_address"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:textSize="18dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/subway_line"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/subway_label_memo"
|
||||||
|
tools:text="to zs34jgefi30f...10ijgek234e" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/subway_label_confirmations"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:textColor="@color/tx_text_light_dimmed"
|
||||||
|
android:textSize="18dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/bottom_box_background"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/subway_line"
|
||||||
|
tools:text="confirmed" />
|
||||||
|
|
||||||
|
<!-- -->
|
||||||
|
<!-- Content: Bottom -->
|
||||||
|
<!-- -->
|
||||||
|
|
||||||
|
<!-- %height: 75/734 -->
|
||||||
|
<View
|
||||||
|
android:id="@+id/bottom_box_background"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:background="#25272B"
|
||||||
|
android:transitionName="test_transition"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/guideline_content_bottom"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/guideline_keyline_end"
|
||||||
|
app:layout_constraintHeight_percent="0.1022"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/guideline_keyline_start" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/bottom_box_border"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="2dp"
|
||||||
|
android:background="@color/tx_primary"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/guideline_keyline_end"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/guideline_keyline_start"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/bottom_box_background" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bottom_box_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:text="Total Spent"
|
||||||
|
android:textColor="@color/tx_primary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/bottom_box_value"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/bottom_box_background"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0444"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/bottom_box_background"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/bottom_box_border"
|
||||||
|
app:layout_constraintVertical_chainStyle="packed" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bottom_box_value"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text="$4.32"
|
||||||
|
android:textAppearance="@style/Zcash.TextAppearance.Zec"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:textSize="36sp"
|
||||||
|
app:autoSizeMaxTextSize="36sp"
|
||||||
|
app:autoSizeMinTextSize="6sp"
|
||||||
|
app:autoSizeTextType="uniform"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/bottom_box_background"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/bottom_box_background"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/bottom_box_label"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/bottom_box_label" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- -->
|
||||||
|
<!-- Footer -->
|
||||||
|
<!-- -->
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/button_explore"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/Zcash.Button.OutlinedButton"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:text="Explore"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||||
|
android:textColor="@color/tx_text_light_dimmed_less"
|
||||||
|
app:icon="@drawable/ic_baseline_launch_24"
|
||||||
|
app:iconGravity="textEnd"
|
||||||
|
app:iconTint="@color/tx_text_light_dimmed_less"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/guideline_keyline_end"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/guideline_keyline_start"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/guideline_content_bottom"
|
||||||
|
app:layout_constraintVertical_bias="0.24"
|
||||||
|
app:strokeColor="@color/tx_text_light_dimmed_less" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- -->
|
||||||
|
<!-- Groups -->
|
||||||
|
<!-- -->
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Group
|
||||||
|
android:id="@+id/group_memo_icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:constraint_referenced_ids="icon_memo, hit_area_memo_icon, spacer_memo_icon" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -36,13 +36,6 @@
|
||||||
android:id="@+id/nav_receive"
|
android:id="@+id/nav_receive"
|
||||||
android:name="cash.z.ecc.android.ui.receive.ReceiveFragment"
|
android:name="cash.z.ecc.android.ui.receive.ReceiveFragment"
|
||||||
tools:layout="@layout/fragment_receive_new" >
|
tools:layout="@layout/fragment_receive_new" >
|
||||||
<action
|
|
||||||
android:id="@+id/action_nav_receive_to_nav_scan"
|
|
||||||
app:destination="@id/nav_scan"
|
|
||||||
app:popUpTo="@id/nav_receive"
|
|
||||||
app:popUpToInclusive="true"
|
|
||||||
app:exitAnim="@anim/anim_fade_out_address"
|
|
||||||
app:enterAnim="@anim/anim_fade_in_scanner"/>
|
|
||||||
</fragment>
|
</fragment>
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/nav_scan"
|
android:id="@+id/nav_scan"
|
||||||
|
@ -53,17 +46,19 @@
|
||||||
app:destination="@id/nav_send"
|
app:destination="@id/nav_send"
|
||||||
app:popUpTo="@id/nav_scan"
|
app:popUpTo="@id/nav_scan"
|
||||||
app:popUpToInclusive="true"/>
|
app:popUpToInclusive="true"/>
|
||||||
<action
|
|
||||||
android:id="@+id/action_nav_scan_to_nav_receive"
|
|
||||||
app:popUpTo="@id/nav_scan"
|
|
||||||
app:popUpToInclusive="true"
|
|
||||||
app:destination="@id/nav_receive"
|
|
||||||
app:exitAnim="@anim/anim_fade_out_medium"/>
|
|
||||||
</fragment>
|
</fragment>
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/nav_detail"
|
android:id="@+id/nav_history"
|
||||||
android:name="cash.z.ecc.android.ui.detail.WalletDetailFragment"
|
android:name="cash.z.ecc.android.ui.history.HistoryFragment"
|
||||||
tools:layout="@layout/fragment_detail" />
|
tools:layout="@layout/fragment_history">
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_nav_history_to_nav_transaction"
|
||||||
|
app:destination="@id/nav_transaction" />
|
||||||
|
</fragment>
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/nav_transaction"
|
||||||
|
android:name="cash.z.ecc.android.ui.history.TransactionFragment"
|
||||||
|
tools:layout="@layout/fragment_transaction" />
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/nav_profile"
|
android:id="@+id/nav_profile"
|
||||||
android:name="cash.z.ecc.android.ui.profile.ProfileFragment"
|
android:name="cash.z.ecc.android.ui.profile.ProfileFragment"
|
||||||
|
|
|
@ -70,4 +70,12 @@
|
||||||
<color name="text_dark_dimmed">@color/zcashBlack_54</color>
|
<color name="text_dark_dimmed">@color/zcashBlack_54</color>
|
||||||
<color name="text_shadow">@color/zcashBlack_40</color>
|
<color name="text_shadow">@color/zcashBlack_40</color>
|
||||||
|
|
||||||
|
<!-- text : pending design review -->
|
||||||
|
<!-- these are colors found in designs that fall near but outside the palette but may want to
|
||||||
|
replace existing palette colors, after design review -->
|
||||||
|
<color name="tx_text_light_dimmed">#9B9B9B</color>
|
||||||
|
<color name="tx_text_light_dimmed_less">#D3D3D3</color>
|
||||||
|
<color name="tx_circle_icon_bg">#494A4C</color>
|
||||||
|
<color name="tx_primary">#F5A623</color>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -2,11 +2,19 @@
|
||||||
<!-- Common -->
|
<!-- Common -->
|
||||||
<string name="done">Done</string>
|
<string name="done">Done</string>
|
||||||
<string name="cancel">Cancel</string>
|
<string name="cancel">Cancel</string>
|
||||||
|
<string name="unknown">Unknown</string>
|
||||||
|
<string name="blank"></string>
|
||||||
|
|
||||||
<!-- Home -->
|
<!-- Home -->
|
||||||
<string name="home_history_button_text">View History</string>
|
<string name="home_history_button_text">View History</string>
|
||||||
<string name="home_title">Enter an amount to send</string>
|
<string name="home_title">Enter an amount to send</string>
|
||||||
|
|
||||||
|
<!-- Transaction details -->
|
||||||
|
<string name="transaction_title">Transaction Details</string>
|
||||||
|
<string name="transaction_block_height_prefix">"from block "</string>
|
||||||
|
<string name="transaction_with_memo">with a memo</string>
|
||||||
|
<string name="transaction_prefix_from">reply-to</string>
|
||||||
|
<string name="transaction_prefix_to">to</string>
|
||||||
|
|
||||||
<!-- Send Flow -->
|
<!-- Send Flow -->
|
||||||
<string name="send_hint_input_zcash_address">Enter a shielded Zcash address</string>
|
<string name="send_hint_input_zcash_address">Enter a shielded Zcash address</string>
|
||||||
|
@ -16,6 +24,13 @@
|
||||||
<string name="send_pending_button_text">Cancel</string>
|
<string name="send_pending_button_text">Cancel</string>
|
||||||
<string name="send_failed_button_text">Retry</string>
|
<string name="send_failed_button_text">Retry</string>
|
||||||
<string name="send_complete_button_text">See Details</string>
|
<string name="send_complete_button_text">See Details</string>
|
||||||
|
<string name="send_banner_address_user">Your shielded address</string>
|
||||||
|
<string name="send_banner_address_unknown">@string/unknown</string>
|
||||||
|
|
||||||
|
<!-- Address -->
|
||||||
|
<string name="address_label_shielded">Your Shielded Address</string>
|
||||||
|
<string name="scan_address_title">Scan Recipient Address</string>
|
||||||
|
<string name="receive_address_title">Receive Funds</string>
|
||||||
|
|
||||||
<!-- Feedback -->
|
<!-- Feedback -->
|
||||||
<string name="feedback_question_1">Any details you\'d like to share?</string>
|
<string name="feedback_question_1">Any details you\'d like to share?</string>
|
||||||
|
@ -25,9 +40,16 @@
|
||||||
<string name="feedback_hint_2">My balance was . . .</string>
|
<string name="feedback_hint_2">My balance was . . .</string>
|
||||||
<string name="feedback_hint_3">I\'d like . . .</string>
|
<string name="feedback_hint_3">I\'d like . . .</string>
|
||||||
|
|
||||||
|
<!-- Dialogs -->
|
||||||
|
<string name="dialog_not_again">Don\'t show me again</string>
|
||||||
|
<string name="dialog_first_use_view_tx_title">Potential Privacy Risk</string>
|
||||||
|
<string name="dialog_first_use_view_tx_positive">View Tx</string>
|
||||||
|
<string name="dialog_first_use_view_tx_negative">Cancel</string>
|
||||||
|
<string name="dialog_first_use_view_tx_message">Are you sure you\'d like to leave the app? This could reduce privacy, if you do not trust the destination.</string>
|
||||||
|
|
||||||
<!-- Misc -->
|
<!-- Misc -->
|
||||||
<string name="app_name">ECC Wallet</string>
|
<string name="app_name">ECC Wallet</string>
|
||||||
<string name="mixpanel_project">a178e1ef062133fc121079cb12fa43c7</string>
|
<string name="mixpanel_project">a178e1ef062133fc121079cb12fa43c7</string>
|
||||||
<string name="receive_address_title">Your Shielded Address</string>
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in New Issue