Updated on 2026-08-14

This commit is contained in:
Tangem 2023-06-01 17:31:20 +08:00
parent 2a3a59c0aa
commit 2782d8a846
26 changed files with 662 additions and 251 deletions

View file

@ -37,6 +37,8 @@ dependencies {
implementation(project(":features:swap:data"))
implementation(project(":features:tester:api"))
implementation(project(":features:tester:impl"))
implementation(project(":features:wallet:api"))
implementation(project(":features:wallet:impl"))
/** AndroidX libraries */
implementation(deps.androidx.core.ktx)

View file

@ -21,6 +21,7 @@ import com.tangem.datasource.config.models.Config
import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.domain.DomainLayer
import com.tangem.domain.common.LogConfig
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import com.tangem.tap.common.IntentHandler
import com.tangem.tap.common.analytics.AnalyticsFactory
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
@ -133,6 +134,9 @@ class TapApplication : Application(), ImageLoaderFactory {
@Inject
lateinit var preferencesDataSource: PreferencesDataSource
@Inject
lateinit var walletFeatureToggles: WalletFeatureToggles
override fun onCreate() {
super.onCreate()
@ -146,6 +150,7 @@ class TapApplication : Application(), ImageLoaderFactory {
assetReader = assetReader,
networkConnectionManager = networkConnectionManager,
customTokenFeatureToggles = customTokenFeatureToggles,
walletFeatureToggles = walletFeatureToggles,
),
),
)

View file

@ -1,11 +1,7 @@
package com.tangem.tap.common.extensions
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
import androidx.fragment.app.*
import com.tangem.feature.referral.ReferralFragment
import com.tangem.feature.swap.presentation.SwapFragment
import com.tangem.tap.common.redux.navigation.AppScreen
@ -37,6 +33,7 @@ import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.wallet.R
import timber.log.Timber
import com.tangem.feature.wallet.presentation.WalletFragment as RedesignedWalletFragment
import com.tangem.tap.features.customtoken.impl.presentation.AddCustomTokenFragment as RedesignedAddCustomTokenFragment
fun FragmentActivity.openFragment(
@ -138,7 +135,16 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
AppScreen.OnboardingWallet -> OnboardingWalletFragment()
AppScreen.OnboardingTwins -> TwinsCardsFragment()
AppScreen.OnboardingOther -> OnboardingOtherCardsFragment()
AppScreen.Wallet -> WalletFragment()
AppScreen.Wallet -> {
val featureToggles = store.state.daggerGraphState.get(
getDependency = DaggerGraphState::walletFeatureToggles,
)
if (featureToggles.isRedesignedScreenEnabled) {
RedesignedWalletFragment()
} else {
WalletFragment()
}
}
AppScreen.Send -> SendFragment()
AppScreen.Details -> DetailsFragment()
AppScreen.DetailsSecurity -> SecurityModeFragment()

View file

@ -4,6 +4,7 @@ import com.tangem.datasource.asset.AssetReader
import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.domain.card.ScanCardUseCase
import com.tangem.features.tester.api.TesterRouter
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
import org.rekotlin.StateType
@ -13,6 +14,7 @@ data class DaggerGraphState(
val networkConnectionManager: NetworkConnectionManager? = null,
val customTokenFeatureToggles: CustomTokenFeatureToggles? = null,
val scanCardUseCase: ScanCardUseCase? = null,
val walletFeatureToggles: WalletFeatureToggles? = null,
) : StateType {
inline fun <reified T> get(getDependency: DaggerGraphState.() -> T?): T {

View file

@ -10,5 +10,9 @@
{
"name": "NEW_CARD_SCANNING_ENABLED",
"version": "4.7.0"
},
{
"name": "REDESIGNED_WALLET_SCREEN_ENABLED",
"version": "undefined"
}
]
]

View file

@ -0,0 +1,12 @@
package com.tangem.features.wallet.featuretoggles
/**
* Wallet feature toggles
*
[REDACTED_AUTHOR]
*/
interface WalletFeatureToggles {
/** Availability of redesigned screen */
val isRedesignedScreenEnabled: Boolean
}

View file

@ -15,6 +15,7 @@ dependencies {
implementation(deps.compose.constraintLayout)
implementation(deps.compose.material)
implementation(deps.compose.foundation)
implementation(deps.compose.material3)
implementation(deps.compose.navigation)
implementation(deps.compose.navigation.hilt)
implementation(deps.compose.ui)
@ -32,5 +33,4 @@ dependencies {
/** Feature Apis */
implementation(project(":features:wallet:api"))
}

View file

@ -0,0 +1,21 @@
package com.tangem.feature.wallet.di
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.feature.wallet.featuretoggles.DefaultWalletFeatureToggles
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object WalletFeatureTogglesModule {
@Provides
@Singleton
fun provideWalletFeatureToggles(featureTogglesManager: FeatureTogglesManager): WalletFeatureToggles {
return DefaultWalletFeatureToggles(featureTogglesManager = featureTogglesManager)
}
}

View file

@ -0,0 +1,19 @@
package com.tangem.feature.wallet.featuretoggles
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
/**
* Default implementation of CustomToken feature toggles
*
* @property featureTogglesManager manager for getting information about the availability of feature toggles
*
[REDACTED_AUTHOR]
*/
internal class DefaultWalletFeatureToggles(
private val featureTogglesManager: FeatureTogglesManager,
) : WalletFeatureToggles {
override val isRedesignedScreenEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "REDESIGNED_WALLET_SCREEN_ENABLED")
}

View file

@ -0,0 +1,41 @@
package com.tangem.feature.wallet.presentation
import android.os.Bundle
import android.transition.TransitionInflater
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
import androidx.core.view.WindowCompat
import androidx.fragment.app.Fragment
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
import dagger.hilt.android.AndroidEntryPoint
/**
* Wallet fragment
*
[REDACTED_AUTHOR]
*/
@AndroidEntryPoint
class WalletFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) }
with(TransitionInflater.from(requireContext())) {
enterTransition = inflateTransition(R.transition.slide_right)
exitTransition = inflateTransition(R.transition.fade)
}
return ComposeView(inflater.context).apply {
setContent {
isTransitionGroup = true
TangemTheme {
// TODO: [REDACTED_TASK_KEY] Call WalletScreen
}
}
}
}
}

View file

@ -0,0 +1,9 @@
package com.tangem.feature.wallet.presentation.state
internal enum class TokenUIState {
LOADED, LOADING
}
internal enum class TokenOptionsUIState {
VISIBLE, HIDDEN, DRAG, UNREACHABLE
}

View file

@ -1,8 +1,6 @@
package com.tangem.feature.wallet.presentation.ui.config
package com.tangem.feature.wallet.presentation.state
import androidx.annotation.DrawableRes
import com.tangem.feature.wallet.presentation.ui.state.TokenOptionsUIState
import com.tangem.feature.wallet.presentation.ui.state.TokenUIState
/**
* Token config
@ -18,7 +16,7 @@ import com.tangem.feature.wallet.presentation.ui.state.TokenUIState
* @property tokenOptionsUIState state for token options like 'unreachable', 'hidden' etc
* @property hasPending pending tx in blockchain
*/
data class TokenV2Config(
internal data class TokenV2Config(
val name: String,
val amount: String,
val fiatAmount: String,

View file

@ -0,0 +1,92 @@
package com.tangem.feature.wallet.presentation.state
import androidx.annotation.DrawableRes
/** Wallet card state */
internal sealed interface WalletCardState {
/** Id */
val id: String
/** Title */
val title: String
/** Additional wallet information */
val additionalInfo: String
/** Wallet image resource id */
@get:DrawableRes
val imageResId: Int
/** Lambda be invoked when card is clicked */
val onClick: (() -> Unit)?
/**
* Wallet card content state
*
* @property id wallet id
* @property title wallet name
* @property additionalInfo wallet additional info
* @property imageResId wallet image resource id
* @property onClick lambda be invoked when wallet card is clicked
* @property balance wallet balance
*/
data class Content(
override val id: String,
override val title: String,
override val additionalInfo: String,
override val imageResId: Int,
override val onClick: (() -> Unit)? = null,
val balance: String,
) : WalletCardState
/**
* Wallet card loading state
*
* @property id wallet id
* @property title wallet name
* @property additionalInfo wallet additional info
* @property imageResId wallet image resource id
* @property onClick lambda be invoked when wallet card is clicked
*/
data class Loading(
override val id: String,
override val title: String,
override val additionalInfo: String,
override val imageResId: Int,
override val onClick: (() -> Unit)?,
) : WalletCardState
/**
* Wallet card hidden content state
*
* @property id wallet id
* @property title wallet name
* @property additionalInfo wallet additional info
* @property imageResId wallet image resource id
* @property onClick lambda be invoked when wallet card is clicked
*/
data class HiddenContent(
override val id: String,
override val title: String,
override val additionalInfo: String,
override val imageResId: Int,
override val onClick: (() -> Unit)?,
) : WalletCardState
/**
* Wallet card error state
*
* @property id wallet id
* @property title wallet name
* @property additionalInfo wallet additional info
* @property imageResId wallet image resource id
* @property onClick lambda be invoked when wallet card is clicked
*/
data class Error(
override val id: String,
override val title: String,
override val additionalInfo: String,
override val imageResId: Int,
override val onClick: (() -> Unit)?,
) : WalletCardState
}

View file

@ -0,0 +1,28 @@
package com.tangem.feature.wallet.presentation.state
/**
* Wallet state holder
*
* @property onBackClick lambda be invoked when back button is clicked
* @property headerConfig header config
*
[REDACTED_AUTHOR]
*/
internal data class WalletStateHolder(
val onBackClick: () -> Unit,
val headerConfig: HeaderConfig,
) {
/**
* Header config
*
* @property wallets list of wallets states
* @property onScanCardClick lambda be invoked when scan card button is clicked
* @property onMoreClick lambda be invoked when more button is clicked
*/
data class HeaderConfig(
val wallets: List<WalletCardState>,
val onScanCardClick: () -> Unit,
val onMoreClick: () -> Unit,
)
}

View file

@ -5,10 +5,9 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -21,20 +20,16 @@ import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.R
import com.tangem.core.ui.components.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemTypography
import com.tangem.feature.wallet.presentation.ui.config.PriceChange
import com.tangem.feature.wallet.presentation.ui.config.PriceChangeType
import com.tangem.feature.wallet.presentation.ui.config.TokenV2Config
import com.tangem.feature.wallet.presentation.ui.state.TokenOptionsUIState
import com.tangem.feature.wallet.presentation.ui.state.TokenUIState
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.state.*
private const val DOTS = "•••"
@Composable
fun TokenItemV2(config: TokenV2Config) {
internal fun TokenItemV2(config: TokenV2Config) {
when (config.tokenUIState) {
TokenUIState.LOADED -> LoadedTokenState(config)
TokenUIState.LOADING -> LoadingTokenState()
@ -167,7 +162,6 @@ private fun LoadingTokenState() {
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun BaseSurface(onClick: (() -> Unit)? = null, content: @Composable () -> Unit) {
Surface(
@ -224,12 +218,15 @@ private fun TokenFiatPercentageBlock(fiatAmount: String, priceChange: PriceChang
) {
val iconChangeArrow: Int
val changeTextColor: Color
if (priceChange.type == PriceChangeType.UP) {
iconChangeArrow = R.drawable.img_arrow_up_8
changeTextColor = TangemTheme.colors.text.accent
} else {
iconChangeArrow = R.drawable.img_arrow_down_8
changeTextColor = TangemTheme.colors.text.warning
when (priceChange.type) {
PriceChangeType.UP -> {
iconChangeArrow = R.drawable.img_arrow_up_8
changeTextColor = TangemTheme.colors.text.accent
}
PriceChangeType.DOWN -> {
iconChangeArrow = R.drawable.img_arrow_down_8
changeTextColor = TangemTheme.colors.text.warning
}
}
Image(
modifier = Modifier.align(Alignment.CenterVertically),

View file

@ -1,193 +0,0 @@
package com.tangem.feature.wallet.presentation.ui
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.sp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import com.tangem.core.ui.R
import com.tangem.core.ui.components.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.presentation.ui.config.WalletHeaderConfig
import com.tangem.feature.wallet.presentation.ui.state.BalanceUIState
private const val DOTS = "•••"
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun WalletHeaderCard(config: WalletHeaderConfig, modifier: Modifier = Modifier) {
Surface(
modifier = modifier.defaultMinSize(minHeight = TangemTheme.dimens.size108),
shape = TangemTheme.shapes.roundedCornersXMedium,
color = TangemTheme.colors.background.primary,
onClick = config.onClick ?: {},
enabled = config.onClick != null,
) {
ConstraintLayout(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens.spacing12),
) {
val (balanceBlock, imageItem) = createRefs()
Column(
modifier = Modifier.constrainAs(balanceBlock) {
centerVerticallyTo(parent)
start.linkTo(parent.start)
end.linkTo(imageItem.start)
width = Dimension.fillToConstraints
},
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
) {
HeaderWalletName(
walletName = config.walletName,
balanceState = config.balanceState,
)
HeaderBalanceTitle(
balance = config.balance,
balanceState = config.balanceState,
)
Text(
text = config.additionalInfo,
color = TangemTheme.colors.text.disabled,
style = TangemTheme.typography.caption,
)
}
val imageWidth = TangemTheme.dimens.size120
Image(
modifier = Modifier.constrainAs(imageItem) {
centerVerticallyTo(parent)
top.linkTo(parent.top)
end.linkTo(parent.end)
height = Dimension.fillToConstraints
width = Dimension.value(imageWidth)
},
painter = config.cardImage,
contentDescription = null,
contentScale = ContentScale.FillWidth,
)
}
}
}
@Composable
private fun HeaderWalletName(walletName: String, balanceState: BalanceUIState) {
if (balanceState == BalanceUIState.HIDDEN) {
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) {
Text(
text = walletName,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
maxLines = 1,
)
Icon(
modifier = Modifier.size(size = TangemTheme.dimens.size20),
painter = painterResource(id = R.drawable.ic_eye_off_24),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
)
}
} else {
Text(
text = walletName,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
maxLines = 1,
)
}
}
@Composable
private fun HeaderBalanceTitle(balance: String, balanceState: BalanceUIState) {
when (balanceState) {
BalanceUIState.VISIBLE -> {
ResizableText(
text = balance,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.h2,
fontSizeRange = FontSizeRange(min = 16.sp, max = TangemTheme.typography.h2.fontSize),
modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size32),
)
}
BalanceUIState.LOADING -> {
ShimmerRectangle(
modifier = Modifier.size(
width = TangemTheme.dimens.size102,
height = TangemTheme.dimens.size24,
),
)
}
BalanceUIState.HIDDEN -> {
Text(
text = DOTS,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.h2,
)
}
BalanceUIState.NONE -> {
Text(
text = "",
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.h2,
)
}
}
}
// region Preview
@Composable
private fun WarningsPreview() {
val walletHeaderConfig = WalletHeaderConfig(
walletName = "Wallet 1",
balance = "8923,05 $",
additionalInfo = "3 cards • Seed enabled",
balanceState = BalanceUIState.VISIBLE,
cardImage = painterResource(id = R.drawable.ill_businessman_3d),
onClick = {},
)
Column(modifier = Modifier.fillMaxWidth()) {
WalletHeaderCard(
config = walletHeaderConfig,
)
SpacerH32()
WalletHeaderCard(
config = walletHeaderConfig.copy(balanceState = BalanceUIState.HIDDEN),
)
SpacerH32()
WalletHeaderCard(
config = walletHeaderConfig.copy(balanceState = BalanceUIState.LOADING),
)
SpacerH32()
WalletHeaderCard(
config = walletHeaderConfig.copy(balanceState = BalanceUIState.NONE),
)
SpacerH32()
}
}
@Preview(showBackground = true)
@Composable
private fun Preview_Warning_InLightTheme() {
TangemTheme(isDark = false) {
WarningsPreview()
}
}
@Preview(showBackground = true)
@Composable
private fun Preview_Warning_InDarkTheme() {
TangemTheme(isDark = true) {
WarningsPreview()
}
}
// endregion Preview

View file

@ -0,0 +1,41 @@
package com.tangem.feature.wallet.presentation.ui
import com.tangem.core.ui.R
import com.tangem.feature.wallet.presentation.state.WalletCardState
import java.util.UUID
internal object WalletPreviewData {
val walletCardContent = WalletCardState.Content(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
balance = "8923,05 $",
additionalInfo = "3 cards • Seed enabled",
imageResId = R.drawable.ill_businessman_3d,
onClick = {},
)
val walletCardLoading = WalletCardState.Loading(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
additionalInfo = "3 cards • Seed enabled",
imageResId = R.drawable.ill_businessman_3d,
onClick = {},
)
val walletCardHiddenContent = WalletCardState.HiddenContent(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
additionalInfo = "3 cards • Seed enabled",
imageResId = R.drawable.ill_businessman_3d,
onClick = {},
)
val walletCardError = WalletCardState.Error(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
additionalInfo = "3 cards • Seed enabled",
imageResId = R.drawable.ill_businessman_3d,
onClick = {},
)
}

View file

@ -0,0 +1,25 @@
package com.tangem.feature.wallet.presentation.ui
import androidx.activity.compose.BackHandler
import androidx.compose.material.Scaffold
import androidx.compose.runtime.Composable
import com.tangem.feature.wallet.presentation.state.WalletStateHolder
import com.tangem.feature.wallet.presentation.ui.components.WalletHeader
/**
* Wallet screen
*
* @param state screen state
*
[REDACTED_AUTHOR]
*/
@Composable
internal fun WalletScreen(state: WalletStateHolder) {
BackHandler(onBack = state.onBackClick)
Scaffold(
topBar = { WalletHeader(config = state.headerConfig) },
) {
// TODO: [REDACTED_TASK_KEY] Design a body with tokens and transactions
}
}

View file

@ -0,0 +1,192 @@
package com.tangem.feature.wallet.presentation.ui.components
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
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.sp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import com.tangem.core.ui.components.FontSizeRange
import com.tangem.core.ui.components.ResizableText
import com.tangem.core.ui.components.ShimmerRectangle
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.state.WalletCardState
import com.tangem.feature.wallet.presentation.ui.WalletPreviewData
private const val DOTS = "•••"
/**
* Wallet card
*
* @param state state
* @param modifier modifier
*/
@Composable
internal fun WalletCard(state: WalletCardState, modifier: Modifier = Modifier) {
Surface(
modifier = modifier.defaultMinSize(minHeight = TangemTheme.dimens.size108),
shape = TangemTheme.shapes.roundedCornersXMedium,
color = TangemTheme.colors.background.primary,
onClick = state.onClick ?: {},
enabled = state.onClick != null,
) {
ConstraintLayout(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens.spacing12),
) {
val (balanceBlock, imageItem) = createRefs()
Column(
modifier = Modifier.constrainAs(balanceBlock) {
centerVerticallyTo(parent)
start.linkTo(parent.start)
end.linkTo(imageItem.start)
width = Dimension.fillToConstraints
},
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
) {
Title(state)
Balance(state)
AdditionalInfo(description = state.additionalInfo)
}
val imageWidth = TangemTheme.dimens.size120
WalletImage(
id = state.imageResId,
modifier = Modifier.constrainAs(imageItem) {
centerVerticallyTo(parent)
top.linkTo(parent.top)
end.linkTo(parent.end)
height = Dimension.fillToConstraints
width = Dimension.value(imageWidth)
},
)
}
}
}
@Composable
private fun Title(state: WalletCardState) {
when (state) {
is WalletCardState.HiddenContent -> {
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) {
Text(
text = state.title,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
maxLines = 1,
)
Icon(
modifier = Modifier.size(size = TangemTheme.dimens.size20),
painter = painterResource(id = R.drawable.ic_eye_off_24),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
)
}
}
else -> {
Text(
text = state.title,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
maxLines = 1,
)
}
}
}
@Composable
private fun Balance(state: WalletCardState) {
when (state) {
is WalletCardState.Content -> {
ResizableText(
text = state.balance,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.h2,
fontSizeRange = FontSizeRange(min = 16.sp, max = TangemTheme.typography.h2.fontSize),
modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size32),
)
}
is WalletCardState.Loading -> {
ShimmerRectangle(
modifier = Modifier.size(
width = TangemTheme.dimens.size102,
height = TangemTheme.dimens.size24,
),
)
}
is WalletCardState.HiddenContent -> {
Text(
text = DOTS,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.h2,
)
}
is WalletCardState.Error -> {
Text(
text = "",
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.h2,
)
}
}
}
@Composable
private fun AdditionalInfo(description: String) {
Text(
text = description,
color = TangemTheme.colors.text.disabled,
style = TangemTheme.typography.caption,
)
}
@Composable
private fun WalletImage(@DrawableRes id: Int, modifier: Modifier = Modifier) {
Image(
painter = painterResource(id),
contentDescription = null,
modifier = modifier,
contentScale = ContentScale.FillWidth,
)
}
// region Preview
@Preview
@Composable
private fun Preview_WalletCard_LightTheme(@PreviewParameter(WalletCardStateProvider::class) state: WalletCardState) {
TangemTheme(isDark = false) {
WalletCard(state)
}
}
@Preview
@Composable
private fun Preview_WalletCard_DarkTheme(@PreviewParameter(WalletCardStateProvider::class) state: WalletCardState) {
TangemTheme(isDark = true) {
WalletCard(state)
}
}
private class WalletCardStateProvider : CollectionPreviewParameterProvider<WalletCardState>(
collection = listOf(
WalletPreviewData.walletCardContent,
WalletPreviewData.walletCardLoading,
WalletPreviewData.walletCardHiddenContent,
WalletPreviewData.walletCardError,
),
)
// endregion Preview

View file

@ -0,0 +1,107 @@
package com.tangem.feature.wallet.presentation.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource
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.res.TangemTheme
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.state.WalletCardState
import com.tangem.feature.wallet.presentation.state.WalletStateHolder
import com.tangem.feature.wallet.presentation.ui.WalletPreviewData
/**
* Wallet screen header
*
* @param config config
*
[REDACTED_AUTHOR]
*/
@Composable
internal fun WalletHeader(config: WalletStateHolder.HeaderConfig) {
Column(
modifier = Modifier
.background(color = TangemTheme.colors.background.secondary)
.padding(bottom = TangemTheme.dimens.spacing14),
) {
TopBar(onScanCardClick = config.onScanCardClick, onMoreClick = config.onMoreClick)
LazyRow(
contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16),
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
) {
items(items = config.wallets, key = WalletCardState::id) { state ->
WalletCard(
state = state,
modifier = Modifier.width(LocalConfiguration.current.screenWidthDp.dp - TangemTheme.dimens.size32),
)
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TopBar(onScanCardClick: () -> Unit, onMoreClick: () -> Unit) {
TopAppBar(
title = {
Icon(painter = painterResource(id = R.drawable.img_tangem_logo_90_24), contentDescription = null)
},
actions = {
IconButton(onClick = onScanCardClick) {
Icon(painter = painterResource(id = R.drawable.ic_tap_card_24), contentDescription = "Scan card")
}
IconButton(onClick = onMoreClick) {
Icon(painter = painterResource(id = R.drawable.ic_more_vertical_24), contentDescription = "More")
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = TangemTheme.colors.background.secondary,
titleContentColor = TangemTheme.colors.icon.primary1,
actionIconContentColor = TangemTheme.colors.icon.primary1,
),
)
}
@Preview
@Composable
private fun Preview_WalletHeader_LightTheme(
@PreviewParameter(WalletHeaderProvider::class) state: WalletStateHolder.HeaderConfig,
) {
TangemTheme(isDark = false) {
WalletHeader(state)
}
}
@Preview
@Composable
private fun Preview_WalletHeader_DarkTheme(
@PreviewParameter(WalletHeaderProvider::class) state: WalletStateHolder.HeaderConfig,
) {
TangemTheme(isDark = true) {
WalletHeader(state)
}
}
private class WalletHeaderProvider : CollectionPreviewParameterProvider<WalletStateHolder.HeaderConfig>(
collection = listOf(
WalletStateHolder.HeaderConfig(
wallets = listOf(
WalletPreviewData.walletCardContent,
WalletPreviewData.walletCardLoading,
WalletPreviewData.walletCardHiddenContent,
WalletPreviewData.walletCardError,
),
onScanCardClick = {},
onMoreClick = {},
),
),
)

View file

@ -1,13 +0,0 @@
package com.tangem.feature.wallet.presentation.ui.config
import androidx.compose.ui.graphics.painter.Painter
import com.tangem.feature.wallet.presentation.ui.state.BalanceUIState
data class WalletHeaderConfig(
val walletName: String,
val balance: String,
val additionalInfo: String,
val balanceState: BalanceUIState,
val cardImage: Painter,
val onClick: (() -> Unit)?,
)

View file

@ -1,5 +0,0 @@
package com.tangem.feature.wallet.presentation.ui.state
enum class BalanceUIState {
VISIBLE, LOADING, HIDDEN, NONE
}

View file

@ -1,9 +0,0 @@
package com.tangem.feature.wallet.presentation.ui.state
enum class TokenUIState {
LOADED, LOADING
}
enum class TokenOptionsUIState {
VISIBLE, HIDDEN, DRAG, UNREACHABLE
}

View file

@ -0,0 +1,28 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="90dp"
android:height="24dp"
android:viewportWidth="90"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:fillType="evenOdd"
android:pathData="M20,7.5H4V5.5C4,3.843 5.343,2.5 7,2.5H17C18.657,2.5 20,3.843 20,5.5V7.5ZM9,21.5H7C5.343,21.5 4,20.157 4,18.5V11.5H9V21.5ZM15,21.5H17C18.657,21.5 20,20.157 20,18.5V11.5H15V21.5Z" />
<path
android:fillColor="#000000"
android:pathData="M32.063,18.693C31.148,18.693 30.447,18.452 29.96,17.97C29.485,17.488 29.247,16.795 29.247,15.892V10.723H28V9.078H29.247V7H31.404V9.078H33.453V10.723H31.404V15.693C31.404,16.5 31.76,16.904 32.473,16.904C32.889,16.904 33.239,16.843 33.525,16.723V18.458C33.109,18.615 32.622,18.693 32.063,18.693Z" />
<path
android:fillColor="#000000"
android:pathData="M37.642,18.711C36.727,18.711 35.978,18.482 35.396,18.024C34.814,17.554 34.523,16.849 34.523,15.91C34.523,14.873 34.957,14.115 35.824,13.632C36.691,13.151 37.85,12.91 39.299,12.91H40.457V12.422C40.457,11.771 40.327,11.295 40.065,10.994C39.816,10.681 39.382,10.524 38.764,10.524C37.683,10.524 37.089,11.018 36.982,12.006H34.897C34.98,10.958 35.384,10.175 36.109,9.657C36.846,9.139 37.772,8.88 38.889,8.88C41.372,8.88 42.614,10.03 42.614,12.331V18.53H40.493V17.374C39.828,18.265 38.877,18.711 37.642,18.711ZM38.159,17.139C38.836,17.139 39.388,16.97 39.816,16.632C40.244,16.283 40.457,15.813 40.457,15.223V14.283H39.353C37.558,14.283 36.661,14.795 36.661,15.819C36.661,16.699 37.16,17.139 38.159,17.139Z" />
<path
android:fillColor="#000000"
android:pathData="M44.729,18.53V9.078H46.885V10.578C47.123,10.084 47.503,9.681 48.026,9.367C48.549,9.042 49.172,8.88 49.897,8.88C50.883,8.88 51.661,9.187 52.232,9.801C52.802,10.404 53.087,11.331 53.087,12.584V18.53H50.931V12.783C50.931,12.072 50.782,11.548 50.485,11.211C50.188,10.873 49.719,10.705 49.077,10.705C48.436,10.705 47.907,10.904 47.491,11.301C47.087,11.687 46.885,12.241 46.885,12.964V18.53H44.729Z" />
<path
android:fillColor="#000000"
android:pathData="M59.189,22C57.799,22 56.736,21.717 55.999,21.151C55.263,20.584 54.829,19.819 54.698,18.855H56.872C57.062,19.843 57.829,20.337 59.171,20.337C60.906,20.337 61.773,19.488 61.773,17.789V16.398C61.5,16.879 61.084,17.295 60.526,17.645C59.979,17.982 59.379,18.151 58.726,18.151C57.538,18.151 56.551,17.735 55.767,16.904C54.983,16.072 54.591,14.988 54.591,13.651V13.524C54.591,12.199 54.989,11.096 55.785,10.217C56.581,9.325 57.579,8.88 58.779,8.88C60.157,8.88 61.155,9.428 61.773,10.524V9.078H63.93V17.825C63.918,19.187 63.49,20.223 62.646,20.934C61.815,21.645 60.662,22 59.189,22ZM59.26,16.47C60.021,16.47 60.639,16.211 61.114,15.693C61.601,15.175 61.844,14.47 61.844,13.578V13.452C61.844,12.56 61.619,11.861 61.167,11.355C60.716,10.849 60.104,10.596 59.332,10.596C58.548,10.596 57.93,10.861 57.478,11.392C57.027,11.922 56.801,12.62 56.801,13.488V13.632C56.801,14.488 57.033,15.175 57.496,15.693C57.959,16.211 58.548,16.47 59.26,16.47Z" />
<path
android:fillColor="#000000"
android:pathData="M70.235,18.711C68.845,18.711 67.71,18.277 66.831,17.41C65.952,16.542 65.512,15.373 65.512,13.904V13.759C65.512,12.313 65.946,11.139 66.813,10.235C67.692,9.331 68.803,8.88 70.146,8.88C71.405,8.88 72.451,9.277 73.282,10.072C74.126,10.868 74.548,12.06 74.548,13.651V14.283H67.722C67.758,15.187 67.995,15.88 68.435,16.361C68.874,16.843 69.492,17.084 70.288,17.084C71.548,17.084 72.261,16.602 72.427,15.639H74.512C74.357,16.626 73.906,17.385 73.158,17.916C72.409,18.446 71.435,18.711 70.235,18.711ZM67.758,12.819H72.409C72.302,11.253 71.548,10.47 70.146,10.47C69.504,10.47 68.97,10.681 68.542,11.102C68.126,11.512 67.865,12.084 67.758,12.819Z" />
<path
android:fillColor="#000000"
android:pathData="M76.117,18.53V9.078H78.274V10.524C78.511,10.054 78.88,9.663 79.379,9.349C79.878,9.036 80.454,8.88 81.107,8.88C82.414,8.88 83.293,9.446 83.745,10.578C84.089,10.024 84.547,9.602 85.117,9.313C85.687,9.024 86.281,8.88 86.899,8.88C87.814,8.88 88.557,9.187 89.127,9.801C89.709,10.404 90,11.319 90,12.548V18.53H87.844V12.693C87.844,11.368 87.291,10.705 86.186,10.705C85.616,10.705 85.129,10.898 84.725,11.283C84.333,11.657 84.137,12.187 84.137,12.873V18.53H81.98V12.693C81.98,11.368 81.428,10.705 80.323,10.705C79.753,10.705 79.266,10.898 78.862,11.283C78.47,11.657 78.274,12.187 78.274,12.873V18.53H76.117Z" />
</vector>

View file

@ -25,6 +25,7 @@ compose-compiler = "1.4.3"
compose-runtime = "1.3.3"
compose-foundation = "1.3.1"
compose-material = "1.4.2"
compose-material3 = "1.1.0"
compose-constraint = "1.0.1"
compose-navigation = "2.5.3"
compose-accompanist = "0.28.0"
@ -132,6 +133,7 @@ compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref =
compose-animation = { module = "androidx.compose.animation:animation", version.ref = "compose-runtime" }
compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose-foundation" }
compose-material = { module = "androidx.compose.material:material", version.ref = "compose-material" }
compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "compose-material3" }
compose-constraintLayout = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "compose-constraint" }
compose-shimmer = { module = "com.valentinilk.shimmer:compose-shimmer", version.ref = "compose-shimmer" }
compose-coil = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
@ -211,5 +213,5 @@ mviCore-watcher = { module = "com.github.badoo.mvicore:mvicore-diff", version.re
kotlin-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinSerialization" }
arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" }
arrow-fx = { module = "io.arrow-kt:arrow-fx-coroutines", version.ref = "arrow" }
reactive-network = { module = "com.github.pwittchen:reactivenetwork-rx2", version.ref = "reactiveNetwork" }
reactive-network = { module = "com.github.pwittchen:reactivenetwork-rx2", version.ref = "reactiveNetwork" }
# endregion Other