Updated on 2026-08-14
This commit is contained in:
parent
e5a5d5ef97
commit
2e59b7e116
28 changed files with 802 additions and 38 deletions
|
|
@ -48,6 +48,7 @@ dependencies {
|
|||
/** Core modules */
|
||||
implementation(projects.common.routing)
|
||||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.res)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.core.analytics)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,113 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.model
|
||||
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.clipboard.ClipboardManager
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
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.features.tokendetails.impl.R
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
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,
|
||||
private val router: InnerTokenDetailsRouter,
|
||||
private val userWallet: UserWallet,
|
||||
private val cryptoCurrency: CryptoCurrency,
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val onStateUpdate: (TokenDetailsState) -> Unit,
|
||||
) {
|
||||
|
||||
private val state = MutableStateFlow(CloreMigrationState())
|
||||
|
||||
fun onCloreMigrationClick() {
|
||||
state.value = CloreMigrationState()
|
||||
updateBottomSheet()
|
||||
}
|
||||
|
||||
fun onCloreSignMessage(message: String) {
|
||||
if (message.isBlank()) return
|
||||
|
||||
state.value = state.value.copy(message = message)
|
||||
updateBottomSheet(isSigningInProgress = true)
|
||||
|
||||
coroutineScope.launch(dispatchers.main) {
|
||||
signCloreMessageUseCase(
|
||||
userWallet = userWallet,
|
||||
currency = cryptoCurrency,
|
||||
message = message,
|
||||
).fold(
|
||||
ifLeft = { error ->
|
||||
val errorMessage = when (error) {
|
||||
is SignCloreMessageError.SigningFailed -> stringReference(error.message)
|
||||
SignCloreMessageError.MessageSigningNotSupported ->
|
||||
resourceReference(R.string.warning_clore_migration_error_signing_not_supported)
|
||||
SignCloreMessageError.WalletManagerNotFound ->
|
||||
resourceReference(R.string.warning_clore_migration_error_wallet_manager_not_found)
|
||||
}
|
||||
state.value = state.value.copy(signature = "")
|
||||
updateBottomSheet()
|
||||
uiMessageSender.send(SnackbarMessage(errorMessage))
|
||||
},
|
||||
ifRight = { signature ->
|
||||
state.value = state.value.copy(signature = signature)
|
||||
onStateUpdate(stateFactory.getStateWithUpdatedCloreMigrationSignature(signature))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onOpenCloreClaimPortal() {
|
||||
router.openUrl(CLAIM_PORTAL_URL)
|
||||
}
|
||||
|
||||
private fun updateBottomSheet(isSigningInProgress: Boolean = false) {
|
||||
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() },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val CLAIM_PORTAL_URL = "https://claim-portal.clore.ai/"
|
||||
}
|
||||
}
|
||||
|
||||
private data class CloreMigrationState(
|
||||
val message: String = "",
|
||||
val signature: String = "",
|
||||
)
|
||||
|
|
@ -72,6 +72,19 @@ interface TokenDetailsClickIntents {
|
|||
|
||||
fun onBalanceSelect(config: TokenBalanceSegmentedButtonConfig)
|
||||
|
||||
fun onYieldInfoClick()
|
||||
|
||||
// region Clore migration
|
||||
// TODO: Remove after 2025-04-01 when Clore migration ends ([REDACTED_TASK_KEY])
|
||||
|
||||
fun onCloreMigrationClick()
|
||||
|
||||
fun onCloreSignMessage(message: String)
|
||||
|
||||
fun onOpenCloreClaimPortal()
|
||||
|
||||
// endregion Clore migration
|
||||
|
||||
fun onGoToRefundedTokenClick(cryptoCurrency: CryptoCurrency)
|
||||
|
||||
fun onOpenUrlClick(url: String)
|
||||
|
|
@ -79,6 +92,4 @@ interface TokenDetailsClickIntents {
|
|||
fun onConfirmDisposeExpressStatus()
|
||||
|
||||
fun onDisposeExpressStatus()
|
||||
|
||||
fun onYieldInfoClick()
|
||||
}
|
||||
|
|
@ -155,13 +155,17 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
private val getAccountCryptoCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
|
||||
private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase,
|
||||
private val yieldSupplyGetRewardsBalanceUseCase: YieldSupplyGetRewardsBalanceUseCase,
|
||||
) : Model(), TokenDetailsClickIntents, YieldSupplyDepositedWarningComponent.ModelCallback {
|
||||
private val signCloreMessageUseCase: SignCloreMessageUseCase,
|
||||
) : Model(),
|
||||
TokenDetailsClickIntents,
|
||||
YieldSupplyDepositedWarningComponent.ModelCallback {
|
||||
|
||||
private val params = paramsContainer.require<TokenDetailsComponent.Params>()
|
||||
private val userWalletId: UserWalletId = params.userWalletId
|
||||
private val cryptoCurrency: CryptoCurrency = params.currency
|
||||
|
||||
private val userWallet: UserWallet = getUserWalletUseCase(userWalletId).getOrNull() ?: error("UserWallet not found")
|
||||
private val userWallet: UserWallet = getUserWalletUseCase(userWalletId).getOrNull()
|
||||
?: error("UserWallet not found")
|
||||
|
||||
private val marketPriceJobHolder = JobHolder()
|
||||
private val refreshStateJobHolder = JobHolder()
|
||||
|
|
@ -193,6 +197,27 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
yieldSupplyFeatureToggles = yieldSupplyFeatureToggles,
|
||||
)
|
||||
|
||||
private val internalUiState = MutableStateFlow(stateFactory.getInitialState(cryptoCurrency))
|
||||
val uiState: StateFlow<TokenDetailsState> = internalUiState
|
||||
|
||||
// region Clore migration
|
||||
// TODO: Remove after Clore migration ends ([REDACTED_TASK_KEY])
|
||||
private val cloreMigrationModel by lazy(mode = LazyThreadSafetyMode.NONE) {
|
||||
CloreMigrationModel(
|
||||
stateFactory = stateFactory,
|
||||
signCloreMessageUseCase = signCloreMessageUseCase,
|
||||
clipboardManager = clipboardManager,
|
||||
uiMessageSender = uiMessageSender,
|
||||
router = router,
|
||||
userWallet = userWallet,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
coroutineScope = modelScope,
|
||||
dispatchers = dispatchers,
|
||||
onStateUpdate = { internalUiState.value = it },
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
||||
private val expressStatusFactory by lazy(mode = LazyThreadSafetyMode.NONE) {
|
||||
expressStatusFactory.create(
|
||||
clickIntents = this,
|
||||
|
|
@ -215,9 +240,6 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
TokenDetailsCurrencyStatusAnalyticsSender(analyticsEventsHandler)
|
||||
}
|
||||
|
||||
private val internalUiState = MutableStateFlow(stateFactory.getInitialState(cryptoCurrency))
|
||||
val uiState: StateFlow<TokenDetailsState> = internalUiState
|
||||
|
||||
init {
|
||||
updateTopBarMenu()
|
||||
initButtons()
|
||||
|
|
@ -666,9 +688,8 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
vibratorHapticManager.performOneTime(TangemHapticEffect.OneTime.Click)
|
||||
clipboardManager.setText(text = extendedKey, isSensitive = true)
|
||||
|
||||
uiMessageSender.send(
|
||||
message = SnackbarMessage(message = resourceReference(R.string.wallet_notification_address_copied)),
|
||||
)
|
||||
val message = resourceReference(R.string.wallet_notification_address_copied)
|
||||
uiMessageSender.send(message = SnackbarMessage(message = message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -785,7 +806,8 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
modelScope.launch(dispatchers.main) {
|
||||
when (val addresses = currencyStatus.value.networkAddress) {
|
||||
is NetworkAddress.Selectable -> {
|
||||
internalUiState.value = stateFactory.getStateWithChooseAddressBottomSheet(cryptoCurrency, addresses)
|
||||
internalUiState.value =
|
||||
stateFactory.getStateWithChooseAddressBottomSheet(cryptoCurrency, addresses)
|
||||
}
|
||||
is NetworkAddress.Single -> {
|
||||
router.openUrl(
|
||||
|
|
@ -1265,11 +1287,26 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun handleNavigationParam() {
|
||||
if (params.navigationAction is NavigationAction.Staking) {
|
||||
openStaking()
|
||||
when (params.navigationAction) {
|
||||
is NavigationAction.Staking -> openStaking()
|
||||
is NavigationAction.CloreMigration -> onCloreMigrationClick()
|
||||
is NavigationAction.YieldSupply,
|
||||
null,
|
||||
-> Unit
|
||||
}
|
||||
}
|
||||
|
||||
// region Clore migration
|
||||
// TODO: Remove after Clore migration ends ([REDACTED_TASK_KEY])
|
||||
|
||||
override fun onCloreMigrationClick() = cloreMigrationModel.onCloreMigrationClick()
|
||||
|
||||
override fun onCloreSignMessage(message: String) = cloreMigrationModel.onCloreSignMessage(message)
|
||||
|
||||
override fun onOpenCloreClaimPortal() = cloreMigrationModel.onOpenCloreClaimPortal()
|
||||
|
||||
// endregion Clore migration
|
||||
|
||||
private companion object {
|
||||
const val EXPRESS_STATUS_UPDATE_DELAY = 10_000L
|
||||
}
|
||||
|
|
|
|||
|
|
@ -258,9 +258,15 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) {
|
|||
subtitle = resourceReference(id = R.string.warning_matic_migration_message),
|
||||
)
|
||||
|
||||
data object MigrationClore : Warning(
|
||||
data class MigrationClore(
|
||||
private val onMigrationClick: () -> Unit,
|
||||
) : Warning(
|
||||
title = resourceReference(id = R.string.warning_clore_migration_title),
|
||||
subtitle = resourceReference(id = R.string.warning_clore_migration_message),
|
||||
subtitle = resourceReference(id = R.string.warning_clore_migration_sheet_description),
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(id = R.string.warning_clore_migration_button),
|
||||
onClick = onMigrationClick,
|
||||
),
|
||||
)
|
||||
|
||||
data object UsedOutdatedData : TokenDetailsNotification(
|
||||
|
|
|
|||
|
|
@ -155,7 +155,9 @@ internal class TokenDetailsNotificationConverter(
|
|||
},
|
||||
)
|
||||
is CryptoCurrencyWarning.MigrationMaticToPol -> MigrationMaticToPol
|
||||
is CryptoCurrencyWarning.MigrationClore -> MigrationClore
|
||||
is CryptoCurrencyWarning.MigrationClore -> MigrationClore(
|
||||
onMigrationClick = clickIntents::onCloreMigrationClick,
|
||||
)
|
||||
is CryptoCurrencyWarning.UsedOutdatedDataWarning -> UsedOutdatedData
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import arrow.core.Either
|
|||
import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheetConfig
|
||||
import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheetConfig
|
||||
import com.tangem.common.ui.tokens.getUnavailabilityReasonText
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.clore.CloreMigrationBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.dropdownmenu.TangemDropdownMenuItem
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
|
@ -378,4 +379,62 @@ 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 = clickIntents::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
|
||||
}
|
||||
|
|
@ -35,6 +35,8 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.T
|
|||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsDialogs
|
||||
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
|
||||
|
|
@ -45,7 +47,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
// TODO: Split to blocks [REDACTED_JIRA]
|
||||
@Suppress("LongMethod")
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
internal fun TokenDetailsScreen(
|
||||
state: TokenDetailsState,
|
||||
|
|
@ -164,17 +166,25 @@ internal fun TokenDetailsScreen(
|
|||
|
||||
TokenDetailsDialogs(state = state)
|
||||
|
||||
state.bottomSheetConfig?.let { config ->
|
||||
when (config.content) {
|
||||
is TokenReceiveBottomSheetConfig -> {
|
||||
TokenReceiveBottomSheet(config = config)
|
||||
}
|
||||
is ChooseAddressBottomSheetConfig -> {
|
||||
ChooseAddressBottomSheet(config = config)
|
||||
}
|
||||
is ExpressStatusBottomSheetConfig -> {
|
||||
ExpressStatusBottomSheet(config = config)
|
||||
}
|
||||
TokenDetailsBottomSheets(state = state)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenDetailsBottomSheets(state: TokenDetailsState) {
|
||||
state.bottomSheetConfig?.let { config ->
|
||||
when (config.content) {
|
||||
is TokenReceiveBottomSheetConfig -> {
|
||||
TokenReceiveBottomSheet(config = config)
|
||||
}
|
||||
is ChooseAddressBottomSheetConfig -> {
|
||||
ChooseAddressBottomSheet(config = config)
|
||||
}
|
||||
is ExpressStatusBottomSheetConfig -> {
|
||||
ExpressStatusBottomSheet(config = config)
|
||||
}
|
||||
is CloreMigrationBottomSheetConfig -> {
|
||||
CloreMigrationBottomSheet(config = config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,220 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.clore
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R as CoreR
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
|
||||
import com.tangem.core.ui.components.buttons.SecondarySmallButton
|
||||
import com.tangem.core.ui.components.buttons.SmallButtonConfig
|
||||
import com.tangem.core.ui.components.fields.SimpleTextField
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
|
||||
@Composable
|
||||
internal fun CloreMigrationBottomSheet(config: TangemBottomSheetConfig) {
|
||||
TangemModalBottomSheet<CloreMigrationBottomSheetConfig>(
|
||||
config = config,
|
||||
title = {
|
||||
TangemModalBottomSheetTitle(
|
||||
endIconRes = CoreR.drawable.ic_close_24,
|
||||
onEndClick = config.onDismissRequest,
|
||||
)
|
||||
},
|
||||
) { content: CloreMigrationBottomSheetConfig ->
|
||||
CloreMigrationBottomSheetContent(content = content)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CloreMigrationBottomSheetContent(content: CloreMigrationBottomSheetConfig) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 16.dp)
|
||||
.padding(horizontal = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(space = 16.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.warning_clore_migration_sheet_title),
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
style = TangemTheme.typography.h3,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.warning_clore_migration_sheet_description),
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
style = TangemTheme.typography.body2,
|
||||
modifier = Modifier.padding(horizontal = 8.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
MessageField(
|
||||
placeholder = stringResourceSafe(R.string.warning_clore_migration_message_label),
|
||||
value = content.message,
|
||||
onValueChange = content.onMessageChange,
|
||||
buttonText = stringResourceSafe(R.string.warning_clore_migration_sign_button),
|
||||
onButtonClick = content.onSignClick,
|
||||
isButtonEnabled = content.message.isNotBlank() && !content.isSigningInProgress,
|
||||
isLoading = content.isSigningInProgress,
|
||||
)
|
||||
|
||||
SignatureField(
|
||||
placeholder = stringResourceSafe(R.string.warning_clore_migration_signature_label),
|
||||
value = content.signature,
|
||||
onCopyClick = content.onCopyClick,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
PrimaryButton(
|
||||
text = stringResourceSafe(R.string.warning_clore_migration_open_portal_button),
|
||||
onClick = content.onOpenPortalClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
private fun MessageField(
|
||||
placeholder: String,
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
buttonText: String,
|
||||
onButtonClick: () -> Unit,
|
||||
isButtonEnabled: Boolean,
|
||||
isLoading: Boolean,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = TangemTheme.colors.stroke.primary,
|
||||
shape = TangemTheme.shapes.roundedCornersSmall,
|
||||
)
|
||||
.heightIn(min = 56.dp)
|
||||
.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
SimpleTextField(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
placeholder = TextReference.Str(placeholder),
|
||||
modifier = Modifier.weight(1f),
|
||||
singleLine = false,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
SecondarySmallButton(
|
||||
config = SmallButtonConfig(
|
||||
text = TextReference.Str(buttonText),
|
||||
onClick = onButtonClick,
|
||||
isEnabled = isButtonEnabled,
|
||||
isLoading = isLoading,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("FunctionSignature")
|
||||
@Composable
|
||||
private fun SignatureField(placeholder: String, value: String, onCopyClick: () -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = TangemTheme.colors.stroke.primary,
|
||||
shape = TangemTheme.shapes.roundedCornersSmall,
|
||||
)
|
||||
.heightIn(min = 56.dp)
|
||||
.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
SimpleTextField(
|
||||
value = value,
|
||||
onValueChange = {},
|
||||
placeholder = TextReference.Str(placeholder),
|
||||
modifier = Modifier.weight(1f),
|
||||
readOnly = true,
|
||||
singleLine = false,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
SecondarySmallButton(
|
||||
config = SmallButtonConfig(
|
||||
text = TextReference.Res(R.string.warning_clore_migration_copy_button),
|
||||
onClick = onCopyClick,
|
||||
isEnabled = value.isNotBlank(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun Preview_CloreMigrationBottomSheet() {
|
||||
TangemThemePreview {
|
||||
val config = TangemBottomSheetConfig(
|
||||
isShown = true,
|
||||
content = CloreMigrationBottomSheetConfig(
|
||||
message = "Claim request for CLORE tokens to Ethereum address " +
|
||||
"0x742d35Cc6634C0532925a3b844Bc9e7595f60126 from CEsMERNgBVPo9Dkc99pRDMPD3mxwPMxHZi",
|
||||
signature = "H4sIAAAAAAAEAGNgGAWjYBSMglEwCkYBHQEA",
|
||||
isSigningInProgress = false,
|
||||
onMessageChange = {},
|
||||
onSignClick = {},
|
||||
onCopyClick = {},
|
||||
onOpenPortalClick = {},
|
||||
),
|
||||
onDismissRequest = {},
|
||||
)
|
||||
|
||||
CloreMigrationBottomSheet(config)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
private fun Preview_CloreMigrationBottomSheet_Empty() {
|
||||
TangemThemePreview {
|
||||
val config = TangemBottomSheetConfig(
|
||||
isShown = true,
|
||||
content = CloreMigrationBottomSheetConfig(
|
||||
message = "",
|
||||
signature = "",
|
||||
isSigningInProgress = false,
|
||||
onMessageChange = {},
|
||||
onSignClick = {},
|
||||
onCopyClick = {},
|
||||
onOpenPortalClick = {},
|
||||
),
|
||||
onDismissRequest = {},
|
||||
)
|
||||
|
||||
CloreMigrationBottomSheet(config)
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.clore
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
|
||||
@Immutable
|
||||
data class CloreMigrationBottomSheetConfig(
|
||||
val message: String = "",
|
||||
val signature: String = "",
|
||||
val isSigningInProgress: Boolean = false,
|
||||
val onMessageChange: (String) -> Unit,
|
||||
val onSignClick: () -> Unit,
|
||||
val onCopyClick: () -> Unit,
|
||||
val onOpenPortalClick: () -> Unit,
|
||||
) : TangemBottomSheetConfigContent
|
||||
|
|
@ -202,6 +202,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
navigationAction = navigationAction,
|
||||
apy = apy,
|
||||
)
|
||||
is NavigationAction.CloreMigration -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -298,6 +299,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
},
|
||||
)
|
||||
}
|
||||
is NavigationAction.CloreMigration -> return
|
||||
}
|
||||
analyticsEventHandler.send(event)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
|||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.requireColdWallet
|
||||
|
|
@ -35,6 +36,7 @@ import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
|
|||
import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent
|
||||
import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent.Program
|
||||
import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent.PromotionBannerClicked
|
||||
import com.tangem.domain.tokens.model.details.NavigationAction
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType
|
||||
import com.tangem.domain.wallets.models.UnlockWalletsError
|
||||
import com.tangem.domain.wallets.usecase.*
|
||||
|
|
@ -65,6 +67,8 @@ internal interface WalletWarningsClickIntents {
|
|||
|
||||
fun onAddBackupCardClick()
|
||||
|
||||
fun onCloreMigrationClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onCloseAlreadySignedHashesWarningClick()
|
||||
|
||||
fun onGenerateMissedAddressesClick(missedAddressCurrencies: List<CryptoCurrency>)
|
||||
|
|
@ -146,6 +150,16 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
|||
prepareAndStartOnboardingProcess()
|
||||
}
|
||||
|
||||
override fun onCloreMigrationClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
val userWallet = getSelectedUserWallet() ?: return
|
||||
|
||||
router.openTokenDetails(
|
||||
userWalletId = userWallet.walletId,
|
||||
currencyStatus = cryptoCurrencyStatus,
|
||||
navigationAction = NavigationAction.CloreMigration,
|
||||
)
|
||||
}
|
||||
|
||||
private fun prepareAndStartOnboardingProcess() {
|
||||
modelScope.launch(dispatchers.main) {
|
||||
getSelectedUserWallet()?.requireColdWallet()?.let {
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
|
|||
is WalletNotification.UsedOutdatedData,
|
||||
is WalletNotification.UnlockVisaAccess,
|
||||
is WalletNotification.Warning.YeildSupplyApprove, // TODO apply correct event
|
||||
is WalletNotification.CloreMigration,
|
||||
-> null
|
||||
is WalletNotification.FinishWalletActivation -> {
|
||||
val activationState = if (warning.isBackupExists) {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import com.tangem.feature.wallet.impl.R
|
|||
import com.tangem.feature.wallet.presentation.account.AccountDependencies
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
|
||||
import com.tangem.hot.sdk.model.HotWalletId
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.utils.extensions.addIf
|
||||
import com.tangem.utils.extensions.isPositive
|
||||
import com.tangem.utils.extensions.orZero
|
||||
|
|
@ -317,6 +318,29 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
element = WalletNotification.Warning.SomeNetworksUnreachable,
|
||||
condition = flattenCurrencies.hasUnreachableNetworks(),
|
||||
)
|
||||
|
||||
addCloreMigrationNotification(flattenCurrencies, clickIntents)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addCloreMigrationNotification(
|
||||
flattenCurrencies: Lce<TokenListError, List<CryptoCurrencyStatus>>,
|
||||
clickIntents: WalletClickIntents,
|
||||
) {
|
||||
val cloreCurrency = flattenCurrencies.findCloreCurrency() ?: return
|
||||
|
||||
add(
|
||||
WalletNotification.CloreMigration(
|
||||
onStartMigrationClick = { clickIntents.onCloreMigrationClick(cloreCurrency) },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun Lce<TokenListError, List<CryptoCurrencyStatus>>.findCloreCurrency(): CryptoCurrencyStatus? {
|
||||
val currencies = getOrNull(isPartialContentAccepted = true) ?: return null
|
||||
|
||||
return currencies.find { currencyStatus ->
|
||||
BlockchainUtils.isClore(currencyStatus.currency.network.rawId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addPushReminderNotification(
|
||||
|
|
|
|||
|
|
@ -414,4 +414,18 @@ sealed class WalletNotification(val config: NotificationConfig) {
|
|||
iconSize = 54.dp,
|
||||
),
|
||||
)
|
||||
|
||||
data class CloreMigration(
|
||||
val onStartMigrationClick: () -> Unit,
|
||||
) : WalletNotification(
|
||||
config = NotificationConfig(
|
||||
title = resourceReference(com.tangem.core.res.R.string.warning_clore_migration_title),
|
||||
subtitle = resourceReference(com.tangem.core.res.R.string.warning_clore_migration_sheet_description),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(com.tangem.core.res.R.string.warning_clore_migration_button),
|
||||
onClick = onStartMigrationClick,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue