From 9dab50a2c08a9ca8d0c98081f1616d6ecfe43139 Mon Sep 17 00:00:00 2001 From: Rui Date: Tue, 15 Oct 2024 13:22:46 -0700 Subject: [PATCH 1/4] Library/dependencies update --- build.gradle | 6 +++--- gradle/wrapper/gradle-wrapper.properties | 2 +- v4/build.gradle | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index 30af2abf..e7d4a261 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext.ktlintVersion = '0.48.0' - ext.kotlinVersion = '1.9.20' - ext.androidApplicationVersion = '8.6.1' + ext.kotlinVersion = '1.9.21' + ext.androidApplicationVersion = '8.7.1' repositories { google() @@ -11,7 +11,7 @@ buildscript { dependencies { classpath "com.android.tools.build:gradle:$androidApplicationVersion" classpath "com.squareup:javapoet:1.13.0" - classpath 'com.google.gms:google-services:4.4.0' + classpath 'com.google.gms:google-services:4.4.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 512386dd..952140e0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Tue Jan 30 10:45:32 PST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/v4/build.gradle b/v4/build.gradle index 8af9706a..e8223182 100644 --- a/v4/build.gradle +++ b/v4/build.gradle @@ -103,12 +103,12 @@ ext { androidXTestRulesVersion = '1.5.0' androidXAnnotations = '1.4.0' appcompatVersion = '1.6.1' - archLifecycleVersion = '2.7.0' + archLifecycleVersion = '2.8.6' archTestingVersion = '2.2.0' biometricVersion = '1.2.0-alpha05' camerax_version = '1.3.1' coilVersion = '2.1.0' - composeCompilerVersion = '1.5.4' + composeCompilerVersion = '1.5.7' composeVersion = '1.6.0' coroutinesVersion = '1.6.1' customviewVersion = '1.2.0-alpha02' @@ -118,7 +118,7 @@ ext { espressoVersion = '3.5.0' firebaseBomVersion = '33.0.0' hamcrestVersion = '1.3' - hiltAndroidXVersion = '1.1.0' + hiltAndroidXVersion = '1.2.0' junitVersion = '4.13.2' junitExtVersion = '1.1.3' kaspressoVersion = '1.5.2' From d9c7a1b333a499cc70bd8aa11d315be23bcefefd Mon Sep 17 00:00:00 2001 From: Rui Date: Tue, 15 Oct 2024 17:52:04 -0700 Subject: [PATCH 2/4] Fix layout issues with other languages --- v4/build.gradle | 2 +- .../feature/shared/views/BuySellView.kt | 2 + .../DydxTradeInputMarginButtonsView.kt | 5 ++- .../vault/components/DydxVaultChartView.kt | 39 +++++++++++-------- .../vault/components/DydxVaultInfoView.kt | 28 ++++++------- .../components/VaultAmountBox.kt | 2 + 6 files changed, 44 insertions(+), 34 deletions(-) diff --git a/v4/build.gradle b/v4/build.gradle index e8223182..73bee576 100644 --- a/v4/build.gradle +++ b/v4/build.gradle @@ -88,7 +88,7 @@ ext { compileSdkVersion = 34 // App dependencies - abacusVersion = '1.12.24' + abacusVersion = '1.12.28' carteraVersion = '0.1.15' kollectionsVersion = '2.0.16' diff --git a/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/views/BuySellView.kt b/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/views/BuySellView.kt index 15a83289..46d27c6d 100644 --- a/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/views/BuySellView.kt +++ b/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/views/BuySellView.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import exchange.dydx.abacus.protocols.LocalizerProtocol +import exchange.dydx.platformui.components.textgroups.PlatformAutoSizingText import exchange.dydx.platformui.designSystem.theme.ThemeColor import exchange.dydx.platformui.designSystem.theme.ThemeFont import exchange.dydx.platformui.designSystem.theme.color @@ -98,6 +99,7 @@ object BuySellView { .themeFont(fontSize = ThemeFont.FontSize.medium, fontType = ThemeFont.FontType.plus) .themeColor(foreground = state.color), text = state.text ?: "", + maxLines = 1, ) } } diff --git a/v4/feature/trade/src/main/java/exchange/dydx/trading/feature/trade/tradeinput/components/DydxTradeInputMarginButtonsView.kt b/v4/feature/trade/src/main/java/exchange/dydx/trading/feature/trade/tradeinput/components/DydxTradeInputMarginButtonsView.kt index db2f79b1..0582115a 100644 --- a/v4/feature/trade/src/main/java/exchange/dydx/trading/feature/trade/tradeinput/components/DydxTradeInputMarginButtonsView.kt +++ b/v4/feature/trade/src/main/java/exchange/dydx/trading/feature/trade/tradeinput/components/DydxTradeInputMarginButtonsView.kt @@ -64,11 +64,13 @@ object DydxTradeInputMarginButtonsView : DydxComponent { PlatformButton( modifier = if (state.isIsolatedMarketSelected) { Modifier + .weight(1f) } else { Modifier .fillMaxWidth() }, state = PlatformButtonState.Secondary, + fitText = true, text = state.localizer.localize( if (state.isIsolatedMarketSelected) { "APP.GENERAL.ISOLATED" @@ -81,8 +83,7 @@ object DydxTradeInputMarginButtonsView : DydxComponent { } if (state.isIsolatedMarketSelected) { PlatformButton( - modifier = Modifier - .fillMaxWidth(), + modifier = Modifier, state = PlatformButtonState.Secondary, text = state.isolatedMarketTargetLeverageText, fitText = true, diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultChartView.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultChartView.kt index 43845469..b45a5135 100644 --- a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultChartView.kt +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultChartView.kt @@ -1,6 +1,7 @@ package exchange.dydx.trading.feature.vault.components import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -8,6 +9,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState @@ -108,24 +110,27 @@ object DydxVaultChartView : DydxComponent { Row( modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(16.dp), ) { - PlatformPillTextGroup( - modifier = Modifier, - items = state.typeTitles ?: emptyList(), - selectedItems = state.typeTitles ?: emptyList(), - currentSelection = state.typeIndex ?: 0, - onSelectionChanged = { index -> - state.onTypeChanged(index) - }, - itemStyle = TextStyle.dydxDefault - .themeFont(fontSize = ThemeFont.FontSize.mini) - .themeColor(ThemeColor.SemanticColor.text_tertiary), - selectedItemStyle = TextStyle.dydxDefault - .themeFont(fontSize = ThemeFont.FontSize.mini) - .themeColor(ThemeColor.SemanticColor.text_primary), - ) - - Spacer(modifier = Modifier.weight(1f)) + Row( + modifier = Modifier.weight(1f), + ) { + PlatformPillTextGroup( + modifier = Modifier, + items = state.typeTitles ?: emptyList(), + selectedItems = state.typeTitles ?: emptyList(), + currentSelection = state.typeIndex ?: 0, + onSelectionChanged = { index -> + state.onTypeChanged(index) + }, + itemStyle = TextStyle.dydxDefault + .themeFont(fontSize = ThemeFont.FontSize.mini) + .themeColor(ThemeColor.SemanticColor.text_tertiary), + selectedItemStyle = TextStyle.dydxDefault + .themeFont(fontSize = ThemeFont.FontSize.mini) + .themeColor(ThemeColor.SemanticColor.text_primary), + ) + } Box { PlatformPillItem( diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultInfoView.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultInfoView.kt index 05dbda79..51f16c88 100644 --- a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultInfoView.kt +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultInfoView.kt @@ -2,8 +2,10 @@ package exchange.dydx.trading.feature.vault.components import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape @@ -79,16 +81,15 @@ object DydxVaultInfoView : DydxComponent { modifier = Modifier .padding(16.dp) .fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), ) { TopRowItem( modifier = Modifier.weight(1f), title = state.localizer.localize("APP.VAULTS.YOUR_VAULT_BALANCE"), - valueComposable = { + valueComposable = { modifier -> Text( text = state.balance ?: "-", - modifier = Modifier, + modifier = modifier, style = TextStyle.dydxDefault .themeFont(fontSize = ThemeFont.FontSize.medium) .themeColor(ThemeColor.SemanticColor.text_primary), @@ -98,9 +99,9 @@ object DydxVaultInfoView : DydxComponent { TopRowItem( modifier = Modifier.weight(1f), title = state.localizer.localize("APP.VAULTS.YOUR_ALL_TIME_PNL"), - valueComposable = { + valueComposable = { modifier -> SignedAmountView.Content( - modifier = Modifier, + modifier = modifier, state = state.pnl, textStyle = TextStyle.dydxDefault .themeFont(fontSize = ThemeFont.FontSize.medium), @@ -127,10 +128,10 @@ object DydxVaultInfoView : DydxComponent { BottomRowItem( modifier = Modifier, title = state.localizer.localize("APP.VAULTS.VAULT_THIRTY_DAY_APR"), - valueComposable = { + valueComposable = { modifier -> if (state.apr != null) { SignedAmountView.Content( - modifier = Modifier, + modifier = modifier, state = state.apr, textStyle = TextStyle.dydxDefault .themeFont(fontSize = ThemeFont.FontSize.medium), @@ -138,7 +139,7 @@ object DydxVaultInfoView : DydxComponent { } else { Text( text = "-", - modifier = Modifier, + modifier = modifier, style = TextStyle.dydxDefault .themeFont(fontSize = ThemeFont.FontSize.medium) .themeColor(ThemeColor.SemanticColor.text_primary), @@ -149,10 +150,10 @@ object DydxVaultInfoView : DydxComponent { BottomRowItem( modifier = Modifier.weight(1f), title = state.localizer.localize("APP.VAULTS.TVL"), - valueComposable = { + valueComposable = { modifier -> Text( text = state.tvl ?: "-", - modifier = Modifier, + modifier = modifier, style = TextStyle.dydxDefault .themeFont(fontSize = ThemeFont.FontSize.medium) .themeColor(ThemeColor.SemanticColor.text_primary), @@ -186,8 +187,7 @@ object DydxVaultInfoView : DydxComponent { .themeFont(fontSize = ThemeFont.FontSize.base) .themeColor(ThemeColor.SemanticColor.text_tertiary), ) - - valueComposable(Modifier) + valueComposable(Modifier.padding(top = 1.dp)) } } @@ -204,7 +204,7 @@ object DydxVaultInfoView : DydxComponent { .themeFont(fontSize = ThemeFont.FontSize.base) .themeColor(ThemeColor.SemanticColor.text_tertiary), ) - valueComposable(Modifier) + valueComposable(Modifier.padding(top = 1.dp)) } } } diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/components/VaultAmountBox.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/components/VaultAmountBox.kt index f22b7309..c8a3097c 100644 --- a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/components/VaultAmountBox.kt +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/components/VaultAmountBox.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.max import androidx.compose.ui.zIndex import exchange.dydx.abacus.protocols.LocalizerProtocol import exchange.dydx.abacus.protocols.ParserProtocol @@ -177,6 +178,7 @@ object VaultAmountBox { style = TextStyle.dydxDefault .themeColor(ThemeColor.SemanticColor.text_tertiary) .themeFont(fontSize = ThemeFont.FontSize.small), + maxLines = 1, ) Spacer(modifier = Modifier.weight(1f)) From 5949433201cbd17bff5ab6c64d1a6116b3c45782 Mon Sep 17 00:00:00 2001 From: Rui Date: Tue, 15 Oct 2024 18:02:27 -0700 Subject: [PATCH 3/4] Lint --- .../exchange/dydx/trading/feature/shared/views/BuySellView.kt | 1 - .../trading/feature/vault/components/DydxVaultChartView.kt | 2 -- .../trading/feature/vault/components/DydxVaultInfoView.kt | 4 +--- .../vault/depositwithdraw/components/VaultAmountBox.kt | 1 - 4 files changed, 1 insertion(+), 7 deletions(-) diff --git a/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/views/BuySellView.kt b/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/views/BuySellView.kt index 46d27c6d..95e3b72e 100644 --- a/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/views/BuySellView.kt +++ b/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/views/BuySellView.kt @@ -20,7 +20,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import exchange.dydx.abacus.protocols.LocalizerProtocol -import exchange.dydx.platformui.components.textgroups.PlatformAutoSizingText import exchange.dydx.platformui.designSystem.theme.ThemeColor import exchange.dydx.platformui.designSystem.theme.ThemeFont import exchange.dydx.platformui.designSystem.theme.color diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultChartView.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultChartView.kt index b45a5135..37b634cc 100644 --- a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultChartView.kt +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultChartView.kt @@ -5,11 +5,9 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultInfoView.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultInfoView.kt index 51f16c88..a7938021 100644 --- a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultInfoView.kt +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultInfoView.kt @@ -2,10 +2,8 @@ package exchange.dydx.trading.feature.vault.components import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape @@ -81,7 +79,7 @@ object DydxVaultInfoView : DydxComponent { modifier = Modifier .padding(16.dp) .fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), ) { TopRowItem( modifier = Modifier.weight(1f), diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/components/VaultAmountBox.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/components/VaultAmountBox.kt index c8a3097c..345ff76b 100644 --- a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/components/VaultAmountBox.kt +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/depositwithdraw/components/VaultAmountBox.kt @@ -19,7 +19,6 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.max import androidx.compose.ui.zIndex import exchange.dydx.abacus.protocols.LocalizerProtocol import exchange.dydx.abacus.protocols.ParserProtocol From 97615dd428d734e95185bfd4c56e0eba1db5e1b0 Mon Sep 17 00:00:00 2001 From: Rui Date: Wed, 16 Oct 2024 11:34:15 -0700 Subject: [PATCH 4/4] Show the selected entry from the vault chart --- .../DydxVaultChartSelectedInfoView.kt | 100 ++++++++++++++++++ .../DydxVaultChartSelectedInfoViewModel.kt | 82 ++++++++++++++ .../components/DydxVaultChartViewModel.kt | 35 ++++-- .../vault/components/DydxVaultInfoView.kt | 87 ++++++++------- .../components/DydxVaultInfoViewModel.kt | 21 ++-- .../trading/feature/vault/di/VaultModule.kt | 52 +++++++++ 6 files changed, 327 insertions(+), 50 deletions(-) create mode 100644 v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultChartSelectedInfoView.kt create mode 100644 v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultChartSelectedInfoViewModel.kt create mode 100644 v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/di/VaultModule.kt diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultChartSelectedInfoView.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultChartSelectedInfoView.kt new file mode 100644 index 00000000..a5fce865 --- /dev/null +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultChartSelectedInfoView.kt @@ -0,0 +1,100 @@ +package exchange.dydx.trading.feature.vault.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.hilt.navigation.compose.hiltViewModel +import exchange.dydx.abacus.protocols.LocalizerProtocol +import exchange.dydx.platformui.compose.collectAsStateWithLifecycle +import exchange.dydx.platformui.designSystem.theme.ThemeColor +import exchange.dydx.platformui.designSystem.theme.ThemeFont +import exchange.dydx.platformui.designSystem.theme.ThemeShapes +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 +import exchange.dydx.trading.common.component.DydxComponent +import exchange.dydx.trading.feature.shared.views.SignedAmountView + +@Preview +@Composable +fun Preview_DydxVaultChartSelectedInfoView() { + DydxThemedPreviewSurface { + DydxVaultChartSelectedInfoView.Content(Modifier, DydxVaultChartSelectedInfoView.ViewState.preview) + } +} + +object DydxVaultChartSelectedInfoView : DydxComponent { + data class ViewState( + val localizer: LocalizerProtocol, + val change: SignedAmountView.ViewState? = null, + val currentValue: String? = null, + val entryDate: String? = null, + ) { + companion object { + val preview = ViewState( + localizer = MockLocalizer(), + change = SignedAmountView.ViewState.preview, + currentValue = "$1.0M", + entryDate = "2021-01-01", + ) + } + } + + @Composable + override fun Content(modifier: Modifier) { + val viewModel: DydxVaultChartSelectedInfoViewModel = hiltViewModel() + + val state = viewModel.state.collectAsStateWithLifecycle(initialValue = null).value + Content(modifier, state) + } + + @Composable + fun Content(modifier: Modifier, state: ViewState?) { + if (state == null) { + return + } + + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(ThemeShapes.VerticalPadding), + ) { + Text( + text = state.entryDate ?: "", + modifier = Modifier, + style = TextStyle.dydxDefault + .themeFont(fontSize = ThemeFont.FontSize.base) + .themeColor(ThemeColor.SemanticColor.text_tertiary), + ) + + Row( + modifier = Modifier, + horizontalArrangement = Arrangement.spacedBy(ThemeShapes.HorizontalPadding), + ) { + Text( + text = state.currentValue ?: "-", + modifier = modifier, + style = TextStyle.dydxDefault + .themeFont(fontSize = ThemeFont.FontSize.medium) + .themeColor(ThemeColor.SemanticColor.text_primary), + ) + + SignedAmountView.Content( + modifier = modifier, + state = state.change, + textStyle = TextStyle.dydxDefault + .themeFont(fontSize = ThemeFont.FontSize.medium), + ) + + Spacer(modifier = Modifier.weight(1f)) + } + } + } +} diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultChartSelectedInfoViewModel.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultChartSelectedInfoViewModel.kt new file mode 100644 index 00000000..de0e483c --- /dev/null +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultChartSelectedInfoViewModel.kt @@ -0,0 +1,82 @@ +package exchange.dydx.trading.feature.vault.components + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import exchange.dydx.abacus.functional.vault.VaultHistoryEntry +import exchange.dydx.abacus.protocols.LocalizerProtocol +import exchange.dydx.trading.common.DydxViewModel +import exchange.dydx.trading.common.formatter.DydxFormatter +import exchange.dydx.trading.feature.shared.views.SignedAmountView +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterNotNull +import javax.inject.Inject + +@HiltViewModel +class DydxVaultChartSelectedInfoViewModel @Inject constructor( + private val localizer: LocalizerProtocol, + private val formatter: DydxFormatter, + private val selectedChartEntryFlow: Flow, + private val vaultHistoryFlow: Flow<@JvmSuppressWildcards List?>, + private val chartTypeFlow: Flow<@JvmSuppressWildcards ChartType?>, +) : ViewModel(), DydxViewModel { + + val state: Flow = + combine( + vaultHistoryFlow, + chartTypeFlow.filterNotNull(), + selectedChartEntryFlow, + ) { history, chartType, selectedChartEntry -> + createViewState(history, chartType, selectedChartEntry) + } + .distinctUntilChanged() + + private fun createViewState( + history: List?, + chartType: ChartType, + selectedChartEntry: VaultHistoryEntry? + ): DydxVaultChartSelectedInfoView.ViewState { + val (value, percent) = when (chartType) { + ChartType.EQUITY -> createDiffs( + history?.firstOrNull()?.equity, + selectedChartEntry?.equity, + ) + + ChartType.PNL -> createDiffs( + history?.firstOrNull()?.totalPnl, + selectedChartEntry?.totalPnl, + ) + } + val curValue = when (chartType) { + ChartType.EQUITY -> selectedChartEntry?.equity + ChartType.PNL -> selectedChartEntry?.totalPnl + } + return DydxVaultChartSelectedInfoView.ViewState( + localizer = localizer, + currentValue = formatter.dollar(curValue, digits = 2), + entryDate = formatter.dateTime(selectedChartEntry?.dateInstance), + change = SignedAmountView.ViewState.fromDouble(value) { + if (it == 0.0) { + "" + } else { + val dollarValue = formatter.dollar(value, digits = 2) ?: "-" + val percentValue = formatter.percent(percent, 2) ?: "-" + "$dollarValue ($percentValue)" + } + }, + ) + } + + private fun createDiffs(first: Double?, current: Double?): Pair { + if (first == null || current == null) { + return Pair(0.0, 0.0) + } + val percent = if (first != 0.0) { + (current - first) / first + } else { + 0.0 + } + return Pair(current - first, percent) + } +} diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultChartViewModel.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultChartViewModel.kt index 7aaaaf60..5873cf40 100644 --- a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultChartViewModel.kt +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultChartViewModel.kt @@ -1,8 +1,9 @@ package exchange.dydx.trading.feature.vault.components -import android.util.Half.toFloat import androidx.lifecycle.ViewModel import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.listener.OnChartValueSelectedListener import dagger.hilt.android.lifecycle.HiltViewModel import exchange.dydx.abacus.functional.vault.VaultHistoryEntry import exchange.dydx.abacus.protocols.LocalizerProtocol @@ -17,15 +18,18 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.datetime.Clock -import kotlinx.datetime.Instant import java.time.Duration +import java.time.Instant import javax.inject.Inject @HiltViewModel class DydxVaultChartViewModel @Inject constructor( private val localizer: LocalizerProtocol, private val abacusStateManager: AbacusStateManagerProtocol, -) : ViewModel(), DydxViewModel { + private val selectedChartEntry: MutableStateFlow, + private val vaultHistory: MutableStateFlow?>, + private val chartType: MutableStateFlow, +) : ViewModel(), DydxViewModel, OnChartValueSelectedListener { private val typeIndex = MutableStateFlow(0) private val resolutionIndex = MutableStateFlow(1) @@ -42,6 +46,10 @@ class DydxVaultChartViewModel @Inject constructor( } .distinctUntilChanged() + init { + chartType.value = ChartType.allTypes[typeIndex.value] + } + private fun createViewState( history: IList?, currentTypeIndex: Int, @@ -53,6 +61,7 @@ class DydxVaultChartViewModel @Inject constructor( typeIndex = currentTypeIndex, onTypeChanged = { typeIndex.value = it + chartType.value = ChartType.allTypes[it] }, resolutionTitles = ChartResolution.allResolutions.map { it.title(localizer) }, resolutionIndex = currentResolutionIndex, @@ -66,6 +75,7 @@ class DydxVaultChartViewModel @Inject constructor( resolution = ChartResolution.allResolutions[currentResolutionIndex], ), lineWidth = 3.0, + selectionListener = this, ), ) } @@ -78,13 +88,14 @@ class DydxVaultChartViewModel @Inject constructor( val filtered = history?.filter { entry -> val now = Clock.System.now() val then = entry.dateInstance ?: return@filter false - val diff = now.toEpochMilliseconds() - then.toEpochMilliseconds() + val diff = now.toEpochMilliseconds() - then.toEpochMilli() when (resolution) { ChartResolution.DAY -> diff <= Duration.ofDays(1).toMillis() ChartResolution.WEEK -> diff <= Duration.ofDays(7).toMillis() ChartResolution.MONTH -> diff <= Duration.ofDays(30).toMillis() } }?.reversed() + vaultHistory.value = filtered if (filtered.isNullOrEmpty()) { return LineChartDataSet(emptyList(), type.title(localizer)) } @@ -98,16 +109,24 @@ class DydxVaultChartViewModel @Inject constructor( if (x == null || y == null) { return@map null } - Entry(x, y) + Entry(x, y, entry) } return LineChartDataSet(entries, type.title(localizer)) } + + override fun onValueSelected(e: Entry?, h: Highlight?) { + selectedChartEntry.value = e?.data as? VaultHistoryEntry + } + + override fun onNothingSelected() { + selectedChartEntry.value = null + } } -private val VaultHistoryEntry.dateInstance: Instant? - get() = date?.let { Instant.fromEpochMilliseconds(it.toLong()) } +internal val VaultHistoryEntry.dateInstance: Instant? + get() = date?.let { Instant.ofEpochMilli(it.toLong()) } -private enum class ChartType { +enum class ChartType { PNL, EQUITY; diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultInfoView.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultInfoView.kt index a7938021..a076cc39 100644 --- a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultInfoView.kt +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultInfoView.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Text @@ -46,6 +47,7 @@ object DydxVaultInfoView : DydxComponent { val pnl: SignedAmountView.ViewState? = null, val apr: SignedAmountView.ViewState? = null, val tvl: String? = null, + val chartEntrySelected: Boolean = false, ) { companion object { val preview = ViewState( @@ -117,50 +119,63 @@ object DydxVaultInfoView : DydxComponent { PlatformDivider() Row( - modifier = Modifier - .padding(16.dp) - .fillMaxWidth(), + modifier = Modifier.height(96.dp).padding(16.dp), verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp), ) { - BottomRowItem( - modifier = Modifier, - title = state.localizer.localize("APP.VAULTS.VAULT_THIRTY_DAY_APR"), - valueComposable = { modifier -> - if (state.apr != null) { - SignedAmountView.Content( - modifier = modifier, - state = state.apr, - textStyle = TextStyle.dydxDefault - .themeFont(fontSize = ThemeFont.FontSize.medium), - ) - } else { - Text( - text = "-", - modifier = modifier, - style = TextStyle.dydxDefault - .themeFont(fontSize = ThemeFont.FontSize.medium) - .themeColor(ThemeColor.SemanticColor.text_primary), - ) - } - }, - ) - BottomRowItem( - modifier = Modifier.weight(1f), - title = state.localizer.localize("APP.VAULTS.TVL"), - valueComposable = { modifier -> + if (state.chartEntrySelected) { + DydxVaultChartSelectedInfoView.Content(Modifier) + } else { + VaultInfoContent(Modifier, state) + } + } + + PlatformDivider() + } + } + + @Composable + private fun VaultInfoContent(modifier: Modifier, state: ViewState) { + Row( + modifier = modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp), + ) { + BottomRowItem( + modifier = Modifier, + title = state.localizer.localize("APP.VAULTS.VAULT_THIRTY_DAY_APR"), + valueComposable = { modifier -> + if (state.apr != null) { + SignedAmountView.Content( + modifier = modifier, + state = state.apr, + textStyle = TextStyle.dydxDefault + .themeFont(fontSize = ThemeFont.FontSize.medium), + ) + } else { Text( - text = state.tvl ?: "-", + text = "-", modifier = modifier, style = TextStyle.dydxDefault .themeFont(fontSize = ThemeFont.FontSize.medium) .themeColor(ThemeColor.SemanticColor.text_primary), ) - }, - ) - } - - PlatformDivider() + } + }, + ) + BottomRowItem( + modifier = Modifier.weight(1f), + title = state.localizer.localize("APP.VAULTS.TVL"), + valueComposable = { modifier -> + Text( + text = state.tvl ?: "-", + modifier = modifier, + style = TextStyle.dydxDefault + .themeFont(fontSize = ThemeFont.FontSize.medium) + .themeColor(ThemeColor.SemanticColor.text_primary), + ) + }, + ) } } diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultInfoViewModel.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultInfoViewModel.kt index de966ca7..451789f7 100644 --- a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultInfoViewModel.kt +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/components/DydxVaultInfoViewModel.kt @@ -2,6 +2,7 @@ package exchange.dydx.trading.feature.vault.components import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel +import exchange.dydx.abacus.functional.vault.VaultHistoryEntry import exchange.dydx.abacus.output.Vault import exchange.dydx.abacus.protocols.LocalizerProtocol import exchange.dydx.dydxstatemanager.AbacusStateManagerProtocol @@ -9,8 +10,8 @@ import exchange.dydx.trading.common.DydxViewModel import exchange.dydx.trading.common.formatter.DydxFormatter import exchange.dydx.trading.feature.shared.views.SignedAmountView import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map import javax.inject.Inject import kotlin.math.absoluteValue @@ -19,15 +20,22 @@ class DydxVaultInfoViewModel @Inject constructor( private val localizer: LocalizerProtocol, private val abacusStateManager: AbacusStateManagerProtocol, private val formatter: DydxFormatter, + private val selectedChartEntryFlow: Flow, ) : ViewModel(), DydxViewModel { - val state: Flow = abacusStateManager.state.vault - .map { - createViewState(it) + val state: Flow = + combine( + abacusStateManager.state.vault, + selectedChartEntryFlow, + ) { vault, selectedChartEntry -> + createViewState(vault, selectedChartEntry) } - .distinctUntilChanged() + .distinctUntilChanged() - private fun createViewState(vault: Vault?): DydxVaultInfoView.ViewState { + private fun createViewState( + vault: Vault?, + selectedChartEntry: VaultHistoryEntry?, + ): DydxVaultInfoView.ViewState { val pnl = SignedAmountView.ViewState.fromDouble(vault?.account?.allTimeReturnUsdc) { formatter.dollar(it?.absoluteValue, 2) ?: "-" } @@ -40,6 +48,7 @@ class DydxVaultInfoViewModel @Inject constructor( pnl = pnl, apr = apr, tvl = formatter.dollar(vault?.details?.totalValue, digits = 2), + chartEntrySelected = selectedChartEntry != null, ) } } diff --git a/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/di/VaultModule.kt b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/di/VaultModule.kt new file mode 100644 index 00000000..408f5cd6 --- /dev/null +++ b/v4/feature/vault/src/main/java/exchange/dydx/trading/feature/vault/di/VaultModule.kt @@ -0,0 +1,52 @@ +package exchange.dydx.trading.feature.vault.di + +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ActivityRetainedComponent +import dagger.hilt.android.scopes.ActivityRetainedScoped +import exchange.dydx.abacus.functional.vault.VaultHistoryEntry +import exchange.dydx.trading.feature.vault.components.ChartType +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow + +@Module +@InstallIn(ActivityRetainedComponent::class) +interface VaultModule { + + @Binds + fun bindVaultSelectedHistoryEntryFlow( + mutableFlow: MutableStateFlow, + ): Flow + + @Binds + fun bindVaultHistoryEntryFlow( + mutableFlow: MutableStateFlow?>, + ): Flow?> + + @Binds + fun bindVaultChartTypeFlow( + mutableFlow: MutableStateFlow, + ): Flow + + companion object { + @Provides + @ActivityRetainedScoped + fun provideMutableSelectedVaultHistoryEntryFlow(): MutableStateFlow { + return MutableStateFlow(null) + } + + @Provides + @ActivityRetainedScoped + fun provideMutableVaultHistoryFlow(): MutableStateFlow?> { + return MutableStateFlow(null) + } + + @Provides + @ActivityRetainedScoped + fun provideMutableVaultChartTypeFlow(): MutableStateFlow { + return MutableStateFlow(null) + } + } +}