Fix nearly all bugs from yesterday's stress test.

- reset the send data after a successful transaction submission
    - failed txs will keep the data around for a retry
- improved wallet details
    - added a footer so that it is easier to see scroll behavior
    - unblocked the navigation icons
    - fix change displayed to be ZEC instead of zatoshi
    - fix spacing and sizing for amount text
    - colored the amount available text at the top (emphasize amount)
- improved t-address experience
    - valid t-addresses are recognized in the UI
    - remove shield for transparent sends
    - don't allow memos
    - message the user about unshielded transactions
- improved icon positioning on home screen (and profile)
This commit is contained in:
Kevin Gorham 2020-01-10 13:58:10 -05:00
parent 62bbd30c40
commit 771d10358e
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
17 changed files with 171 additions and 37 deletions

View File

@ -2,6 +2,7 @@ package cash.z.ecc.android.ui.detail
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.AdapterView
import androidx.paging.PagedListAdapter
import androidx.recyclerview.widget.DiffUtil
import cash.z.ecc.android.R
@ -13,12 +14,12 @@ class TransactionAdapter<T : ConfirmedTransaction> :
override fun areItemsTheSame(
oldItem: T,
newItem: T
) = oldItem.minedHeight == newItem.minedHeight
) = oldItem.minedHeight == newItem.minedHeight && oldItem.noteId == newItem.noteId
override fun areContentsTheSame(
oldItem: T,
newItem: T
) = oldItem.equals(newItem)
) = oldItem == newItem
}
) {
@ -33,5 +34,4 @@ class TransactionAdapter<T : ConfirmedTransaction> :
holder: TransactionViewHolder<T>,
position: Int
) = holder.bindTo(getItem(position))
}

View File

@ -4,10 +4,12 @@ import android.view.View
import android.widget.TextView
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.wallet.sdk.entity.ConfirmedTransaction
import cash.z.wallet.sdk.ext.toAbbreviatedAddress
import cash.z.wallet.sdk.ext.convertZatoshiToZecString
import cash.z.wallet.sdk.ext.isShielded
import java.text.SimpleDateFormat
import java.util.*
@ -16,9 +18,12 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
private val amountText = itemView.findViewById<TextView>(R.id.text_transaction_amount)
private val topText = itemView.findViewById<TextView>(R.id.text_transaction_top)
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())
fun bindTo(transaction: T?) {
// update view
var lineOne: String = ""
var lineTwo: String = ""
var amount: String = ""
@ -58,5 +63,6 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
amountText.setTextColor(amountColor.toAppColor())
val context = itemView.context
indicator.background = context.resources.getDrawable(indicatorBackground)
shieldIcon.goneIf(!transaction?.toAddress.isShielded())
}
}

View File

@ -52,7 +52,8 @@ class WalletDetailFragment : BaseFragment<FragmentDetailBinding>() {
val change = (balance.totalZatoshi - balance.availableZatoshi)
binding.textBalanceDescription.apply {
goneIf(change <= 0L)
text = "(expecting +$change ZEC)".toColoredSpan(R.color.text_light, "+${change.convertZatoshiToZecString()}")
val changeString = change.convertZatoshiToZecString()
text = "(expecting +$changeString ZEC)".toColoredSpan(R.color.text_light, "+${changeString}")
}
}
@ -68,4 +69,9 @@ class WalletDetailFragment : BaseFragment<FragmentDetailBinding>() {
twig("got a new paged list of transactions")
adapter.submitList(transactions)
}
// TODO: maybe implement this for better fade behavior. Or do an actual scroll behavior instead, yeah do that. Or an item decoration.
fun onLastItemShown(item: ConfirmedTransaction, position: Int) {
binding.footerFade.alpha = position.toFloat() / (binding.recyclerTransactions.adapter?.itemCount ?: 1)
}
}

View File

@ -4,6 +4,7 @@ import android.content.ClipboardManager
import android.content.Context
import android.content.res.ColorStateList
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.EditText

View File

@ -88,6 +88,10 @@ class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
binding.backButton.goneIf(!binding.textStatus.text.toString().contains("Awaiting"))
binding.buttonNext.goneIf(isSending)
binding.progressHorizontal.goneIf(!isSending)
if (pendingTransaction?.isSubmitSuccess() == true) {
sendViewModel.reset()
}
}
private fun onExit() {

View File

@ -22,10 +22,10 @@ class SendMemoFragment : BaseFragment<FragmentSendMemoBinding>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonNext.setOnClickListener {
onAddMemo()
onTopButton()
}
binding.buttonSkip.setOnClickListener {
onSkip()
onBottomButton()
}
R.id.action_nav_send_memo_to_nav_send_address.let {
binding.backButtonHitArea.onClickNavTo(it)
@ -38,7 +38,7 @@ class SendMemoFragment : BaseFragment<FragmentSendMemoBinding>() {
binding.inputMemo.setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
onAddMemo()
onTopButton()
true
} else {
false
@ -54,8 +54,19 @@ class SendMemoFragment : BaseFragment<FragmentSendMemoBinding>() {
}
private fun applyModel() {
binding.inputMemo.setText(sendViewModel.memo)
binding.checkIncludeAddress.isChecked = sendViewModel.includeFromAddress
sendViewModel.isShielded.let { isShielded ->
binding.groupShielded.goneIf(!isShielded)
binding.groupTransparent.goneIf(isShielded)
if (isShielded) {
binding.inputMemo.setText(sendViewModel.memo)
binding.checkIncludeAddress.isChecked = sendViewModel.includeFromAddress
binding.buttonNext.text = "ADD MEMO"
binding.buttonSkip.text = "SEND WITHOUT MEMO"
} else {
binding.buttonNext.text = "GO BACK"
binding.buttonSkip.text = "PROCEED"
}
}
}
private fun onIncludeMemo(checked: Boolean) {
@ -64,18 +75,22 @@ class SendMemoFragment : BaseFragment<FragmentSendMemoBinding>() {
if (checked) binding.inputMemo.setHint("") else binding.inputMemo.setHint("Add a memo here")
}
private fun onSkip() {
private fun onTopButton() {
if (sendViewModel.isShielded) {
sendViewModel.memo = binding.inputMemo.text.toString()
onNext()
} else {
mainActivity?.navController?.navigate(R.id.action_nav_send_memo_to_nav_send_address)
}
}
private fun onBottomButton() {
binding.inputMemo.setText("")
sendViewModel.memo = ""
sendViewModel.includeFromAddress = false
onNext()
}
private fun onAddMemo() {
sendViewModel.memo = binding.inputMemo.text.toString()
onNext()
}
private fun onNext() {
mainActivity?.navController?.navigate(R.id.action_nav_send_memo_to_send_confirm)
}

View File

@ -68,6 +68,14 @@ class SendViewModel @Inject constructor() : ViewModel() {
}
}
fun reset() {
fromAddress = ""
toAddress = ""
memo = ""
zatoshiAmount = -1L
includeFromAddress = false
}
var fromAddress: String = ""
var toAddress: String = ""
var memo: String = ""
@ -80,4 +88,5 @@ class SendViewModel @Inject constructor() : ViewModel() {
}
field = value
}
val isShielded get() = toAddress.startsWith("z")
}

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape
xmlns:android="http://schemas.android.com/apk/res/android">
<corners
android:bottomLeftRadius="10dp"
android:bottomRightRadius="10dp"
android:topLeftRadius="0dp"
android:topRightRadius="0dp" />
<solid android:color="@color/text_light_dimmed" />
</shape>
</item>
<item
android:end="1dp"
android:start="1dp"
android:left="1dp"
android:right="1dp"
android:bottom="1dp">
<shape>
<corners
android:bottomLeftRadius="10dp"
android:bottomRightRadius="10dp"
android:topLeftRadius="0dp"
android:topRightRadius="0dp" />
<solid android:color="@color/background_banner" />
</shape>
</item></layer-list>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:angle="270"
android:endColor="@color/background_banner"
android:startColor="@android:color/transparent" />
</shape>

View File

@ -22,9 +22,6 @@
android:bottomRightRadius="0dp"
android:topLeftRadius="10dp"
android:topRightRadius="10dp" />
<stroke
android:color="#282828"
android:width="1dp" />
<solid android:color="@color/background_banner" />
</shape>
</item></layer-list>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z"/>
</vector>

View File

@ -101,6 +101,7 @@
android:layout_height="wrap_content"
android:text="(enter an amount to send)"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
android:textColor="@color/text_light_dimmed"
app:layout_constraintBottom_toBottomOf="@id/back_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@ -166,7 +167,7 @@
android:id="@+id/recycler_transactions"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toBottomOf="@id/footer_fade"
app:layout_constraintEnd_toEndOf="@id/guideline_keyline_end"
app:layout_constraintStart_toStartOf="@id/guideline_keyline_start"
app:layout_constraintTop_toBottomOf="@id/header"
@ -174,5 +175,25 @@
tools:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_transaction"
tools:orientation="vertical" />
<View
android:id="@+id/footer"
android:layout_width="0dp"
android:layout_height="32dp"
android:layout_marginBottom="8dp"
android:background="@drawable/background_footer"
app:layout_constraintEnd_toEndOf="@id/guideline_keyline_end"
app:layout_constraintStart_toStartOf="@id/guideline_keyline_start"
app:layout_constraintBottom_toBottomOf="parent"/>
<View
android:id="@+id/footer_fade"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginEnd="1dp"
android:layout_marginStart="1dp"
android:background="@drawable/background_gradient_bottom"
app:layout_constraintEnd_toEndOf="@id/guideline_keyline_end"
app:layout_constraintStart_toStartOf="@id/guideline_keyline_start"
app:layout_constraintBottom_toTopOf="@id/footer"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -280,7 +280,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.052"
app:layout_constraintHorizontal_bias="0.108"
app:layout_constraintHorizontal_bias="0.088"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0574"
@ -296,7 +296,7 @@
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="0.8883"
app:layout_constraintHorizontal_bias="0.912"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.064"
app:layout_constraintWidth_percent="0.08"

View File

@ -44,7 +44,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.8883"
app:layout_constraintHorizontal_bias="0.912"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.064"

View File

@ -41,13 +41,13 @@
android:background="@drawable/background_banner"
android:elevation="6dp"
android:gravity="top"
android:imeActionLabel="add memo"
android:inputType="textImeMultiLine"
android:imeOptions="actionDone"
android:hint="Add a memo here"
android:imeActionLabel="add memo"
android:imeOptions="actionDone"
android:inputType="textImeMultiLine"
android:paddingBottom="8dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/text_light"
@ -65,38 +65,39 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:elevation="6dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
android:paddingEnd="16dp"
android:paddingStart="16dp"
android:textColor="@color/text_light_dimmed"
tools:text="sent from z23lk4jjl2k3jl43kkj542l3kl4hj2l3k1j41l2kjk423lkj423lklhk2jrhiuhrh2j4hh2hkj23hkj4"
app:layout_constraintStart_toStartOf="@id/input_memo"
app:layout_constraintBottom_toBottomOf="@id/input_memo"
app:layout_constraintEnd_toEndOf="@id/input_memo"
app:layout_constraintBottom_toBottomOf="@id/input_memo" />
app:layout_constraintStart_toStartOf="@id/input_memo"
tools:text="sent from z23lk4jjl2k3jl43kkj542l3kl4hj2l3k1j41l2kjk423lkj423lklhk2jrhiuhrh2j4hh2hkj23hkj4" />
<View
android:layout_width="0dp"
android:layout_height="1px"
android:elevation="6dp"
android:layout_marginBottom="4dp"
android:background="@color/text_light_dimmed"
app:layout_constraintStart_toStartOf="@id/text_included_address"
android:elevation="6dp"
app:layout_constraintEnd_toEndOf="@id/text_included_address"
app:layout_constraintTop_toTopOf="@id/text_included_address"/>
app:layout_constraintStart_toStartOf="@id/text_included_address"
app:layout_constraintTop_toTopOf="@id/text_included_address" />
<CheckBox
android:id="@+id/check_include_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="0dp"
android:layout_marginTop="16dp"
android:padding="0dp"
android:layout_marginRight="0dp"
android:text="Include your sending address in memo"
app:layout_constraintStart_toStartOf="@+id/input_memo"
app:layout_constraintTop_toBottomOf="@+id/input_memo" />
<TextView
android:id="@+id/text_info_shielded"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
@ -108,6 +109,25 @@
app:layout_constraintStart_toStartOf="@id/input_memo"
app:layout_constraintTop_toBottomOf="@id/check_include_address" />
<TextView
android:id="@+id/text_info_transparent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ic_warning_24dp"
android:drawablePadding="16dp"
android:drawableTint="@color/colorPrimary"
android:textAlignment="textStart"
android:text="You are sending to a transparent address, which reduces your privacy and does not support memos."
android:textColor="@color/text_light"
android:textSize="18dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.263"
app:layout_constraintWidth_percent="0.9" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_next"
android:layout_width="0dp"
@ -134,4 +154,17 @@
app:layout_constraintEnd_toEndOf="@id/button_next"
app:layout_constraintStart_toStartOf="@id/button_next"
app:layout_constraintTop_toBottomOf="@id/button_next" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_transparent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="text_info_transparent" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_shielded"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="input_memo, check_include_address, text_included_address, text_info_shielded"
tools:visibility="gone" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -118,6 +118,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:paddingStart="12dp"
android:autoSizeTextType="uniform"
android:gravity="center_vertical"
android:maxLines="1"
@ -127,6 +128,6 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/indicator"
app:layout_constraintWidth_percent="0.25"
tools:text="+ 4345.2444" />
tools:text="+ 434.2444234" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -162,7 +162,7 @@
<!-- -->
<!-- Global actions -->
<!-- -->
<action
android:id="@+id/action_global_nav_scan"
app:destination="@id/nav_scan" />