diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt index 1646791082..de7706a43d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt @@ -1,6 +1,5 @@ package com.tangem.core.ui.components.buttons -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth @@ -12,7 +11,6 @@ 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.datasource.CollectionPreviewParameterProvider -import androidx.compose.ui.unit.dp import com.tangem.core.ui.R import com.tangem.core.ui.components.buttons.actions.ActionButton import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig @@ -24,12 +22,10 @@ import kotlinx.collections.immutable.persistentListOf fun HorizontalActionChips( buttons: ImmutableList, modifier: Modifier = Modifier, - contentPadding: PaddingValues = PaddingValues(0.dp), + contentPadding: PaddingValues = PaddingValues(TangemTheme.dimens.spacing0), ) { LazyRow( - modifier = modifier - .background(color = TangemTheme.colors.background.secondary) - .fillMaxWidth(), + modifier = modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8), verticalAlignment = Alignment.CenterVertically, contentPadding = contentPadding, diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt index d10357cd54..2a78274eb5 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt @@ -8,6 +8,8 @@ import java.util.Locale object BigDecimalFormatter { + const val EMPTY_BALANCE_SIGN = "—" + private const val TEMP_CURRENCY_CODE = "USD" fun formatCryptoAmount( diff --git a/features/tokendetails/impl/build.gradle.kts b/features/tokendetails/impl/build.gradle.kts index 604cb6ce5e..d3af011dcd 100644 --- a/features/tokendetails/impl/build.gradle.kts +++ b/features/tokendetails/impl/build.gradle.kts @@ -26,6 +26,8 @@ dependencies { implementation(deps.compose.accompanist.systemUiController) implementation(deps.compose.coil) + implementation(deps.kotlin.immutable.collections) + /** DI */ implementation(deps.hilt.android) kapt(deps.hilt.kapt) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt index cb065cf32f..c94cca8569 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt @@ -1,9 +1,13 @@ package com.tangem.feature.tokendetails.presentation.tokendetails +import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig +import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsBalanceBlockState import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsTopAppBarConfig import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenInfoBlockState import com.tangem.features.tokendetails.impl.R +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList internal object TokenDetailsPreviewData { @@ -34,8 +38,42 @@ internal object TokenDetailsPreviewData { ), ) + private val actionButtons = persistentListOf( + ActionButtonConfig( + text = "Buy", + iconResId = R.drawable.ic_plus_24, + onClick = {}, + ), + ActionButtonConfig( + text = "Send", + iconResId = R.drawable.ic_arrow_up_24, + onClick = {}, + ), + ActionButtonConfig( + text = "Receive", + iconResId = R.drawable.ic_arrow_down_24, + onClick = {}, + ), + ActionButtonConfig( + text = "Exchange", + iconResId = R.drawable.ic_exchange_vertical_24, + onClick = {}, + ), + ) + + private val disabledActionButtons = actionButtons.map { it.copy(enabled = false) }.toPersistentList() + + val balanceLoading = TokenDetailsBalanceBlockState.Loading(actionButtons = disabledActionButtons) + val balanceContent = TokenDetailsBalanceBlockState.Content( + actionButtons = actionButtons, + fiatBalance = "123,00$", + cryptoBalance = "866,96 USDT", + ) + val balanceError = TokenDetailsBalanceBlockState.Error(actionButtons = disabledActionButtons) + val tokenDetailsState = TokenDetailsState( topAppBarConfig = tokenDetailsTopAppBarConfig, tokenInfoBlockState = tokenInfoBlockState, + tokenBalanceBlockState = balanceLoading, ) } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt new file mode 100644 index 0000000000..a67edd1a61 --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt @@ -0,0 +1,23 @@ +package com.tangem.feature.tokendetails.presentation.tokendetails.state + +import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig +import kotlinx.collections.immutable.ImmutableList + +sealed class TokenDetailsBalanceBlockState { + + abstract val actionButtons: ImmutableList + + data class Loading( + override val actionButtons: ImmutableList, + ) : TokenDetailsBalanceBlockState() + + data class Content( + override val actionButtons: ImmutableList, + val fiatBalance: String, + val cryptoBalance: String, + ) : TokenDetailsBalanceBlockState() + + data class Error( + override val actionButtons: ImmutableList, + ) : TokenDetailsBalanceBlockState() +} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt index 438b92545d..988dd22bfa 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt @@ -3,4 +3,5 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state data class TokenDetailsState( val topAppBarConfig: TokenDetailsTopAppBarConfig, val tokenInfoBlockState: TokenInfoBlockState, + val tokenBalanceBlockState: TokenDetailsBalanceBlockState, ) \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt index 380d2ad51f..bd876c400d 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt @@ -1,5 +1,6 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding @@ -10,6 +11,7 @@ import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState +import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsBalanceBlock import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsTopAppBar import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenInfoBlock @@ -24,8 +26,10 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) { .padding(paddingValues = scaffoldPaddings) .padding(horizontal = TangemTheme.dimens.spacing16) .fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), ) { TokenInfoBlock(state = state.tokenInfoBlockState) + TokenDetailsBalanceBlock(state = state.tokenBalanceBlockState) } } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt new file mode 100644 index 0000000000..df54b41461 --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt @@ -0,0 +1,136 @@ +package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import com.tangem.core.ui.components.RectangleShimmer +import com.tangem.core.ui.components.buttons.HorizontalActionChips +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData +import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsBalanceBlockState +import com.tangem.features.tokendetails.impl.R + +@Composable +internal fun TokenDetailsBalanceBlock(state: TokenDetailsBalanceBlockState, modifier: Modifier = Modifier) { + Surface( + modifier = modifier.fillMaxWidth(), + shape = TangemTheme.shapes.roundedCornersXMedium, + color = TangemTheme.colors.background.primary, + ) { + Column { + Text( + modifier = Modifier + .padding( + top = TangemTheme.dimens.spacing12, + start = TangemTheme.dimens.spacing12, + end = TangemTheme.dimens.spacing12, + ), + text = stringResource(id = R.string.onboarding_balance_title), + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.body2, + maxLines = 1, + ) + FiatBalance( + state = state, + modifier = Modifier + .padding(top = TangemTheme.dimens.spacing4) + .padding(horizontal = TangemTheme.dimens.spacing12), + ) + CryptoBalance( + state = state, + modifier = Modifier + .padding(top = TangemTheme.dimens.spacing4) + .padding(horizontal = TangemTheme.dimens.spacing12), + ) + + HorizontalActionChips( + buttons = state.actionButtons, + modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing12), + contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing12), + ) + } + } +} + +@Composable +private fun FiatBalance(state: TokenDetailsBalanceBlockState, modifier: Modifier = Modifier) { + when (state) { + is TokenDetailsBalanceBlockState.Loading -> RectangleShimmer( + modifier = modifier.size( + width = TangemTheme.dimens.size102, + height = TangemTheme.dimens.size24, + ), + ) + is TokenDetailsBalanceBlockState.Content -> Text( + modifier = modifier, + text = state.fiatBalance, + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + ) + is TokenDetailsBalanceBlockState.Error -> Text( + modifier = modifier, + text = BigDecimalFormatter.EMPTY_BALANCE_SIGN, + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + ) + } +} + +@Composable +private fun CryptoBalance(state: TokenDetailsBalanceBlockState, modifier: Modifier = Modifier) { + when (state) { + is TokenDetailsBalanceBlockState.Loading -> RectangleShimmer( + modifier = modifier.size( + width = TangemTheme.dimens.size70, + height = TangemTheme.dimens.size16, + ), + ) + is TokenDetailsBalanceBlockState.Content -> Text( + modifier = modifier, + text = state.cryptoBalance, + style = TangemTheme.typography.caption, + color = TangemTheme.colors.text.primary1, + ) + is TokenDetailsBalanceBlockState.Error -> Text( + modifier = modifier, + text = BigDecimalFormatter.EMPTY_BALANCE_SIGN, + style = TangemTheme.typography.caption, + color = TangemTheme.colors.text.primary1, + ) + } +} + +@Preview +@Composable +private fun Preview_TokenDetailsBalanceBlock_LightTheme( + @PreviewParameter(TokenDetailsBalanceBlockStateProvider::class) state: TokenDetailsBalanceBlockState, +) { + TangemTheme(isDark = false) { + TokenDetailsBalanceBlock(state) + } +} + +@Preview +@Composable +private fun Preview_TokenDetailsBalanceBlock_DarkTheme( + @PreviewParameter(TokenDetailsBalanceBlockStateProvider::class) state: TokenDetailsBalanceBlockState, +) { + TangemTheme(isDark = true) { + TokenDetailsBalanceBlock(state) + } +} + +private class TokenDetailsBalanceBlockStateProvider : CollectionPreviewParameterProvider( + collection = listOf( + TokenDetailsPreviewData.balanceLoading, + TokenDetailsPreviewData.balanceContent, + TokenDetailsPreviewData.balanceError, + ), +) \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 50e489eedb..bcbf76c58a 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -4,12 +4,17 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import javax.inject.Inject import kotlin.properties.Delegates +private const val LOADING_DELAY = 4_000L + @HiltViewModel internal class TokenDetailsViewModel @Inject constructor() : ViewModel() { @@ -18,6 +23,14 @@ internal class TokenDetailsViewModel @Inject constructor() : ViewModel() { var uiState by mutableStateOf(getInitialState()) private set + init { + // simulate loading state + viewModelScope.launch { + delay(LOADING_DELAY) + uiState = uiState.copy(tokenBalanceBlockState = TokenDetailsPreviewData.balanceContent) + } + } + private fun getInitialState() = TokenDetailsPreviewData.tokenDetailsState.copy( topAppBarConfig = TokenDetailsPreviewData.tokenDetailsTopAppBarConfig.copy( onBackClick = ::onBackClick,