From 594dcd42321c78b1ded53d6f4edc6fccf343936f Mon Sep 17 00:00:00 2001 From: Rui Date: Wed, 2 Oct 2024 13:25:50 -0700 Subject: [PATCH 1/2] Validation --- .../receipt/validation/DydxValidationView.kt | 4 ++ .../trading/feature/vault/VaultInputState.kt | 23 ++++++++ .../depositwithdraw/ValidationError+Vault.kt | 21 +++++++ .../DydxVaultConfirmationViewModel.kt | 55 ++++++++++++++----- .../deposit/DydxVaultDepositView.kt | 11 +++- .../deposit/DydxVaultDepositViewModel.kt | 12 +++- .../withdraw/DydxVaultWithdrawView.kt | 11 +++- .../withdraw/DydxVaultWithdrawViewModel.kt | 10 +++- .../dydxstatemanager/AbacusStateManager.kt | 5 ++ 9 files changed, 131 insertions(+), 21 deletions(-) create mode 100644 v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/ValidationError+Vault.kt diff --git a/v4/feature/receipt/src/main/java/exchange/dydx/trading/feature/receipt/validation/DydxValidationView.kt b/v4/feature/receipt/src/main/java/exchange/dydx/trading/feature/receipt/validation/DydxValidationView.kt index 322e6cbe..7f768bb9 100644 --- a/v4/feature/receipt/src/main/java/exchange/dydx/trading/feature/receipt/validation/DydxValidationView.kt +++ b/v4/feature/receipt/src/main/java/exchange/dydx/trading/feature/receipt/validation/DydxValidationView.kt @@ -111,6 +111,10 @@ object DydxValidationView : DydxComponent { modifier: Modifier, viewState: ViewState ) { + if (viewState.title.isNullOrEmpty() && viewState.message.isNullOrEmpty()) { + return + } + var size by remember { mutableStateOf(IntSize.Zero) } val shape = RoundedCornerShape(8.dp) diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/VaultInputState.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/VaultInputState.kt index 0e536f6a..b410f41d 100644 --- a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/VaultInputState.kt +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/VaultInputState.kt @@ -6,6 +6,9 @@ import exchange.dydx.abacus.functional.vault.VaultFormAccountData import exchange.dydx.abacus.functional.vault.VaultFormAction import exchange.dydx.abacus.functional.vault.VaultFormData import exchange.dydx.abacus.functional.vault.VaultFormValidationResult +import exchange.dydx.abacus.output.input.ErrorType +import exchange.dydx.abacus.output.input.ValidationError +import exchange.dydx.abacus.protocols.LocalizerProtocol import exchange.dydx.dydxstatemanager.AbacusStateManagerProtocol import indexer.models.chain.OnChainVaultDepositWithdrawSlippageResponse import kotlinx.coroutines.flow.Flow @@ -29,6 +32,7 @@ enum class VaultInputStage { @ActivityRetainedScoped class VaultInputState @Inject constructor( private val abacusStateManager: AbacusStateManagerProtocol, + private val localizer: LocalizerProtocol, ) { val type: MutableStateFlow = MutableStateFlow(null) val amount: MutableStateFlow = MutableStateFlow(null) @@ -81,6 +85,7 @@ class VaultInputState @Inject constructor( accountData = accountData, vaultAccount = account, slippageResponse = slippageResponse, + localizer = localizer, ) } .distinctUntilChanged() @@ -93,3 +98,21 @@ class VaultInputState @Inject constructor( slippageResponse.value = null } } + +val VaultFormValidationResult.firstError: ValidationError? + get() = errors.firstOrNull() { it.type == ErrorType.error } + +val VaultFormValidationResult.firstWarning: ValidationError? + get() = errors.firstOrNull() { it.type == ErrorType.warning } + +val VaultFormValidationResult.displayedError: ValidationError? + get() = firstError ?: firstWarning + +val VaultFormValidationResult.hasBlockingError: Boolean + get() = firstError != null + +val VaultFormValidationResult.canDeposit: Boolean + get() = submissionData?.deposit != null + +val VaultFormValidationResult.canWithdraw: Boolean + get() = submissionData?.withdraw != null \ No newline at end of file diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/ValidationError+Vault.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/ValidationError+Vault.kt new file mode 100644 index 00000000..47e63446 --- /dev/null +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/ValidationError+Vault.kt @@ -0,0 +1,21 @@ +package exchange.dydx.trading.feature.vault.depositwithdraw + +import exchange.dydx.abacus.output.input.ErrorType +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( + localizer: LocalizerProtocol +): DydxValidationView.ViewState { + return DydxValidationView.ViewState( + localizer = localizer, + state = when (this.type) { + ErrorType.error -> DydxValidationView.State.Error + ErrorType.warning -> DydxValidationView.State.Warning + ErrorType.required -> DydxValidationView.State.None + }, + title = this.resources.title?.localized ?: this.resources.title?.stringKey, + message = this.resources.text?.localized ?: this.resources.text?.stringKey, + ) +} \ No newline at end of file diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/confirmation/DydxVaultConfirmationViewModel.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/confirmation/DydxVaultConfirmationViewModel.kt index 7e9147c7..b64987e7 100644 --- a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/confirmation/DydxVaultConfirmationViewModel.kt +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/confirmation/DydxVaultConfirmationViewModel.kt @@ -17,6 +17,8 @@ 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.VaultInputType +import exchange.dydx.trading.feature.vault.canDeposit +import exchange.dydx.trading.feature.vault.canWithdraw import exchange.dydx.trading.integration.cosmos.CosmosV4WebviewClientProtocol import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -77,24 +79,46 @@ class DydxVaultConfirmationViewModel @Inject constructor( router.navigateBack() inputState.stage.value = VaultInputStage.EDIT }, - ctaButton = InputCtaButton.ViewState( - localizer = localizer, - ctaButtonState = InputCtaButton.State.Enabled( - when (type) { - VaultInputType.DEPOSIT -> localizer.localize("APP.VAULTS.CONFIRM_DEPOSIT_CTA") - VaultInputType.WITHDRAW -> localizer.localize("APP.VAULTS.CONFIRM_WITHDRAW_CTA") - }, - ), - ctaAction = { - when (type) { - VaultInputType.DEPOSIT -> submitDeposit(result?.submissionData?.deposit) - VaultInputType.WITHDRAW -> submitWithdraw(result?.submissionData?.withdraw) - } - }, - ), + ctaButton = createInputCtaButton(type, result), ) } + private fun createInputCtaButton( + type: VaultInputType, + result: VaultFormValidationResult? + ): 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) { + InputCtaButton.State.Enabled(ctaButtonTitle) + } else { + InputCtaButton.State.Disabled(ctaButtonTitle) + }, + ctaAction = { + submitDeposit(result?.submissionData?.deposit) + }, + ) + } + VaultInputType.WITHDRAW -> { + val ctaButtonTitle = localizer.localize("APP.VAULTS.CONFIRM_WITHDRAW_CTA") + return InputCtaButton.ViewState( + localizer = localizer, + ctaButtonState = if (result?.canWithdraw == true) { + InputCtaButton.State.Enabled(ctaButtonTitle) + } else { + InputCtaButton.State.Disabled(ctaButtonTitle) + }, + ctaAction = { + submitWithdraw(result?.submissionData?.withdraw) + }, + ) + } + } + } + private fun submitDeposit(depositData: VaultDepositData?) { val depositData = depositData ?: return cosmosClient.depositToMegavault( @@ -102,6 +126,7 @@ class DydxVaultConfirmationViewModel @Inject constructor( amountUsdc = depositData.amount, completion = { response -> print(response) + abacusStateManager.refreshVaultAccount() }, ) } diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/deposit/DydxVaultDepositView.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/deposit/DydxVaultDepositView.kt index 751a0302..8f01eb83 100644 --- a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/deposit/DydxVaultDepositView.kt +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/deposit/DydxVaultDepositView.kt @@ -36,12 +36,14 @@ object DydxVaultDepositView : DydxComponent { data class ViewState( val localizer: LocalizerProtocol, val transferAmount: VaultAmountBox.ViewState? = null, + val validation: DydxValidationView.ViewState? = null, val ctaButton: InputCtaButton.ViewState? = null, ) { companion object { val preview = ViewState( localizer = MockLocalizer(), transferAmount = VaultAmountBox.ViewState.preview, + validation = DydxValidationView.ViewState.preview, ctaButton = InputCtaButton.ViewState.preview, ) } @@ -81,8 +83,13 @@ object DydxVaultDepositView : DydxComponent { ) } - item { - DydxValidationView.Content(Modifier.animateItemPlacement()) + if (state.validation != null) { + item { + DydxValidationView.Content( + modifier = Modifier.animateItemPlacement(), + state = state.validation, + ) + } } } diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/deposit/DydxVaultDepositViewModel.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/deposit/DydxVaultDepositViewModel.kt index 28d4ff36..2d39ed9d 100644 --- a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/deposit/DydxVaultDepositViewModel.kt +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/deposit/DydxVaultDepositViewModel.kt @@ -11,11 +11,16 @@ 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 +import exchange.dydx.trading.feature.vault.hasBlockingError import exchange.dydx.trading.integration.cosmos.CosmosV4WebviewClientProtocol import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -75,9 +80,14 @@ class DydxVaultDepositViewModel @Inject constructor( inputState.amount.value = parser.asDouble(amount) }, ), + validation = result?.displayedError?.createViewModel(localizer), ctaButton = InputCtaButton.ViewState( localizer = localizer, - ctaButtonState = InputCtaButton.State.Enabled(localizer.localize("APP.VAULTS.PREVIEW_DEPOSIT")), + 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")) + }, ctaAction = { inputState.stage.value = VaultInputStage.CONFIRM router.navigateTo(route = VaultRoutes.confirmation, presentation = DydxRouter.Presentation.Push) diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/withdraw/DydxVaultWithdrawView.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/withdraw/DydxVaultWithdrawView.kt index 23edac17..30165f10 100644 --- a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/withdraw/DydxVaultWithdrawView.kt +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/withdraw/DydxVaultWithdrawView.kt @@ -36,12 +36,14 @@ object DydxVaultWithdrawView : DydxComponent { data class ViewState( val localizer: LocalizerProtocol, val transferAmount: VaultAmountBox.ViewState? = null, + val validation: DydxValidationView.ViewState? = null, val ctaButton: InputCtaButton.ViewState? = null, ) { companion object { val preview = ViewState( localizer = MockLocalizer(), transferAmount = VaultAmountBox.ViewState.preview, + validation = DydxValidationView.ViewState.preview, ctaButton = InputCtaButton.ViewState.preview, ) } @@ -81,8 +83,13 @@ object DydxVaultWithdrawView : DydxComponent { ) } - item { - DydxValidationView.Content(Modifier.animateItemPlacement()) + if (state.validation != null) { + item { + DydxValidationView.Content( + modifier = Modifier.animateItemPlacement(), + state = state.validation, + ) + } } } diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/withdraw/DydxVaultWithdrawViewModel.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/withdraw/DydxVaultWithdrawViewModel.kt index 28218013..d5707a42 100644 --- a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/withdraw/DydxVaultWithdrawViewModel.kt +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/withdraw/DydxVaultWithdrawViewModel.kt @@ -20,6 +20,9 @@ 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.depositwithdraw.components.VaultAmountBox +import exchange.dydx.trading.feature.vault.depositwithdraw.createViewModel +import exchange.dydx.trading.feature.vault.displayedError +import exchange.dydx.trading.feature.vault.hasBlockingError import exchange.dydx.trading.integration.cosmos.CosmosV4WebviewClientProtocol import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -91,9 +94,14 @@ class DydxVaultWithdrawViewModel @Inject constructor( updateAmount(value = parser.asDouble(amount), vaultAccount = vault?.account) }, ), + validation = result?.displayedError?.createViewModel(localizer), ctaButton = InputCtaButton.ViewState( localizer = localizer, - ctaButtonState = InputCtaButton.State.Enabled(localizer.localize("APP.VAULTS.PREVIEW_WITHDRAW")), + ctaButtonState = if (result?.hasBlockingError == true || inputState.amount.value == null) { + InputCtaButton.State.Disabled(localizer.localize("APP.VAULTS.PREVIEW_WITHDRAW")) + } else { + InputCtaButton.State.Enabled(localizer.localize("APP.VAULTS.PREVIEW_WITHDRAW")) + }, ctaAction = { inputState.stage.value = VaultInputStage.CONFIRM router.navigateTo(route = VaultRoutes.confirmation, presentation = DydxRouter.Presentation.Push) diff --git a/v4/integration/dydxStateManager/src/main/java/exchange/dydx/dydxstatemanager/AbacusStateManager.kt b/v4/integration/dydxStateManager/src/main/java/exchange/dydx/dydxstatemanager/AbacusStateManager.kt index 84e0e908..904a23df 100644 --- a/v4/integration/dydxStateManager/src/main/java/exchange/dydx/dydxstatemanager/AbacusStateManager.kt +++ b/v4/integration/dydxStateManager/src/main/java/exchange/dydx/dydxstatemanager/AbacusStateManager.kt @@ -140,6 +140,7 @@ interface AbacusStateManagerProtocol { } fun registerPushToken(token: String, language: String) + fun refreshVaultAccount() } // Temporary location, should probably make a separate dagger-qualifiers module. @@ -461,6 +462,10 @@ class AbacusStateManager @Inject constructor( asyncStateManager.registerPushNotification(token, language) } + override fun refreshVaultAccount() { + asyncStateManager.refreshVaultAccount() + } + // MARK: StateNotificationProtocol override fun apiStateChanged(apiState: ApiState?) { From 568dc621a8496dd0b4dc85d00417f2e7d636d1a4 Mon Sep 17 00:00:00 2001 From: Rui Date: Wed, 2 Oct 2024 15:35:13 -0700 Subject: [PATCH 2/2] Vault validations --- v4/build.gradle | 2 +- .../trading/feature/vault/VaultInputState.kt | 2 +- ...or+Vault.kt => ValidationErrorVaultExt.kt} | 4 +- .../components/VaultSlippageCheckbox.kt | 77 +++++++++++++++++++ .../confirmation/DydxVaultConfirmationView.kt | 10 +++ .../DydxVaultConfirmationViewModel.kt | 64 +++++++++++++-- .../deposit/DydxVaultDepositViewModel.kt | 6 +- .../components/icons/PlatformIcons.kt | 13 ++-- 8 files changed, 158 insertions(+), 20 deletions(-) rename v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/{ValidationError+Vault.kt => ValidationErrorVaultExt.kt} (94%) create mode 100644 v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/components/VaultSlippageCheckbox.kt diff --git a/v4/build.gradle b/v4/build.gradle index 82c75529..34e7cf8d 100644 --- a/v4/build.gradle +++ b/v4/build.gradle @@ -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' diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/VaultInputState.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/VaultInputState.kt index b410f41d..9fef77de 100644 --- a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/VaultInputState.kt +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/VaultInputState.kt @@ -115,4 +115,4 @@ val VaultFormValidationResult.canDeposit: Boolean get() = submissionData?.deposit != null val VaultFormValidationResult.canWithdraw: Boolean - get() = submissionData?.withdraw != null \ No newline at end of file + get() = submissionData?.withdraw != null diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/ValidationError+Vault.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/ValidationErrorVaultExt.kt similarity index 94% rename from v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/ValidationError+Vault.kt rename to v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/ValidationErrorVaultExt.kt index 47e63446..a4f86c2a 100644 --- a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/ValidationError+Vault.kt +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/ValidationErrorVaultExt.kt @@ -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( @@ -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, ) -} \ No newline at end of file +} diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/components/VaultSlippageCheckbox.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/components/VaultSlippageCheckbox.kt new file mode 100644 index 00000000..a2344ce7 --- /dev/null +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/components/VaultSlippageCheckbox.kt @@ -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), + ) + } + } +} diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/confirmation/DydxVaultConfirmationView.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/confirmation/DydxVaultConfirmationView.kt index 62f3ece0..ce0f4701 100644 --- a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/confirmation/DydxVaultConfirmationView.kt +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/confirmation/DydxVaultConfirmationView.kt @@ -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 @@ -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( @@ -72,6 +74,7 @@ object DydxVaultConfirmationView : DydxComponent { destinationValue = "Vault", ctaButton = InputCtaButton.ViewState.preview, direction = Direction.Deposit, + slippage = VaultSlippageCheckbox.ViewState.preview, ) } } @@ -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() diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/confirmation/DydxVaultConfirmationViewModel.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/confirmation/DydxVaultConfirmationViewModel.kt index b64987e7..5e8135e9 100644 --- a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/confirmation/DydxVaultConfirmationViewModel.kt +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/confirmation/DydxVaultConfirmationViewModel.kt @@ -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 @@ -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 @@ -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 @@ -36,21 +41,25 @@ class DydxVaultConfirmationViewModel @Inject constructor( private val parser: ParserProtocol, ) : ViewModel(), DydxViewModel { + private val isSubmitting: MutableStateFlow = MutableStateFlow(false) + val state: Flow = 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, @@ -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) @@ -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) @@ -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() + } + } } diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/deposit/DydxVaultDepositViewModel.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/deposit/DydxVaultDepositViewModel.kt index 2d39ed9d..010b67ba 100644 --- a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/deposit/DydxVaultDepositViewModel.kt +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/deposit/DydxVaultDepositViewModel.kt @@ -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 @@ -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 diff --git a/v4/platformUI/src/main/java/exchange/dydx/platformui/components/icons/PlatformIcons.kt b/v4/platformUI/src/main/java/exchange/dydx/platformui/components/icons/PlatformIcons.kt index 2808c94b..7950ce40 100644 --- a/v4/platformUI/src/main/java/exchange/dydx/platformui/components/icons/PlatformIcons.kt +++ b/v4/platformUI/src/main/java/exchange/dydx/platformui/components/icons/PlatformIcons.kt @@ -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 @@ -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 @@ -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)