Updated on 2026-08-14
This commit is contained in:
parent
14726d2fb4
commit
5b031d5c0e
15 changed files with 448 additions and 70 deletions
|
|
@ -20,6 +20,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.tangem.common.ui.R
|
import com.tangem.common.ui.R
|
||||||
|
import com.tangem.common.ui.userwallet.CardImage
|
||||||
|
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||||
import com.tangem.core.ui.components.SpacerW12
|
import com.tangem.core.ui.components.SpacerW12
|
||||||
import com.tangem.core.ui.components.account.AccountIconSize
|
import com.tangem.core.ui.components.account.AccountIconSize
|
||||||
import com.tangem.core.ui.ds.row.TangemRowContainer
|
import com.tangem.core.ui.ds.row.TangemRowContainer
|
||||||
|
|
@ -111,6 +113,18 @@ fun PortfolioSelectRowV2(
|
||||||
size = AccountIconSize.RedesignedDefault,
|
size = AccountIconSize.RedesignedDefault,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else if (state.imageState != null) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.layoutId(TangemRowLayoutId.HEAD)
|
||||||
|
.padding(end = TangemTheme.dimens2.x3),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
CardImage(
|
||||||
|
modifier = Modifier.size(TangemTheme.dimens2.x10),
|
||||||
|
imageState = state.imageState,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
|
|
@ -156,6 +170,7 @@ data class PortfolioSelectUM(
|
||||||
val isAccountMode: Boolean,
|
val isAccountMode: Boolean,
|
||||||
val isMultiChoice: Boolean,
|
val isMultiChoice: Boolean,
|
||||||
val onClick: () -> Unit,
|
val onClick: () -> Unit,
|
||||||
|
val imageState: UserWalletItemUM.ImageState? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Preview(widthDp = 360, showBackground = true)
|
@Preview(widthDp = 360, showBackground = true)
|
||||||
|
|
|
||||||
|
|
@ -727,6 +727,7 @@
|
||||||
<string name="generic_error">An error occurred</string>
|
<string name="generic_error">An error occurred</string>
|
||||||
<string name="generic_error_code">An error occurred. Code: %s.</string>
|
<string name="generic_error_code">An error occurred. Code: %s.</string>
|
||||||
<string name="generic_requires_memo_error">Requires memo</string>
|
<string name="generic_requires_memo_error">Requires memo</string>
|
||||||
|
<string name="get_token_title">Get %1$s</string>
|
||||||
<string name="give_permission_current_transaction">Transaction</string>
|
<string name="give_permission_current_transaction">Transaction</string>
|
||||||
<string name="give_permission_policy_type_footer">By approving, you allow the smart contract to use your tokens in future transactions.</string>
|
<string name="give_permission_policy_type_footer">By approving, you allow the smart contract to use your tokens in future transactions.</string>
|
||||||
<string name="give_permission_rows_amount">Amount %s</string>
|
<string name="give_permission_rows_amount">Amount %s</string>
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTi
|
||||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.core.ui.extensions.resourceReference
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
import com.tangem.core.ui.extensions.wrappedList
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioSelectorComponent
|
import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioSelectorComponent
|
||||||
import com.tangem.features.commonfeatures.impl.R
|
import com.tangem.features.commonfeatures.impl.R
|
||||||
|
|
@ -69,7 +70,7 @@ internal fun AddToPortfolioBottomSheet(
|
||||||
AddToPortfolioRoutes.AddToken,
|
AddToPortfolioRoutes.AddToken,
|
||||||
AddToPortfolioRoutes.Empty,
|
AddToPortfolioRoutes.Empty,
|
||||||
is AddToPortfolioRoutes.NetworkSelector,
|
is AddToPortfolioRoutes.NetworkSelector,
|
||||||
AddToPortfolioRoutes.TokenActions,
|
is AddToPortfolioRoutes.TokenActions,
|
||||||
-> true
|
-> true
|
||||||
}
|
}
|
||||||
if (isScrollableContent) {
|
if (isScrollableContent) {
|
||||||
|
|
@ -92,11 +93,12 @@ private fun AddToPortfolioBottomSheetTitle(
|
||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val title: TextReference = when (stack.active.configuration) {
|
val title: TextReference = when (val config = stack.active.configuration) {
|
||||||
AddToPortfolioRoutes.AddToken -> resourceReference(R.string.common_add_token)
|
AddToPortfolioRoutes.AddToken -> resourceReference(R.string.common_add_token)
|
||||||
AddToPortfolioRoutes.Empty -> TextReference.EMPTY
|
AddToPortfolioRoutes.Empty -> TextReference.EMPTY
|
||||||
is AddToPortfolioRoutes.NetworkSelector -> resourceReference(R.string.common_choose_network)
|
is AddToPortfolioRoutes.NetworkSelector -> resourceReference(R.string.common_choose_network)
|
||||||
AddToPortfolioRoutes.TokenActions -> resourceReference(R.string.common_get_token)
|
is AddToPortfolioRoutes.TokenActions ->
|
||||||
|
resourceReference(R.string.get_token_title, wrappedList(config.currencyName))
|
||||||
AddToPortfolioRoutes.UserPortfolio -> resourceReference(R.string.markets_portfolio_block_title)
|
AddToPortfolioRoutes.UserPortfolio -> resourceReference(R.string.markets_portfolio_block_title)
|
||||||
AddToPortfolioRoutes.PortfolioSelector -> (stack.active.instance as PortfolioSelectorComponent)
|
AddToPortfolioRoutes.PortfolioSelector -> (stack.active.instance as PortfolioSelectorComponent)
|
||||||
.title.collectAsStateWithLifecycle().value
|
.title.collectAsStateWithLifecycle().value
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ internal class DefaultAddToPortfolioComponent @AssistedInject constructor(
|
||||||
): ComposableContentComponent = when (config) {
|
): ComposableContentComponent = when (config) {
|
||||||
AddToPortfolioRoutes.AddToken -> addTokenComponent
|
AddToPortfolioRoutes.AddToken -> addTokenComponent
|
||||||
AddToPortfolioRoutes.PortfolioSelector -> portfolioSelectorComponent
|
AddToPortfolioRoutes.PortfolioSelector -> portfolioSelectorComponent
|
||||||
AddToPortfolioRoutes.TokenActions -> tokenActionsComponent
|
is AddToPortfolioRoutes.TokenActions -> tokenActionsComponent
|
||||||
AddToPortfolioRoutes.Empty -> ComposableContentComponent.EMPTY
|
AddToPortfolioRoutes.Empty -> ComposableContentComponent.EMPTY
|
||||||
AddToPortfolioRoutes.UserPortfolio -> createUserPortfolioComponent(componentContext)
|
AddToPortfolioRoutes.UserPortfolio -> createUserPortfolioComponent(componentContext)
|
||||||
is AddToPortfolioRoutes.NetworkSelector -> chooseNetworkComponentFactory.create(
|
is AddToPortfolioRoutes.NetworkSelector -> chooseNetworkComponentFactory.create(
|
||||||
|
|
|
||||||
|
|
@ -305,7 +305,9 @@ internal class AddToPortfolioModel @Inject constructor(
|
||||||
setupTokenActionsFlow(selectedPortfolioSnapshot, addedToken)
|
setupTokenActionsFlow(selectedPortfolioSnapshot, addedToken)
|
||||||
.onEach { cryptoCurrencyData ->
|
.onEach { cryptoCurrencyData ->
|
||||||
tokenActionsData.emit(cryptoCurrencyData)
|
tokenActionsData.emit(cryptoCurrencyData)
|
||||||
navigation.replaceAll(AddToPortfolioRoutes.TokenActions)
|
navigation.replaceAll(
|
||||||
|
AddToPortfolioRoutes.TokenActions(cryptoCurrencyData.status.currency.name),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.onEmpty { finishSuccessFlow(result) }
|
.onEmpty { finishSuccessFlow(result) }
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.commonfeatures.impl.addtoportfolio.model
|
||||||
|
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.core.ui.extensions.resourceReference
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
import com.tangem.core.ui.extensions.wrappedList
|
||||||
import com.tangem.features.commonfeatures.impl.R
|
import com.tangem.features.commonfeatures.impl.R
|
||||||
|
|
||||||
internal data class AddToPortfolioRouteUiSpec(
|
internal data class AddToPortfolioRouteUiSpec(
|
||||||
|
|
@ -42,8 +43,8 @@ internal fun AddToPortfolioRoutes.uiSpec(): AddToPortfolioRouteUiSpec = when (th
|
||||||
shouldApplyHorizontalPadding = false,
|
shouldApplyHorizontalPadding = false,
|
||||||
footer = AddToPortfolioFooterKind.UserPortfolioAdd,
|
footer = AddToPortfolioFooterKind.UserPortfolioAdd,
|
||||||
)
|
)
|
||||||
AddToPortfolioRoutes.TokenActions -> AddToPortfolioRouteUiSpec(
|
is AddToPortfolioRoutes.TokenActions -> AddToPortfolioRouteUiSpec(
|
||||||
title = resourceReference(R.string.common_get_token),
|
title = resourceReference(R.string.get_token_title, wrappedList(currencyName)),
|
||||||
isScrollable = false,
|
isScrollable = false,
|
||||||
shouldApplyHorizontalPadding = true,
|
shouldApplyHorizontalPadding = true,
|
||||||
footer = AddToPortfolioFooterKind.None,
|
footer = AddToPortfolioFooterKind.None,
|
||||||
|
|
|
||||||
|
|
@ -27,5 +27,5 @@ internal sealed interface AddToPortfolioRoutes : Route {
|
||||||
data object UserPortfolio : AddToPortfolioRoutes
|
data object UserPortfolio : AddToPortfolioRoutes
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data object TokenActions : AddToPortfolioRoutes
|
data class TokenActions(val currencyName: String) : AddToPortfolioRoutes
|
||||||
}
|
}
|
||||||
|
|
@ -17,18 +17,23 @@ import com.tangem.features.commonfeatures.api.addtoportfolio.SelectedPortfolio
|
||||||
import com.tangem.features.commonfeatures.impl.R
|
import com.tangem.features.commonfeatures.impl.R
|
||||||
import com.tangem.features.commonfeatures.impl.addtoportfolio.AddTokenComponent
|
import com.tangem.features.commonfeatures.impl.addtoportfolio.AddTokenComponent
|
||||||
import com.tangem.features.commonfeatures.impl.addtoportfolio.model.AddTokenUiBuilder.Companion.toggleProgress
|
import com.tangem.features.commonfeatures.impl.addtoportfolio.model.AddTokenUiBuilder.Companion.toggleProgress
|
||||||
|
import com.tangem.features.wallet.utils.UserWalletImageFetcher
|
||||||
import com.tangem.lib.crypto.BlockchainUtils
|
import com.tangem.lib.crypto.BlockchainUtils
|
||||||
|
import com.tangem.operations.attestation.ArtworkSize
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import com.tangem.utils.coroutines.JobHolder
|
import com.tangem.utils.coroutines.JobHolder
|
||||||
import com.tangem.utils.coroutines.saveIn
|
import com.tangem.utils.coroutines.saveIn
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ModelScoped
|
@ModelScoped
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
internal class AddTokenModel @Inject constructor(
|
internal class AddTokenModel @Inject constructor(
|
||||||
paramsContainer: ParamsContainer,
|
paramsContainer: ParamsContainer,
|
||||||
|
private val walletImageFetcher: UserWalletImageFetcher,
|
||||||
private val uiBuilder: AddTokenUiBuilder,
|
private val uiBuilder: AddTokenUiBuilder,
|
||||||
private val messageSender: UiMessageSender,
|
private val messageSender: UiMessageSender,
|
||||||
private val coldWalletAndHasMissedDerivationsUseCase: ColdWalletAndHasMissedDerivationsUseCase,
|
private val coldWalletAndHasMissedDerivationsUseCase: ColdWalletAndHasMissedDerivationsUseCase,
|
||||||
|
|
@ -46,15 +51,22 @@ internal class AddTokenModel @Inject constructor(
|
||||||
field = MutableStateFlow(value = null)
|
field = MutableStateFlow(value = null)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
val walletImageFlow = params.selectedPortfolio
|
||||||
|
.map { it.userWallet }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.flatMapLatest { walletImageFetcher.walletImage(it, ArtworkSize.SMALL) }
|
||||||
|
|
||||||
combine(
|
combine(
|
||||||
flow = params.selectedNetwork.distinctUntilChanged(),
|
flow = params.selectedNetwork.distinctUntilChanged(),
|
||||||
flow2 = params.selectedPortfolio.distinctUntilChanged(),
|
flow2 = params.selectedPortfolio.distinctUntilChanged(),
|
||||||
transform = { selectedNetwork, selectedPortfolio ->
|
flow3 = walletImageFlow,
|
||||||
|
transform = { selectedNetwork, selectedPortfolio, walletImage ->
|
||||||
addTokenJob.join()
|
addTokenJob.join()
|
||||||
val isTangemIconVisible = needColdWalletInteraction(selectedNetwork, selectedPortfolio)
|
val isTangemIconVisible = needColdWalletInteraction(selectedNetwork, selectedPortfolio)
|
||||||
uiBuilder.updateContent(
|
uiBuilder.updateContent(
|
||||||
selectedPortfolio = selectedPortfolio,
|
selectedPortfolio = selectedPortfolio,
|
||||||
selectedNetwork = selectedNetwork,
|
selectedNetwork = selectedNetwork,
|
||||||
|
walletImage = walletImage,
|
||||||
isTangemIconVisible = isTangemIconVisible,
|
isTangemIconVisible = isTangemIconVisible,
|
||||||
onConfirmClick = { onAddClick(selectedNetwork, selectedPortfolio).saveIn(addTokenJob) },
|
onConfirmClick = { onAddClick(selectedNetwork, selectedPortfolio).saveIn(addTokenJob) },
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import com.tangem.common.ui.account.CryptoPortfolioIconConverter
|
||||||
import com.tangem.common.ui.account.PortfolioSelectUM
|
import com.tangem.common.ui.account.PortfolioSelectUM
|
||||||
import com.tangem.common.ui.account.toUM
|
import com.tangem.common.ui.account.toUM
|
||||||
import com.tangem.common.ui.addtoken.AddTokenUM
|
import com.tangem.common.ui.addtoken.AddTokenUM
|
||||||
|
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||||
import com.tangem.core.decompose.di.ModelScoped
|
import com.tangem.core.decompose.di.ModelScoped
|
||||||
import com.tangem.core.decompose.model.ParamsContainer
|
import com.tangem.core.decompose.model.ParamsContainer
|
||||||
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||||
|
|
@ -35,13 +36,18 @@ internal class AddTokenUiBuilder @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createPortfolio(selectedPortfolio: SelectedPortfolio): PortfolioSelectUM {
|
private fun createPortfolio(
|
||||||
|
selectedPortfolio: SelectedPortfolio,
|
||||||
|
walletImage: UserWalletItemUM.ImageState,
|
||||||
|
): PortfolioSelectUM {
|
||||||
val accountIcon: AccountIconUM?
|
val accountIcon: AccountIconUM?
|
||||||
val portfolioName: TextReference
|
val portfolioName: TextReference
|
||||||
|
val imageState: UserWalletItemUM.ImageState?
|
||||||
when (selectedPortfolio.isAccountMode) {
|
when (selectedPortfolio.isAccountMode) {
|
||||||
false -> {
|
false -> {
|
||||||
accountIcon = null
|
accountIcon = null
|
||||||
portfolioName = stringReference(selectedPortfolio.userWallet.name)
|
portfolioName = stringReference(selectedPortfolio.userWallet.name)
|
||||||
|
imageState = walletImage
|
||||||
}
|
}
|
||||||
true -> {
|
true -> {
|
||||||
val accountStatus = selectedPortfolio.account.account
|
val accountStatus = selectedPortfolio.account.account
|
||||||
|
|
@ -50,6 +56,7 @@ internal class AddTokenUiBuilder @Inject constructor(
|
||||||
is CryptoPortfolio -> CryptoPortfolioIconConverter.convert(accountStatus.account.icon)
|
is CryptoPortfolio -> CryptoPortfolioIconConverter.convert(accountStatus.account.icon)
|
||||||
is Payment -> AccountIconUM.Payment
|
is Payment -> AccountIconUM.Payment
|
||||||
}
|
}
|
||||||
|
imageState = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return PortfolioSelectUM(
|
return PortfolioSelectUM(
|
||||||
|
|
@ -58,12 +65,14 @@ internal class AddTokenUiBuilder @Inject constructor(
|
||||||
isAccountMode = selectedPortfolio.isAccountMode,
|
isAccountMode = selectedPortfolio.isAccountMode,
|
||||||
isMultiChoice = selectedPortfolio.isAvailableMorePortfolio,
|
isMultiChoice = selectedPortfolio.isAvailableMorePortfolio,
|
||||||
onClick = { params.callbacks.onChangePortfolioClick() },
|
onClick = { params.callbacks.onChangePortfolioClick() },
|
||||||
|
imageState = imageState,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateContent(
|
fun updateContent(
|
||||||
selectedPortfolio: SelectedPortfolio,
|
selectedPortfolio: SelectedPortfolio,
|
||||||
selectedNetwork: SelectedNetwork,
|
selectedNetwork: SelectedNetwork,
|
||||||
|
walletImage: UserWalletItemUM.ImageState,
|
||||||
isTangemIconVisible: Boolean,
|
isTangemIconVisible: Boolean,
|
||||||
onConfirmClick: () -> Unit,
|
onConfirmClick: () -> Unit,
|
||||||
): AddTokenUM {
|
): AddTokenUM {
|
||||||
|
|
@ -78,7 +87,7 @@ internal class AddTokenUiBuilder @Inject constructor(
|
||||||
onConfirmClick = onConfirmClick,
|
onConfirmClick = onConfirmClick,
|
||||||
)
|
)
|
||||||
val networkUM = createNetwork(selectedNetwork)
|
val networkUM = createNetwork(selectedNetwork)
|
||||||
val portfolioUM = createPortfolio(selectedPortfolio)
|
val portfolioUM = createPortfolio(selectedPortfolio, walletImage)
|
||||||
val currency = selectedNetwork.cryptoCurrency
|
val currency = selectedNetwork.cryptoCurrency
|
||||||
val tokenToAdd = TokenItemState.Content(
|
val tokenToAdd = TokenItemState.Content(
|
||||||
id = currency.id.value,
|
id = currency.id.value,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import com.tangem.common.ui.tokens.TokenItemGrouping.toGroupedItems
|
||||||
import com.tangem.common.ui.tokens.TokenItemGrouping.toUngroupedItems
|
import com.tangem.common.ui.tokens.TokenItemGrouping.toUngroupedItems
|
||||||
import com.tangem.common.ui.tokens.TokenItemStateConverter
|
import com.tangem.common.ui.tokens.TokenItemStateConverter
|
||||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||||
import com.tangem.core.ui.components.icons.IconTint
|
|
||||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||||
import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState
|
import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState
|
||||||
import com.tangem.core.ui.components.tokenlist.state.PortfolioTokensListItemUM
|
import com.tangem.core.ui.components.tokenlist.state.PortfolioTokensListItemUM
|
||||||
|
|
@ -55,12 +54,11 @@ internal class ChooseTokenListItemConverter(
|
||||||
}
|
}
|
||||||
|
|
||||||
private val fiatAmountStateProvider: ((TotalFiatBalance, isExpanded: Boolean) -> FiatAmountState?) =
|
private val fiatAmountStateProvider: ((TotalFiatBalance, isExpanded: Boolean) -> FiatAmountState?) =
|
||||||
{ totalBalance, isExpanded ->
|
{ totalBalance, _ ->
|
||||||
when {
|
if (isSearchingState) {
|
||||||
isSearchingState -> FiatAmountState.Empty
|
FiatAmountState.Empty
|
||||||
!isExpanded -> FiatAmountState.Icon(R.drawable.ic_chewron_down_20, IconTint.Informative)
|
} else {
|
||||||
else -> AccountCryptoPortfolioItemStateConverter
|
AccountCryptoPortfolioItemStateConverter.createFiatAmountState(totalBalance, appCurrency)
|
||||||
.createFiatAmountState(totalBalance, appCurrency)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,16 @@
|
||||||
package com.tangem.features.commonfeatures.impl.choosetoken.ui
|
package com.tangem.features.commonfeatures.impl.choosetoken.ui
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.BoundsTransform
|
||||||
|
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||||
|
import androidx.compose.animation.SharedTransitionScope.ResizeMode.Companion.scaleToBounds
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.core.animateIntAsState
|
||||||
|
import androidx.compose.animation.core.snap
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.togetherWith
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
|
@ -7,6 +18,7 @@ import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.*
|
import androidx.compose.foundation.lazy.*
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
|
@ -15,16 +27,21 @@ import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.semantics.semantics
|
import androidx.compose.ui.semantics.semantics
|
||||||
|
import androidx.compose.ui.text.lerp
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.tangem.common.ui.tokens.portfolioTokensList
|
import com.tangem.common.ui.tokens.NonContentItemContent
|
||||||
|
import com.tangem.common.ui.tokens.SlideInItemVisibility
|
||||||
import com.tangem.core.ui.components.SpacerH
|
import com.tangem.core.ui.components.SpacerH
|
||||||
|
import com.tangem.core.ui.components.account.AccountIconSize
|
||||||
|
import com.tangem.core.ui.components.account.toBoxSize
|
||||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||||
import com.tangem.core.ui.components.fields.SearchBar
|
import com.tangem.core.ui.components.fields.SearchBar
|
||||||
|
|
@ -34,6 +51,8 @@ import com.tangem.core.ui.components.list.InfiniteListHandler
|
||||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||||
import com.tangem.core.ui.components.token.AccountItemPreviewData
|
import com.tangem.core.ui.components.token.AccountItemPreviewData
|
||||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||||
|
import com.tangem.core.ui.components.tokenlist.NON_CONTENT_TOKENS_LIST_KEY
|
||||||
|
import com.tangem.core.ui.components.tokenlist.PortfolioTokensListItem
|
||||||
import com.tangem.core.ui.components.tokenlist.TokenListItem
|
import com.tangem.core.ui.components.tokenlist.TokenListItem
|
||||||
import com.tangem.core.ui.components.tokenlist.state.PortfolioItemContentUM
|
import com.tangem.core.ui.components.tokenlist.state.PortfolioItemContentUM
|
||||||
import com.tangem.core.ui.components.tokenlist.state.PortfolioTokensListItemUM
|
import com.tangem.core.ui.components.tokenlist.state.PortfolioTokensListItemUM
|
||||||
|
|
@ -41,26 +60,42 @@ import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||||
import com.tangem.core.ui.ds.image.DeviceIconUM
|
import com.tangem.core.ui.ds.image.DeviceIconUM
|
||||||
import com.tangem.core.ui.ds.image.TangemDeviceIcon
|
import com.tangem.core.ui.ds.image.TangemDeviceIcon
|
||||||
|
import com.tangem.core.ui.ds.image.TangemIcon
|
||||||
|
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||||
|
import com.tangem.core.ui.ds.row.header.TangemHeaderRow
|
||||||
|
import com.tangem.core.ui.ds.row.internal.TangemRowTailUM
|
||||||
|
import com.tangem.core.ui.ds.row.token.TangemTokenRow
|
||||||
|
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||||
|
import com.tangem.core.ui.ds.row.token.internal.TokenRowTitle
|
||||||
import com.tangem.core.ui.extensions.*
|
import com.tangem.core.ui.extensions.*
|
||||||
import com.tangem.core.ui.res.LocalRedesignEnabled
|
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import com.tangem.core.ui.test.BuyTokenScreenTestTags
|
import com.tangem.core.ui.test.BuyTokenScreenTestTags
|
||||||
|
import com.tangem.core.ui.utils.ProvideSharedTransitionScope
|
||||||
import com.tangem.core.ui.utils.TangemSharedTransitionLayout
|
import com.tangem.core.ui.utils.TangemSharedTransitionLayout
|
||||||
import com.tangem.core.ui.utils.lazyListItemPosition
|
import com.tangem.core.ui.utils.lazyListItemPosition
|
||||||
import com.tangem.core.ui.utils.rememberHideKeyboardNestedScrollConnection
|
import com.tangem.core.ui.utils.rememberHideKeyboardNestedScrollConnection
|
||||||
|
import com.tangem.core.ui.utils.sharedBoundsSafely
|
||||||
import com.tangem.features.commonfeatures.api.choosetoken.model.ChooseTokenPortfolioFullBlockUM
|
import com.tangem.features.commonfeatures.api.choosetoken.model.ChooseTokenPortfolioFullBlockUM
|
||||||
import com.tangem.features.commonfeatures.api.choosetoken.model.TokenListUMData
|
import com.tangem.features.commonfeatures.api.choosetoken.model.TokenListUMData
|
||||||
import com.tangem.features.commonfeatures.api.choosetoken.model.WalletListUM
|
import com.tangem.features.commonfeatures.api.choosetoken.model.WalletListUM
|
||||||
import com.tangem.features.commonfeatures.api.choosetoken.model.WalletTabUM
|
import com.tangem.features.commonfeatures.api.choosetoken.model.WalletTabUM
|
||||||
import com.tangem.features.commonfeatures.impl.R
|
import com.tangem.features.commonfeatures.impl.R
|
||||||
import com.tangem.features.commonfeatures.impl.choosetoken.market.state.SwapMarketState
|
import com.tangem.features.commonfeatures.impl.choosetoken.market.state.SwapMarketState
|
||||||
|
import com.tangem.utils.StringsSigns.DOT
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toPersistentList
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
private const val LOAD_MORE_BUFFER = 25
|
private const val LOAD_MORE_BUFFER = 25
|
||||||
|
private const val ACCOUNT_COLLAPSE_STEP_MS = 50
|
||||||
|
private const val ACCOUNT_COLLAPSE_MAX_DELAY_MS = 250
|
||||||
|
private const val ACCOUNT_COLLAPSE_BASE_DELAY_MS = 150
|
||||||
|
private const val ACCOUNT_CONTENT_ANIM_MS = 350
|
||||||
|
private const val ACCOUNT_CONTENT_ANIM_DELAY_MS = 90
|
||||||
|
private const val ACCOUNT_BOUNDS_ANIM_MS = 250
|
||||||
|
|
||||||
private val ChooseTokenFullUM.isNotFoundState: Boolean
|
private val ChooseTokenFullUM.isNotFoundState: Boolean
|
||||||
get() {
|
get() {
|
||||||
|
|
@ -298,11 +333,10 @@ private fun LazyListScope.tokensListItems(tokensListData: TokenListUMData, isBal
|
||||||
when (tokensListData) {
|
when (tokensListData) {
|
||||||
is TokenListUMData.AccountList -> {
|
is TokenListUMData.AccountList -> {
|
||||||
tokensListData.tokensList.forEachIndexed { index, item ->
|
tokensListData.tokensList.forEachIndexed { index, item ->
|
||||||
portfolioTokensList(
|
accountWithTokens(
|
||||||
portfolio = item,
|
portfolio = item,
|
||||||
portfolioIndex = index,
|
portfolioIndex = index,
|
||||||
isBalanceHidden = isBalanceHidden,
|
isBalanceHidden = isBalanceHidden,
|
||||||
testTag = BuyTokenScreenTestTags.LAZY_LIST_ITEM,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -338,6 +372,298 @@ private fun LazyListScope.tokensList(items: ImmutableList<TokensListItemUM>, isB
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("LongMethod")
|
||||||
|
private fun LazyListScope.accountWithTokens(
|
||||||
|
portfolio: TokensListItemUM.Portfolio,
|
||||||
|
portfolioIndex: Int,
|
||||||
|
isBalanceHidden: Boolean,
|
||||||
|
) {
|
||||||
|
val tokens = portfolio.tokens
|
||||||
|
val isExpanded = portfolio.isExpanded
|
||||||
|
val lastIndex = maxOf(tokens.lastIndex.inc(), 1)
|
||||||
|
|
||||||
|
item(key = "account-${portfolio.id}", contentType = "choose-token-account") {
|
||||||
|
val effectiveLastIndex by animateIntAsState(
|
||||||
|
targetValue = if (isExpanded) lastIndex else 0,
|
||||||
|
animationSpec = if (isExpanded) {
|
||||||
|
snap()
|
||||||
|
} else {
|
||||||
|
snap(
|
||||||
|
delayMillis = minOf(
|
||||||
|
ACCOUNT_COLLAPSE_STEP_MS * maxOf(tokens.lastIndex, 0),
|
||||||
|
ACCOUNT_COLLAPSE_MAX_DELAY_MS,
|
||||||
|
) + ACCOUNT_COLLAPSE_BASE_DELAY_MS,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = "accountLastIndex",
|
||||||
|
)
|
||||||
|
AccountRow(
|
||||||
|
portfolio = portfolio,
|
||||||
|
isBalanceHidden = isBalanceHidden,
|
||||||
|
modifier = Modifier
|
||||||
|
.testTag(BuyTokenScreenTestTags.LAZY_LIST_ITEM)
|
||||||
|
.semantics { lazyListItemPosition = portfolioIndex }
|
||||||
|
.roundedShapeItemDecoration(
|
||||||
|
currentIndex = 0,
|
||||||
|
radius = TangemTheme.dimens.radius14,
|
||||||
|
lastIndex = effectiveLastIndex,
|
||||||
|
backgroundColor = TangemTheme.colors.background.primary,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (portfolio.content is PortfolioItemContentUM.Empty) {
|
||||||
|
item(key = "$NON_CONTENT_TOKENS_LIST_KEY account-${portfolio.id}") {
|
||||||
|
SlideInItemVisibility(
|
||||||
|
visible = isExpanded,
|
||||||
|
currentIndex = 1,
|
||||||
|
lastIndex = lastIndex,
|
||||||
|
modifier = Modifier.roundedShapeItemDecoration(
|
||||||
|
currentIndex = 1,
|
||||||
|
radius = TangemTheme.dimens.radius14,
|
||||||
|
lastIndex = 1,
|
||||||
|
backgroundColor = TangemTheme.colors.background.primary,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
NonContentItemContent(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing16))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
itemsIndexed(
|
||||||
|
items = tokens,
|
||||||
|
key = { _, token -> "${token.id}-choose-account-${portfolio.id}" },
|
||||||
|
contentType = { _, token -> token::class.java },
|
||||||
|
) { tokenIndex, token ->
|
||||||
|
val indexWithHeader = tokenIndex.inc()
|
||||||
|
val lastTokenBottomPadding = TangemTheme.dimens.spacing8
|
||||||
|
SlideInItemVisibility(
|
||||||
|
visible = isExpanded,
|
||||||
|
currentIndex = tokenIndex,
|
||||||
|
lastIndex = lastIndex,
|
||||||
|
modifier = Modifier
|
||||||
|
.testTag(BuyTokenScreenTestTags.LAZY_LIST_ITEM)
|
||||||
|
.roundedShapeItemDecoration(
|
||||||
|
currentIndex = indexWithHeader,
|
||||||
|
radius = TangemTheme.dimens.radius14,
|
||||||
|
lastIndex = lastIndex,
|
||||||
|
backgroundColor = TangemTheme.colors.background.primary,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
PortfolioTokensListItem(
|
||||||
|
state = token,
|
||||||
|
isBalanceHidden = isBalanceHidden,
|
||||||
|
modifier = Modifier.conditional(indexWithHeader == lastIndex) {
|
||||||
|
padding(bottom = lastTokenBottomPadding)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("LongMethod")
|
||||||
|
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||||
|
@Composable
|
||||||
|
private fun AccountRow(
|
||||||
|
portfolio: TokensListItemUM.Portfolio,
|
||||||
|
isBalanceHidden: Boolean,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val tokenRowUM = accountTokenRowUM(portfolio)
|
||||||
|
val subtitle = (tokenRowUM.subtitleUM as? TangemTokenRowUM.SubtitleUM.Content)?.text
|
||||||
|
Row(
|
||||||
|
modifier = modifier,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
ProvideSharedTransitionScope(Modifier.weight(1f)) {
|
||||||
|
val iconSharedContentState = rememberSharedContentState(key = "account-icon-${portfolio.id}")
|
||||||
|
val titleSharedContentState = rememberSharedContentState(key = "account-title-${portfolio.id}")
|
||||||
|
val boundsTransform = BoundsTransform { _, _ -> tween(ACCOUNT_BOUNDS_ANIM_MS) }
|
||||||
|
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = portfolio.isExpanded,
|
||||||
|
transitionSpec = {
|
||||||
|
fadeIn(animationSpec = tween(ACCOUNT_CONTENT_ANIM_MS, delayMillis = ACCOUNT_CONTENT_ANIM_DELAY_MS))
|
||||||
|
.togetherWith(fadeOut(animationSpec = tween(ACCOUNT_CONTENT_ANIM_MS)))
|
||||||
|
},
|
||||||
|
label = "accountExpand",
|
||||||
|
) { isExpandedState ->
|
||||||
|
val animatedContentScope = this
|
||||||
|
val composables = remember(tokenRowUM) {
|
||||||
|
AccountRowComposables(
|
||||||
|
icon = { iconModifier ->
|
||||||
|
val iconSize = if (isExpandedState) {
|
||||||
|
AccountIconSize.RedesignExtraSmall
|
||||||
|
} else {
|
||||||
|
AccountIconSize.RedesignedDefault
|
||||||
|
}
|
||||||
|
val sizedIcon = when (val icon = tokenRowUM.headIconUM) {
|
||||||
|
is TangemIconUM.Currency -> icon.copy(
|
||||||
|
currencyIconState = when (val iconState = icon.currencyIconState) {
|
||||||
|
is CurrencyIconState.CryptoPortfolio.Icon -> iconState.copy(size = iconSize)
|
||||||
|
is CurrencyIconState.CryptoPortfolio.Letter -> iconState.copy(size = iconSize)
|
||||||
|
else -> iconState
|
||||||
|
},
|
||||||
|
)
|
||||||
|
else -> icon
|
||||||
|
}
|
||||||
|
TangemIcon(
|
||||||
|
tangemIconUM = sizedIcon,
|
||||||
|
modifier = iconModifier
|
||||||
|
.size(iconSize.toBoxSize())
|
||||||
|
.sharedBoundsSafely(
|
||||||
|
sharedContentState = iconSharedContentState,
|
||||||
|
animatedVisibilityScope = animatedContentScope,
|
||||||
|
boundsTransform = boundsTransform,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
title = { titleModifier ->
|
||||||
|
val targetFraction = if (isExpandedState) 0f else 1f
|
||||||
|
val animationFraction = animateFloatAsState(
|
||||||
|
targetValue = targetFraction,
|
||||||
|
animationSpec = tween(durationMillis = ACCOUNT_CONTENT_ANIM_MS),
|
||||||
|
label = "accountTitle",
|
||||||
|
)
|
||||||
|
val startStyle = TangemTheme.typography2.captionSemibold12
|
||||||
|
val stopStyle = TangemTheme.typography2.bodySemibold16
|
||||||
|
val textStyle by remember(animationFraction.value) {
|
||||||
|
derivedStateOf { lerp(startStyle, stopStyle, animationFraction.value) }
|
||||||
|
}
|
||||||
|
val resizedTitle = when (val titleUM = tokenRowUM.titleUM) {
|
||||||
|
is TangemTokenRowUM.TitleUM.Content -> titleUM.copy(
|
||||||
|
text = styledStringReference(
|
||||||
|
titleUM.text.resolveReference(),
|
||||||
|
{ textStyle.toSpanStyle() },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else -> titleUM
|
||||||
|
}
|
||||||
|
TokenRowTitle(
|
||||||
|
titleUM = resizedTitle,
|
||||||
|
modifier = titleModifier.sharedBoundsSafely(
|
||||||
|
sharedContentState = titleSharedContentState,
|
||||||
|
animatedVisibilityScope = animatedContentScope,
|
||||||
|
boundsTransform = boundsTransform,
|
||||||
|
resizeMode = scaleToBounds(ContentScale.Fit, Alignment.CenterStart),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isExpandedState) {
|
||||||
|
TangemHeaderRow(
|
||||||
|
subtitle = subtitle,
|
||||||
|
isBalanceHidden = isBalanceHidden,
|
||||||
|
titleContent = composables.title,
|
||||||
|
headContent = composables.icon,
|
||||||
|
tailUM = TangemRowTailUM.Empty,
|
||||||
|
onItemClick = tokenRowUM.onItemClick,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
TangemTokenRow(
|
||||||
|
tokenRowUM = tokenRowUM,
|
||||||
|
isBalanceHidden = isBalanceHidden,
|
||||||
|
headComponent = composables.icon,
|
||||||
|
titleComponent = composables.title,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AccountTail(
|
||||||
|
isExpanded = portfolio.isExpanded,
|
||||||
|
onClick = { tokenRowUM.onItemClick?.invoke() },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AccountTail(isExpanded: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.padding(start = TangemTheme.dimens2.x2, end = TangemTheme.dimens2.x4)
|
||||||
|
.size(TangemTheme.dimens2.x9)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.clickable(onClick = onClick),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = isExpanded,
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
label = "accountTail",
|
||||||
|
) { expanded ->
|
||||||
|
if (expanded) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier
|
||||||
|
.offset(x = TangemTheme.dimens.spacing2)
|
||||||
|
.size(TangemTheme.dimens2.x4),
|
||||||
|
painter = painterResource(id = R.drawable.ic_minimize_24),
|
||||||
|
tint = TangemTheme.colors2.graphic.neutral.tertiaryConstant,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
AccountExpandButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AccountExpandButton(modifier: Modifier = Modifier) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.size(TangemTheme.dimens2.x9)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(TangemTheme.colors2.button.backgroundSecondary),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.size(TangemTheme.dimens2.x5),
|
||||||
|
painter = painterResource(id = R.drawable.ic_chewron_down_20),
|
||||||
|
tint = TangemTheme.colors2.button.iconPrimary,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
private class AccountRowComposables(
|
||||||
|
val title: @Composable (Modifier) -> Unit,
|
||||||
|
val icon: @Composable (Modifier) -> Unit,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun accountTokenRowUM(portfolio: TokensListItemUM.Portfolio): TangemTokenRowUM.Content {
|
||||||
|
val account = portfolio.tokenItemUM
|
||||||
|
val name = (account.titleState as? TokenItemState.TitleState.Content)?.text ?: TextReference.EMPTY
|
||||||
|
val tokensCount = (account.subtitleState as? TokenItemState.SubtitleState.TextContent)?.value
|
||||||
|
val balance = (account.fiatAmountState as? TokenItemState.FiatAmountState.Content)?.text
|
||||||
|
return TangemTokenRowUM.Content(
|
||||||
|
id = portfolio.id,
|
||||||
|
headIconUM = TangemIconUM.Currency(currencyIconState = account.iconState),
|
||||||
|
titleUM = TangemTokenRowUM.TitleUM.Content(text = name),
|
||||||
|
subtitleUM = buildAccountSubtitle(tokensCount, balance)
|
||||||
|
?.let { TangemTokenRowUM.SubtitleUM.Content(text = it) }
|
||||||
|
?: TangemTokenRowUM.SubtitleUM.Empty,
|
||||||
|
topEndContentUM = TangemTokenRowUM.EndContentUM.Empty,
|
||||||
|
bottomEndContentUM = TangemTokenRowUM.EndContentUM.Empty,
|
||||||
|
tailUM = TangemRowTailUM.Empty,
|
||||||
|
onItemClick = { account.onItemClick?.invoke(account) },
|
||||||
|
onItemLongClick = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildAccountSubtitle(tokensCount: TextReference?, balance: String?): TextReference? {
|
||||||
|
return when {
|
||||||
|
tokensCount != null && balance != null ->
|
||||||
|
combinedReference(tokensCount, stringReference(" $DOT "), stringReference(balance))
|
||||||
|
tokensCount != null -> tokensCount
|
||||||
|
balance != null -> stringReference(balance)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun LazyListScope.emptyTokensList(modifier: Modifier = Modifier) {
|
private fun LazyListScope.emptyTokensList(modifier: Modifier = Modifier) {
|
||||||
item("EmptyTokensList") {
|
item("EmptyTokensList") {
|
||||||
Box(
|
Box(
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ import com.tangem.common.ui.markets.action.TokenActionsContext
|
||||||
import com.tangem.features.commonfeatures.impl.tokenactions.TokenActionsComponent
|
import com.tangem.features.commonfeatures.impl.tokenactions.TokenActionsComponent
|
||||||
import com.tangem.features.commonfeatures.impl.userportfolio.UserPortfolioComponent
|
import com.tangem.features.commonfeatures.impl.userportfolio.UserPortfolioComponent
|
||||||
import com.tangem.features.commonfeatures.impl.R
|
import com.tangem.features.commonfeatures.impl.R
|
||||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
|
|
@ -42,15 +41,12 @@ internal class DefaultManageFundsComponent @AssistedInject constructor(
|
||||||
chooseTokenComponentFactory: ChooseTokenComponent.Factory,
|
chooseTokenComponentFactory: ChooseTokenComponent.Factory,
|
||||||
tokenActionsComponentFactory: TokenActionsComponent.Factory,
|
tokenActionsComponentFactory: TokenActionsComponent.Factory,
|
||||||
userPortfolioComponentFactory: UserPortfolioComponent.Factory,
|
userPortfolioComponentFactory: UserPortfolioComponent.Factory,
|
||||||
walletFeatureToggles: WalletFeatureToggles,
|
|
||||||
) : AppComponentContext by appComponentContext, ManageFundsComponent {
|
) : AppComponentContext by appComponentContext, ManageFundsComponent {
|
||||||
|
|
||||||
private val model: ManageFundsModel = getOrCreateModel(params)
|
private val model: ManageFundsModel = getOrCreateModel(params)
|
||||||
|
|
||||||
private val isCompactTokenActions: Boolean = params.launchMode is ManageFundsComponent.LaunchMode.TokenActionsOnly
|
private val isCompactTokenActions: Boolean = params.launchMode is ManageFundsComponent.LaunchMode.TokenActionsOnly
|
||||||
|
|
||||||
private val isAddFundsStage1Enabled: Boolean = walletFeatureToggles.isAddFundsStage1Enabled
|
|
||||||
|
|
||||||
private val tokenActionsComponent: TokenActionsComponent by lazy {
|
private val tokenActionsComponent: TokenActionsComponent by lazy {
|
||||||
tokenActionsComponentFactory.create(
|
tokenActionsComponentFactory.create(
|
||||||
context = child(key = "manageFundsTokenActions"),
|
context = child(key = "manageFundsTokenActions"),
|
||||||
|
|
@ -107,7 +103,7 @@ internal class DefaultManageFundsComponent @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
WithOptionalRedesignTheme(isEnabled = isAddFundsStage1Enabled) {
|
TangemThemeRedesign {
|
||||||
TangemBottomSheet<TangemBottomSheetConfigContent.Empty>(
|
TangemBottomSheet<TangemBottomSheetConfigContent.Empty>(
|
||||||
onBack = if (canGoBack) model::onBack else ::dismiss,
|
onBack = if (canGoBack) model::onBack else ::dismiss,
|
||||||
config = TangemBottomSheetConfig(
|
config = TangemBottomSheetConfig(
|
||||||
|
|
@ -179,7 +175,7 @@ internal class DefaultManageFundsComponent @AssistedInject constructor(
|
||||||
) {
|
) {
|
||||||
userPortfolioComponent.Content(modifier)
|
userPortfolioComponent.Content(modifier)
|
||||||
}
|
}
|
||||||
ManageFundsModel.UiRoute.TokenActions -> tokenActionsComponent.Content(modifier)
|
is ManageFundsModel.UiRoute.TokenActions -> tokenActionsComponent.Content(modifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -191,8 +187,13 @@ internal class DefaultManageFundsComponent @AssistedInject constructor(
|
||||||
onCloseClick: () -> Unit,
|
onCloseClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val spec = route.uiSpec(model.flowType)
|
val spec = route.uiSpec(model.flowType)
|
||||||
|
val title = if (route is ManageFundsModel.UiRoute.TokenActions) {
|
||||||
|
route.title
|
||||||
|
} else {
|
||||||
|
spec.title
|
||||||
|
}
|
||||||
TangemTopBar(
|
TangemTopBar(
|
||||||
title = spec.title,
|
title = title,
|
||||||
subtitle = spec.subtitle,
|
subtitle = spec.subtitle,
|
||||||
type = TangemTopBarType.BottomSheet,
|
type = TangemTopBarType.BottomSheet,
|
||||||
startContent = if (canGoBack) {
|
startContent = if (canGoBack) {
|
||||||
|
|
@ -219,15 +220,6 @@ internal class DefaultManageFundsComponent @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun WithOptionalRedesignTheme(isEnabled: Boolean, content: @Composable () -> Unit) {
|
|
||||||
if (isEnabled) {
|
|
||||||
TangemThemeRedesign(content = content)
|
|
||||||
} else {
|
|
||||||
content()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory : ManageFundsComponent.Factory {
|
interface Factory : ManageFundsComponent.Factory {
|
||||||
override fun create(
|
override fun create(
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
import com.tangem.core.decompose.di.ModelScoped
|
import com.tangem.core.decompose.di.ModelScoped
|
||||||
import com.tangem.core.decompose.model.Model
|
import com.tangem.core.decompose.model.Model
|
||||||
import com.tangem.core.decompose.model.ParamsContainer
|
import com.tangem.core.decompose.model.ParamsContainer
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
import com.tangem.core.ui.extensions.wrappedList
|
||||||
import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier
|
import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier
|
||||||
import com.tangem.domain.account.status.usecase.GetCryptoCurrencyActionsUseCaseV2
|
import com.tangem.domain.account.status.usecase.GetCryptoCurrencyActionsUseCaseV2
|
||||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||||
|
|
@ -26,6 +29,7 @@ import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenAnalyticsPa
|
||||||
import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenBridge
|
import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenBridge
|
||||||
import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenResult
|
import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenResult
|
||||||
import com.tangem.features.commonfeatures.api.tokenactions.BottomAction
|
import com.tangem.features.commonfeatures.api.tokenactions.BottomAction
|
||||||
|
import com.tangem.features.commonfeatures.impl.R
|
||||||
import com.tangem.features.commonfeatures.impl.managefunds.analytics.ManageFundsAnalyticsEvent
|
import com.tangem.features.commonfeatures.impl.managefunds.analytics.ManageFundsAnalyticsEvent
|
||||||
import com.tangem.features.commonfeatures.impl.tokenactions.TokenActionsComponent
|
import com.tangem.features.commonfeatures.impl.tokenactions.TokenActionsComponent
|
||||||
import com.tangem.features.commonfeatures.impl.userportfolio.state.UserPortfolioStateController
|
import com.tangem.features.commonfeatures.impl.userportfolio.state.UserPortfolioStateController
|
||||||
|
|
@ -206,7 +210,7 @@ internal class ManageFundsModel @Inject constructor(
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
tokenActionsTrigger.value = TokenActionsRequest(wallet, match.first, match.second)
|
tokenActionsTrigger.value = TokenActionsRequest(wallet, match.first, match.second)
|
||||||
replaceRoot(UiRoute.TokenActions)
|
replaceRoot(tokenActionsRoute(match.second))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -218,7 +222,7 @@ internal class ManageFundsModel @Inject constructor(
|
||||||
1 -> {
|
1 -> {
|
||||||
val entry = entries.first()
|
val entry = entries.first()
|
||||||
tokenActionsTrigger.value = TokenActionsRequest(entry.userWallet, entry.account, entry.status)
|
tokenActionsTrigger.value = TokenActionsRequest(entry.userWallet, entry.account, entry.status)
|
||||||
replaceRoot(UiRoute.TokenActions)
|
replaceRoot(tokenActionsRoute(entry.status))
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
filteredEntries.value = entries
|
filteredEntries.value = entries
|
||||||
|
|
@ -261,7 +265,12 @@ internal class ManageFundsModel @Inject constructor(
|
||||||
private fun openTokenActions(request: TokenActionsRequest, bottomAction: BottomAction) {
|
private fun openTokenActions(request: TokenActionsRequest, bottomAction: BottomAction) {
|
||||||
tokenActionsTrigger.value = request
|
tokenActionsTrigger.value = request
|
||||||
currentBottomAction.value = bottomAction
|
currentBottomAction.value = bottomAction
|
||||||
pushRoute(UiRoute.TokenActions)
|
pushRoute(tokenActionsRoute(request.status))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tokenActionsRoute(status: CryptoCurrencyStatus): UiRoute.TokenActions {
|
||||||
|
val title = resourceReference(R.string.get_token_title, wrappedList(status.currency.name))
|
||||||
|
return UiRoute.TokenActions(title = title)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun replaceRoot(route: UiRoute) {
|
private fun replaceRoot(route: UiRoute) {
|
||||||
|
|
@ -277,7 +286,7 @@ internal class ManageFundsModel @Inject constructor(
|
||||||
data object Loading : UiRoute
|
data object Loading : UiRoute
|
||||||
data object ChooseToken : UiRoute
|
data object ChooseToken : UiRoute
|
||||||
data object UserPortfolio : UiRoute
|
data object UserPortfolio : UiRoute
|
||||||
data object TokenActions : UiRoute
|
data class TokenActions(val title: TextReference) : UiRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class TokenActionsRequest(
|
private data class TokenActionsRequest(
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ internal fun ManageFundsModel.UiRoute.uiSpec(flowType: ManageFundsComponent.Flow
|
||||||
shouldApplyHorizontalPadding = false,
|
shouldApplyHorizontalPadding = false,
|
||||||
shouldFillHeight = false,
|
shouldFillHeight = false,
|
||||||
)
|
)
|
||||||
ManageFundsModel.UiRoute.TokenActions -> ManageFundsRouteUiSpec(
|
is ManageFundsModel.UiRoute.TokenActions -> ManageFundsRouteUiSpec(
|
||||||
title = resourceReference(if (isTransfer) R.string.common_transfer else R.string.common_get_token),
|
title = resourceReference(if (isTransfer) R.string.common_transfer else R.string.common_get_token),
|
||||||
subtitle = null,
|
subtitle = null,
|
||||||
shouldApplyHorizontalPadding = true,
|
shouldApplyHorizontalPadding = true,
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import com.tangem.features.commonfeatures.impl.R
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.wallets.usecase.GetWalletIconUseCase
|
import com.tangem.domain.wallets.usecase.GetWalletIconUseCase
|
||||||
|
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||||
import com.tangem.features.commonfeatures.impl.tokenactions.TokenActionsComponent
|
import com.tangem.features.commonfeatures.impl.tokenactions.TokenActionsComponent
|
||||||
import com.tangem.features.commonfeatures.impl.tokenactions.ui.state.PortfolioBadgeUM
|
import com.tangem.features.commonfeatures.impl.tokenactions.ui.state.PortfolioBadgeUM
|
||||||
import com.tangem.features.commonfeatures.impl.tokenactions.ui.state.TokenActionsUM
|
import com.tangem.features.commonfeatures.impl.tokenactions.ui.state.TokenActionsUM
|
||||||
|
|
@ -42,6 +43,7 @@ internal class TokenActionsUiBuilder @Inject constructor(
|
||||||
paramsContainer: ParamsContainer,
|
paramsContainer: ParamsContainer,
|
||||||
private val designFeatureToggles: DesignFeatureToggles,
|
private val designFeatureToggles: DesignFeatureToggles,
|
||||||
private val getWalletIconUseCase: GetWalletIconUseCase,
|
private val getWalletIconUseCase: GetWalletIconUseCase,
|
||||||
|
private val getWalletsUseCase: GetWalletsUseCase,
|
||||||
private val walletIconUMConverter: WalletIconUMConverter,
|
private val walletIconUMConverter: WalletIconUMConverter,
|
||||||
) {
|
) {
|
||||||
private val params = paramsContainer.require<TokenActionsComponent.Params>()
|
private val params = paramsContainer.require<TokenActionsComponent.Params>()
|
||||||
|
|
@ -145,38 +147,47 @@ internal class TokenActionsUiBuilder @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createPortfolioBadge(cryptoCurrencyData: CryptoCurrencyData): PortfolioBadgeUM {
|
private fun createPortfolioBadge(cryptoCurrencyData: CryptoCurrencyData): PortfolioBadgeUM {
|
||||||
return if (cryptoCurrencyData.isAccountMode) {
|
return when {
|
||||||
val icon = CryptoPortfolioIconConverter.convert(cryptoCurrencyData.account.account.icon)
|
cryptoCurrencyData.isAccountMode -> {
|
||||||
val name = cryptoCurrencyData
|
val icon = CryptoPortfolioIconConverter.convert(cryptoCurrencyData.account.account.icon)
|
||||||
.account
|
val name = cryptoCurrencyData
|
||||||
.account
|
.account
|
||||||
.accountName
|
.account
|
||||||
.toUM()
|
.accountName
|
||||||
.value
|
.toUM()
|
||||||
PortfolioBadgeUM.Account(
|
.value
|
||||||
badge = TangemBadgeUM(
|
PortfolioBadgeUM.Account(
|
||||||
text = name,
|
badge = TangemBadgeUM(
|
||||||
tangemIconUM = TangemIconUM.Icon(
|
text = name,
|
||||||
iconRes = icon.value.getResId(),
|
tangemIconUM = TangemIconUM.Icon(
|
||||||
tintReference = { icon.color.getUiColor() },
|
iconRes = icon.value.getResId(),
|
||||||
|
tintReference = { icon.color.getUiColor() },
|
||||||
|
),
|
||||||
|
size = TangemBadgeSize.X6,
|
||||||
|
shape = TangemBadgeShape.Rounded,
|
||||||
|
iconPosition = TangemBadgeIconPosition.Start,
|
||||||
|
shouldRespectIconTint = true,
|
||||||
),
|
),
|
||||||
size = TangemBadgeSize.X6,
|
)
|
||||||
shape = TangemBadgeShape.Rounded,
|
}
|
||||||
iconPosition = TangemBadgeIconPosition.Start,
|
isSingleWallet() -> PortfolioBadgeUM.None
|
||||||
shouldRespectIconTint = true,
|
else -> {
|
||||||
),
|
val userWallet = cryptoCurrencyData.userWallet
|
||||||
)
|
PortfolioBadgeUM.Wallet(
|
||||||
} else {
|
name = stringReference(userWallet.name),
|
||||||
val userWallet = cryptoCurrencyData.userWallet
|
deviceIcon = walletIconUMConverter.convert(
|
||||||
PortfolioBadgeUM.Wallet(
|
getWalletIconUseCase(cryptoCurrencyData.userWallet),
|
||||||
name = stringReference(userWallet.name),
|
),
|
||||||
deviceIcon = walletIconUMConverter.convert(
|
)
|
||||||
getWalletIconUseCase(cryptoCurrencyData.userWallet),
|
}
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isSingleWallet(): Boolean {
|
||||||
|
val count = runCatching { getWalletsUseCase.invokeSync().size }.getOrNull() ?: return false
|
||||||
|
return count <= 1
|
||||||
|
}
|
||||||
|
|
||||||
private fun createSubtitle2State(status: CryptoCurrencyStatus): TokenItemState.Subtitle2State? {
|
private fun createSubtitle2State(status: CryptoCurrencyStatus): TokenItemState.Subtitle2State? {
|
||||||
return when (status.value) {
|
return when (status.value) {
|
||||||
is CryptoCurrencyStatus.Loaded,
|
is CryptoCurrencyStatus.Loaded,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue