Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closes #365: Adjust transfer screen #383

Merged
merged 6 commits into from
Jun 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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