From 98b8c6b58e921812332ff7e48570ce82c60f9289 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 9 Oct 2025 16:00:52 +0700 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/di/domain/MarketsDomainModule.kt | 10 + .../markets/GetTokenMarketCryptoCurrency.kt | 21 ++ features/markets/impl/build.gradle.kts | 2 + .../portfolio/add/api/AvailableToAddData.kt | 44 +++ .../portfolio/add/impl/AddTokenComponent.kt | 56 ++++ .../converter/AvailableToAddDataConverter.kt | 71 +++++ .../portfolio/add/impl/model/AddTokenModel.kt | 86 ++++++ .../add/impl/model/AddTokenUiBuilder.kt | 98 ++++++ .../portfolio/add/impl/ui/AddTokenContent.kt | 281 ++++++++++++++++++ .../portfolio/add/impl/ui/state/AddTokenUM.kt | 37 +++ 10 files changed, 706 insertions(+) create mode 100644 domain/markets/src/main/java/com/tangem/domain/markets/GetTokenMarketCryptoCurrency.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/api/AvailableToAddData.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/AddTokenComponent.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/converter/AvailableToAddDataConverter.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenModel.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenUiBuilder.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/ui/AddTokenContent.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/ui/state/AddTokenUM.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt index 4a0fe276d8..60c8926df6 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt @@ -79,6 +79,16 @@ object MarketsDomainModule { ) } + @Provides + @Singleton + fun provideGetTokenMarketCryptoCurrency( + marketsTokenRepository: MarketsTokenRepository, + ): GetTokenMarketCryptoCurrency { + return GetTokenMarketCryptoCurrency( + marketsTokenRepository = marketsTokenRepository, + ) + } + @Provides @Singleton fun provideFilterNetworksUseCase( diff --git a/domain/markets/src/main/java/com/tangem/domain/markets/GetTokenMarketCryptoCurrency.kt b/domain/markets/src/main/java/com/tangem/domain/markets/GetTokenMarketCryptoCurrency.kt new file mode 100644 index 0000000000..1cc4d6c0b8 --- /dev/null +++ b/domain/markets/src/main/java/com/tangem/domain/markets/GetTokenMarketCryptoCurrency.kt @@ -0,0 +1,21 @@ +package com.tangem.domain.markets + +import com.tangem.domain.markets.repositories.MarketsTokenRepository +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWalletId + +class GetTokenMarketCryptoCurrency( + private val marketsTokenRepository: MarketsTokenRepository, +) { + suspend operator fun invoke( + userWalletId: UserWalletId, + tokenMarketParams: TokenMarketParams, + network: TokenMarketInfo.Network, + ): CryptoCurrency? { + return marketsTokenRepository.createCryptoCurrency( + userWalletId = userWalletId, + token = tokenMarketParams, + network = network, + ) + } +} \ No newline at end of file diff --git a/features/markets/impl/build.gradle.kts b/features/markets/impl/build.gradle.kts index 63c479bfc9..7bcf648384 100644 --- a/features/markets/impl/build.gradle.kts +++ b/features/markets/impl/build.gradle.kts @@ -18,11 +18,13 @@ dependencies { api(projects.features.sendV2.api) api(projects.features.tokenRecieve.api) api(projects.features.wallet.api) + api(projects.features.account.api) /* Data */ implementation(projects.data.common) /* Domain */ + implementation(projects.domain.account) implementation(projects.domain.appCurrency) implementation(projects.domain.appCurrency.models) implementation(projects.domain.balanceHiding) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/api/AvailableToAddData.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/api/AvailableToAddData.kt new file mode 100644 index 0000000000..7f9e509ab3 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/api/AvailableToAddData.kt @@ -0,0 +1,44 @@ +package com.tangem.features.markets.portfolio.add.api + +import com.tangem.domain.markets.TokenMarketInfo +import com.tangem.domain.models.account.AccountId +import com.tangem.domain.models.account.AccountStatus +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId + +data class AvailableToAddData( + val availableToAddWallets: Map, +) { + val isSinglePortfolio: Boolean + get() = availableToAddWallets.size == 1 && availableToAddWallets.values.first().accounts.size == 1 +} + +data class AvailableToAddWallet( + val userWallet: UserWallet, + val accounts: Set, + val availableNetworks: Set, + val availableToAddAccounts: Map, +) { + val isSingleAvailableNetworks: Boolean get() = availableNetworks.size == 1 + val singleAvailableNetworks: TokenMarketInfo.Network get() = availableNetworks.first() +} + +data class AvailableToAddAccount( + val account: AccountStatus, + val availableNetworks: Set, + val availableToAddNetworks: Set, +) + +data class SelectedPortfolio( + val userWallet: UserWallet, + val account: AccountStatus, + val isAccountMode: Boolean, + val availableMorePortfolio: Boolean, +) + +data class SelectedNetwork( + val selectedNetwork: TokenMarketInfo.Network, + val cryptoCurrency: CryptoCurrency, + val availableMoreNetwork: Boolean, +) \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/AddTokenComponent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/AddTokenComponent.kt new file mode 100644 index 0000000000..f4d441890e --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/AddTokenComponent.kt @@ -0,0 +1,56 @@ +package com.tangem.features.markets.portfolio.add.impl + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.markets.TokenMarketParams +import com.tangem.features.markets.portfolio.add.api.SelectedNetwork +import com.tangem.features.markets.portfolio.add.api.SelectedPortfolio +import com.tangem.features.markets.portfolio.add.impl.model.AddTokenModel +import com.tangem.features.markets.portfolio.add.impl.ui.AddTokenContent +import com.tangem.features.markets.portfolio.impl.analytics.PortfolioAnalyticsEvent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.Flow + +internal class AddTokenComponent @AssistedInject constructor( + @Assisted context: AppComponentContext, + @Assisted private val params: Params, +) : AppComponentContext by context, ComposableContentComponent { + + private val model: AddTokenModel = getOrCreateModel(params) + + @Composable + override fun Content(modifier: Modifier) { + val state = model.uiState.collectAsStateWithLifecycle() + val um = state.value ?: return + AddTokenContent( + modifier = modifier, + state = um, + ) + } + + data class Params( + val marketParams: TokenMarketParams, + val eventBuilder: PortfolioAnalyticsEvent.EventBuilder, + val selectedPortfolio: Flow, + val selectedNetwork: Flow, + val callbacks: Callbacks, + ) + + interface Callbacks { + fun onChangeNetworkClick() + fun onChangePortfolioClick() + fun onTokenAdded() + } + + @AssistedFactory + interface Factory : ComponentFactory { + override fun create(context: AppComponentContext, params: Params): AddTokenComponent + } +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/converter/AvailableToAddDataConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/converter/AvailableToAddDataConverter.kt new file mode 100644 index 0000000000..7167f6bc11 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/converter/AvailableToAddDataConverter.kt @@ -0,0 +1,71 @@ +package com.tangem.features.markets.portfolio.add.impl.converter + +import com.tangem.domain.markets.FilterAvailableNetworksForWalletUseCase +import com.tangem.domain.markets.TokenMarketInfo +import com.tangem.domain.models.account.AccountId +import com.tangem.domain.models.account.AccountStatus +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.features.account.PortfolioFetcher +import com.tangem.features.markets.portfolio.add.api.AvailableToAddAccount +import com.tangem.features.markets.portfolio.add.api.AvailableToAddData +import com.tangem.features.markets.portfolio.add.api.AvailableToAddWallet +import javax.inject.Inject + +class AvailableToAddDataConverter @Inject constructor( + private val filterAvailableNetworksForWalletUseCase: FilterAvailableNetworksForWalletUseCase, +) { + + suspend fun convert( + balances: Map, + availableNetworks: Set, + ): AvailableToAddData { + fun AccountStatus.getAvailableToAddAccount(): AvailableToAddAccount { + val availableToAddNetworks = availableNetworks + .filterTo(mutableSetOf()) { network -> !this.isAlreadyAddedNetworks(network) } + return AvailableToAddAccount( + account = this, + availableNetworks = availableNetworks, + availableToAddNetworks = availableToAddNetworks, + ) + } + + fun UserWallet.getAvailableToAddWallet(balance: PortfolioFetcher.PortfolioBalance): AvailableToAddWallet { + val filteredNetworks = this.filteredAvailableNetworks(availableNetworks) + val accounts = balance.accountsBalance.accountStatuses + val availableToAddAccounts: Map = accounts + .map { it.account.accountId to it.getAvailableToAddAccount() } + .filter { (_, account) -> account.availableToAddNetworks.isNotEmpty() } + .toMap() + return AvailableToAddWallet( + userWallet = this, + accounts = accounts, + availableNetworks = filteredNetworks, + availableToAddAccounts = availableToAddAccounts, + ) + } + + val availableToAddWallets: Map = balances + .map { (wallet, balance) -> + val availableToAddWallet = wallet.getAvailableToAddWallet(balance) + wallet.walletId to availableToAddWallet + } + .filter { (_, wallet) -> wallet.availableToAddAccounts.isNotEmpty() } + .toMap() + + return AvailableToAddData( + availableToAddWallets = availableToAddWallets, + ) + } + + private fun UserWallet.filteredAvailableNetworks(networks: Set) = + filterAvailableNetworksForWalletUseCase( + userWalletId = this.walletId, + networks = networks, + ) + + private fun AccountStatus.isAlreadyAddedNetworks(network: TokenMarketInfo.Network): Boolean = when (this) { + is AccountStatus.CryptoPortfolio -> this.tokenList.flattenCurrencies() + .any { status -> network.networkId == status.currency.network.backendId } + } +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenModel.kt new file mode 100644 index 0000000000..b74ecede69 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenModel.kt @@ -0,0 +1,86 @@ +package com.tangem.features.markets.portfolio.add.impl.model + +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.domain.markets.SaveMarketTokensUseCase +import com.tangem.domain.wallets.usecase.ColdWalletAndHasMissedDerivationsUseCase +import com.tangem.features.markets.portfolio.add.api.SelectedNetwork +import com.tangem.features.markets.portfolio.add.api.SelectedPortfolio +import com.tangem.features.markets.portfolio.add.impl.AddTokenComponent +import com.tangem.features.markets.portfolio.add.impl.model.AddTokenUiBuilder.Companion.toggleProgress +import com.tangem.features.markets.portfolio.add.impl.ui.state.AddTokenUM +import com.tangem.lib.crypto.BlockchainUtils +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.JobHolder +import com.tangem.utils.coroutines.saveIn +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch +import javax.inject.Inject + +@ModelScoped +internal class AddTokenModel @Inject constructor( + paramsContainer: ParamsContainer, + private val saveMarketTokensUseCase: SaveMarketTokensUseCase, + private val uiBuilder: AddTokenUiBuilder, + private val coldWalletAndHasMissedDerivationsUseCase: ColdWalletAndHasMissedDerivationsUseCase, + override val dispatchers: CoroutineDispatcherProvider, + private val analyticsEventHandler: AnalyticsEventHandler, +) : Model() { + + private val params = paramsContainer.require() + private val analyticsEventBuilder = params.eventBuilder + private val addTokenJob = JobHolder() + + val uiState: StateFlow + field = MutableStateFlow(value = null) + + init { + channelFlow { + val selectedNetwork = params.selectedNetwork.stateIn(this) + val selectedPortfolio = params.selectedPortfolio.stateIn(this) + combine( + flow = selectedNetwork, + flow2 = selectedPortfolio, + ) { selectedNetwork, selectedPortfolio -> + addTokenJob.cancel() + val isTangemIconVisible = needColdWalletInteraction(selectedNetwork, selectedPortfolio) + uiBuilder.updateContent( + selectedPortfolio = selectedPortfolio, + selectedNetwork = selectedNetwork, + isTangemIconVisible = isTangemIconVisible, + onConfirmClick = { onAddClick(selectedNetwork, selectedPortfolio).saveIn(addTokenJob) }, + ) + }.collect { newUI -> send(newUI) } + } + .flowOn(dispatchers.default) + .onEach { newUI -> uiState.value = newUI } + .launchIn(modelScope) + } + + private fun onAddClick(selectedNetwork: SelectedNetwork, selectedPortfolio: SelectedPortfolio) = modelScope.launch { + val um = uiState.value ?: return@launch + uiState.value = um.toggleProgress(true) + val blockchainNames = listOf(selectedNetwork.selectedNetwork) + .mapNotNull { BlockchainUtils.getNetworkInfo(it.networkId)?.name } + analyticsEventHandler.send(analyticsEventBuilder.addToPortfolioContinue(blockchainNames)) + // todo account + saveMarketTokensUseCase( + userWalletId = selectedPortfolio.userWallet.walletId, + tokenMarketParams = params.marketParams, + addedNetworks = setOf(selectedNetwork.selectedNetwork), + removedNetworks = setOf(), + ) + params.callbacks.onTokenAdded() + uiState.value = um.toggleProgress(false) + } + + private suspend fun needColdWalletInteraction( + selectedNetwork: SelectedNetwork, + selectedPortfolio: SelectedPortfolio, + ): Boolean = coldWalletAndHasMissedDerivationsUseCase.invoke( + userWalletId = selectedPortfolio.userWallet.walletId, + networksWithDerivationPath = mapOf(selectedNetwork.selectedNetwork.networkId to null), + ) +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenUiBuilder.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenUiBuilder.kt new file mode 100644 index 0000000000..4d4dd3366a --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenUiBuilder.kt @@ -0,0 +1,98 @@ +package com.tangem.features.markets.portfolio.add.impl.model + +import com.tangem.common.ui.account.CryptoPortfolioIconUM +import com.tangem.common.ui.account.toUM +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter +import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.iconResId +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.models.account.AccountStatus +import com.tangem.features.markets.impl.R +import com.tangem.features.markets.portfolio.add.api.SelectedNetwork +import com.tangem.features.markets.portfolio.add.api.SelectedPortfolio +import com.tangem.features.markets.portfolio.add.impl.AddTokenComponent +import com.tangem.features.markets.portfolio.add.impl.ui.state.AddTokenUM + +@ModelScoped +internal class AddTokenUiBuilder( + paramsContainer: ParamsContainer, +) { + private val params = paramsContainer.require() + + private fun createNetwork(selectedNetwork: SelectedNetwork): AddTokenUM.Network { + return AddTokenUM.Network( + icon = selectedNetwork.cryptoCurrency.network.iconResId, + name = stringReference(selectedNetwork.cryptoCurrency.network.name), + editable = selectedNetwork.availableMoreNetwork, + onClick = { params.callbacks.onChangeNetworkClick() }, + ) + } + + private fun createPortfolio(selectedPortfolio: SelectedPortfolio): AddTokenUM.Portfolio { + val accountIcon: CryptoPortfolioIconUM? + val portfolioName: TextReference + when (selectedPortfolio.isAccountMode) { + false -> { + accountIcon = null + portfolioName = stringReference(selectedPortfolio.userWallet.name) + } + true -> { + portfolioName = selectedPortfolio.account.account.accountName.toUM().value + accountIcon = when (selectedPortfolio.account) { + is AccountStatus.CryptoPortfolio -> selectedPortfolio.account.account.icon.toUM() + } + } + } + return AddTokenUM.Portfolio( + accountIconUM = accountIcon, + name = portfolioName, + editable = selectedPortfolio.availableMorePortfolio, + onClick = { params.callbacks.onChangePortfolioClick() }, + ) + } + + fun updateContent( + selectedPortfolio: SelectedPortfolio, + selectedNetwork: SelectedNetwork, + isTangemIconVisible: Boolean, + onConfirmClick: () -> Unit, + ): AddTokenUM { + val button = AddTokenUM.Button( + isEnabled = true, + showProgress = false, + isTangemIconVisible = isTangemIconVisible, + text = resourceReference(R.string.common_add), + onConfirmClick = onConfirmClick, + ) + val networkUM = createNetwork(selectedNetwork) + val portfolioUM = createPortfolio(selectedPortfolio) + val currency = selectedNetwork.cryptoCurrency + val tokenToAdd = TokenItemState.Content( + id = currency.id.value, + iconState = CryptoCurrencyToIconStateConverter().convert(currency), + titleState = TokenItemState.TitleState.Content(stringReference(currency.name)), + fiatAmountState = TokenItemState.FiatAmountState.Content(text = ""), + subtitle2State = TokenItemState.Subtitle2State.TextContent(text = ""), + subtitleState = TokenItemState.SubtitleState.TextContent(stringReference(currency.symbol)), + onItemClick = null, + onItemLongClick = null, + ) + return AddTokenUM( + tokenToAdd = tokenToAdd, + network = networkUM, + portfolio = portfolioUM, + button = button, + ) + } + + companion object { + + fun AddTokenUM.toggleProgress(showProgress: Boolean) = this.copy( + button = this.button.copy(showProgress = showProgress), + ) + } +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/ui/AddTokenContent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/ui/AddTokenContent.kt new file mode 100644 index 0000000000..02464553bd --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/ui/AddTokenContent.kt @@ -0,0 +1,281 @@ +package com.tangem.features.markets.portfolio.add.impl.ui + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.style.TextOverflow +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.tangem.common.ui.account.AccountIcon +import com.tangem.common.ui.account.AccountIconPreviewData +import com.tangem.common.ui.account.toUM +import com.tangem.core.ui.components.PrimaryButtonIconEnd +import com.tangem.core.ui.components.SpacerH +import com.tangem.core.ui.components.SpacerH16 +import com.tangem.core.ui.components.SpacerW12 +import com.tangem.core.ui.components.account.AccountIconSize +import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition +import com.tangem.core.ui.components.currency.icon.CurrencyIconState +import com.tangem.core.ui.components.token.TokenItem +import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemColorPalette +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.test.WalletConnectBottomSheetTestTags +import com.tangem.domain.models.account.AccountName +import com.tangem.features.markets.impl.R +import com.tangem.features.markets.portfolio.add.impl.ui.state.AddTokenUM +import java.util.UUID + +@Composable +internal fun AddTokenContent(state: AddTokenUM, modifier: Modifier = Modifier) { + Column( + modifier = modifier.fillMaxWidth(), + ) { + TokenItem( + modifier = Modifier + .clip(RoundedCornerShape(TangemTheme.dimens.radius14)) + .background(color = TangemTheme.colors.background.action), + state = state.tokenToAdd, + isBalanceHidden = false, + ) + + SpacerH(TangemTheme.dimens.spacing14) + Column( + modifier = Modifier.background( + color = TangemTheme.colors.background.action, + shape = RoundedCornerShape(TangemTheme.dimens.radius14), + ), + ) { + PortfolioRow(state.portfolio) + HorizontalDivider( + modifier = Modifier.padding(horizontal = 12.dp), + thickness = TangemTheme.dimens.size0_5, + color = TangemTheme.colors.stroke.primary, + ) + NetworkRow(state.network) + } + + SpacerH16() + + AddButton( + modifier = modifier.fillMaxWidth(), + state = state.button, + ) + } +} + +@Composable +private fun PortfolioRow(state: AddTokenUM.Portfolio, modifier: Modifier = Modifier) { + Row( + modifier = modifier + .clickable(enabled = state.editable, onClick = state.onClick) + .padding(12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + val leftText = if (state.isAccountMode) R.string.account_details_title else R.string.wc_common_wallet + Text( + modifier = Modifier.weight(1f), + text = stringResourceSafe(leftText), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.primary1, + ) + SpacerW12() + if (state.accountIconUM != null) { + AccountIcon( + name = state.name, + icon = state.accountIconUM, + size = AccountIconSize.Small, + ) + } + Text( + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(horizontal = 4.dp), + text = state.name.resolveReference(), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.primary1, + ) + if (state.editable) { + Icon( + modifier = Modifier + .size(width = 18.dp, height = 24.dp) + .testTag(WalletConnectBottomSheetTestTags.NETWORKS_SELECTOR_ICON), + painter = painterResource(id = R.drawable.ic_select_18_24), + contentDescription = null, + tint = TangemTheme.colors.icon.informative, + ) + } + } +} + +@Composable +private fun NetworkRow(state: AddTokenUM.Network, modifier: Modifier = Modifier) { + Row( + modifier = modifier + .clickable(enabled = state.editable, onClick = state.onClick) + .padding(12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + modifier = Modifier.weight(1f), + text = stringResourceSafe(R.string.wc_common_networks), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.primary1, + ) + SpacerW12() + Icon( + modifier = Modifier.size(24.dp), + tint = Color.Unspecified, + imageVector = ImageVector.vectorResource(id = state.icon), + contentDescription = null, + ) + Text( + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(horizontal = 4.dp), + text = state.name.resolveReference(), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.primary1, + ) + if (state.editable) { + Icon( + modifier = Modifier + .size(width = 18.dp, height = 24.dp) + .testTag(WalletConnectBottomSheetTestTags.NETWORKS_SELECTOR_ICON), + painter = painterResource(id = R.drawable.ic_select_18_24), + contentDescription = null, + tint = TangemTheme.colors.icon.informative, + ) + } + } +} + +@Composable +private fun AddButton(state: AddTokenUM.Button, modifier: Modifier = Modifier) { + val endIcon = if (state.isEnabled && state.isTangemIconVisible) { + TangemButtonIconPosition.End(R.drawable.ic_tangem_24) + } else { + TangemButtonIconPosition.None + } + PrimaryButtonIconEnd( + modifier = modifier, + text = state.text.resolveReference(), + iconResId = endIcon.iconResId, + onClick = state.onConfirmClick, + enabled = state.isEnabled, + showProgress = state.showProgress, + ) +} + +@Preview(widthDp = 360, showBackground = true) +@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview(@PreviewParameter(PreviewProvider::class) state: AddTokenUM) { + TangemThemePreview { + AddTokenContent( + state = state, + ) + } +} + +private class PreviewProvider : PreviewParameterProvider { + private val tokenState + get() = TokenItemState.Content( + id = UUID.randomUUID().toString(), + iconState = CurrencyIconState.TokenIcon( + url = null, + topBadgeIconResId = R.drawable.img_eth_22, + fallbackTint = TangemColorPalette.Black, + fallbackBackground = TangemColorPalette.Meadow, + isGrayscale = false, + shouldShowCustomBadge = false, + ), + titleState = TokenItemState.TitleState.Content( + text = stringReference(value = "Tether"), + ), + fiatAmountState = FiatAmountState.Content(text = ""), + subtitle2State = TokenItemState.Subtitle2State.TextContent(text = ""), + subtitleState = TokenItemState.SubtitleState.TextContent(value = stringReference("USDT")), + onItemClick = {}, + onItemLongClick = {}, + ) + + val networkUM + get() = AddTokenUM.Network( + icon = R.drawable.img_eth_22, + name = stringReference("Ethereum"), + editable = true, + onClick = {}, + ) + + val button + get() = AddTokenUM.Button( + isEnabled = false, + showProgress = false, + isTangemIconVisible = false, + text = resourceReference(R.string.common_add), + onConfirmClick = { }, + ) + + val account + get() = AddTokenUM.Portfolio( + accountIconUM = AccountIconPreviewData.randomAccountIcon(), + name = AccountName.DefaultMain.toUM().value, + editable = true, + onClick = {}, + ) + val wallet + get() = AddTokenUM.Portfolio( + accountIconUM = null, + name = stringReference("Wallet"), + editable = true, + onClick = {}, + ) + + override val values: Sequence + get() = sequenceOf( + AddTokenUM( + tokenToAdd = tokenState, + network = networkUM, + portfolio = account, + button = button, + ), + AddTokenUM( + tokenToAdd = tokenState, + network = networkUM, + portfolio = wallet, + button = button, + ), + AddTokenUM( + tokenToAdd = tokenState, + network = networkUM.copy(editable = false), + portfolio = wallet.copy(editable = false), + button = button.copy( + isEnabled = true, + isTangemIconVisible = true, + ), + ), + ) +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/ui/state/AddTokenUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/ui/state/AddTokenUM.kt new file mode 100644 index 0000000000..7ab163ffb2 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/ui/state/AddTokenUM.kt @@ -0,0 +1,37 @@ +package com.tangem.features.markets.portfolio.add.impl.ui.state + +import androidx.annotation.DrawableRes +import com.tangem.common.ui.account.CryptoPortfolioIconUM +import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.extensions.TextReference + +data class AddTokenUM( + val tokenToAdd: TokenItemState, + val network: Network, + val portfolio: Portfolio, + val button: Button, +) { + data class Portfolio( + val accountIconUM: CryptoPortfolioIconUM?, + val name: TextReference, + val editable: Boolean, + val onClick: () -> Unit, + ) { + val isAccountMode get() = accountIconUM != null + } + + data class Network( + @DrawableRes val icon: Int, + val name: TextReference, + val editable: Boolean, + val onClick: () -> Unit, + ) + + data class Button( + val isEnabled: Boolean, + val showProgress: Boolean, + val isTangemIconVisible: Boolean, + val onConfirmClick: () -> Unit, + val text: TextReference, + ) +} \ No newline at end of file