Skip to content

Commit

Permalink
Vault validations
Browse files Browse the repository at this point in the history
  • Loading branch information
ruixhuang committed Oct 2, 2024
1 parent 594dcd4 commit 568dc62
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 20 deletions.
2 changes: 1 addition & 1 deletion v4/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ ext {
compileSdkVersion = 34

// App dependencies
abacusVersion = '1.12.14'
abacusVersion = '1.12.16'
carteraVersion = '0.1.15'
kollectionsVersion = '2.0.16'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,4 @@ val VaultFormValidationResult.canDeposit: Boolean
get() = submissionData?.deposit != null

val VaultFormValidationResult.canWithdraw: Boolean
get() = submissionData?.withdraw != null
get() = submissionData?.withdraw != null
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import exchange.dydx.abacus.output.input.ValidationError
import exchange.dydx.abacus.protocols.LocalizerProtocol
import exchange.dydx.trading.feature.receipt.validation.DydxValidationView

internal fun ValidationError.createViewModel(
internal fun ValidationError.createViewModel(
localizer: LocalizerProtocol
): DydxValidationView.ViewState {
return DydxValidationView.ViewState(
Expand All @@ -18,4 +18,4 @@ internal fun ValidationError.createViewModel(
title = this.resources.title?.localized ?: this.resources.title?.stringKey,
message = this.resources.text?.localized ?: this.resources.text?.stringKey,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package exchange.dydx.trading.feature.vault.depositwithdraw.components

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import exchange.dydx.abacus.protocols.LocalizerProtocol
import exchange.dydx.platformui.components.icons.PlatformSelectedIcon
import exchange.dydx.platformui.components.icons.PlatformUnselectedIcon
import exchange.dydx.platformui.designSystem.theme.ThemeColor
import exchange.dydx.platformui.designSystem.theme.ThemeFont
import exchange.dydx.platformui.designSystem.theme.dydxDefault
import exchange.dydx.platformui.designSystem.theme.themeColor
import exchange.dydx.platformui.designSystem.theme.themeFont
import exchange.dydx.platformui.theme.DydxThemedPreviewSurface
import exchange.dydx.platformui.theme.MockLocalizer

@Preview
@Composable
fun Preview_VaultSlippageCheckbox() {
DydxThemedPreviewSurface {
VaultSlippageCheckbox.Content(Modifier, VaultSlippageCheckbox.ViewState.preview)
}
}

object VaultSlippageCheckbox {
data class ViewState(
val localizer: LocalizerProtocol,
val text: String?,
val checked: Boolean = false,
val onCheckedChange: (Boolean) -> Unit = {},
) {
companion object {
val preview = ViewState(
localizer = MockLocalizer(),
text = "1.0M",
)
}
}

@Composable
fun Content(modifier: Modifier, state: ViewState?) {
if (state == null) {
return
}

Row(
modifier = modifier
.clickable {
state.onCheckedChange(!state.checked)
},
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
if (state.checked) {
PlatformSelectedIcon(size = 24.dp)
} else {
PlatformUnselectedIcon(size = 24.dp)
}

Text(
text = state.text ?: "",
style = TextStyle.dydxDefault
.themeColor(ThemeColor.SemanticColor.text_secondary)
.themeFont(fontSize = ThemeFont.FontSize.base),
modifier = Modifier.padding(start = 8.dp).weight(1f),
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import exchange.dydx.trading.common.component.DydxComponent
import exchange.dydx.trading.feature.shared.R
import exchange.dydx.trading.feature.shared.views.HeaderView
import exchange.dydx.trading.feature.shared.views.InputCtaButton
import exchange.dydx.trading.feature.vault.depositwithdraw.components.VaultSlippageCheckbox
import exchange.dydx.trading.feature.vault.receipt.DydxVaultReceiptView

@Preview
Expand All @@ -62,6 +63,7 @@ object DydxVaultConfirmationView : DydxComponent {
val ctaButton: InputCtaButton.ViewState? = null,
val backAction: (() -> Unit)? = null,
val direction: Direction? = null,
val slippage: VaultSlippageCheckbox.ViewState? = null,
) {
companion object {
val preview = ViewState(
Expand All @@ -72,6 +74,7 @@ object DydxVaultConfirmationView : DydxComponent {
destinationValue = "Vault",
ctaButton = InputCtaButton.ViewState.preview,
direction = Direction.Deposit,
slippage = VaultSlippageCheckbox.ViewState.preview,
)
}
}
Expand Down Expand Up @@ -139,6 +142,13 @@ object DydxVaultConfirmationView : DydxComponent {
modifier = Modifier.offset(y = ThemeShapes.VerticalPadding),
)

if (state.slippage != null) {
VaultSlippageCheckbox.Content(
modifier = Modifier.padding(ThemeShapes.HorizontalPadding),
state = state.slippage,
)
}

InputCtaButton.Content(
modifier = Modifier
.fillMaxWidth()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package exchange.dydx.trading.feature.vault.depositwithdraw.confirmation

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import exchange.dydx.abacus.functional.vault.VaultDepositData
import exchange.dydx.abacus.functional.vault.VaultFormValidationResult
Expand All @@ -9,6 +10,7 @@ import exchange.dydx.abacus.output.Vault
import exchange.dydx.abacus.protocols.LocalizerProtocol
import exchange.dydx.abacus.protocols.ParserProtocol
import exchange.dydx.dydxstatemanager.AbacusStateManagerProtocol
import exchange.dydx.dydxstatemanager.localizeWithParams
import exchange.dydx.trading.common.DydxViewModel
import exchange.dydx.trading.common.formatter.DydxFormatter
import exchange.dydx.trading.common.navigation.DydxRouter
Expand All @@ -19,10 +21,13 @@ import exchange.dydx.trading.feature.vault.VaultInputState
import exchange.dydx.trading.feature.vault.VaultInputType
import exchange.dydx.trading.feature.vault.canDeposit
import exchange.dydx.trading.feature.vault.canWithdraw
import exchange.dydx.trading.feature.vault.depositwithdraw.components.VaultSlippageCheckbox
import exchange.dydx.trading.integration.cosmos.CosmosV4WebviewClientProtocol
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
Expand All @@ -36,21 +41,25 @@ class DydxVaultConfirmationViewModel @Inject constructor(
private val parser: ParserProtocol,
) : ViewModel(), DydxViewModel {

private val isSubmitting: MutableStateFlow<Boolean> = MutableStateFlow(false)

val state: Flow<DydxVaultConfirmationView.ViewState?> =
combine(
abacusStateManager.state.vault,
inputState.amount,
inputState.type.filterNotNull(),
inputState.result,
) { vault, amount, type, result ->
createViewState(vault, amount, type, result)
isSubmitting,
) { vault, amount, type, result, isSubmitting ->
createViewState(vault, amount, type, result, isSubmitting)
}

private fun createViewState(
vault: Vault?,
amount: Double?,
type: VaultInputType,
result: VaultFormValidationResult?
result: VaultFormValidationResult?,
isSubmitting: Boolean,
): DydxVaultConfirmationView.ViewState {
return DydxVaultConfirmationView.ViewState(
localizer = localizer,
Expand Down Expand Up @@ -79,20 +88,45 @@ class DydxVaultConfirmationViewModel @Inject constructor(
router.navigateBack()
inputState.stage.value = VaultInputStage.EDIT
},
ctaButton = createInputCtaButton(type, result),
ctaButton = createInputCtaButton(type, result, isSubmitting),
slippage = createSlippage(type, result),
)
}

private fun createInputCtaButton(
private fun createSlippage(
type: VaultInputType,
result: VaultFormValidationResult?
): VaultSlippageCheckbox.ViewState? {
if (type == VaultInputType.WITHDRAW && result?.summaryData?.needSlippageAck == true) {
val slippage = formatter.percent(result?.summaryData?.estimatedSlippage, digits = 2) ?: ""
val slippageText = localizer.localizeWithParams(
path = "APP.VAULTS.SLIPPAGE_ACK",
params = mapOf("AMOUNT" to slippage),
)
return VaultSlippageCheckbox.ViewState(
localizer = localizer,
text = slippageText,
checked = inputState.slippageAcked.value,
onCheckedChange = { inputState.slippageAcked.value = it },
)
} else {
return null
}
}

private fun createInputCtaButton(
type: VaultInputType,
result: VaultFormValidationResult?,
isSubmitting: Boolean,
): InputCtaButton.ViewState {
when (type) {
VaultInputType.DEPOSIT -> {
val ctaButtonTitle = localizer.localize("APP.VAULTS.CONFIRM_DEPOSIT_CTA")
return InputCtaButton.ViewState(
localizer = localizer,
ctaButtonState = if (result?.canDeposit == true) {
ctaButtonState = if (isSubmitting) {
InputCtaButton.State.Disabled(localizer.localize("APP.TRADE.SUBMITTING"))
} else if (result?.canDeposit == true) {
InputCtaButton.State.Enabled(ctaButtonTitle)
} else {
InputCtaButton.State.Disabled(ctaButtonTitle)
Expand All @@ -106,7 +140,9 @@ class DydxVaultConfirmationViewModel @Inject constructor(
val ctaButtonTitle = localizer.localize("APP.VAULTS.CONFIRM_WITHDRAW_CTA")
return InputCtaButton.ViewState(
localizer = localizer,
ctaButtonState = if (result?.canWithdraw == true) {
ctaButtonState = if (isSubmitting) {
InputCtaButton.State.Disabled(localizer.localize("APP.TRADE.SUBMITTING"))
} else if (result?.canWithdraw == true) {
InputCtaButton.State.Enabled(ctaButtonTitle)
} else {
InputCtaButton.State.Disabled(ctaButtonTitle)
Expand All @@ -121,25 +157,39 @@ class DydxVaultConfirmationViewModel @Inject constructor(

private fun submitDeposit(depositData: VaultDepositData?) {
val depositData = depositData ?: return
isSubmitting.value = true
cosmosClient.depositToMegavault(
subaccountNumber = parser.asInt(depositData.subaccountFrom) ?: 0,
amountUsdc = depositData.amount,
completion = { response ->
print(response)
abacusStateManager.refreshVaultAccount()
inputState.reset()
routeToVault()
},
)
}

private fun submitWithdraw(withdrawData: VaultWithdrawData?) {
val withdrawData = withdrawData ?: return
isSubmitting.value = true
cosmosClient.withdrawFromMegavault(
subaccountNumber = parser.asInt(withdrawData.subaccountTo) ?: 0,
shares = withdrawData.shares.toLong(),
minAmount = withdrawData.minAmount.toLong(),
completion = { response ->
print(response)
abacusStateManager.refreshVaultAccount()
inputState.reset()
routeToVault()
},
)
}

private fun routeToVault() {
viewModelScope.launch {
router.navigateBack()
router.navigateBack()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ import exchange.dydx.trading.common.DydxViewModel
import exchange.dydx.trading.common.formatter.DydxFormatter
import exchange.dydx.trading.common.navigation.DydxRouter
import exchange.dydx.trading.common.navigation.VaultRoutes
import exchange.dydx.trading.feature.receipt.validation.DydxValidationView
import exchange.dydx.trading.feature.shared.views.AmountText
import exchange.dydx.trading.feature.shared.views.InputCtaButton
import exchange.dydx.trading.feature.vault.VaultInputStage
import exchange.dydx.trading.feature.vault.VaultInputState
import exchange.dydx.trading.feature.vault.canDeposit
import exchange.dydx.trading.feature.vault.depositwithdraw.components.VaultAmountBox
import exchange.dydx.trading.feature.vault.depositwithdraw.createViewModel
import exchange.dydx.trading.feature.vault.displayedError
Expand Down Expand Up @@ -80,13 +78,13 @@ class DydxVaultDepositViewModel @Inject constructor(
inputState.amount.value = parser.asDouble(amount)
},
),
validation = result?.displayedError?.createViewModel(localizer),
validation = result?.displayedError?.createViewModel(localizer),
ctaButton = InputCtaButton.ViewState(
localizer = localizer,
ctaButtonState = if (result?.hasBlockingError == true || inputState.amount.value == null) {
InputCtaButton.State.Disabled(localizer.localize("APP.VAULTS.PREVIEW_DEPOSIT"))
} else {
InputCtaButton.State.Enabled(localizer.localize("APP.VAULTS.PREVIEW_DEPOSIT"))
InputCtaButton.State.Enabled(localizer.localize("APP.VAULTS.PREVIEW_DEPOSIT"))
},
ctaAction = {
inputState.stage.value = VaultInputStage.CONFIRM
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.SnackbarDefaults.backgroundColor
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
Expand Down Expand Up @@ -113,13 +114,14 @@ fun PlatformSelectedIcon(
size: Dp = 20.dp,
padding: Dp = 4.dp,
) {
val shape = RoundedCornerShape(7.dp)
Image(
painter = painterResource(id = R.drawable.icon_check),
contentDescription = "",
modifier = Modifier
.background(backgroundColor.color, CircleShape)
.border(1.dp, borderColor.color, CircleShape)
.clip(CircleShape)
.background(backgroundColor.color, shape)
.border(1.dp, borderColor.color, shape)
.clip(shape)
.size(size)
.padding(padding),
colorFilter = ColorFilter
Expand All @@ -135,10 +137,11 @@ fun PlatformUnselectedIcon(
size: Dp = 20.dp,
padding: Dp = 4.dp,
) {
val shape = RoundedCornerShape(7.dp)
Canvas(
modifier = Modifier
.border(1.dp, borderColor.color, CircleShape)
.clip(CircleShape)
.border(1.dp, borderColor.color, shape)
.clip(shape)
.size(size),
) {
drawCircle(color = backgroundColor.color)
Expand Down

0 comments on commit 568dc62

Please sign in to comment.