Updated on 2026-08-14
This commit is contained in:
parent
c8070ec66e
commit
bee2be0994
7 changed files with 340 additions and 1 deletions
|
|
@ -22,6 +22,11 @@ dependencies {
|
|||
implementation(projects.core.ui)
|
||||
implementation(projects.core.navigation)
|
||||
|
||||
/** Compose */
|
||||
implementation(tangemDeps.vico.core)
|
||||
implementation(tangemDeps.vico.compose)
|
||||
implementation(tangemDeps.vico.compose.m3)
|
||||
|
||||
/** Common */
|
||||
implementation(projects.common.ui)
|
||||
implementation(projects.common.routing)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.features.yield.supply.impl.chart
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.yield.supply.impl.chart.model.YieldSupplyChartModel
|
||||
import com.tangem.features.yield.supply.impl.chart.ui.YieldSupplyChartContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultYieldSupplyChartComponent @AssistedInject constructor(
|
||||
@Assisted private val appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: YieldSupplyChartModel.Params,
|
||||
) : AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: YieldSupplyChartModel = getOrCreateModel(params = params)
|
||||
|
||||
@Composable
|
||||
fun Content(modifier: Modifier = Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
YieldSupplyChartContent(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
internal interface Factory {
|
||||
fun create(context: AppComponentContext, params: YieldSupplyChartModel.Params): DefaultYieldSupplyChartComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.yield.supply.impl.chart.di
|
||||
|
||||
import com.tangem.core.decompose.di.ModelComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.yield.supply.impl.chart.model.YieldSupplyChartModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(ModelComponent::class)
|
||||
internal interface YieldSupplyChartModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(YieldSupplyChartModel::class)
|
||||
fun bindYieldSupplyChartModel(impl: YieldSupplyChartModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.features.yield.supply.impl.chart.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@Immutable
|
||||
internal sealed class YieldSupplyChartUM {
|
||||
|
||||
@Immutable
|
||||
data object Loading : YieldSupplyChartUM()
|
||||
|
||||
@Immutable
|
||||
data class Error(val onRetry: () -> Unit) : YieldSupplyChartUM()
|
||||
|
||||
@Immutable
|
||||
data class Data(val chartData: YieldSupplyMarketChartData) : YieldSupplyChartUM()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
internal data class YieldSupplyMarketChartData(
|
||||
val y: ImmutableList<Double>,
|
||||
val x: ImmutableList<Double>,
|
||||
) {
|
||||
companion object {
|
||||
@Suppress("MagicNumber")
|
||||
fun mock(): YieldSupplyMarketChartData {
|
||||
// TODO [REDACTED_TASK_KEY] replace mock values with real APY series
|
||||
val y = listOf(
|
||||
4.6, 4.0, 4.2, 3.3, 2.5, 5.6, 4.2, 6.7, 3.5, 2.5, 4.5, 3.4,
|
||||
).toImmutableList()
|
||||
val x = List(y.size) { 1.0 }.toImmutableList()
|
||||
return YieldSupplyMarketChartData(y = y, x = x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.features.yield.supply.impl.chart.model
|
||||
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.features.yield.supply.impl.chart.entity.YieldSupplyChartUM
|
||||
import com.tangem.features.yield.supply.impl.chart.entity.YieldSupplyMarketChartData
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("UnusedPrivateProperty")
|
||||
@ModelScoped
|
||||
internal class YieldSupplyChartModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
data class Params(
|
||||
val tokenContractAddress: String,
|
||||
val onRetry: () -> Unit = {},
|
||||
)
|
||||
|
||||
private val params: Params = paramsContainer.require()
|
||||
|
||||
val uiState: StateFlow<YieldSupplyChartUM>
|
||||
field = MutableStateFlow<YieldSupplyChartUM>(YieldSupplyChartUM.Loading)
|
||||
|
||||
init {
|
||||
// TODO [REDACTED_TASK_KEY]
|
||||
val chartData = YieldSupplyMarketChartData.mock()
|
||||
uiState.update { YieldSupplyChartUM.Data(chartData = chartData) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
package com.tangem.features.yield.supply.impl.chart.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
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.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.patrykandpatrick.vico.compose.cartesian.CartesianChartHost
|
||||
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberAxisLabelComponent
|
||||
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberBottomAxis
|
||||
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberCustomStartAxis
|
||||
import com.patrykandpatrick.vico.compose.cartesian.decoration.rememberHorizontalLine
|
||||
import com.patrykandpatrick.vico.compose.cartesian.fullWidth
|
||||
import com.patrykandpatrick.vico.compose.cartesian.rememberCartesianChart
|
||||
import com.patrykandpatrick.vico.compose.cartesian.rememberVicoScrollState
|
||||
import com.patrykandpatrick.vico.compose.cartesian.rememberVicoZoomState
|
||||
import com.patrykandpatrick.vico.compose.cartesian.layer.rememberColumnCartesianLayer
|
||||
import com.patrykandpatrick.vico.compose.common.component.rememberLineComponent
|
||||
import com.patrykandpatrick.vico.compose.common.shape.dashed
|
||||
import com.patrykandpatrick.vico.core.cartesian.HorizontalLayout
|
||||
import com.patrykandpatrick.vico.core.cartesian.Zoom
|
||||
import com.patrykandpatrick.vico.core.cartesian.axis.AxisPosition
|
||||
import com.patrykandpatrick.vico.core.cartesian.axis.HorizontalAxis
|
||||
import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModel
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.ColumnCartesianLayerModel
|
||||
import com.patrykandpatrick.vico.core.common.shape.Shape
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerH12
|
||||
import com.tangem.core.ui.components.TextShimmer
|
||||
import com.tangem.core.ui.components.buttons.SecondarySmallButton
|
||||
import com.tangem.core.ui.components.buttons.SmallButtonConfig
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.yield.supply.impl.chart.entity.YieldSupplyChartUM
|
||||
import com.tangem.features.yield.supply.impl.chart.entity.YieldSupplyMarketChartData
|
||||
|
||||
@Composable
|
||||
internal fun YieldSupplyChartContent(state: YieldSupplyChartUM, modifier: Modifier = Modifier) {
|
||||
val itemModifier = modifier
|
||||
.height(105.dp)
|
||||
.fillMaxWidth()
|
||||
when (state) {
|
||||
is YieldSupplyChartUM.Loading -> YieldSupplyChartLoading(modifier = itemModifier)
|
||||
is YieldSupplyChartUM.Error -> {
|
||||
Column(
|
||||
modifier = itemModifier,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.markets_loading_error_title),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
SpacerH12()
|
||||
SecondarySmallButton(
|
||||
config = SmallButtonConfig(
|
||||
text = resourceReference(R.string.try_to_load_data_again_button_title),
|
||||
onClick = state.onRetry,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
is YieldSupplyChartUM.Data -> YieldSupplyChartData(state = state, modifier = itemModifier)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
private fun YieldSupplyChartLoading(modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 12.dp),
|
||||
) {
|
||||
repeat(4) { index ->
|
||||
TextShimmer(
|
||||
modifier = Modifier
|
||||
.size(16.dp),
|
||||
radius = 4.dp,
|
||||
style = TangemTheme.typography.body1,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 24.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
repeat(5) {
|
||||
TextShimmer(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.height(16.dp),
|
||||
radius = 4.dp,
|
||||
style = TangemTheme.typography.body1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size16)
|
||||
.align(Alignment.Center),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun YieldSupplyChartData(state: YieldSupplyChartUM.Data, modifier: Modifier = Modifier) {
|
||||
val yValues = state.chartData.y
|
||||
val model = CartesianChartModel(ColumnCartesianLayerModel.build { series(yValues) })
|
||||
|
||||
val layer = rememberColumnCartesianLayer()
|
||||
|
||||
val startAxis: VerticalAxis<AxisPosition.Vertical.Start> = rememberCustomStartAxis(
|
||||
label = rememberAxisLabelComponent(color = TangemTheme.colors.text.tertiary),
|
||||
valueFormatter = CartesianValueFormatter { value, _, _ ->
|
||||
val pct = value.toInt()
|
||||
"$pct%"
|
||||
},
|
||||
)
|
||||
|
||||
val bottomAxis: HorizontalAxis<AxisPosition.Horizontal.Bottom> = rememberBottomAxis(
|
||||
label = rememberAxisLabelComponent(color = TangemTheme.colors.text.tertiary),
|
||||
guideline = null,
|
||||
tick = null,
|
||||
line = null,
|
||||
)
|
||||
|
||||
val average = yValues.map { it.toDouble() }.average()
|
||||
|
||||
val referenceLine = rememberHorizontalLine(
|
||||
y = { average },
|
||||
line = rememberLineComponent(
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
thickness = TangemTheme.dimens.size1,
|
||||
shape = Shape.dashed(Shape.Rectangle, TangemTheme.dimens.size4, TangemTheme.dimens.size4),
|
||||
),
|
||||
)
|
||||
|
||||
val chart = rememberCartesianChart(
|
||||
layer,
|
||||
startAxis = startAxis,
|
||||
bottomAxis = bottomAxis,
|
||||
decorations = listOf(referenceLine),
|
||||
horizontalLayout = HorizontalLayout.fullWidth(),
|
||||
)
|
||||
|
||||
CartesianChartHost(
|
||||
modifier = modifier,
|
||||
chart = chart,
|
||||
model = model,
|
||||
zoomState = rememberVicoZoomState(initialZoom = Zoom.Content, zoomEnabled = false),
|
||||
scrollState = rememberVicoScrollState(scrollEnabled = false),
|
||||
)
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun YieldSupplyChartContent_Preview(
|
||||
@PreviewParameter(YieldSupplyChartPreviewProvider::class) state: YieldSupplyChartUM,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
YieldSupplyChartContent(state = state)
|
||||
}
|
||||
}
|
||||
|
||||
private class YieldSupplyChartPreviewProvider : PreviewParameterProvider<YieldSupplyChartUM> {
|
||||
override val values: Sequence<YieldSupplyChartUM>
|
||||
get() = sequenceOf(
|
||||
YieldSupplyChartUM.Loading,
|
||||
YieldSupplyChartUM.Error(onRetry = {}),
|
||||
YieldSupplyChartUM.Data(chartData = YieldSupplyMarketChartData.mock()),
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -178,7 +178,9 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
|
||||
uiState.update { yieldSupplyUM }
|
||||
|
||||
(cryptoCurrency as? CryptoCurrency.Token)?.let(::loadTokenStatus)
|
||||
if (yieldSupplyUM is YieldSupplyUM.Loading) {
|
||||
(cryptoCurrency as? CryptoCurrency.Token)?.let(::loadTokenStatus)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue