Skip to content

Commit

Permalink
Closes #365: Adjust transfer screen (#383)
Browse files Browse the repository at this point in the history
  • Loading branch information
rmeissner authored Jun 6, 2019
1 parent d79a625 commit 9ccebe0
Show file tree
Hide file tree
Showing 34 changed files with 796 additions and 326 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,33 @@ import pm.gnosis.utils.stringWithNoTrailingZeroes
import java.math.BigDecimal
import java.math.BigInteger
import java.math.RoundingMode
import java.math.RoundingMode.DOWN

data class ERC20Token(
val address: Solidity.Address,
val name: String,
val symbol: String,
val decimals: Int,
val logoUrl: String = "",
val displayDecimals: Int = decimals
val displayDecimals: Int = DEFAULT_DISPLAY_DECIMALS
) {
fun convertAmount(unscaledAmount: BigInteger): BigDecimal =
BigDecimal(unscaledAmount).setScale(decimals).div(BigDecimal.TEN.pow(decimals))

fun displayString(amount: BigInteger, showSymbol: Boolean = true, decimalsToDisplay: Int = displayDecimals) =
"${convertAmount(amount).setScale(decimalsToDisplay, RoundingMode.UP).stringWithNoTrailingZeroes()}${if(showSymbol) " $symbol" else ""}"
fun displayString(amount: BigInteger, showSymbol: Boolean = true, decimalsToDisplay: Int = displayDecimals, roundingMode: RoundingMode = DOWN) =
"${convertAmount(amount).setScale(decimalsToDisplay, roundingMode).stringWithNoTrailingZeroes()}${if(showSymbol) " $symbol" else ""}"

companion object {
val ETHER_TOKEN = ERC20Token(Solidity.Address(BigInteger.ZERO), decimals = 18, symbol = "ETH", name = "Ether", displayDecimals = 5)
const val DEFAULT_DISPLAY_DECIMALS = 5
val ETHER_TOKEN = ERC20Token(Solidity.Address(BigInteger.ZERO), decimals = 18, symbol = "ETH", name = "Ether")
}
}

data class ERC20TokenWithBalance(val token: ERC20Token, val balance: BigInteger? = null) {
//FIXME: should symbol be always part of the balance string or should it be presenters job to decide how to display it
fun displayString(showSymbol: Boolean = true) =
fun displayString(showSymbol: Boolean = true, roundingMode: RoundingMode = DOWN) =
balance?.let {
token.displayString(it, showSymbol)
token.displayString(it, showSymbol, roundingMode = roundingMode)
} ?: "-"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,6 @@ class AddressInputHelper(
}

private fun handleError(t: Throwable) {
errorCallback?.invoke(t) ?: dialog.context.toast("Could not find an Ethereum address")
errorCallback?.invoke(t) ?: dialog.context.toast(R.string.invalid_ethereum_address)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import pm.gnosis.svalinn.common.utils.withArgs
import pm.gnosis.utils.asEthereumAddress
import pm.gnosis.utils.asEthereumAddressString
import timber.log.Timber
import java.math.RoundingMode
import javax.inject.Inject


Expand Down Expand Up @@ -87,7 +88,9 @@ class SafeCreationFundFragment : BaseFragment() {

private fun onCreationInfo(info: SafeCreationFundContract.CreationInfo) {
layout_pending_safe_address.text = info.safeAddress
info.paymentToken?.let { layout_pending_safe_required_minimum_transfer_value.text = it.displayString(info.paymentAmount) }
info.paymentToken?.let {
layout_pending_safe_required_minimum_transfer_value.text = it.displayString(info.paymentAmount, roundingMode = RoundingMode.UP)
}
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import pm.gnosis.utils.hexStringToByteArray
import pm.gnosis.utils.nullOnThrow
import timber.log.Timber
import java.math.BigInteger
import java.math.RoundingMode
import javax.inject.Inject

class ReplaceExtensionSubmitActivity : ViewModelActivity<ReplaceExtensionSubmitContract>() {
Expand Down Expand Up @@ -66,7 +67,7 @@ class ReplaceExtensionSubmitActivity : ViewModelActivity<ReplaceExtensionSubmitC
layout_replace_browser_extension_submit.isEnabled = false

disposables += viewModel.loadFeeInfo()
.subscribeBy { layout_replace_browser_extension_fee.text = it.displayString() }
.subscribeBy { layout_replace_browser_extension_fee.text = it.displayString(roundingMode = RoundingMode.UP) }

addressHelper.populateAddressInfo(
layout_replace_browser_extension_info_safe_address,
Expand Down Expand Up @@ -97,12 +98,14 @@ class ReplaceExtensionSubmitActivity : ViewModelActivity<ReplaceExtensionSubmitC

private fun onSafeBalance(status: ReplaceExtensionSubmitContract.SubmitStatus) {
layout_replace_browser_extension_submit.isEnabled = status.canSubmit && !submissionInProgress
layout_replace_browser_extension_safe_balance.text = status.balance.displayString()
layout_replace_browser_extension_fees_error.visible(!status.canSubmit)
layout_replace_browser_extension_gas_token_balance.text = status.balance.displayString()
}

private fun onSafeBalanceError(throwable: Throwable) {
if (throwable !is ReplaceExtensionSubmitContract.NoTokenBalanceException) Timber.e(throwable)
layout_replace_browser_extension_safe_balance.text = getString(R.string.error_retrieving_safe_balance)
layout_replace_browser_extension_fees_error.visible(false)
layout_replace_browser_extension_gas_token_balance.text = getString(R.string.error_retrieving_safe_balance)
layout_replace_browser_extension_submit.isEnabled = false
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,9 @@ class ReplaceExtensionSubmitViewModel @Inject constructor(
.map { tokenBalances ->
if (tokenBalances.size != 1) throw NoTokenBalanceException()
tokenBalances.first().let { (token, balance) ->
val canSubmit = requiredFunds() <= balance
SubmitStatus(ERC20TokenWithBalance(token, balance), canSubmit)
val balanceAfterTx = (balance ?: BigInteger.ZERO) - requiredFunds()
val canSubmit = balanceAfterTx >= BigInteger.ZERO
SubmitStatus(ERC20TokenWithBalance(token, balanceAfterTx), canSubmit)
}
}
.mapToResult()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import pm.gnosis.svalinn.common.utils.withArgs
import pm.gnosis.utils.asEthereumAddress
import pm.gnosis.utils.asEthereumAddressString
import timber.log.Timber
import java.math.RoundingMode
import javax.inject.Inject

class RecoveringSafeFundFragment : BaseFragment() {
Expand Down Expand Up @@ -84,7 +85,8 @@ class RecoveringSafeFundFragment : BaseFragment() {
info.paymentToken?.let {
val requiredFundsString = it.token.displayString(info.paymentAmount)
layout_recovering_safe_fund_amount_label.text = getString(R.string.pending_safe_deposit_value, requiredFundsString)
layout_recovering_safe_fund_description.text = getString(R.string.fund_recovery_fees, it.displayString(), requiredFundsString)
layout_recovering_safe_fund_description.text =
getString(R.string.fund_recovery_fees, it.displayString(roundingMode = RoundingMode.UP), requiredFundsString)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class RecoveringSafeSubmitFragment : BaseFragment() {
.subscribeForResult(onNext = {
layout_recovering_safe_submit_data_balance_value.text = it.paymentToken.displayString(it.balance)
layout_recovering_safe_submit_data_fees_value.text = it.paymentToken.displayString(it.paymentAmount)
layout_recovering_safe_submit_data_fees_error.visible(!it.canSubmit)
layout_recovering_safe_submit_button.visible(it.canSubmit)
layout_recovering_safe_submit_button.isEnabled = it.canSubmit
layout_recovering_safe_submit_retry.visible(!it.canSubmit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,12 @@ class RecoveringSafeViewModel @Inject constructor(
.zipWith(tokenRepository.loadToken(safe.gasToken),
BiFunction { execState: TransactionExecutionRepository.SafeExecuteState, token: ERC20Token ->
val paymentAmount = safe.requiredFunds()
val balanceAfterTx = execState.balance - paymentAmount
RecoveryExecuteInfo(
execState.balance,
balanceAfterTx,
paymentAmount,
token,
execState.balance >= paymentAmount
balanceAfterTx >= BigInteger.ZERO
)
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,24 @@ package pm.gnosis.heimdall.ui.transactions.create
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.core.content.ContextCompat
import com.jakewharton.rxbinding2.view.clicks
import com.jakewharton.rxbinding2.widget.textChanges
import com.squareup.picasso.Picasso
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.functions.BiFunction
import io.reactivex.rxkotlin.plusAssign
import io.reactivex.rxkotlin.subscribeBy
import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.BehaviorSubject
import io.reactivex.subjects.PublishSubject
import kotlinx.android.synthetic.main.layout_create_asset_transfer.*
import pm.gnosis.crypto.utils.asEthereumAddressChecksumString
import pm.gnosis.heimdall.R
import pm.gnosis.heimdall.data.repositories.models.ERC20Token
import pm.gnosis.heimdall.data.repositories.models.ERC20Token.Companion.ETHER_TOKEN
import pm.gnosis.heimdall.di.components.ViewComponent
import pm.gnosis.heimdall.helpers.AddressHelper
import pm.gnosis.heimdall.helpers.AddressInputHelper
Expand All @@ -43,6 +48,11 @@ class CreateAssetTransferActivity : ViewModelActivity<CreateAssetTransferContrac
@Inject
lateinit var toolbarHelper: ToolbarHelper

@Inject
lateinit var picasso: Picasso

private val receiverInputSubject = BehaviorSubject.create<Solidity.Address>()

private lateinit var addressInputHelper: AddressInputHelper

override fun screenId() = ScreenId.TRANSACTION_ENTER_DATA
Expand All @@ -62,19 +72,14 @@ class CreateAssetTransferActivity : ViewModelActivity<CreateAssetTransferContrac
}

private fun handleNewAddress(address: Solidity.Address) {
disposables += Single.fromCallable {
address.asEthereumAddressChecksumString()
}
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(onSuccess = ::onAddressProvided, onError = { toast(R.string.invalid_ethereum_address) })
}

private fun onAddressProvided(address: String) {
if (!address.isBlank()) {
layout_create_asset_transfer_input_receiver.text = address
layout_create_asset_transfer_input_receiver.setTextColor(getColorCompat(R.color.dark_slate_blue))
}
layout_create_asset_transfer_receiver_hint.text = null
addressHelper.populateAddressInfo(
layout_create_asset_transfer_receiver_address,
layout_create_asset_transfer_receiver_name,
layout_create_asset_transfer_receiver_image,
address
).forEach { disposables += it }
receiverInputSubject.onNext(address)
}

override fun onStart() {
Expand All @@ -96,12 +101,9 @@ class CreateAssetTransferActivity : ViewModelActivity<CreateAssetTransferContrac
.doOnNext {
layout_create_asset_transfer_input_value.setTextColor(getColorCompat(R.color.dark_slate_blue))
},
layout_create_asset_transfer_input_receiver.textChanges()
.doOnNext {
layout_create_asset_transfer_input_receiver.setTextColor(getColorCompat(R.color.dark_slate_blue))
},
BiFunction { value: CharSequence, receiver: CharSequence ->
CreateAssetTransferContract.Input(value.toString(), receiver.toString())
receiverInputSubject,
BiFunction { value: CharSequence, receiver: Solidity.Address ->
CreateAssetTransferContract.Input(value.toString(), receiver)
}
)
.doOnNext { disableContinue() }
Expand All @@ -117,7 +119,7 @@ class CreateAssetTransferActivity : ViewModelActivity<CreateAssetTransferContrac
disposables += layout_create_asset_transfer_back_button.clicks()
.subscribeBy { onBackPressed() }

disposables += layout_create_asset_transfer_input_receiver.clicks()
disposables += layout_create_asset_transfer_receiver_hint.clicks()
.subscribeBy { addressInputHelper.showDialog() }

disposables += layout_create_asset_transfer_fees_info.clicks()
Expand All @@ -137,42 +139,59 @@ class CreateAssetTransferActivity : ViewModelActivity<CreateAssetTransferContrac

private fun applyUpdate(update: ViewUpdate) {
when (update) {
is CreateAssetTransferContract.ViewUpdate.Estimate -> {
layout_create_asset_transfer_balance_value.text = update.gasToken.displayString(update.balance)
layout_create_asset_transfer_fees_value.text = "- ${update.gasToken.displayString(update.estimate)}"
layout_create_asset_transfer_continue_button.visible(update.canExecute)
if (!update.canExecute) layout_create_asset_transfer_input_value.setTextColor(getColorCompat(R.color.tomato))
is ViewUpdate.Estimate -> {
layout_create_asset_transfer_gas_token_balance_after_transfer_value.text =
update.gasToken.displayString()
layout_create_asset_transfer_fees_value.text = "-${update.gasToken.token.displayString(update.networkFee)}"
layout_create_asset_transfer_continue_button.visible(update.sufficientFunds)
layout_create_asset_transfer_fees_error.visible(!update.sufficientFunds)

if (update.assetBalanceAfterTransfer != null) {
layout_create_asset_transfer_asset_balance_after_transfer_value.setTextColor(getColorCompat(R.color.battleship_grey))
layout_create_asset_transfer_asset_balance_after_transfer_value.text = update.assetBalanceAfterTransfer.displayString()
layout_create_asset_transfer_asset_balance_after_transfer_label.visible(true)
layout_create_asset_transfer_asset_balance_after_transfer_value.visible(true)
} else {
layout_create_asset_transfer_gas_token_balance_after_transfer_label.setTextColor(getColorCompat(R.color.dark_slate_blue))
layout_create_asset_transfer_asset_balance_after_transfer_label.visible(false)
layout_create_asset_transfer_asset_balance_after_transfer_value.visible(false)
}

if (!update.sufficientFunds) layout_create_asset_transfer_input_value.setTextColor(getColorCompat(R.color.tomato))
else {
layout_create_asset_transfer_input_receiver.setTextColor(getColorCompat(R.color.dark_slate_blue))
layout_create_asset_transfer_input_value.setTextColor(getColorCompat(R.color.dark_slate_blue))
}
}
CreateAssetTransferContract.ViewUpdate.EstimateError -> disableContinue()
is CreateAssetTransferContract.ViewUpdate.TokenInfo -> {
is ViewUpdate.EstimateError -> {
disableContinue()
errorSnackbar(layout_create_asset_transfer_input_label, update.error)
}
is ViewUpdate.TokenInfo -> {
layout_create_asset_transfer_title.text = getString(R.string.send_x, update.value.token.name)
layout_create_asset_transfer_safe_balance.text = update.value.displayString()
layout_create_asset_transfer_input_label.text = update.value.token.symbol
layout_create_asset_transfer_input_icon.setImageDrawable(null)
if (update.value.token == ETHER_TOKEN) layout_create_asset_transfer_input_icon.setImageResource(R.drawable.ic_ether_symbol)
else picasso.load(update.value.token.logoUrl).into(layout_create_asset_transfer_input_icon)
}
is CreateAssetTransferContract.ViewUpdate.InvalidInput -> {
is ViewUpdate.InvalidInput -> {
layout_create_asset_transfer_input_value.setTextColor(
getColorCompat(
if (update.amount) R.color.tomato else R.color.dark_slate_blue
)
)
layout_create_asset_transfer_input_receiver.setTextColor(
getColorCompat(
if (update.address) R.color.tomato else R.color.dark_slate_blue
)
)
}
is CreateAssetTransferContract.ViewUpdate.StartReview -> {
is ViewUpdate.StartReview -> {
startActivity(update.intent)
}
}
}

private fun disableContinue() {
layout_create_asset_transfer_balance_value.text = "-"
layout_create_asset_transfer_asset_balance_after_transfer_label.visible(false)
layout_create_asset_transfer_asset_balance_after_transfer_value.visible(false)
layout_create_asset_transfer_fees_error.visible(false)
layout_create_asset_transfer_gas_token_balance_after_transfer_value.text = "-"
layout_create_asset_transfer_fees_value.text = "-"
layout_create_asset_transfer_continue_button.visible(false)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,17 @@ abstract class CreateAssetTransferContract : ViewModel() {
reviewEvents: Observable<Unit>
): ObservableTransformer<Input, Result<ViewUpdate>>

data class Input(val amount: String, val address: String)
data class Input(val amount: String, val address: Solidity.Address)

sealed class ViewUpdate {
data class Estimate(val estimate: BigInteger, val balance: BigInteger, val gasToken: ERC20Token, val canExecute: Boolean) : ViewUpdate()
object EstimateError : ViewUpdate()
data class Estimate(
val gasToken: ERC20TokenWithBalance,
val networkFee: BigInteger,
val assetBalanceAfterTransfer: ERC20TokenWithBalance?, // null if gasToken and assetToken are the same
val sufficientFunds: Boolean
) : ViewUpdate()

data class EstimateError(val error: Throwable) : ViewUpdate()
data class TokenInfo(val value: ERC20TokenWithBalance) : ViewUpdate()
data class InvalidInput(val amount: Boolean, val address: Boolean) : ViewUpdate()
data class StartReview(val intent: Intent) : ViewUpdate()
Expand Down
Loading

0 comments on commit 9ccebe0

Please sign in to comment.