Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-02 16:17:48 +03:00
parent 9869f3059d
commit 24c7340558
8 changed files with 143 additions and 130 deletions

View file

@ -22,6 +22,8 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDeta
import com.tangem.feature.tokendetails.presentation.tokendetails.route.TokenDetailsBottomSheetConfig
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.TokenDetailsScreen
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.TokenDetailsScreenLegacy
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.ChooseAddressBottomSheetComponent
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.CloreMigrationBottomSheetComponent
import com.tangem.features.markets.token.block.TokenMarketBlockComponent
import com.tangem.features.tokendetails.TokenDetailsComponent
import com.tangem.features.tokenreceive.TokenReceiveComponent
@ -136,6 +138,16 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor(
tokenAction = route.tokenAction,
),
)
is TokenDetailsBottomSheetConfig.ChooseAddress -> ChooseAddressBottomSheetComponent(
currency = route.currency,
networkAddress = route.networkAddress,
onAddressSelected = model::onAddressTypeSelected,
onDismiss = model.bottomSheetNavigation::dismiss,
)
is TokenDetailsBottomSheetConfig.CloreMigration -> CloreMigrationBottomSheetComponent(
cloreMigrationModel = model.cloreMigrationModel,
onDismiss = model.bottomSheetNavigation::dismiss,
)
}
@AssistedFactory

View file

@ -10,18 +10,18 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.transaction.error.SignCloreMessageError
import com.tangem.domain.transaction.usecase.SignCloreMessageUseCase
import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsStateFactory
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.clore.CloreMigrationBottomSheetConfig
import com.tangem.features.tokendetails.impl.R
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
// TODO: Remove after Clore migration ends ([REDACTED_TASK_KEY])
@Suppress("LongParameterList")
internal class CloreMigrationModel(
private val stateFactory: TokenDetailsStateFactory,
private val signCloreMessageUseCase: SignCloreMessageUseCase,
private val clipboardManager: ClipboardManager,
private val uiMessageSender: UiMessageSender,
@ -30,11 +30,13 @@ internal class CloreMigrationModel(
private val cryptoCurrency: CryptoCurrency,
private val coroutineScope: CoroutineScope,
private val dispatchers: CoroutineDispatcherProvider,
private val onStateUpdate: (TokenDetailsState) -> Unit,
) {
private val state = MutableStateFlow(CloreMigrationState())
private val _bottomSheetConfig = MutableStateFlow(createBottomSheetConfig())
val bottomSheetConfig: StateFlow<CloreMigrationBottomSheetConfig> = _bottomSheetConfig.asStateFlow()
fun onCloreMigrationClick() {
state.value = CloreMigrationState()
updateBottomSheet()
@ -66,7 +68,7 @@ internal class CloreMigrationModel(
},
ifRight = { signature ->
state.value = state.value.copy(signature = signature)
onStateUpdate(stateFactory.getStateWithUpdatedCloreMigrationSignature(signature))
updateBottomSheet()
},
)
}
@ -77,28 +79,30 @@ internal class CloreMigrationModel(
}
private fun updateBottomSheet(isSigningInProgress: Boolean = false) {
_bottomSheetConfig.value = createBottomSheetConfig(isSigningInProgress)
}
private fun createBottomSheetConfig(isSigningInProgress: Boolean = false): CloreMigrationBottomSheetConfig {
val currentState = state.value
onStateUpdate(
stateFactory.getStateWithCloreMigrationBottomSheet(
message = currentState.message,
signature = currentState.signature,
isSigningInProgress = isSigningInProgress,
onMessageChange = { newMessage ->
state.value = state.value.copy(message = newMessage)
updateBottomSheet()
},
onSignClick = { onCloreSignMessage(state.value.message) },
onCopyClick = {
val signature = state.value.signature
if (signature.isNotBlank()) {
clipboardManager.setText(text = signature, isSensitive = false)
uiMessageSender.send(
SnackbarMessage(resourceReference(R.string.wallet_notification_address_copied)),
)
}
},
onOpenPortalClick = { onOpenCloreClaimPortal() },
),
return CloreMigrationBottomSheetConfig(
message = currentState.message,
signature = currentState.signature,
isSigningInProgress = isSigningInProgress,
onMessageChange = { newMessage ->
state.value = state.value.copy(message = newMessage)
updateBottomSheet()
},
onSignClick = { onCloreSignMessage(state.value.message) },
onCopyClick = {
val signature = state.value.signature
if (signature.isNotBlank()) {
clipboardManager.setText(text = signature, isSensitive = false)
uiMessageSender.send(
SnackbarMessage(resourceReference(R.string.wallet_notification_address_copied)),
)
}
},
onOpenPortalClick = { onOpenCloreClaimPortal() },
)
}

View file

@ -199,7 +199,6 @@ internal class TokenDetailsModel @Inject constructor(
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
tokenDetailsClickIntents = this,
expressTransactionsClickIntents = this,
networkHasDerivationUseCase = networkHasDerivationUseCase,
getUserWalletUseCase = getUserWalletUseCase,
userWalletId = userWalletId,
@ -213,9 +212,8 @@ internal class TokenDetailsModel @Inject constructor(
// region Clore migration
// TODO: Remove after Clore migration ends ([REDACTED_TASK_KEY])
private val cloreMigrationModel by lazy(mode = LazyThreadSafetyMode.NONE) {
val cloreMigrationModel by lazy(mode = LazyThreadSafetyMode.NONE) {
CloreMigrationModel(
stateFactory = stateFactory,
signCloreMessageUseCase = signCloreMessageUseCase,
clipboardManager = clipboardManager,
uiMessageSender = uiMessageSender,
@ -224,7 +222,6 @@ internal class TokenDetailsModel @Inject constructor(
cryptoCurrency = cryptoCurrency,
coroutineScope = modelScope,
dispatchers = dispatchers,
onStateUpdate = { internalUiState.value = it },
)
}
// endregion
@ -791,8 +788,12 @@ internal class TokenDetailsModel @Inject constructor(
modelScope.launch(dispatchers.main) {
when (val addresses = currencyStatus.value.networkAddress) {
is NetworkAddress.Selectable -> {
internalUiState.value =
stateFactory.getStateWithChooseAddressBottomSheet(cryptoCurrency, addresses)
bottomSheetNavigation.activate(
configuration = TokenDetailsBottomSheetConfig.ChooseAddress(
currency = cryptoCurrency,
networkAddress = addresses,
),
)
}
is NetworkAddress.Single -> {
router.openUrl(
@ -810,7 +811,7 @@ internal class TokenDetailsModel @Inject constructor(
private fun showErrorIfDemoModeOrElse(action: () -> Unit) {
if (userWallet is UserWallet.Cold && isDemoCardUseCase(cardId = userWallet.cardId)) {
internalUiState.value = stateFactory.getStateWithClosedBottomSheet()
bottomSheetNavigation.dismiss()
uiMessageSender.send(
message = SnackbarMessage(message = resourceReference(R.string.alert_demo_feature_disabled)),
@ -829,7 +830,7 @@ internal class TokenDetailsModel @Inject constructor(
addressType = AddressType.valueOf(addressModel.type.name),
),
)
internalUiState.value = stateFactory.getStateWithClosedBottomSheet()
bottomSheetNavigation.dismiss()
}
}
@ -1343,7 +1344,12 @@ internal class TokenDetailsModel @Inject constructor(
// region Clore migration
// TODO: Remove after Clore migration ends ([REDACTED_TASK_KEY])
override fun onCloreMigrationClick() = cloreMigrationModel.onCloreMigrationClick()
override fun onCloreMigrationClick() {
cloreMigrationModel.onCloreMigrationClick()
bottomSheetNavigation.activate(
configuration = TokenDetailsBottomSheetConfig.CloreMigration,
)
}
override fun onCloreSignMessage(message: String) = cloreMigrationModel.onCloreSignMessage(message)

View file

@ -3,6 +3,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.route
import com.tangem.core.decompose.navigation.Route
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.tokens.model.details.TokenAction
import kotlinx.serialization.Serializable
@ -17,4 +18,13 @@ sealed class TokenDetailsBottomSheetConfig : Route {
val cryptoCurrency: CryptoCurrency,
val tokenAction: TokenAction,
) : TokenDetailsBottomSheetConfig()
@Serializable
data class ChooseAddress(
val currency: CryptoCurrency,
val networkAddress: NetworkAddress,
) : TokenDetailsBottomSheetConfig()
@Serializable
data object CloreMigration : TokenDetailsBottomSheetConfig()
}

View file

@ -1,8 +1,6 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
import arrow.core.Either
import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.dropdownmenu.TangemDropdownMenuItem
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
@ -12,7 +10,6 @@ import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.staking.model.StakingAvailability
@ -23,13 +20,11 @@ import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.wallets.usecase.NetworkHasDerivationUseCase
import com.tangem.domain.yield.supply.models.YieldSupplyRewardBalance
import com.tangem.feature.tokendetails.presentation.tokendetails.model.ExpressTransactionsClickIntents
import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenBalanceSegmentedButtonConfig
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsAppBarMenuConfig
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsBalanceBlockState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.clore.CloreMigrationBottomSheetConfig
import com.tangem.features.tokendetails.impl.R
import com.tangem.utils.Provider
import kotlinx.collections.immutable.toImmutableList
@ -40,7 +35,6 @@ internal class TokenDetailsStateFactory(
private val appCurrencyProvider: Provider<AppCurrency>,
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
private val tokenDetailsClickIntents: TokenDetailsClickIntents,
private val expressTransactionsClickIntents: ExpressTransactionsClickIntents,
private val networkHasDerivationUseCase: NetworkHasDerivationUseCase,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val userWalletId: UserWalletId,
@ -129,23 +123,6 @@ internal class TokenDetailsStateFactory(
return refreshStateConverter.convert(false)
}
fun getStateWithChooseAddressBottomSheet(
currency: CryptoCurrency,
networkAddress: NetworkAddress,
): TokenDetailsState {
return currentStateProvider().copy(
bottomSheetConfig = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = expressTransactionsClickIntents::onDismissBottomSheet,
content = ChooseAddressBottomSheetConfig(
currency = currency,
networkAddress = networkAddress,
onClick = tokenDetailsClickIntents::onAddressTypeSelected,
),
),
)
}
fun getStateWithClosedBottomSheet(): TokenDetailsState {
val state = currentStateProvider()
return state.copy(
@ -253,62 +230,4 @@ internal class TokenDetailsStateFactory(
}.toImmutableList(),
)
}
// region Clore migration
// TODO: Remove after Clore migration ends ([REDACTED_TASK_KEY])
fun getStateWithCloreMigrationBottomSheet(
message: String,
signature: String,
isSigningInProgress: Boolean,
onMessageChange: (String) -> Unit,
onSignClick: () -> Unit,
onCopyClick: () -> Unit,
onOpenPortalClick: () -> Unit,
): TokenDetailsState {
return currentStateProvider().copy(
bottomSheetConfig = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = expressTransactionsClickIntents::onDismissBottomSheet,
content = CloreMigrationBottomSheetConfig(
message = message,
signature = signature,
isSigningInProgress = isSigningInProgress,
onMessageChange = onMessageChange,
onSignClick = onSignClick,
onCopyClick = onCopyClick,
onOpenPortalClick = onOpenPortalClick,
),
),
)
}
fun getStateWithUpdatedCloreMigrationSignature(signature: String): TokenDetailsState {
val state = currentStateProvider()
val bottomSheetConfig = state.bottomSheetConfig ?: return state
val content = bottomSheetConfig.content as? CloreMigrationBottomSheetConfig ?: return state
return state.copy(
bottomSheetConfig = bottomSheetConfig.copy(
content = content.copy(
signature = signature,
isSigningInProgress = false,
),
),
)
}
fun getStateWithCloreMigrationSigning(): TokenDetailsState {
val state = currentStateProvider()
val bottomSheetConfig = state.bottomSheetConfig ?: return state
val content = bottomSheetConfig.content as? CloreMigrationBottomSheetConfig ?: return state
return state.copy(
bottomSheetConfig = bottomSheetConfig.copy(
content = content.copy(isSigningInProgress = true),
),
)
}
// endregion Clore migration
}

View file

@ -14,8 +14,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheet
import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheetConfig
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
import com.tangem.common.ui.expressStatus.expressTransactionsItems
import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshContainer
@ -32,8 +30,6 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.component
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsBalanceBlockLegacy
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsTopAppBar
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenInfoBlock
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.clore.CloreMigrationBottomSheet
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.clore.CloreMigrationBottomSheetConfig
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressStatusBottomSheet
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.staking.TokenStakingBlock
import com.tangem.features.markets.token.block.TokenMarketBlockComponent
@ -160,16 +156,8 @@ internal fun TokenDetailsScreenLegacy(
}
state.bottomSheetConfig?.let { config ->
when (config.content) {
is ChooseAddressBottomSheetConfig -> {
ChooseAddressBottomSheet(config = config)
}
is ExpressStatusBottomSheetConfig -> {
ExpressStatusBottomSheet(config = config)
}
is CloreMigrationBottomSheetConfig -> {
CloreMigrationBottomSheet(config = config)
}
if (config.content is ExpressStatusBottomSheetConfig) {
ExpressStatusBottomSheet(config = config)
}
}
}

View file

@ -0,0 +1,39 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheet
import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheetConfig
import com.tangem.common.ui.bottomsheet.receive.AddressModel
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.NetworkAddress
internal class ChooseAddressBottomSheetComponent(
private val currency: CryptoCurrency,
private val networkAddress: NetworkAddress,
private val onAddressSelected: (AddressModel) -> Unit,
private val onDismiss: () -> Unit,
) : ComposableBottomSheetComponent {
override fun dismiss() {
onDismiss()
}
@Composable
override fun BottomSheet() {
val config = remember(this) {
TangemBottomSheetConfig(
isShown = true,
onDismissRequest = ::dismiss,
content = ChooseAddressBottomSheetConfig(
currency = currency,
networkAddress = networkAddress,
onClick = onAddressSelected,
),
)
}
ChooseAddressBottomSheet(config = config)
}
}

View file

@ -0,0 +1,35 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.feature.tokendetails.presentation.tokendetails.model.CloreMigrationModel
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.clore.CloreMigrationBottomSheet
// TODO: Remove after Clore migration ends ([REDACTED_TASK_KEY])
internal class CloreMigrationBottomSheetComponent(
private val cloreMigrationModel: CloreMigrationModel,
private val onDismiss: () -> Unit,
) : ComposableBottomSheetComponent {
override fun dismiss() {
onDismiss()
}
@Composable
override fun BottomSheet() {
val content by cloreMigrationModel.bottomSheetConfig.collectAsStateWithLifecycle()
val config = remember(content) {
TangemBottomSheetConfig(
isShown = true,
onDismissRequest = ::dismiss,
content = content,
)
}
CloreMigrationBottomSheet(config = config)
}
}