Updated on 2026-08-14
This commit is contained in:
commit
8eb1a28f29
236 changed files with 3852 additions and 2068 deletions
|
|
@ -28,7 +28,7 @@ interface PortfolioFetcher {
|
|||
val walletBalance: Lce<TokenListError, TotalFiatBalance>,
|
||||
val accountsBalance: AccountStatusList,
|
||||
) {
|
||||
val userWallet: UserWallet get() = accountsBalance.userWallet
|
||||
val userWalletId: UserWalletId get() = accountsBalance.userWalletId
|
||||
}
|
||||
|
||||
sealed interface Mode {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@ import com.tangem.core.ui.message.DialogMessage
|
|||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.core.ui.message.ToastMessage
|
||||
import com.tangem.domain.account.usecase.ArchiveCryptoPortfolioUseCase
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.features.account.AccountDetailsComponent
|
||||
import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.portfolioIcon
|
||||
import com.tangem.features.account.details.entity.AccountDetailsUM
|
||||
|
|
@ -30,6 +33,7 @@ internal class AccountDetailsModel @Inject constructor(
|
|||
private val router: Router,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val archiveCryptoPortfolioUseCase: ArchiveCryptoPortfolioUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<AccountDetailsComponent.Params>()
|
||||
|
|
@ -42,8 +46,11 @@ internal class AccountDetailsModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun onManageTokensClick() {
|
||||
// todo account add account param
|
||||
router.push(AppRoute.ManageTokens(source = AppRoute.ManageTokens.Source.SETTINGS))
|
||||
val route = AppRoute.ManageTokens(
|
||||
source = AppRoute.ManageTokens.Source.SETTINGS,
|
||||
portfolioId = PortfolioId(params.account.accountId),
|
||||
)
|
||||
router.push(route)
|
||||
}
|
||||
|
||||
private fun onArchiveAccountClick() {
|
||||
|
|
@ -89,6 +96,8 @@ internal class AccountDetailsModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
val isMultiCurrency = getUserWalletUseCase(params.account.accountId.userWalletId)
|
||||
.getOrNull()?.isMultiCurrency ?: false
|
||||
return AccountDetailsUM(
|
||||
accountName = params.account.accountName.toUM().value,
|
||||
accountIcon = params.account.portfolioIcon.toUM(),
|
||||
|
|
@ -96,6 +105,7 @@ internal class AccountDetailsModel @Inject constructor(
|
|||
onAccountEditClick = ::onEditAccountClick,
|
||||
onManageTokensClick = ::onManageTokensClick,
|
||||
archiveMode = archiveMode,
|
||||
isManageTokensAvailable = isMultiCurrency,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ internal data class AccountDetailsUM(
|
|||
val accountName: TextReference,
|
||||
val accountIcon: CryptoPortfolioIconUM,
|
||||
val archiveMode: ArchiveMode,
|
||||
val isManageTokensAvailable: Boolean,
|
||||
val onCloseClick: () -> Unit,
|
||||
val onAccountEditClick: () -> Unit,
|
||||
val onManageTokensClick: () -> Unit,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import com.tangem.common.ui.R
|
|||
import com.tangem.common.ui.account.AccountIconPreviewData
|
||||
import com.tangem.common.ui.account.AccountRow
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.components.buttons.SecondarySmallButton
|
||||
import com.tangem.core.ui.components.buttons.SmallButtonConfig
|
||||
|
|
@ -49,6 +48,7 @@ internal fun AccountDetailsContent(state: AccountDetailsUM, modifier: Modifier =
|
|||
)
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
|
|
@ -61,21 +61,22 @@ internal fun AccountDetailsContent(state: AccountDetailsUM, modifier: Modifier =
|
|||
style = TangemTheme.typography.h1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
SpacerH16()
|
||||
AccountRow(state)
|
||||
SpacerH16()
|
||||
ManageTokensRow(state)
|
||||
if (state.isManageTokensAvailable) {
|
||||
ManageTokensRow(state)
|
||||
}
|
||||
when (state.archiveMode) {
|
||||
is AccountDetailsUM.ArchiveMode.Available -> {
|
||||
SpacerH16()
|
||||
ArchiveAccountRow(state.archiveMode)
|
||||
SpacerH(8.dp)
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
text = stringResourceSafe(R.string.account_details_archive_description),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
Column {
|
||||
ArchiveAccountRow(state.archiveMode)
|
||||
SpacerH(8.dp)
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
text = stringResourceSafe(R.string.account_details_archive_description),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
AccountDetailsUM.ArchiveMode.None -> Unit
|
||||
}
|
||||
|
|
@ -186,10 +187,12 @@ private class PreviewStateProvider : CollectionPreviewParameterProvider<AccountD
|
|||
),
|
||||
accountName = stringReference(accountName),
|
||||
accountIcon = portfolioIcon,
|
||||
isManageTokensAvailable = true,
|
||||
)
|
||||
add(first)
|
||||
portfolioIcon = AccountIconPreviewData.randomAccountIcon(letter = true)
|
||||
add(first.copy(accountIcon = portfolioIcon))
|
||||
add(first.copy(archiveMode = AccountDetailsUM.ArchiveMode.None))
|
||||
add(first.copy(isManageTokensAvailable = false))
|
||||
},
|
||||
)
|
||||
|
|
@ -8,6 +8,7 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.settings.CanUseBiometryUseCase
|
||||
import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository
|
||||
import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository.Attempts
|
||||
import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository.Companion.MAX_FAST_FORWARD_ATTEMPTS
|
||||
|
|
@ -31,6 +32,7 @@ internal class HotAccessCodeRequestModel @Inject constructor(
|
|||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val hotAccessCodeAttemptsRepository: HotWalletAccessCodeAttemptsRepository,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val canUseBiometryUseCase: CanUseBiometryUseCase,
|
||||
) : Model() {
|
||||
|
||||
private val result = MutableStateFlow<HotWalletPasswordRequester.Result?>(null)
|
||||
|
|
@ -60,7 +62,7 @@ internal class HotAccessCodeRequestModel @Inject constructor(
|
|||
it.copy(
|
||||
isShown = true,
|
||||
accessCode = "",
|
||||
useBiometricVisible = attemptRequest.hasBiometry,
|
||||
useBiometricVisible = attemptRequest.isBiometryButtonVisible(),
|
||||
onAccessCodeChange = ::onAccessCodeChange,
|
||||
)
|
||||
}
|
||||
|
|
@ -83,7 +85,7 @@ internal class HotAccessCodeRequestModel @Inject constructor(
|
|||
it.copy(
|
||||
accessCodeColor = PinTextColor.WrongCode,
|
||||
onAccessCodeChange = {},
|
||||
useBiometricVisible = currentRequest.hasBiometry,
|
||||
useBiometricVisible = currentRequest.isBiometryButtonVisible(),
|
||||
)
|
||||
}
|
||||
delay(timeMillis = 500) // Delay to show the wrong access code state
|
||||
|
|
@ -212,6 +214,9 @@ internal class HotAccessCodeRequestModel @Inject constructor(
|
|||
dismiss()
|
||||
}
|
||||
|
||||
private suspend fun HotWalletPasswordRequester.AttemptRequest.isBiometryButtonVisible(): Boolean =
|
||||
hasBiometry && canUseBiometryUseCase()
|
||||
|
||||
private fun dismissState() {
|
||||
uiState.update {
|
||||
it.copy(isShown = false)
|
||||
|
|
|
|||
|
|
@ -2,21 +2,13 @@ package com.tangem.features.managetokens.component
|
|||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
interface ManageTokensComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(
|
||||
val mode: ManageTokensMode,
|
||||
val source: ManageTokensSource,
|
||||
) {
|
||||
constructor(userWalletId: UserWalletId?, source: ManageTokensSource) : this(
|
||||
source = source,
|
||||
mode = userWalletId
|
||||
?.let { ManageTokensMode.Wallet(userWalletId) }
|
||||
?: ManageTokensMode.None,
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, ManageTokensComponent>
|
||||
}
|
||||
|
|
@ -17,6 +17,13 @@ sealed interface ManageTokensMode {
|
|||
}
|
||||
|
||||
sealed interface AddCustomTokenMode {
|
||||
data class Wallet(val userWalletId: UserWalletId) : AddCustomTokenMode
|
||||
|
||||
val userWalletId: UserWalletId
|
||||
get() = when (this) {
|
||||
is Account -> accountId.userWalletId
|
||||
is Wallet -> userWalletId
|
||||
}
|
||||
|
||||
data class Wallet(override val userWalletId: UserWalletId) : AddCustomTokenMode
|
||||
data class Account(val accountId: AccountId) : AddCustomTokenMode
|
||||
}
|
||||
|
|
@ -25,6 +25,8 @@ dependencies {
|
|||
implementation(projects.common.ui)
|
||||
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.account.status)
|
||||
implementation(projects.domain.account)
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.manageTokens)
|
||||
|
|
@ -35,6 +37,14 @@ dependencies {
|
|||
implementation(projects.domain.swap.models)
|
||||
implementation(projects.domain.notifications)
|
||||
|
||||
// region Project - Libs
|
||||
implementation(projects.libs.crypto)
|
||||
// endregion
|
||||
|
||||
// region Tangem SDKs
|
||||
implementation(tangemDeps.blockchain)
|
||||
// endregion
|
||||
|
||||
/* AndroidX */
|
||||
implementation(deps.androidx.activity.compose)
|
||||
implementation(deps.lifecycle.compose)
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ internal sealed class ManageTokensUM {
|
|||
isSavingInProgress: Boolean = this is ManageContent && this.isSavingInProgress,
|
||||
scrollToTop: StateEvent<Unit> = this.scrollToTop,
|
||||
needToInteractWithColdWallet: Boolean = this is ManageContent && this.needToInteractWithColdWallet,
|
||||
topBar: ManageTokensTopBarUM? = this.topBar,
|
||||
): ManageTokensUM {
|
||||
return when (this) {
|
||||
is ManageContent -> copy(
|
||||
|
|
@ -65,6 +66,7 @@ internal sealed class ManageTokensUM {
|
|||
isSavingInProgress = isSavingInProgress,
|
||||
scrollToTop = scrollToTop,
|
||||
needToInteractWithColdWallet = needToInteractWithColdWallet,
|
||||
topBar = topBar,
|
||||
)
|
||||
is ReadContent -> copy(
|
||||
search = search,
|
||||
|
|
@ -72,6 +74,7 @@ internal sealed class ManageTokensUM {
|
|||
isInitialBatchLoading = isInitialBatchLoading,
|
||||
isNextBatchLoading = isNextBatchLoading,
|
||||
scrollToTop = scrollToTop,
|
||||
topBar = topBar,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,22 @@ package com.tangem.features.managetokens.model
|
|||
import arrow.core.getOrElse
|
||||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.ui.account.toUM
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.managetokens.GetSupportedNetworksUseCase
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.features.managetokens.component.AddCustomTokenMode
|
||||
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent
|
||||
|
|
@ -25,10 +34,12 @@ import com.tangem.features.managetokens.entity.item.SelectableItemUM
|
|||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.features.managetokens.utils.mapper.toCurrencyNetworkModel
|
||||
import com.tangem.features.managetokens.utils.mapper.toDerivationPathModel
|
||||
import com.tangem.lib.crypto.derivation.AccountNodeRecognizer
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
|
@ -38,6 +49,8 @@ internal class CustomTokenSelectorModel @Inject constructor(
|
|||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val getSupportedNetworksUseCase: GetSupportedNetworksUseCase,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
|
|
@ -146,9 +159,8 @@ internal class CustomTokenSelectorModel @Inject constructor(
|
|||
return derivationPaths
|
||||
}
|
||||
|
||||
private suspend fun getSupportedNetworks(mode: AddCustomTokenMode): List<Network> = when (mode) {
|
||||
is AddCustomTokenMode.Account -> TODO("Account")
|
||||
is AddCustomTokenMode.Wallet -> getSupportedNetworksUseCase(mode.userWalletId).getOrElse { e ->
|
||||
private suspend fun getSupportedNetworks(mode: AddCustomTokenMode): List<Network> {
|
||||
return getSupportedNetworksUseCase(mode.userWalletId).getOrElse { e ->
|
||||
val message = SnackbarMessage(message = resourceReference(R.string.common_unknown_error))
|
||||
messageSender.send(message)
|
||||
|
||||
|
|
@ -168,7 +180,60 @@ internal class CustomTokenSelectorModel @Inject constructor(
|
|||
fun selectCustomDerivationPath(value: SelectedDerivationPath) {
|
||||
when (params) {
|
||||
is NetworkSelector -> return
|
||||
is DerivationPathSelector -> params.onDerivationPathSelected(value)
|
||||
is DerivationPathSelector -> if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
params.checkAccountDerivation(value)
|
||||
} else {
|
||||
params.onDerivationPathSelected(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun DerivationPathSelector.checkAccountDerivation(derivationPath: SelectedDerivationPath) =
|
||||
modelScope.launch {
|
||||
val accountName = derivationPath.id
|
||||
?.let { Blockchain.fromId(it.rawId.value) }?.let(::AccountNodeRecognizer)
|
||||
?.let { recognizer -> derivationPath.value.value?.let { recognizer.recognize(it) } }
|
||||
?.let { accountNode ->
|
||||
fun AccountStatus.CryptoPortfolio.sameNodeAndNotMain() = !this.account.isMainAccount &&
|
||||
this.account.derivationIndex.value.toLong() == accountNode
|
||||
|
||||
val accounts = singleAccountStatusListSupplier(mode.userWalletId)
|
||||
.first().accountStatuses
|
||||
val account = accounts.find {
|
||||
when (it) {
|
||||
is AccountStatus.CryptoPortfolio -> it.sameNodeAndNotMain()
|
||||
}
|
||||
}
|
||||
val accountName = when (account) {
|
||||
is AccountStatus.CryptoPortfolio -> account.account.accountName.toUM()
|
||||
null -> null
|
||||
}
|
||||
accountName
|
||||
}
|
||||
|
||||
if (accountName == null) {
|
||||
onDerivationPathSelected(derivationPath)
|
||||
} else {
|
||||
showAccountNameExist(
|
||||
accountName = accountName.value,
|
||||
onClick = { onDerivationPathSelected(derivationPath) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showAccountNameExist(accountName: TextReference, onClick: () -> Unit) {
|
||||
val firstAction = EventMessageAction(
|
||||
title = resourceReference(R.string.common_got_it),
|
||||
onClick = onClick,
|
||||
)
|
||||
val dialogMessage = DialogMessage(
|
||||
title = resourceReference(R.string.custom_token_another_account_dialog_title),
|
||||
message = resourceReference(
|
||||
R.string.custom_token_another_account_dialog_description,
|
||||
wrappedList(accountName),
|
||||
),
|
||||
firstActionBuilder = { firstAction },
|
||||
)
|
||||
messageSender.send(dialogMessage)
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import com.tangem.core.ui.event.triggeredEvent
|
|||
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.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.features.managetokens.analytics.CustomTokenAnalyticsEvent
|
||||
import com.tangem.features.managetokens.analytics.ManageTokensAnalyticEvent
|
||||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||
|
|
@ -40,13 +41,14 @@ import kotlinx.coroutines.launch
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
@ModelScoped
|
||||
internal class ManageTokensModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
manageTokensListManagerFactory: ManageTokensListManager.Factory,
|
||||
manageTokensUseCasesFacadeFactory: ManageTokensUseCasesFacade.Factory,
|
||||
paramsContainer: ParamsContainer,
|
||||
|
|
@ -86,6 +88,7 @@ internal class ManageTokensModel @Inject constructor(
|
|||
modelScope.launch {
|
||||
manageTokensListManager.launchPagination(isCollapsed = true)
|
||||
}
|
||||
checkIsSupportAddCustomTokens()
|
||||
}
|
||||
|
||||
fun reloadList() {
|
||||
|
|
@ -105,16 +108,34 @@ internal class ManageTokensModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getTopBarInitialState(): ManageTokensTopBarUM = when (params.mode) {
|
||||
is ManageTokensMode.Wallet -> manageContentTopBar()
|
||||
is ManageTokensMode.Account -> ManageTokensTopBarUM.ReadContent(
|
||||
title = resourceReference(id = R.string.main_manage_tokens),
|
||||
onBackButtonClick = router::pop,
|
||||
)
|
||||
ManageTokensMode.None -> ManageTokensTopBarUM.ReadContent(
|
||||
title = resourceReference(R.string.common_search_tokens),
|
||||
onBackButtonClick = router::pop,
|
||||
)
|
||||
}
|
||||
|
||||
private fun manageContentTopBar() = ManageTokensTopBarUM.ManageContent(
|
||||
title = resourceReference(id = R.string.main_manage_tokens),
|
||||
onBackButtonClick = router::pop,
|
||||
endButton = TopAppBarButtonUM.Icon(
|
||||
iconRes = R.drawable.ic_plus_24,
|
||||
onClicked = ::navigateToAddCustomToken,
|
||||
),
|
||||
)
|
||||
|
||||
private fun createReadContentModel(): ManageTokensUM.ReadContent {
|
||||
return ManageTokensUM.ReadContent(
|
||||
popBack = router::pop,
|
||||
isInitialBatchLoading = true,
|
||||
isNextBatchLoading = false,
|
||||
items = getLoadingItems(),
|
||||
topBar = ManageTokensTopBarUM.ReadContent(
|
||||
title = resourceReference(R.string.common_search_tokens),
|
||||
onBackButtonClick = router::pop,
|
||||
),
|
||||
topBar = getTopBarInitialState(),
|
||||
search = SearchBarUM(
|
||||
placeholderText = resourceReference(R.string.common_search),
|
||||
query = "",
|
||||
|
|
@ -132,14 +153,7 @@ internal class ManageTokensModel @Inject constructor(
|
|||
isInitialBatchLoading = true,
|
||||
isNextBatchLoading = false,
|
||||
items = getLoadingItems(),
|
||||
topBar = ManageTokensTopBarUM.ManageContent(
|
||||
title = resourceReference(id = R.string.main_manage_tokens),
|
||||
onBackButtonClick = router::pop,
|
||||
endButton = TopAppBarButtonUM.Icon(
|
||||
iconRes = R.drawable.ic_plus_24,
|
||||
onClicked = ::navigateToAddCustomToken,
|
||||
),
|
||||
),
|
||||
topBar = getTopBarInitialState(),
|
||||
search = SearchBarUM(
|
||||
placeholderText = resourceReference(R.string.common_search),
|
||||
query = "",
|
||||
|
|
@ -174,6 +188,20 @@ internal class ManageTokensModel @Inject constructor(
|
|||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun checkIsSupportAddCustomTokens() {
|
||||
when (val mode = params.mode) {
|
||||
is ManageTokensMode.Account -> modelScope.launch {
|
||||
val mainAccount = singleAccountStatusListSupplier(mode.accountId.userWalletId).first().mainAccount
|
||||
if (mode.accountId == mainAccount.account.accountId) {
|
||||
state.update { it.copySealed(topBar = manageContentTopBar()) }
|
||||
}
|
||||
}
|
||||
ManageTokensMode.None,
|
||||
is ManageTokensMode.Wallet,
|
||||
-> Unit // use init state
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateItems(items: ImmutableList<CurrencyItemUM>) {
|
||||
val updatedState = state.updateAndGet { state ->
|
||||
state.copySealed(
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ import com.tangem.core.ui.utils.WindowInsetsZero
|
|||
import com.tangem.core.ui.utils.rememberHideKeyboardNestedScrollConnection
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||
import com.tangem.features.managetokens.component.ManageTokensMode
|
||||
import com.tangem.features.managetokens.component.ManageTokensSource
|
||||
import com.tangem.features.managetokens.component.preview.PreviewManageTokensComponent
|
||||
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
|
||||
|
|
@ -442,20 +443,23 @@ private class PreviewManageTokensComponentProvider : PreviewParameterProvider<Ma
|
|||
showTangemIcon = true,
|
||||
params = ManageTokensComponent.Params(
|
||||
source = ManageTokensSource.ONBOARDING,
|
||||
userWalletId = UserWalletId("0x"),
|
||||
mode = ManageTokensMode.Wallet(UserWalletId("0x")),
|
||||
),
|
||||
),
|
||||
PreviewManageTokensComponent(
|
||||
isLoading = false,
|
||||
showTangemIcon = true,
|
||||
params = ManageTokensComponent.Params(source = ManageTokensSource.ONBOARDING, userWalletId = null),
|
||||
params = ManageTokensComponent.Params(
|
||||
source = ManageTokensSource.ONBOARDING,
|
||||
mode = ManageTokensMode.None,
|
||||
),
|
||||
),
|
||||
PreviewManageTokensComponent(
|
||||
isLoading = false,
|
||||
showTangemIcon = false,
|
||||
params = ManageTokensComponent.Params(
|
||||
source = ManageTokensSource.ONBOARDING,
|
||||
userWalletId = UserWalletId("0x"),
|
||||
mode = ManageTokensMode.Wallet(UserWalletId("0x")),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ dependencies {
|
|||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.settings)
|
||||
implementation(projects.domain.transaction.models)
|
||||
implementation(projects.domain.account.status)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.onramp.selecttoken
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
|
|
@ -41,11 +42,13 @@ internal class DefaultOnrampOperationComponent @AssistedInject constructor(
|
|||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state = model.state.collectAsStateWithLifecycle()
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
val onrampTokenListState by onrampTokenListComponent.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
OnrampSelectToken(
|
||||
state = state.value,
|
||||
state = state,
|
||||
onrampTokenListComponent = onrampTokenListComponent,
|
||||
onrampTokenListState = onrampTokenListState,
|
||||
hotCryptoComponent = hotCryptoComponent,
|
||||
modifier = modifier,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,12 +21,14 @@ import com.tangem.features.onramp.hottokens.HotCryptoComponent
|
|||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.selecttoken.entity.OnrampOperationUM
|
||||
import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
internal fun OnrampSelectToken(
|
||||
state: OnrampOperationUM,
|
||||
onrampTokenListComponent: OnrampTokenListComponent,
|
||||
onrampTokenListState: TokenListUM,
|
||||
hotCryptoComponent: HotCryptoComponent?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
|
@ -50,8 +52,9 @@ internal fun OnrampSelectToken(
|
|||
)
|
||||
}
|
||||
|
||||
item(key = "token_list", contentType = "token_list") {
|
||||
onrampTokenListComponent.Content(
|
||||
with(onrampTokenListComponent) {
|
||||
content(
|
||||
uiState = onrampTokenListState,
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.onramp.swap
|
|||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
|
|
@ -54,12 +55,16 @@ internal class DefaultSwapSelectTokensComponent @AssistedInject constructor(
|
|||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state = model.state.collectAsStateWithLifecycle()
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
val fromTokensState by selectFromTokenListComponent.uiState.collectAsStateWithLifecycle()
|
||||
val toTokensState by selectToTokenListComponent.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
SwapSelectTokens(
|
||||
state = state.value,
|
||||
state = state,
|
||||
selectFromTokenListComponent = selectFromTokenListComponent,
|
||||
selectFromTokenListState = fromTokensState,
|
||||
selectToTokenListComponent = selectToTokenListComponent,
|
||||
selectToTokenListState = toTokensState,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,15 @@ package com.tangem.features.onramp.swap.availablepairs
|
|||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.decompose.ComposableListContentComponent
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/** Token list component that present list of available tokens for swap */
|
||||
@Stable
|
||||
internal interface AvailableSwapPairsComponent : ComposableContentComponent {
|
||||
internal interface AvailableSwapPairsComponent : ComposableListContentComponent<TokenListUM> {
|
||||
|
||||
/** Component factory */
|
||||
interface Factory : ComponentFactory<Params, AvailableSwapPairsComponent>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
package com.tangem.features.onramp.swap.availablepairs
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.onramp.swap.availablepairs.model.AvailableSwapPairsModel
|
||||
import com.tangem.features.onramp.tokenlist.ui.TokenList
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
import com.tangem.features.onramp.tokenlist.ui.onrampTokenList
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@Stable
|
||||
internal class DefaultAvailableSwapPairsComponent @AssistedInject constructor(
|
||||
|
|
@ -21,11 +21,11 @@ internal class DefaultAvailableSwapPairsComponent @AssistedInject constructor(
|
|||
|
||||
private val model: AvailableSwapPairsModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
override val uiState: StateFlow<TokenListUM>
|
||||
get() = model.state
|
||||
|
||||
TokenList(state = state, modifier = modifier)
|
||||
override fun LazyListScope.content(uiState: TokenListUM, modifier: Modifier) {
|
||||
onrampTokenList(state = uiState)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.features.onramp.swap.availablepairs.entity.converters
|
||||
|
||||
import com.tangem.common.ui.account.AccountCryptoPortfolioItemStateConverter
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class LoadingAccountTokenItemConverter(
|
||||
private val appCurrency: AppCurrency,
|
||||
) : Converter<AccountStatus.CryptoPortfolio, TokensListItemUM.Portfolio> {
|
||||
|
||||
override fun convert(value: AccountStatus.CryptoPortfolio): TokensListItemUM.Portfolio {
|
||||
val (account, currencies) = value
|
||||
|
||||
return TokensListItemUM.Portfolio(
|
||||
tokenItemUM = AccountCryptoPortfolioItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
account = account,
|
||||
onItemClick = null,
|
||||
).convert(TotalFiatBalance.Failed),
|
||||
isExpanded = true,
|
||||
isCollapsable = false,
|
||||
tokens = currencies.flattenCurrencies().map(LoadingTokenListItemConverter::convert).toPersistentList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package com.tangem.features.onramp.swap.availablepairs.entity.transformers
|
||||
|
||||
import com.tangem.common.ui.account.AccountCryptoPortfolioItemStateConverter
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMData
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer
|
||||
import com.tangem.features.onramp.tokenlist.entity.utils.OnrampTokenItemStateConverterFactory
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class SetNoAvailablePairsTransformerV2(
|
||||
private val appCurrency: AppCurrency,
|
||||
private val accountList: Map<Account.CryptoPortfolio, List<CryptoCurrencyStatus>>,
|
||||
private val isBalanceHidden: Boolean,
|
||||
private val isAccountsMode: Boolean,
|
||||
private val unavailableErrorText: TextReference,
|
||||
) : TokenListUMTransformer {
|
||||
private val unavailableConverter = OnrampTokenItemStateConverterFactory
|
||||
.createUnavailableItemConverterV2(appCurrency = appCurrency, unavailableErrorText = unavailableErrorText)
|
||||
|
||||
override fun transform(prevState: TokenListUM): TokenListUM {
|
||||
return prevState.copy(
|
||||
availableItems = persistentListOf(),
|
||||
unavailableItems = persistentListOf(),
|
||||
tokensListData = if (isAccountsMode) {
|
||||
TokenListUMData.AccountList(
|
||||
tokensList = accountList.map { (account, cryptoCurrencies) ->
|
||||
TokensListItemUM.Portfolio(
|
||||
tokenItemUM = AccountCryptoPortfolioItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
account = account,
|
||||
onItemClick = null,
|
||||
).convert(TotalFiatBalance.Failed),
|
||||
isExpanded = true,
|
||||
isCollapsable = false,
|
||||
tokens = unavailableConverter.convertList(cryptoCurrencies)
|
||||
.map(TokensListItemUM::Token)
|
||||
.toPersistentList(),
|
||||
)
|
||||
}.toPersistentList(),
|
||||
)
|
||||
} else {
|
||||
TokenListUMData.TokenList(
|
||||
tokensList = accountList.flatMap { (_, cryptoCurrencies) ->
|
||||
unavailableConverter.convertList(cryptoCurrencies)
|
||||
.map(TokensListItemUM::Token)
|
||||
}.toPersistentList(),
|
||||
)
|
||||
},
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
warning = NotificationUM.Warning.SwapNoAvailablePair,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,15 @@
|
|||
package com.tangem.features.onramp.swap.availablepairs.model
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.components.fields.InputManager
|
||||
import com.tangem.core.ui.extensions.capitalize
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
|
|
@ -15,6 +18,8 @@ import com.tangem.domain.core.utils.getOrElse
|
|||
import com.tangem.domain.core.utils.lceContent
|
||||
import com.tangem.domain.core.utils.lceError
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
|
|
@ -28,11 +33,13 @@ import com.tangem.features.onramp.swap.availablepairs.AvailableSwapPairsComponen
|
|||
import com.tangem.features.onramp.swap.availablepairs.entity.transformers.SetErrorWarningTransformer
|
||||
import com.tangem.features.onramp.swap.availablepairs.entity.transformers.SetLoadingTokenItemsTransformer
|
||||
import com.tangem.features.onramp.swap.availablepairs.entity.transformers.SetNoAvailablePairsTransformer
|
||||
import com.tangem.features.onramp.swap.availablepairs.entity.transformers.SetNoAvailablePairsTransformerV2
|
||||
import com.tangem.features.onramp.swap.entity.AccountAvailabilityUM
|
||||
import com.tangem.features.onramp.swap.entity.AccountCurrencyUM
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMController
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer
|
||||
import com.tangem.features.onramp.tokenlist.entity.transformer.SetNothingToFoundStateTransformer
|
||||
import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateTokenItemsTransformer
|
||||
import com.tangem.features.onramp.tokenlist.entity.transformer.*
|
||||
import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer
|
||||
import com.tangem.features.onramp.utils.UpdateSearchBarCallbacksTransformer
|
||||
import com.tangem.features.onramp.utils.UpdateSearchQueryTransformer
|
||||
|
|
@ -43,7 +50,7 @@ import javax.inject.Inject
|
|||
|
||||
private typealias AvailablePairsState = Lce<Throwable, List<SwapPairLeast>>
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
internal class AvailableSwapPairsModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
|
|
@ -53,7 +60,10 @@ internal class AvailableSwapPairsModel @Inject constructor(
|
|||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
private val getAvailablePairsUseCase: GetAvailablePairsUseCase,
|
||||
private val getWalletsUseCase: GetWalletsUseCase,
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
getWalletsUseCase: GetWalletsUseCase,
|
||||
) : Model() {
|
||||
|
||||
val state: StateFlow<TokenListUM> = tokenListUMController.state
|
||||
|
|
@ -62,13 +72,17 @@ internal class AvailableSwapPairsModel @Inject constructor(
|
|||
private val userWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }
|
||||
|
||||
private val tokenListFlow = getTokenListUseCaseFlow()
|
||||
|
||||
private val accountListFlow = getAccountListUseCaseFlow()
|
||||
private val availablePairsByNetworkFlow = MutableStateFlow<Map<LeastTokenInfo, AvailablePairsState>>(emptyMap())
|
||||
|
||||
init {
|
||||
initializeSearchBarCallbacks()
|
||||
if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
subscribeOnUpdateStateV2()
|
||||
} else {
|
||||
subscribeOnUpdateState()
|
||||
}
|
||||
|
||||
subscribeOnUpdateState()
|
||||
initializeSearchBarCallbacks()
|
||||
subscribeOnAvailablePairsUpdates()
|
||||
}
|
||||
|
||||
|
|
@ -79,12 +93,20 @@ internal class AvailableSwapPairsModel @Inject constructor(
|
|||
maybeTokenList.getOrElse(
|
||||
ifLoading = { it ?: TokenList.Empty },
|
||||
ifError = { TokenList.Empty },
|
||||
)
|
||||
.flattenCurrencies()
|
||||
).flattenCurrencies()
|
||||
}
|
||||
.shareIn(scope = modelScope, started = SharingStarted.Eagerly, replay = 1)
|
||||
}
|
||||
|
||||
private fun getAccountListUseCaseFlow(): SharedFlow<List<AccountStatus>> {
|
||||
return singleAccountStatusListSupplier(SingleAccountStatusListProducer.Params(params.userWalletId))
|
||||
.distinctUntilChanged()
|
||||
.map { accountStatusList ->
|
||||
accountStatusList.accountStatuses.toList()
|
||||
}.flowOn(dispatchers.default)
|
||||
.shareIn(scope = modelScope, started = SharingStarted.Eagerly, replay = 1)
|
||||
}
|
||||
|
||||
private fun initializeSearchBarCallbacks() {
|
||||
tokenListUMController.update(
|
||||
transformer = UpdateSearchBarCallbacksTransformer(
|
||||
|
|
@ -130,6 +152,53 @@ internal class AvailableSwapPairsModel @Inject constructor(
|
|||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun subscribeOnUpdateStateV2() {
|
||||
combine(
|
||||
flow = getAccountsAndModeFlow(),
|
||||
flow2 = getAppCurrencyAndBalanceHidingFlow(),
|
||||
flow3 = params.selectedStatus,
|
||||
flow4 = searchManager.query,
|
||||
flow5 = availablePairsByNetworkFlow
|
||||
.map { it[params.selectedStatus.value?.toLeastTokenInfo()] }
|
||||
.distinctUntilChanged(),
|
||||
) { accountListAndMode, appCurrencyAndBalanceHiding, selectedStatus, query, availablePairsState ->
|
||||
val (accountList, isAccountsMode) = accountListAndMode
|
||||
availablePairsState?.fold(
|
||||
ifLoading = {
|
||||
SetLoadingAccountTokenListTransformer(
|
||||
appCurrency = appCurrencyAndBalanceHiding.first,
|
||||
accountList = accountList,
|
||||
isAccountsMode = isAccountsMode,
|
||||
)
|
||||
},
|
||||
ifContent = { pairs ->
|
||||
handleContentStateV2(
|
||||
appCurrencyAndBalanceHiding = appCurrencyAndBalanceHiding,
|
||||
accountList = accountList,
|
||||
selectedStatus = selectedStatus,
|
||||
query = query,
|
||||
availablePairs = pairs,
|
||||
isAccountsMode = isAccountsMode,
|
||||
)
|
||||
},
|
||||
ifError = {
|
||||
handleErrorStateV2(
|
||||
cause = it,
|
||||
networkInfo = params.selectedStatus.value?.toLeastTokenInfo(),
|
||||
accountList = accountList,
|
||||
)
|
||||
},
|
||||
) ?: SetLoadingAccountTokenListTransformer(
|
||||
appCurrency = appCurrencyAndBalanceHiding.first,
|
||||
accountList = accountList,
|
||||
isAccountsMode = isAccountsMode,
|
||||
)
|
||||
}
|
||||
.onEach(tokenListUMController::update)
|
||||
.flowOn(dispatchers.default)
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun handleContentState(
|
||||
appCurrencyAndBalanceHiding: Pair<AppCurrency, Boolean>,
|
||||
currencies: List<CryptoCurrencyStatus>,
|
||||
|
|
@ -176,6 +245,53 @@ internal class AvailableSwapPairsModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleContentStateV2(
|
||||
appCurrencyAndBalanceHiding: Pair<AppCurrency, Boolean>,
|
||||
accountList: List<AccountStatus>,
|
||||
selectedStatus: CryptoCurrencyStatus?,
|
||||
query: String,
|
||||
availablePairs: List<SwapPairLeast>,
|
||||
isAccountsMode: Boolean,
|
||||
): TokenListUMTransformer {
|
||||
val (appCurrency, isBalanceHidden) = appCurrencyAndBalanceHiding
|
||||
|
||||
val filterByQueryAccountList = accountList.associate { accountStatus ->
|
||||
when (accountStatus) {
|
||||
is AccountStatus.CryptoPortfolio -> accountStatus.account to accountStatus.tokenList.flattenCurrencies()
|
||||
.filter { it.currency != selectedStatus?.currency }
|
||||
.filterByQuery(query = query)
|
||||
}
|
||||
}
|
||||
|
||||
if (availablePairs.isEmpty()) {
|
||||
return SetNoAvailablePairsTransformerV2(
|
||||
appCurrency = appCurrency,
|
||||
accountList = filterByQueryAccountList,
|
||||
unavailableErrorText = resourceReference(R.string.tokens_list_unavailable_to_swap_source_header),
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
isAccountsMode = isAccountsMode,
|
||||
)
|
||||
}
|
||||
|
||||
return if (query.isNotEmpty() && filterByQueryAccountList.isEmpty()) {
|
||||
SetNothingToFoundStateTransformerV2(
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
emptySearchMessageReference = resourceReference(
|
||||
id = R.string.action_buttons_swap_empty_search_message,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
UpdateAccountTokenListTransformer(
|
||||
appCurrency = appCurrency,
|
||||
onItemClick = params.onTokenClick,
|
||||
accountList = filterByQueryAccountList.filterByAvailability(availablePairs = availablePairs),
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
unavailableErrorText = resourceReference(R.string.tokens_list_unavailable_to_swap_source_header),
|
||||
isAccountsMode = isAccountsMode,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleErrorState(
|
||||
cause: Throwable,
|
||||
networkInfo: LeastTokenInfo?,
|
||||
|
|
@ -193,6 +309,26 @@ internal class AvailableSwapPairsModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun handleErrorStateV2(
|
||||
cause: Throwable,
|
||||
networkInfo: LeastTokenInfo?,
|
||||
accountList: List<AccountStatus>,
|
||||
): SetErrorWarningTransformer {
|
||||
return SetErrorWarningTransformer(
|
||||
cause = cause,
|
||||
onRefresh = {
|
||||
modelScope.launch {
|
||||
if (networkInfo != null) {
|
||||
accountList.filterIsInstance<AccountStatus.CryptoPortfolio>()
|
||||
.forEach { (_, currencies) ->
|
||||
updateAvailablePairs(networkInfo, currencies.flattenCurrencies())
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun subscribeOnAvailablePairsUpdates() {
|
||||
modelScope.launch {
|
||||
params.selectedStatus
|
||||
|
|
@ -203,9 +339,19 @@ internal class AvailableSwapPairsModel @Inject constructor(
|
|||
val isAlreadyLoaded = availablePairsByNetworkFlow.value[networkInfo]?.isContent() == true
|
||||
if (isAlreadyLoaded) return@collectLatest
|
||||
|
||||
val statuses = tokenListFlow.firstOrNull() ?: return@collectLatest
|
||||
|
||||
updateAvailablePairs(networkInfo = networkInfo, statuses = statuses)
|
||||
if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
val accountList = accountListFlow.firstOrNull() ?: return@collectLatest
|
||||
updateAvailablePairs(
|
||||
networkInfo = networkInfo,
|
||||
statuses = accountList.filterIsInstance<AccountStatus.CryptoPortfolio>()
|
||||
.flatMap { accountStatus ->
|
||||
accountStatus.flattenCurrencies()
|
||||
}.toSet().toList(),
|
||||
)
|
||||
} else {
|
||||
val statuses = tokenListFlow.firstOrNull() ?: return@collectLatest
|
||||
updateAvailablePairs(networkInfo = networkInfo, statuses = statuses)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -247,6 +393,14 @@ internal class AvailableSwapPairsModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun getAccountsAndModeFlow(): Flow<Pair<List<AccountStatus>, Boolean>> {
|
||||
return combine(
|
||||
flow = accountListFlow.distinctUntilChanged(),
|
||||
flow2 = isAccountsModeEnabledUseCase().distinctUntilChanged(),
|
||||
transform = ::Pair,
|
||||
)
|
||||
}
|
||||
|
||||
private fun onSearchQueryChange(newQuery: String) {
|
||||
if (state.value.searchBarUM.query == newQuery) return
|
||||
|
||||
|
|
@ -286,6 +440,29 @@ internal class AvailableSwapPairsModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun Map<Account.CryptoPortfolio, List<CryptoCurrencyStatus>>.filterByAvailability(
|
||||
availablePairs: List<SwapPairLeast>,
|
||||
): List<AccountAvailabilityUM> {
|
||||
return map { (account, currencies) ->
|
||||
AccountAvailabilityUM(
|
||||
account = account,
|
||||
currencyList = currencies.map { status ->
|
||||
val isAvailable = availablePairs.map(SwapPairLeast::to).contains(status.toLeastTokenInfo())
|
||||
|
||||
val isAvailableToSwap = isAvailable &&
|
||||
status.value !is CryptoCurrencyStatus.MissedDerivation &&
|
||||
status.value !is CryptoCurrencyStatus.Unreachable &&
|
||||
!status.currency.isCustom
|
||||
|
||||
AccountCurrencyUM(
|
||||
cryptoCurrencyStatus = status,
|
||||
isAvailable = isAvailableToSwap,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.toLeastTokenInfo(): LeastTokenInfo {
|
||||
return LeastTokenInfo(
|
||||
contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress ?: "0",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.features.onramp.swap.entity
|
||||
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
|
||||
internal data class AccountAvailabilityUM(
|
||||
val account: Account.CryptoPortfolio,
|
||||
val currencyList: List<AccountCurrencyUM>,
|
||||
)
|
||||
|
||||
internal data class AccountCurrencyUM(
|
||||
val isAvailable: Boolean,
|
||||
val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
)
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.features.onramp.swap.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.common.ui.account.CryptoPortfolioIconUM
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
|
|
@ -11,7 +13,7 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
internal sealed interface ExchangeCardUM {
|
||||
|
||||
/** Title reference */
|
||||
val titleReference: TextReference
|
||||
val titleUM: TitleUM
|
||||
|
||||
/** Remove button UI model */
|
||||
val removeButtonUM: RemoveButtonUM?
|
||||
|
|
@ -19,11 +21,11 @@ internal sealed interface ExchangeCardUM {
|
|||
/**
|
||||
* Empty state
|
||||
*
|
||||
* @property titleReference title reference
|
||||
* @property titleUM title reference
|
||||
* @property subtitleReference empty token subtitle reference
|
||||
*/
|
||||
data class Empty(
|
||||
override val titleReference: TextReference,
|
||||
override val titleUM: TitleUM,
|
||||
val subtitleReference: TextReference,
|
||||
) : ExchangeCardUM {
|
||||
|
||||
|
|
@ -33,15 +35,29 @@ internal sealed interface ExchangeCardUM {
|
|||
/**
|
||||
* Filled
|
||||
*
|
||||
* @property titleReference title reference
|
||||
* @property titleUM title reference
|
||||
* @property removeButtonUM remove button UI model
|
||||
* @property tokenItemState token item state
|
||||
*/
|
||||
data class Filled(
|
||||
override val titleReference: TextReference,
|
||||
override val titleUM: TitleUM,
|
||||
override val removeButtonUM: RemoveButtonUM?,
|
||||
val tokenItemState: TokenItemState,
|
||||
) : ExchangeCardUM
|
||||
|
||||
data class RemoveButtonUM(val onClick: () -> Unit)
|
||||
|
||||
@Immutable
|
||||
sealed interface TitleUM {
|
||||
|
||||
data class Text(
|
||||
val title: TextReference,
|
||||
) : TitleUM
|
||||
|
||||
data class Account(
|
||||
val prefixText: TextReference,
|
||||
val name: TextReference,
|
||||
val icon: CryptoPortfolioIconUM,
|
||||
) : TitleUM
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.onramp.swap.entity.transformer
|
||||
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.features.onramp.swap.entity.ExchangeCardUM
|
||||
import com.tangem.features.onramp.swap.entity.SwapSelectTokensUM
|
||||
import com.tangem.features.onramp.swap.entity.SwapSelectTokensUMTransformer
|
||||
|
|
@ -17,6 +18,8 @@ import com.tangem.features.onramp.swap.entity.utils.toFilled
|
|||
internal class SelectFromTokenTransformer(
|
||||
private val selectedTokenItemState: TokenItemState,
|
||||
private val onRemoveClick: () -> Unit,
|
||||
private val account: Account.CryptoPortfolio,
|
||||
private val isAccountsMode: Boolean,
|
||||
) : SwapSelectTokensUMTransformer {
|
||||
|
||||
override fun transform(prevState: SwapSelectTokensUM): SwapSelectTokensUM {
|
||||
|
|
@ -24,6 +27,9 @@ internal class SelectFromTokenTransformer(
|
|||
exchangeFrom = prevState.exchangeFrom.toFilled(
|
||||
selectedTokenItemState = selectedTokenItemState,
|
||||
removeButtonUM = ExchangeCardUM.RemoveButtonUM(onClick = onRemoveClick),
|
||||
account = account,
|
||||
isAccountsMode = isAccountsMode,
|
||||
isFromCurrency = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.onramp.swap.entity.transformer
|
||||
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.features.onramp.swap.entity.ExchangeCardUM
|
||||
import com.tangem.features.onramp.swap.entity.SwapSelectTokensUM
|
||||
import com.tangem.features.onramp.swap.entity.SwapSelectTokensUMTransformer
|
||||
|
|
@ -15,12 +16,19 @@ import com.tangem.features.onramp.swap.entity.utils.toFilled
|
|||
*/
|
||||
internal class SelectToTokenTransformer(
|
||||
private val selectedTokenItemState: TokenItemState,
|
||||
private val isAccountsMode: Boolean,
|
||||
private val account: Account.CryptoPortfolio,
|
||||
) : SwapSelectTokensUMTransformer {
|
||||
|
||||
override fun transform(prevState: SwapSelectTokensUM): SwapSelectTokensUM {
|
||||
return prevState.copy(
|
||||
exchangeFrom = prevState.exchangeFrom.hideRemoveButton(),
|
||||
exchangeTo = prevState.exchangeTo.toFilled(selectedTokenItemState = selectedTokenItemState),
|
||||
exchangeTo = prevState.exchangeTo.toFilled(
|
||||
selectedTokenItemState = selectedTokenItemState,
|
||||
isAccountsMode = isAccountsMode,
|
||||
account = account,
|
||||
isFromCurrency = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
package com.tangem.features.onramp.swap.entity.utils
|
||||
|
||||
import com.tangem.common.ui.account.toUM
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.swap.entity.ExchangeCardUM
|
||||
|
||||
/** Create empty exchange "from" card */
|
||||
internal fun createEmptyExchangeFrom(): ExchangeCardUM.Empty {
|
||||
return ExchangeCardUM.Empty(
|
||||
titleReference = resourceReference(id = R.string.swapping_from_title),
|
||||
titleUM = ExchangeCardUM.TitleUM.Text(resourceReference(id = R.string.swapping_from_title)),
|
||||
subtitleReference = resourceReference(id = R.string.action_buttons_you_want_to_swap),
|
||||
)
|
||||
}
|
||||
|
|
@ -16,7 +18,7 @@ internal fun createEmptyExchangeFrom(): ExchangeCardUM.Empty {
|
|||
/** Create empty exchange "to" card */
|
||||
internal fun createEmptyExchangeTo(): ExchangeCardUM.Empty {
|
||||
return ExchangeCardUM.Empty(
|
||||
titleReference = resourceReference(id = R.string.swapping_to_title),
|
||||
titleUM = ExchangeCardUM.TitleUM.Text(resourceReference(id = R.string.swapping_to_title)),
|
||||
subtitleReference = resourceReference(id = R.string.action_buttons_you_want_to_receive),
|
||||
)
|
||||
}
|
||||
|
|
@ -29,10 +31,25 @@ internal fun createEmptyExchangeTo(): ExchangeCardUM.Empty {
|
|||
*/
|
||||
internal fun ExchangeCardUM.toFilled(
|
||||
selectedTokenItemState: TokenItemState,
|
||||
account: Account.CryptoPortfolio,
|
||||
isAccountsMode: Boolean,
|
||||
isFromCurrency: Boolean,
|
||||
removeButtonUM: ExchangeCardUM.RemoveButtonUM? = null,
|
||||
): ExchangeCardUM.Filled {
|
||||
return ExchangeCardUM.Filled(
|
||||
titleReference = titleReference,
|
||||
titleUM = if (isAccountsMode) {
|
||||
ExchangeCardUM.TitleUM.Account(
|
||||
prefixText = if (isFromCurrency) {
|
||||
resourceReference(R.string.common_from)
|
||||
} else {
|
||||
resourceReference(R.string.common_to)
|
||||
},
|
||||
name = account.accountName.toUM().value,
|
||||
icon = account.icon.toUM(),
|
||||
)
|
||||
} else {
|
||||
titleUM
|
||||
},
|
||||
tokenItemState = selectedTokenItemState,
|
||||
removeButtonUM = removeButtonUM,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ import com.tangem.core.decompose.model.Model
|
|||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.features.onramp.component.SwapSelectTokensComponent
|
||||
import com.tangem.features.onramp.swap.entity.SwapSelectTokensController
|
||||
|
|
@ -32,6 +34,7 @@ internal class SwapSelectTokensModel @Inject constructor(
|
|||
private val router: Router,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
|
||||
) : Model() {
|
||||
|
||||
val state: StateFlow<SwapSelectTokensUM> = controller.state
|
||||
|
|
@ -43,9 +46,12 @@ internal class SwapSelectTokensModel @Inject constructor(
|
|||
|
||||
private val params = paramsContainer.require<SwapSelectTokensComponent.Params>()
|
||||
|
||||
private var isAccountsMode: Boolean = false
|
||||
|
||||
init {
|
||||
controller.update { it.copy(onBackClick = ::onBackClick) }
|
||||
|
||||
subscribeOnAccountsMode()
|
||||
subscribeOnBalanceHidingSettings()
|
||||
}
|
||||
|
||||
|
|
@ -66,6 +72,11 @@ internal class SwapSelectTokensModel @Inject constructor(
|
|||
transformer = SelectFromTokenTransformer(
|
||||
selectedTokenItemState = selectedTokenItemState,
|
||||
onRemoveClick = ::onRemoveFromTokenClick,
|
||||
isAccountsMode = isAccountsMode,
|
||||
account = Account.CryptoPortfolio.createMainAccount(
|
||||
userWalletId = params.userWalletId,
|
||||
cryptoCurrencies = setOf(status.currency),
|
||||
), // todo account from from cryptocurrency
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -84,7 +95,16 @@ internal class SwapSelectTokensModel @Inject constructor(
|
|||
modelScope.launch {
|
||||
_toCurrencyStatus.value = status
|
||||
|
||||
controller.update(transformer = SelectToTokenTransformer(selectedTokenItemState))
|
||||
controller.update(
|
||||
transformer = SelectToTokenTransformer(
|
||||
selectedTokenItemState = selectedTokenItemState,
|
||||
isAccountsMode = isAccountsMode,
|
||||
account = Account.CryptoPortfolio.createMainAccount(
|
||||
userWalletId = params.userWalletId,
|
||||
cryptoCurrencies = setOf(status.currency),
|
||||
), // todo account from from cryptocurrency
|
||||
),
|
||||
)
|
||||
|
||||
// require some delay to show state with selected "from" and "to" tokens
|
||||
delay(timeMillis = 500)
|
||||
|
|
@ -119,6 +139,16 @@ internal class SwapSelectTokensModel @Inject constructor(
|
|||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun subscribeOnAccountsMode() {
|
||||
isAccountsModeEnabledUseCase()
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
isAccountsMode = it
|
||||
}
|
||||
.flowOn(dispatchers.default)
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun onBackClick() {
|
||||
analyticsEventHandler.send(
|
||||
event = MainScreenAnalyticsEvent.ButtonClose(source = AnalyticsParam.ScreensSources.Swap),
|
||||
|
|
|
|||
|
|
@ -14,11 +14,14 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.account.AccountLabel
|
||||
import com.tangem.core.ui.components.account.AccountIconSize
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.components.rows.NetworkTitle
|
||||
|
|
@ -46,13 +49,14 @@ internal fun ExchangeCard(state: ExchangeCardUM, isBalanceHidden: Boolean, modif
|
|||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 116.dp)
|
||||
.background(
|
||||
color = TangemTheme.colors.background.primary,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
),
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(TangemTheme.colors.background.primary),
|
||||
verticalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Title(titleReference = state.titleReference, removeButtonUM = state.removeButtonUM)
|
||||
Title(
|
||||
titleUM = state.titleUM,
|
||||
removeButtonUM = state.removeButtonUM,
|
||||
)
|
||||
|
||||
AnimatedContent(
|
||||
targetState = state,
|
||||
|
|
@ -73,16 +77,39 @@ internal fun ExchangeCard(state: ExchangeCardUM, isBalanceHidden: Boolean, modif
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun Title(titleReference: TextReference, removeButtonUM: ExchangeCardUM.RemoveButtonUM?) {
|
||||
private fun Title(titleUM: ExchangeCardUM.TitleUM, removeButtonUM: ExchangeCardUM.RemoveButtonUM?) {
|
||||
NetworkTitle(
|
||||
title = {
|
||||
Text(
|
||||
text = titleReference.resolveReference(),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
)
|
||||
AnimatedContent(
|
||||
titleUM,
|
||||
) { currentState ->
|
||||
when (currentState) {
|
||||
is ExchangeCardUM.TitleUM.Account -> Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = currentState.prefixText.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
AccountLabel(
|
||||
name = currentState.name,
|
||||
icon = currentState.icon,
|
||||
iconSize = AccountIconSize.ExtraSmall,
|
||||
nameStyle = TangemTheme.typography.subtitle2,
|
||||
nameColor = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
is ExchangeCardUM.TitleUM.Text -> Text(
|
||||
text = currentState.title.resolveReference(),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
action = { RemoveButton(state = removeButtonUM) },
|
||||
)
|
||||
|
|
@ -153,7 +180,7 @@ private class ExchangeCardUMProvider : PreviewParameterProvider<ExchangeCardUM>
|
|||
|
||||
override val values: Sequence<ExchangeCardUM> = sequenceOf(
|
||||
ExchangeCardUM.Empty(
|
||||
titleReference = resourceReference(id = R.string.swapping_from_title),
|
||||
titleUM = ExchangeCardUM.TitleUM.Text(resourceReference(id = R.string.swapping_from_title)),
|
||||
subtitleReference = resourceReference(id = R.string.action_buttons_you_want_to_swap),
|
||||
),
|
||||
createFilled(removeButtonUM = null),
|
||||
|
|
@ -162,7 +189,7 @@ private class ExchangeCardUMProvider : PreviewParameterProvider<ExchangeCardUM>
|
|||
|
||||
private fun createFilled(removeButtonUM: ExchangeCardUM.RemoveButtonUM?): ExchangeCardUM.Filled {
|
||||
return ExchangeCardUM.Filled(
|
||||
titleReference = resourceReference(id = R.string.swapping_from_title),
|
||||
titleUM = ExchangeCardUM.TitleUM.Text(resourceReference(id = R.string.swapping_from_title)),
|
||||
removeButtonUM = removeButtonUM,
|
||||
tokenItemState = TokenItemState.Content(
|
||||
id = "1",
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import androidx.compose.runtime.LaunchedEffect
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -23,6 +24,7 @@ import com.tangem.features.onramp.swap.availablepairs.AvailableSwapPairsComponen
|
|||
import com.tangem.features.onramp.swap.entity.ExchangeCardUM
|
||||
import com.tangem.features.onramp.swap.entity.SwapSelectTokensUM
|
||||
import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
|
||||
/**
|
||||
* Swap select tokens
|
||||
|
|
@ -39,7 +41,9 @@ import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent
|
|||
internal fun SwapSelectTokens(
|
||||
state: SwapSelectTokensUM,
|
||||
selectFromTokenListComponent: OnrampTokenListComponent,
|
||||
selectFromTokenListState: TokenListUM,
|
||||
selectToTokenListComponent: AvailableSwapPairsComponent,
|
||||
selectToTokenListState: TokenListUM,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
BackHandler(onBack = state.onBackClick)
|
||||
|
|
@ -77,33 +81,33 @@ internal fun SwapSelectTokens(
|
|||
}
|
||||
|
||||
if (state.exchangeFrom is ExchangeCardUM.Empty) {
|
||||
item(key = "select_from", contentType = "select_from") {
|
||||
selectFromTokenListComponent.Content(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.animateItem(),
|
||||
with(selectFromTokenListComponent) {
|
||||
content(
|
||||
uiState = selectFromTokenListState,
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (state.exchangeFrom is ExchangeCardUM.Filled) {
|
||||
item(key = "exchange_to", contentType = "exchange_to") {
|
||||
ExchangeCard(
|
||||
state = state.exchangeTo,
|
||||
isBalanceHidden = state.isBalanceHidden,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(bottom = 12.dp)
|
||||
.animateItem(),
|
||||
)
|
||||
if (selectToTokenListState.warning != NotificationUM.Warning.SwapNoAvailablePair) {
|
||||
ExchangeCard(
|
||||
state = state.exchangeTo,
|
||||
isBalanceHidden = state.isBalanceHidden,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(bottom = 12.dp)
|
||||
.animateItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (state.exchangeTo is ExchangeCardUM.Empty) {
|
||||
item(key = "select_to", contentType = "select_to") {
|
||||
selectToTokenListComponent.Content(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.animateItem(),
|
||||
with(selectToTokenListComponent) {
|
||||
content(
|
||||
uiState = selectToTokenListState,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
package com.tangem.features.onramp.tokenlist
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
import com.tangem.features.onramp.tokenlist.model.OnrampTokenListModel
|
||||
import com.tangem.features.onramp.tokenlist.ui.TokenList
|
||||
import com.tangem.features.onramp.tokenlist.ui.onrampTokenList
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@Stable
|
||||
internal class DefaultOnrampTokenListComponent @AssistedInject constructor(
|
||||
|
|
@ -21,11 +21,11 @@ internal class DefaultOnrampTokenListComponent @AssistedInject constructor(
|
|||
|
||||
private val model: OnrampTokenListModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
override val uiState: StateFlow<TokenListUM>
|
||||
get() = model.state
|
||||
|
||||
TokenList(state = state, modifier = modifier)
|
||||
override fun LazyListScope.content(uiState: TokenListUM, modifier: Modifier) {
|
||||
onrampTokenList(state = uiState)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
|
|
|
|||
|
|
@ -3,14 +3,15 @@ package com.tangem.features.onramp.tokenlist
|
|||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.decompose.ComposableListContentComponent
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.onramp.tokenlist.entity.OnrampOperation
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
|
||||
/** Token list component that present list of token for multi-currency wallet */
|
||||
@Stable
|
||||
internal interface OnrampTokenListComponent : ComposableContentComponent {
|
||||
internal interface OnrampTokenListComponent : ComposableListContentComponent<TokenListUM> {
|
||||
|
||||
/** Component factory */
|
||||
interface Factory : ComponentFactory<Params, OnrampTokenListComponent>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,19 @@ internal data class TokenListUM(
|
|||
val searchBarUM: SearchBarUM,
|
||||
val availableItems: ImmutableList<TokensListItemUM>,
|
||||
val unavailableItems: ImmutableList<TokensListItemUM>,
|
||||
val tokensListData: TokenListUMData,
|
||||
val isBalanceHidden: Boolean,
|
||||
val warning: NotificationUM? = null,
|
||||
)
|
||||
)
|
||||
|
||||
internal sealed interface TokenListUMData {
|
||||
data class AccountList(
|
||||
val tokensList: ImmutableList<TokensListItemUM.Portfolio>,
|
||||
) : TokenListUMData
|
||||
|
||||
data class TokenList(
|
||||
val tokensList: ImmutableList<TokensListItemUM>,
|
||||
) : TokenListUMData
|
||||
|
||||
data object EmptyList : TokenListUMData
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ internal class TokenListUMController @Inject constructor() {
|
|||
),
|
||||
availableItems = persistentListOf(),
|
||||
unavailableItems = persistentListOf(),
|
||||
tokensListData = TokenListUMData.EmptyList,
|
||||
isBalanceHidden = false,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.features.onramp.tokenlist.entity.transformer
|
||||
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.features.onramp.swap.availablepairs.entity.converters.LoadingAccountTokenItemConverter
|
||||
import com.tangem.features.onramp.swap.availablepairs.entity.converters.LoadingTokenListItemConverter
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMData
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class SetLoadingAccountTokenListTransformer(
|
||||
appCurrency: AppCurrency,
|
||||
private val accountList: List<AccountStatus>,
|
||||
private val isAccountsMode: Boolean,
|
||||
) : TokenListUMTransformer {
|
||||
|
||||
private val accountListItemConverter = LoadingAccountTokenItemConverter(appCurrency)
|
||||
|
||||
override fun transform(prevState: TokenListUM): TokenListUM {
|
||||
return prevState.copy(
|
||||
availableItems = persistentListOf(),
|
||||
unavailableItems = persistentListOf(),
|
||||
tokensListData = if (isAccountsMode) {
|
||||
TokenListUMData.AccountList(
|
||||
tokensList = accountListItemConverter.convertList(
|
||||
accountList.filterIsInstance<AccountStatus.CryptoPortfolio>(),
|
||||
).toPersistentList(),
|
||||
)
|
||||
} else {
|
||||
TokenListUMData.TokenList(
|
||||
tokensList = accountList.flatMap { account ->
|
||||
when (account) {
|
||||
is AccountStatus.CryptoPortfolio -> LoadingTokenListItemConverter.convertList(
|
||||
account.tokenList.flattenCurrencies(),
|
||||
)
|
||||
}
|
||||
}.toPersistentList(),
|
||||
)
|
||||
},
|
||||
warning = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMData
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
|
@ -26,9 +27,9 @@ internal class SetNothingToFoundStateTransformer(
|
|||
id = emptySearchMessageReference.hashCode(),
|
||||
text = emptySearchMessageReference,
|
||||
).let(::add)
|
||||
}
|
||||
.toImmutableList(),
|
||||
}.toImmutableList(),
|
||||
unavailableItems = persistentListOf(),
|
||||
tokensListData = TokenListUMData.EmptyList,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.features.onramp.tokenlist.entity.transformer
|
||||
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMData
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal class SetNothingToFoundStateTransformerV2(
|
||||
private val isBalanceHidden: Boolean,
|
||||
private val emptySearchMessageReference: TextReference,
|
||||
) : TokenListUMTransformer {
|
||||
|
||||
override fun transform(prevState: TokenListUM): TokenListUM {
|
||||
return prevState.copy(
|
||||
availableItems = persistentListOf(),
|
||||
unavailableItems = persistentListOf(),
|
||||
tokensListData = TokenListUMData.TokenList(tokensList = buildList {
|
||||
TokensListItemUM.Text(
|
||||
id = emptySearchMessageReference.hashCode(),
|
||||
text = emptySearchMessageReference,
|
||||
).let(::add)
|
||||
}.toImmutableList()),
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.features.onramp.tokenlist.entity.transformer
|
||||
|
||||
import com.tangem.common.ui.account.AccountCryptoPortfolioItemStateConverter
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.features.onramp.swap.entity.AccountAvailabilityUM
|
||||
import com.tangem.features.onramp.tokenlist.entity.utils.OnrampTokenItemStateConverterFactory
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class UpdateAccountTokenItemConverter(
|
||||
private val appCurrency: AppCurrency,
|
||||
private val unavailableErrorText: TextReference,
|
||||
onItemClick: (TokenItemState, CryptoCurrencyStatus) -> Unit,
|
||||
) : Converter<AccountAvailabilityUM, TokensListItemUM.Portfolio> {
|
||||
|
||||
private val availableConverter = OnrampTokenItemStateConverterFactory
|
||||
.createAvailableItemConverter(appCurrency, onItemClick)
|
||||
|
||||
private val unavailableConverter = OnrampTokenItemStateConverterFactory
|
||||
.createUnavailableItemConverterV2(appCurrency = appCurrency, unavailableErrorText = unavailableErrorText)
|
||||
|
||||
override fun convert(value: AccountAvailabilityUM): TokensListItemUM.Portfolio {
|
||||
return TokensListItemUM.Portfolio(
|
||||
tokenItemUM = AccountCryptoPortfolioItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
account = value.account,
|
||||
onItemClick = null,
|
||||
).convert(TotalFiatBalance.Failed),
|
||||
isExpanded = true,
|
||||
isCollapsable = false,
|
||||
tokens = value.currencyList.asSequence().map { (isAvailable, status) ->
|
||||
if (isAvailable) {
|
||||
availableConverter.convert(status)
|
||||
} else {
|
||||
unavailableConverter.convert(status)
|
||||
}
|
||||
}.map(TokensListItemUM::Token).toPersistentList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package com.tangem.features.onramp.tokenlist.entity.transformer
|
||||
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.features.onramp.swap.entity.AccountAvailabilityUM
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMData
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer
|
||||
import com.tangem.features.onramp.tokenlist.entity.utils.OnrampTokenItemStateConverterFactory
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class UpdateAccountTokenListTransformer(
|
||||
private val appCurrency: AppCurrency,
|
||||
private val onItemClick: (TokenItemState, CryptoCurrencyStatus) -> Unit,
|
||||
private val accountList: List<AccountAvailabilityUM>,
|
||||
private val isBalanceHidden: Boolean,
|
||||
private val unavailableErrorText: TextReference,
|
||||
private val warning: NotificationUM? = null,
|
||||
private val isAccountsMode: Boolean,
|
||||
) : TokenListUMTransformer {
|
||||
|
||||
private val accountListItemConverter = UpdateAccountTokenItemConverter(
|
||||
appCurrency = appCurrency,
|
||||
onItemClick = onItemClick,
|
||||
unavailableErrorText = unavailableErrorText,
|
||||
)
|
||||
|
||||
private val availableConverter = OnrampTokenItemStateConverterFactory
|
||||
.createAvailableItemConverter(appCurrency, onItemClick)
|
||||
|
||||
private val unavailableConverter = OnrampTokenItemStateConverterFactory
|
||||
.createUnavailableItemConverterV2(appCurrency = appCurrency, unavailableErrorText = unavailableErrorText)
|
||||
|
||||
override fun transform(prevState: TokenListUM): TokenListUM {
|
||||
return prevState.copy(
|
||||
availableItems = persistentListOf(),
|
||||
unavailableItems = persistentListOf(),
|
||||
tokensListData = if (isAccountsMode) {
|
||||
TokenListUMData.AccountList(
|
||||
tokensList = accountListItemConverter.convertList(accountList).toPersistentList(),
|
||||
)
|
||||
} else {
|
||||
TokenListUMData.TokenList(
|
||||
tokensList = accountList.flatMap { (_, currencyList) ->
|
||||
currencyList.asSequence().map { (isAvailable, status) ->
|
||||
if (isAvailable) {
|
||||
availableConverter.convert(status)
|
||||
} else {
|
||||
unavailableConverter.convert(status)
|
||||
}
|
||||
}.map(TokensListItemUM::Token).toPersistentList()
|
||||
}.toPersistentList(),
|
||||
)
|
||||
},
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
warning = warning,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.common.ui.tokens.TokenItemStateConverter.Companion.getFormatte
|
|||
import com.tangem.common.ui.tokens.TokenItemStateConverter.Companion.isFlickering
|
||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
|
|
@ -21,7 +22,13 @@ internal object OnrampTokenItemStateConverterFactory {
|
|||
): TokenItemStateConverter {
|
||||
return TokenItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
subtitleStateProvider = { createSubtitleState(status = it, isAvailable = true) },
|
||||
subtitleStateProvider = {
|
||||
createSubtitleState(
|
||||
status = it,
|
||||
isAvailable = true,
|
||||
text = stringReference(value = it.currency.symbol),
|
||||
)
|
||||
},
|
||||
subtitle2StateProvider = ::createSubtitle2State,
|
||||
fiatAmountStateProvider = {
|
||||
createFiatAmountStateProvider(status = it, appCurrency = appCurrency, isAvailable = true)
|
||||
|
|
@ -40,7 +47,13 @@ internal object OnrampTokenItemStateConverterFactory {
|
|||
isAvailable = false,
|
||||
)
|
||||
},
|
||||
subtitleStateProvider = { createSubtitleState(status = it, isAvailable = false) },
|
||||
subtitleStateProvider = {
|
||||
createSubtitleState(
|
||||
status = it,
|
||||
text = stringReference(value = it.currency.symbol),
|
||||
isAvailable = false,
|
||||
)
|
||||
},
|
||||
subtitle2StateProvider = ::createSubtitle2State,
|
||||
fiatAmountStateProvider = {
|
||||
createFiatAmountStateProvider(status = it, appCurrency = appCurrency, isAvailable = false)
|
||||
|
|
@ -48,12 +61,43 @@ internal object OnrampTokenItemStateConverterFactory {
|
|||
)
|
||||
}
|
||||
|
||||
private fun createSubtitleState(status: CryptoCurrencyStatus, isAvailable: Boolean): TokenItemState.SubtitleState {
|
||||
fun createUnavailableItemConverterV2(
|
||||
appCurrency: AppCurrency,
|
||||
unavailableErrorText: TextReference,
|
||||
): TokenItemStateConverter {
|
||||
return TokenItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
iconStateProvider = { CryptoCurrencyToIconStateConverter(isAvailable = false).convert(it) },
|
||||
titleStateProvider = {
|
||||
TokenItemState.TitleState.Content(
|
||||
text = stringReference(value = it.currency.name),
|
||||
isAvailable = false,
|
||||
)
|
||||
},
|
||||
subtitleStateProvider = {
|
||||
createSubtitleState(
|
||||
status = it,
|
||||
isAvailable = false,
|
||||
text = unavailableErrorText,
|
||||
)
|
||||
},
|
||||
subtitle2StateProvider = ::createSubtitle2State,
|
||||
fiatAmountStateProvider = {
|
||||
createFiatAmountStateProvider(status = it, appCurrency = appCurrency, isAvailable = false)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun createSubtitleState(
|
||||
status: CryptoCurrencyStatus,
|
||||
isAvailable: Boolean,
|
||||
text: TextReference,
|
||||
): TokenItemState.SubtitleState {
|
||||
return when (status.value) {
|
||||
CryptoCurrencyStatus.Loading -> TokenItemState.SubtitleState.Loading
|
||||
else -> {
|
||||
TokenItemState.SubtitleState.TextContent(
|
||||
value = stringReference(value = status.currency.symbol),
|
||||
value = text,
|
||||
isAvailable = isAvailable,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,11 @@ import com.tangem.core.decompose.model.Model
|
|||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.components.fields.InputManager
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
|
|
@ -13,6 +18,8 @@ import com.tangem.domain.core.lce.Lce
|
|||
import com.tangem.domain.core.utils.getOrElse
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
|
|
@ -23,13 +30,11 @@ import com.tangem.domain.tokens.error.TokenListError
|
|||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.swap.entity.AccountAvailabilityUM
|
||||
import com.tangem.features.onramp.swap.entity.AccountCurrencyUM
|
||||
import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent
|
||||
import com.tangem.features.onramp.tokenlist.entity.OnrampOperation
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMController
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer
|
||||
import com.tangem.features.onramp.tokenlist.entity.transformer.SetNothingToFoundStateTransformer
|
||||
import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateTokenItemsTransformer
|
||||
import com.tangem.features.onramp.tokenlist.entity.*
|
||||
import com.tangem.features.onramp.tokenlist.entity.transformer.*
|
||||
import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer
|
||||
import com.tangem.features.onramp.utils.UpdateSearchBarCallbacksTransformer
|
||||
import com.tangem.features.onramp.utils.UpdateSearchQueryTransformer
|
||||
|
|
@ -42,7 +47,9 @@ import kotlinx.coroutines.flow.*
|
|||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
typealias AccountCryptoList = Map<Account.CryptoPortfolio, List<CryptoCurrencyStatus>>
|
||||
|
||||
@Suppress("LargeClass", "LongParameterList")
|
||||
internal class OnrampTokenListModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
|
|
@ -55,6 +62,9 @@ internal class OnrampTokenListModel @Inject constructor(
|
|||
private val rampStateManager: RampStateManager,
|
||||
private val getUserCountryUseCase: GetUserCountryUseCase,
|
||||
private val getAssetRequirementsUseCase: GetAssetRequirementsUseCase,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
|
||||
) : Model() {
|
||||
|
||||
val state: StateFlow<TokenListUM> = tokenListUMController.state
|
||||
|
|
@ -71,8 +81,11 @@ internal class OnrampTokenListModel @Inject constructor(
|
|||
onActiveChange = ::onSearchBarActiveChange,
|
||||
),
|
||||
)
|
||||
|
||||
subscribeOnUpdateState()
|
||||
if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
subscribeOnUpdateStateV2()
|
||||
} else {
|
||||
subscribeOnUpdateState()
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeOnUpdateState() {
|
||||
|
|
@ -95,12 +108,7 @@ internal class OnrampTokenListModel @Inject constructor(
|
|||
if (query.isNotEmpty() && filterByQueryTokenList.isEmpty()) {
|
||||
SetNothingToFoundStateTransformer(
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
emptySearchMessageReference = when (params.filterOperation) {
|
||||
OnrampOperation.BUY -> R.string.action_buttons_buy_empty_search_message
|
||||
OnrampOperation.SELL -> R.string.action_buttons_sell_empty_search_message
|
||||
OnrampOperation.SWAP -> R.string.action_buttons_swap_empty_search_message
|
||||
}
|
||||
.let(::resourceReference),
|
||||
emptySearchMessageReference = getEmptySearchMessageReference(),
|
||||
)
|
||||
} else {
|
||||
val isInsufficientBalanceForSell = if (params.filterOperation == OnrampOperation.SELL) {
|
||||
|
|
@ -134,6 +142,62 @@ internal class OnrampTokenListModel @Inject constructor(
|
|||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun subscribeOnUpdateStateV2() {
|
||||
combine(
|
||||
flow = singleAccountStatusListSupplier(
|
||||
SingleAccountStatusListProducer.Params(params.userWalletId),
|
||||
).distinctUntilChanged(),
|
||||
flow2 = getAppCurrencyAndBalanceHidingFlow(),
|
||||
flow3 = isAccountsModeEnabledUseCase(),
|
||||
flow4 = searchManager.query,
|
||||
flow5 = hasRestrictionForSellFlow(),
|
||||
) { accountList, appCurrencyAndBalanceHiding, isAccountsMode, query, hasRestrictionForSell ->
|
||||
val (appCurrency, isBalanceHidden) = appCurrencyAndBalanceHiding
|
||||
val filterByQueryAccountList = accountList.filterAccountsByQuery(query)
|
||||
|
||||
if (query.isNotEmpty() && filterByQueryAccountList.isEmpty()) {
|
||||
updateTokenListUM(
|
||||
SetNothingToFoundStateTransformerV2(
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
emptySearchMessageReference = getEmptySearchMessageReference(),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
updateTokenListUM(
|
||||
SetLoadingAccountTokenListTransformer(
|
||||
appCurrency = appCurrency,
|
||||
accountList = accountList.accountStatuses.toList(),
|
||||
isAccountsMode = isAccountsMode,
|
||||
),
|
||||
)
|
||||
updateTokenListUM(
|
||||
UpdateAccountTokenListTransformer(
|
||||
appCurrency = appCurrency,
|
||||
onItemClick = params.onTokenClick,
|
||||
accountList = filterByQueryAccountList.filterByAvailability(),
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
unavailableErrorText = getUnavailableTokensHeaderReference(),
|
||||
warning = getSellWarning(
|
||||
hasRestrictionForSell = hasRestrictionForSell,
|
||||
isInsufficientBalanceForSell = accountList.isInsufficientBalanceForSell(),
|
||||
),
|
||||
isAccountsMode = isAccountsMode,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
.flowOn(dispatchers.default)
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun getAppCurrencyAndBalanceHidingFlow(): Flow<Pair<AppCurrency, Boolean>> {
|
||||
return combine(
|
||||
flow = getSelectedAppCurrencyUseCase().map { it.getOrElse { AppCurrency.Default } }.distinctUntilChanged(),
|
||||
flow2 = getBalanceHidingSettingsUseCase().map { it.isBalanceHidden }.distinctUntilChanged(),
|
||||
transform = ::Pair,
|
||||
)
|
||||
}
|
||||
|
||||
private fun hasRestrictionForSellFlow(): Flow<Boolean> {
|
||||
return if (params.filterOperation == OnrampOperation.SELL) {
|
||||
getUserCountryUseCase().map { maybe ->
|
||||
|
|
@ -154,25 +218,52 @@ internal class OnrampTokenListModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun AccountStatusList.isInsufficientBalanceForSell(): Boolean {
|
||||
return if (params.filterOperation == OnrampOperation.SELL) {
|
||||
(totalFiatBalance as? TotalFiatBalance.Loaded)?.amount?.isZero() == true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUnavailableTokensHeaderReference() = when (params.filterOperation) {
|
||||
OnrampOperation.BUY -> R.string.tokens_list_unavailable_to_purchase_header
|
||||
OnrampOperation.SELL -> R.string.tokens_list_unavailable_to_sell_header
|
||||
OnrampOperation.SWAP -> R.string.tokens_list_unavailable_to_swap_source_header
|
||||
}.let(::resourceReference)
|
||||
|
||||
private fun getEmptySearchMessageReference() = when (params.filterOperation) {
|
||||
OnrampOperation.BUY -> R.string.action_buttons_buy_empty_search_message
|
||||
OnrampOperation.SELL -> R.string.action_buttons_sell_empty_search_message
|
||||
OnrampOperation.SWAP -> R.string.action_buttons_swap_empty_search_message
|
||||
}.let(::resourceReference)
|
||||
|
||||
private fun updateTokenListUM(transformer: TokenListUMTransformer) {
|
||||
tokenListUMController.update { prevState ->
|
||||
transformer.transform(prevState).apply {
|
||||
if (isFirstInitialization(prevState = prevState, newState = this)) {
|
||||
params.onTokenListInitialized()
|
||||
modelScope.launch {
|
||||
tokenListUMController.update { prevState ->
|
||||
transformer.transform(prevState).apply {
|
||||
if (isFirstInitialization(prevState = prevState, newState = this)) {
|
||||
params.onTokenListInitialized()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSellWarning(hasRestrictionForSell: Boolean, isInsufficientBalanceForSell: Boolean) = when {
|
||||
hasRestrictionForSell -> NotificationUM.Warning.SellingRegionalRestriction
|
||||
isInsufficientBalanceForSell -> NotificationUM.Warning.InsufficientBalanceForSelling
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun isFirstInitialization(prevState: TokenListUM, newState: TokenListUM): Boolean {
|
||||
return prevState.availableItems.isEmpty() && prevState.unavailableItems.isEmpty() &&
|
||||
(newState.availableItems.isNotEmpty() || newState.unavailableItems.isNotEmpty())
|
||||
return if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
prevState.tokensListData == TokenListUMData.EmptyList &&
|
||||
newState.tokensListData != TokenListUMData.EmptyList
|
||||
} else {
|
||||
prevState.availableItems.isEmpty() && prevState.unavailableItems.isEmpty() &&
|
||||
(newState.availableItems.isNotEmpty() || newState.unavailableItems.isNotEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSearchQueryChange(newQuery: String) {
|
||||
|
|
@ -195,6 +286,16 @@ internal class OnrampTokenListModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun AccountStatusList.filterAccountsByQuery(query: String) = accountStatuses.asSequence()
|
||||
.associate { accountStatus ->
|
||||
when (accountStatus) {
|
||||
is AccountStatus.CryptoPortfolio -> {
|
||||
val filteredList = accountStatus.tokenList.flattenCurrencies().filterByQuery(query = query)
|
||||
accountStatus.account to filteredList
|
||||
}
|
||||
}
|
||||
}.filter { (_, value) -> value.isNotEmpty() }
|
||||
|
||||
private fun List<CryptoCurrencyStatus>.filterByQuery(query: String): List<CryptoCurrencyStatus> {
|
||||
return filter {
|
||||
it.currency.name.contains(other = query, ignoreCase = true) ||
|
||||
|
|
@ -237,6 +338,49 @@ internal class OnrampTokenListModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun AccountCryptoList.filterByAvailability(): List<AccountAvailabilityUM> {
|
||||
return coroutineScope {
|
||||
map { (account, currencies) ->
|
||||
async {
|
||||
AccountAvailabilityUM(
|
||||
account = account,
|
||||
currencyList = currencies.map { status ->
|
||||
val isOperationAvailable = checkAvailabilityByOperation(status = status)
|
||||
val isNotMissedDerivation = status.value !is CryptoCurrencyStatus.MissedDerivation
|
||||
val isNotLoading = status.value !is CryptoCurrencyStatus.Loading
|
||||
|
||||
val requirements = getAssetRequirementsUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
currency = status.currency,
|
||||
).getOrNull()
|
||||
|
||||
val isAvailableForBuy = rampStateManager.checkAssetRequirements(requirements)
|
||||
val isNotUnreachable = status.value !is CryptoCurrencyStatus.Unreachable
|
||||
|
||||
val isAvailable = when (params.filterOperation) {
|
||||
OnrampOperation.BUY -> {
|
||||
isAvailableForBuy
|
||||
} // unreachable state is available for Buy operation
|
||||
OnrampOperation.SELL -> isNotUnreachable
|
||||
OnrampOperation.SWAP -> {
|
||||
isNotUnreachable && isAvailableForBuy
|
||||
}
|
||||
}
|
||||
|
||||
val isTotalAvailable =
|
||||
isOperationAvailable && isNotMissedDerivation && isNotLoading && isAvailable
|
||||
|
||||
AccountCurrencyUM(
|
||||
cryptoCurrencyStatus = status,
|
||||
isAvailable = isTotalAvailable,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}.awaitAll()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun checkAvailabilityByOperation(status: CryptoCurrencyStatus): Boolean {
|
||||
return when (params.filterOperation) {
|
||||
OnrampOperation.BUY -> {
|
||||
|
|
|
|||
|
|
@ -3,32 +3,34 @@ package com.tangem.features.onramp.tokenlist.ui
|
|||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.SpacerH12
|
||||
import com.tangem.core.ui.components.fields.SearchBar
|
||||
import com.tangem.core.ui.components.fields.TangemSearchBarDefaults
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.components.tokenlist.PortfolioListItem
|
||||
import com.tangem.core.ui.components.tokenlist.PortfolioTokensListItem
|
||||
import com.tangem.core.ui.components.tokenlist.TokenListItem
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.extensions.conditional
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.BuyTokenScreenTestTags
|
||||
import com.tangem.core.ui.utils.lazyListItemPosition
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMData
|
||||
import com.tangem.features.onramp.tokenlist.ui.preview.PreviewTokenListUMProvider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
|
|
@ -36,17 +38,21 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
* Token list
|
||||
*
|
||||
* @param state state
|
||||
* @param modifier modifier
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
internal fun TokenList(state: TokenListUM, modifier: Modifier = Modifier) {
|
||||
Column(modifier) {
|
||||
if (state.warning == null) {
|
||||
SearchBar(searchBarUM = state.searchBarUM)
|
||||
} else {
|
||||
AnimatedContent(targetState = state.warning, label = "") { warning ->
|
||||
internal fun LazyListScope.onrampTokenList(state: TokenListUM) {
|
||||
val itemModifier = Modifier.padding(horizontal = 16.dp)
|
||||
|
||||
if (state.warning == null) {
|
||||
searchBarItem(searchBarUM = state.searchBarUM, modifier = itemModifier)
|
||||
} else {
|
||||
item("NotificationsKey") {
|
||||
AnimatedContent(
|
||||
targetState = state.warning,
|
||||
label = "",
|
||||
modifier = itemModifier,
|
||||
) { warning ->
|
||||
when (warning) {
|
||||
is NotificationUM.Warning.OnrampErrorNotification -> {
|
||||
Notification(
|
||||
|
|
@ -60,31 +66,45 @@ internal fun TokenList(state: TokenListUM, modifier: Modifier = Modifier) {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state.availableItems.isNotEmpty()) {
|
||||
SpacerH12()
|
||||
ItemsBlock(items = state.availableItems, isBalanceHidden = state.isBalanceHidden)
|
||||
}
|
||||
tokensList(items = state.availableItems, isBalanceHidden = state.isBalanceHidden)
|
||||
|
||||
if (state.unavailableItems.isNotEmpty()) {
|
||||
SpacerH12()
|
||||
ItemsBlock(items = state.unavailableItems, isBalanceHidden = state.isBalanceHidden)
|
||||
tokensList(items = state.unavailableItems, isBalanceHidden = state.isBalanceHidden)
|
||||
|
||||
when (val list = state.tokensListData) {
|
||||
is TokenListUMData.AccountList -> list.tokensList.forEach { item ->
|
||||
portfolioTokensList(
|
||||
portfolio = item,
|
||||
isBalanceHidden = state.isBalanceHidden,
|
||||
)
|
||||
}
|
||||
is TokenListUMData.TokenList -> {
|
||||
tokensList(
|
||||
items = list.tokensList,
|
||||
isBalanceHidden = state.isBalanceHidden,
|
||||
)
|
||||
}
|
||||
TokenListUMData.EmptyList -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SearchBar(searchBarUM: SearchBarUM) {
|
||||
SearchBar(
|
||||
state = searchBarUM,
|
||||
colors = TangemSearchBarDefaults.secondaryTextFieldColors,
|
||||
)
|
||||
private fun LazyListScope.searchBarItem(searchBarUM: SearchBarUM, modifier: Modifier = Modifier) {
|
||||
item("SearchKey") {
|
||||
SearchBar(
|
||||
state = searchBarUM,
|
||||
colors = TangemSearchBarDefaults.secondaryTextFieldColors,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ItemsBlock(items: ImmutableList<TokensListItemUM>, isBalanceHidden: Boolean) {
|
||||
items.fastForEachIndexed { index, item ->
|
||||
key(item.id) {
|
||||
private fun LazyListScope.tokensList(items: ImmutableList<TokensListItemUM>, isBalanceHidden: Boolean) {
|
||||
itemsIndexed(
|
||||
items = items,
|
||||
key = { _, item -> item.id },
|
||||
contentType = { _, item -> item::class.java },
|
||||
itemContent = { index, item ->
|
||||
TokenListItem(
|
||||
state = item,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
|
|
@ -92,13 +112,70 @@ private fun ItemsBlock(items: ImmutableList<TokensListItemUM>, isBalanceHidden:
|
|||
.roundedShapeItemDecoration(
|
||||
currentIndex = index,
|
||||
lastIndex = items.lastIndex,
|
||||
addDefaultPadding = false,
|
||||
backgroundColor = TangemTheme.colors.background.primary,
|
||||
)
|
||||
.testTag(BuyTokenScreenTestTags.LAZY_LIST_ITEM)
|
||||
.semantics { lazyListItemPosition = index },
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
internal fun LazyListScope.portfolioTokensList(portfolio: TokensListItemUM.Portfolio, isBalanceHidden: Boolean) {
|
||||
val tokens = portfolio.tokens
|
||||
val isExpanded = portfolio.isExpanded
|
||||
|
||||
portfolioItem(
|
||||
portfolio = portfolio,
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
if (!isExpanded) return
|
||||
itemsIndexed(
|
||||
items = tokens,
|
||||
key = { _, item -> item.id },
|
||||
contentType = { _, item -> item::class.java },
|
||||
itemContent = { tokenIndex, token ->
|
||||
val indexWithHeader = tokenIndex.inc()
|
||||
PortfolioTokensListItem(
|
||||
state = token,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.roundedShapeItemDecoration(
|
||||
currentIndex = indexWithHeader,
|
||||
lastIndex = tokens.lastIndex.inc(),
|
||||
backgroundColor = TangemTheme.colors.background.primary,
|
||||
)
|
||||
.conditional(tokenIndex == tokens.lastIndex) {
|
||||
Modifier.padding(bottom = 8.dp)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun LazyListScope.portfolioItem(
|
||||
portfolio: TokensListItemUM.Portfolio,
|
||||
modifier: Modifier,
|
||||
isBalanceHidden: Boolean,
|
||||
) {
|
||||
item(
|
||||
key = "account-${portfolio.id}",
|
||||
contentType = "account",
|
||||
) {
|
||||
PortfolioListItem(
|
||||
state = portfolio,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.roundedShapeItemDecoration(
|
||||
currentIndex = 0,
|
||||
lastIndex = portfolio.tokens.lastIndex.inc(),
|
||||
backgroundColor = TangemTheme.colors.background.primary,
|
||||
)
|
||||
.then(modifier),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -107,12 +184,12 @@ private fun ItemsBlock(items: ImmutableList<TokensListItemUM>, isBalanceHidden:
|
|||
@Composable
|
||||
private fun Preview_TokenList(@PreviewParameter(PreviewTokenListUMProvider::class) state: TokenListUM) {
|
||||
TangemThemePreview {
|
||||
TokenList(
|
||||
state = state,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(color = TangemTheme.colors.background.secondary)
|
||||
.padding(16.dp),
|
||||
)
|
||||
LazyColumn(
|
||||
modifier = Modifier.background(color = TangemTheme.colors.background.secondary),
|
||||
) {
|
||||
onrampTokenList(
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMData
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal class PreviewTokenListUMProvider : PreviewParameterProvider<TokenListUM> {
|
||||
|
|
@ -42,6 +43,7 @@ internal class PreviewTokenListUMProvider : PreviewParameterProvider<TokenListUM
|
|||
createUnavailableTokenItem(),
|
||||
createUnavailableTokenItem(),
|
||||
),
|
||||
tokensListData = TokenListUMData.EmptyList,
|
||||
isBalanceHidden = false,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -454,7 +454,7 @@ internal class SendModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun initialState(): SendUM = SendUM(
|
||||
amountUM = AmountState.Empty(isRedesignEnabled = true),
|
||||
amountUM = AmountState.Empty,
|
||||
destinationUM = SendDestinationInitialStateTransformer(
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
).transform(DestinationUM.Empty()),
|
||||
|
|
|
|||
|
|
@ -26,12 +26,10 @@ internal class SendAmountComponent(
|
|||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
val isBalanceHidden by params.isBalanceHidingFlow.collectAsStateWithLifecycle()
|
||||
val isSendWithSwapAvailable by model.isSendWithSwapAvailable.collectAsStateWithLifecycle()
|
||||
|
||||
SendAmountContent(
|
||||
amountState = state,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
clickIntents = model,
|
||||
isSendWithSwapAvailable = isSendWithSwapAvailable,
|
||||
modifier = modifier,
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ internal class SendAmountModel @Inject constructor(
|
|||
if (uiState.value is AmountState.Empty && userWallet != null) {
|
||||
val isOnlyOneWallet = getWalletsUseCase.invokeSync().size == 1
|
||||
_uiState.update {
|
||||
AmountStateConverterV2(
|
||||
AmountStateConverter(
|
||||
clickIntents = this,
|
||||
appCurrency = appCurrency,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ import com.tangem.features.send.v2.subcomponents.amount.ui.preview.SendAmountCli
|
|||
@Composable
|
||||
fun SendAmountContent(
|
||||
amountState: AmountState,
|
||||
isBalanceHidden: Boolean,
|
||||
clickIntents: SendAmountClickIntents,
|
||||
isSendWithSwapAvailable: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -38,7 +37,6 @@ fun SendAmountContent(
|
|||
Column(modifier = modifier.background(TangemTheme.colors.background.tertiary)) {
|
||||
AmountScreenContent(
|
||||
amountState = amountState,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
clickIntents = clickIntents,
|
||||
extraContent = {
|
||||
SendConvertTokenButton(
|
||||
|
|
@ -95,7 +93,6 @@ private fun SendAmountContent_Preview(@PreviewParameter(SendAmountContentPreview
|
|||
TangemThemePreview {
|
||||
SendAmountContent(
|
||||
amountState = params,
|
||||
isBalanceHidden = true,
|
||||
clickIntents = SendAmountClickIntentsStub,
|
||||
isSendWithSwapAvailable = true,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -201,16 +201,11 @@ class SendConfirmationNotificationsTransformerV2Test {
|
|||
|
||||
return AmountState.Data(
|
||||
isPrimaryButtonEnabled = true,
|
||||
isRedesignEnabled = false,
|
||||
title = mockk(relaxed = true),
|
||||
availableBalance = mockk(relaxed = true),
|
||||
availableBalanceCrypto = mockk(relaxed = true),
|
||||
availableBalanceFiat = mockk(relaxed = true),
|
||||
tokenName = mockk(relaxed = true),
|
||||
tokenIconState = mockk(relaxed = true),
|
||||
segmentedButtonConfig = persistentListOf(),
|
||||
selectedButton = 0,
|
||||
isSegmentedButtonsEnabled = false,
|
||||
amountTextField = AmountFieldModel(
|
||||
value = "1.5",
|
||||
onValueChange = {},
|
||||
|
|
|
|||
|
|
@ -52,7 +52,9 @@ internal class StakingAnalyticSender(
|
|||
source = when (value.currentStep) {
|
||||
StakingStep.InitialInfo -> StakeScreenSource.Info
|
||||
StakingStep.Amount -> StakeScreenSource.Amount
|
||||
StakingStep.Confirmation -> StakeScreenSource.Confirmation
|
||||
StakingStep.Success,
|
||||
StakingStep.Confirmation,
|
||||
-> StakeScreenSource.Confirmation
|
||||
StakingStep.Validators,
|
||||
StakingStep.RestakeValidator,
|
||||
StakingStep.RewardsValidators,
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ internal class StakingStateController @Inject constructor(
|
|||
cryptoCurrencyBlockchainId = "",
|
||||
currentStep = StakingStep.InitialInfo,
|
||||
initialInfoState = StakingStates.InitialInfoState.Empty(),
|
||||
amountState = AmountState.Empty(isRedesignEnabled = false),
|
||||
amountState = AmountState.Empty,
|
||||
validatorState = StakingStates.ValidatorState.Empty(),
|
||||
rewardsValidatorsState = StakingStates.RewardsValidatorsState.Empty(),
|
||||
confirmationState = StakingStates.ConfirmationState.Empty(),
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ internal class StakingStateRouter(
|
|||
StakingStep.Amount,
|
||||
-> showConfirmation()
|
||||
StakingStep.Confirmation -> showInitial()
|
||||
StakingStep.Success -> appRouter.pop()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -65,6 +66,7 @@ internal class StakingStateRouter(
|
|||
}
|
||||
}
|
||||
StakingStep.Validators -> showConfirmation()
|
||||
StakingStep.Success -> appRouter.pop()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -138,4 +138,5 @@ enum class StakingStep {
|
|||
RestakeValidator,
|
||||
Confirmation,
|
||||
Validators,
|
||||
Success,
|
||||
}
|
||||
|
|
@ -46,10 +46,11 @@ internal class SetAmountDataTransformer(
|
|||
return prevState.copy(
|
||||
amountState = AmountStateConverter(
|
||||
clickIntents = clickIntents,
|
||||
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
|
||||
appCurrencyProvider = appCurrencyProvider,
|
||||
iconStateConverter = iconStateConverter,
|
||||
maxEnterAmount = maxEnterAmount,
|
||||
appCurrency = appCurrencyProvider(),
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatusProvider(),
|
||||
isBalanceHidden = false,
|
||||
).convert(
|
||||
AmountParameters(
|
||||
title = title,
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ internal class SetButtonsStateTransformer(
|
|||
val buttonsState = if (prevState.isButtonsVisible()) {
|
||||
NavigationButtonsState.Data(
|
||||
primaryButton = getPrimaryButton(prevState),
|
||||
prevButton = getPrevButton(prevState),
|
||||
extraButtons = getExtraButtons(prevState).takeIf { txUrl != null },
|
||||
txUrl = txUrl,
|
||||
onTextClick = urlOpener::openUrl,
|
||||
|
|
@ -64,18 +63,6 @@ internal class SetButtonsStateTransformer(
|
|||
)
|
||||
}
|
||||
|
||||
private fun getPrevButton(prevState: StakingUiState): NavigationButton? {
|
||||
return NavigationButton(
|
||||
textReference = TextReference.EMPTY,
|
||||
iconRes = R.drawable.ic_back_24,
|
||||
isSecondary = true,
|
||||
isIconVisible = true,
|
||||
shouldShowProgress = false,
|
||||
isEnabled = true,
|
||||
onClick = prevState.clickIntents::onPrevClick,
|
||||
).takeIf { prevState.currentStep.isPrevButtonVisible() }
|
||||
}
|
||||
|
||||
private fun getExtraButtons(prevState: StakingUiState): Pair<NavigationButton, NavigationButton> {
|
||||
return NavigationButton(
|
||||
textReference = resourceReference(R.string.common_explore),
|
||||
|
|
@ -111,7 +98,7 @@ internal class SetButtonsStateTransformer(
|
|||
resourceReference(R.string.common_stake)
|
||||
}
|
||||
}
|
||||
|
||||
StakingStep.Success -> resourceReference(R.string.common_close)
|
||||
StakingStep.Confirmation -> getConfirmationButtonText()
|
||||
StakingStep.Validators -> resourceReference(R.string.common_continue)
|
||||
StakingStep.Amount,
|
||||
|
|
@ -125,21 +112,17 @@ internal class SetButtonsStateTransformer(
|
|||
val confirmationState = confirmationState as? StakingStates.ConfirmationState.Data
|
||||
val amountState = amountState as? AmountState.Data
|
||||
return if (confirmationState != null && amountState != null) {
|
||||
if (confirmationState.innerState == InnerConfirmationStakingState.COMPLETED) {
|
||||
resourceReference(R.string.common_close)
|
||||
} else {
|
||||
when (actionType) {
|
||||
is StakingActionCommonType.Enter -> {
|
||||
val amount = amountState.amountTextField.cryptoAmount.value.orZero()
|
||||
if (confirmationState.isApprovalNeeded && confirmationState.allowance < amount) {
|
||||
resourceReference(R.string.give_permission_title)
|
||||
} else {
|
||||
resourceReference(R.string.common_stake)
|
||||
}
|
||||
when (actionType) {
|
||||
is StakingActionCommonType.Enter -> {
|
||||
val amount = amountState.amountTextField.cryptoAmount.value.orZero()
|
||||
if (confirmationState.isApprovalNeeded && confirmationState.allowance < amount) {
|
||||
resourceReference(R.string.give_permission_title)
|
||||
} else {
|
||||
resourceReference(R.string.common_stake)
|
||||
}
|
||||
is StakingActionCommonType.Exit -> resourceReference(R.string.common_unstake)
|
||||
is StakingActionCommonType.Pending -> confirmationState.pendingAction?.type.getPendingActionTitle()
|
||||
}
|
||||
is StakingActionCommonType.Exit -> resourceReference(R.string.common_unstake)
|
||||
is StakingActionCommonType.Pending -> confirmationState.pendingAction?.type.getPendingActionTitle()
|
||||
}
|
||||
} else {
|
||||
resourceReference(R.string.common_close)
|
||||
|
|
@ -155,6 +138,7 @@ internal class SetButtonsStateTransformer(
|
|||
StakingStep.Amount -> clickIntents.onAmountEnterClick()
|
||||
StakingStep.Confirmation -> onConfirmationClick()
|
||||
StakingStep.RewardsValidators -> Unit
|
||||
StakingStep.Success -> clickIntents.onBackClick()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -178,17 +162,6 @@ internal class SetButtonsStateTransformer(
|
|||
}
|
||||
}
|
||||
|
||||
private fun StakingStep.isPrevButtonVisible(): Boolean = when (this) {
|
||||
StakingStep.InitialInfo,
|
||||
StakingStep.RewardsValidators,
|
||||
StakingStep.RestakeValidator,
|
||||
StakingStep.Confirmation,
|
||||
StakingStep.Validators,
|
||||
-> false
|
||||
StakingStep.Amount,
|
||||
-> true
|
||||
}
|
||||
|
||||
private fun StakingUiState.isPrimaryButtonDisabled(): Boolean {
|
||||
val initialState = initialInfoState as? StakingStates.InitialInfoState.Data
|
||||
val hasNotStaking = initialState?.yieldBalance == InnerYieldBalanceState.Empty
|
||||
|
|
@ -205,6 +178,7 @@ internal class SetButtonsStateTransformer(
|
|||
StakingStep.RewardsValidators -> rewardsValidatorsState.isPrimaryButtonEnabled
|
||||
StakingStep.RestakeValidator,
|
||||
StakingStep.Validators,
|
||||
StakingStep.Success,
|
||||
-> true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.features.staking.impl.presentation.state.InnerConfirmationStakingState
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStep
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.features.staking.impl.presentation.state.TransactionDoneState
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
|
@ -17,6 +18,7 @@ internal class SetConfirmationStateCompletedTransformer(
|
|||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
return prevState.copy(
|
||||
confirmationState = prevState.confirmationState.copyWrapped(),
|
||||
currentStep = StakingStep.Success,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -229,10 +229,11 @@ internal class SetInitialDataStateTransformer(
|
|||
)
|
||||
return AmountStateConverter(
|
||||
clickIntents = clickIntents,
|
||||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
appCurrencyProvider = appCurrencyProvider,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
appCurrency = appCurrencyProvider(),
|
||||
iconStateConverter = iconStateConverter,
|
||||
maxEnterAmount = maxEnterAmount,
|
||||
isBalanceHidden = false,
|
||||
).convert(
|
||||
AmountParameters(
|
||||
title = stringReference(userWalletProvider().name),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.isNullOrEmpty
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
|
|
@ -28,7 +29,7 @@ internal object SetTitleTransformer : Transformer<StakingUiState> {
|
|||
R.string.staking_title_stake,
|
||||
wrappedList(prevState.cryptoCurrencyName),
|
||||
)
|
||||
|
||||
StakingStep.Success -> TextReference.EMPTY
|
||||
StakingStep.Confirmation -> {
|
||||
when (actionType) {
|
||||
is StakingActionCommonType.Enter -> resourceReference(
|
||||
|
|
|
|||
|
|
@ -14,12 +14,13 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData
|
||||
import com.tangem.common.ui.amountScreen.ui.AmountBlock
|
||||
import com.tangem.common.ui.amountScreen.ui.AmountBlockV2
|
||||
import com.tangem.core.ui.components.transactions.TransactionDoneTitle
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.staking.impl.R
|
||||
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
|
||||
import com.tangem.features.staking.impl.presentation.state.InnerConfirmationStakingState
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingNotification
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
|
|
@ -30,7 +31,6 @@ import com.tangem.features.staking.impl.presentation.state.stub.StakingClickInte
|
|||
import com.tangem.features.staking.impl.presentation.ui.block.NotificationsBlock
|
||||
import com.tangem.features.staking.impl.presentation.ui.block.StakingFeeBlock
|
||||
import com.tangem.features.staking.impl.presentation.ui.block.ValidatorBlock
|
||||
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
|
|
@ -60,7 +60,7 @@ internal fun StakingConfirmationContent(
|
|||
subtitle = resourceReference(R.string.staking_transaction_in_progress_text),
|
||||
)
|
||||
}
|
||||
AmountBlock(
|
||||
AmountBlockV2(
|
||||
amountState = amountState,
|
||||
isClickDisabled = !state.isAmountEditable || isTransactionSent || isTransactionInProgress,
|
||||
isEditingDisabled = !state.isAmountEditable && state.innerState != InnerConfirmationStakingState.COMPLETED,
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ private fun StakingAppBar(uiState: StakingUiState) {
|
|||
val (backIcon, click) = when (uiState.currentStep) {
|
||||
StakingStep.Amount,
|
||||
StakingStep.Confirmation,
|
||||
StakingStep.Success,
|
||||
-> R.drawable.ic_close_24 to uiState.clickIntents::onBackClick
|
||||
StakingStep.Validators,
|
||||
StakingStep.RewardsValidators,
|
||||
|
|
@ -108,6 +109,7 @@ private fun StakingAppBar(uiState: StakingUiState) {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun StakingScreenContent(uiState: StakingUiState, modifier: Modifier = Modifier) {
|
||||
val currentScreen = uiState.currentStep
|
||||
|
|
@ -136,10 +138,10 @@ private fun StakingScreenContent(uiState: StakingUiState, modifier: Modifier = M
|
|||
contentAlignment = Alignment.TopCenter,
|
||||
label = "Staking Screen Navigation",
|
||||
transitionSpec = {
|
||||
val direction = if (initialState.ordinal < targetState.ordinal) {
|
||||
AnimatedContentTransitionScope.SlideDirection.Start
|
||||
} else {
|
||||
AnimatedContentTransitionScope.SlideDirection.End
|
||||
val direction = when {
|
||||
targetState == StakingStep.Success -> AnimatedContentTransitionScope.SlideDirection.Up
|
||||
initialState.ordinal < targetState.ordinal -> AnimatedContentTransitionScope.SlideDirection.Start
|
||||
else -> AnimatedContentTransitionScope.SlideDirection.End
|
||||
}
|
||||
|
||||
slideIntoContainer(towards = direction, animationSpec = tween())
|
||||
|
|
@ -163,7 +165,6 @@ private fun StakingScreenContent(uiState: StakingUiState, modifier: Modifier = M
|
|||
}
|
||||
StakingStep.Amount -> AmountScreenContent(
|
||||
amountState = uiState.amountState,
|
||||
isBalanceHidden = uiState.isBalanceHidden,
|
||||
clickIntents = uiState.clickIntents,
|
||||
modifier = Modifier.background(TangemTheme.colors.background.secondary),
|
||||
)
|
||||
|
|
@ -173,6 +174,12 @@ private fun StakingScreenContent(uiState: StakingUiState, modifier: Modifier = M
|
|||
validatorState = uiState.validatorState,
|
||||
clickIntents = uiState.clickIntents,
|
||||
)
|
||||
StakingStep.Success -> StakingSuccessContent(
|
||||
amountState = uiState.amountState,
|
||||
state = uiState.confirmationState,
|
||||
validatorState = uiState.validatorState,
|
||||
clickIntents = uiState.clickIntents,
|
||||
)
|
||||
StakingStep.RestakeValidator,
|
||||
StakingStep.Validators,
|
||||
-> StakingValidatorListContent(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
package com.tangem.features.staking.impl.presentation.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData
|
||||
import com.tangem.common.ui.amountScreen.ui.AmountBlock
|
||||
import com.tangem.core.ui.components.transactions.TransactionDoneTitle
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.staking.impl.R
|
||||
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
|
||||
import com.tangem.features.staking.impl.presentation.state.InnerConfirmationStakingState
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingNotification
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.previewdata.ConfirmationStatePreviewData
|
||||
import com.tangem.features.staking.impl.presentation.state.previewdata.ValidatorStatePreviewData
|
||||
import com.tangem.features.staking.impl.presentation.state.stub.StakingClickIntentsStub
|
||||
import com.tangem.features.staking.impl.presentation.ui.block.NotificationsBlock
|
||||
import com.tangem.features.staking.impl.presentation.ui.block.StakingFeeBlock
|
||||
import com.tangem.features.staking.impl.presentation.ui.block.ValidatorBlock
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
internal fun StakingSuccessContent(
|
||||
amountState: AmountState,
|
||||
state: StakingStates.ConfirmationState,
|
||||
validatorState: StakingStates.ValidatorState,
|
||||
clickIntents: StakingClickIntents,
|
||||
) {
|
||||
if (state !is StakingStates.ConfirmationState.Data) return
|
||||
val isTransactionSent = state.innerState == InnerConfirmationStakingState.COMPLETED
|
||||
val isTransactionInProgress = state.notifications.any { it is StakingNotification.Warning.TransactionInProgress }
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.secondary)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
TransactionDoneTitle(
|
||||
title = resourceReference(R.string.common_in_progress),
|
||||
subtitle = resourceReference(R.string.staking_transaction_in_progress_text),
|
||||
)
|
||||
AmountBlock(
|
||||
amountState = amountState,
|
||||
isClickDisabled = !state.isAmountEditable || isTransactionSent || isTransactionInProgress,
|
||||
isEditingDisabled = !state.isAmountEditable && state.innerState != InnerConfirmationStakingState.COMPLETED,
|
||||
onClick = clickIntents::onPrevClick,
|
||||
)
|
||||
ValidatorBlock(
|
||||
validatorState = validatorState,
|
||||
isClickable = !isTransactionInProgress,
|
||||
onClick = clickIntents::openValidators,
|
||||
)
|
||||
StakingFeeBlock(feeState = state.feeState)
|
||||
NotificationsBlock(notifications = state.notifications)
|
||||
Spacer(Modifier)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_StakingConfirmationContent() {
|
||||
TangemThemePreview {
|
||||
Column(Modifier.background(TangemTheme.colors.background.primary)) {
|
||||
StakingConfirmationContent(
|
||||
amountState = AmountStatePreviewData.amountState,
|
||||
state = ConfirmationStatePreviewData.assentStakingState,
|
||||
validatorState = ValidatorStatePreviewData.validatorState,
|
||||
clickIntents = StakingClickIntentsStub,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -64,19 +64,13 @@ sealed class SwapAmountFieldUM {
|
|||
data class Empty(
|
||||
override val amountType: SwapAmountType,
|
||||
) : SwapAmountFieldUM() {
|
||||
override val amountField: AmountState = AmountState.Empty(
|
||||
isPrimaryButtonEnabled = false,
|
||||
isRedesignEnabled = true,
|
||||
)
|
||||
override val amountField: AmountState = AmountState.Empty
|
||||
}
|
||||
|
||||
data class Loading(
|
||||
override val amountType: SwapAmountType,
|
||||
) : SwapAmountFieldUM() {
|
||||
override val amountField: AmountState = AmountState.Empty(
|
||||
isPrimaryButtonEnabled = false,
|
||||
isRedesignEnabled = true,
|
||||
)
|
||||
override val amountField: AmountState = AmountState.Empty
|
||||
}
|
||||
|
||||
data class Content(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.features.swap.v2.impl.amount.model.converter
|
||||
|
||||
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
|
||||
import com.tangem.common.ui.amountScreen.converters.AmountStateConverterV2
|
||||
import com.tangem.common.ui.amountScreen.converters.AmountStateConverter
|
||||
import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter
|
||||
import com.tangem.common.ui.amountScreen.models.AmountParameters
|
||||
import com.tangem.core.ui.components.atoms.text.TextEllipsis
|
||||
|
|
@ -44,7 +44,7 @@ internal class SwapAmountFieldConverter(
|
|||
subtitleEllipsisRight = TextEllipsis.OffsetEnd(appCurrency.symbol.length),
|
||||
priceImpact = null,
|
||||
isClickEnabled = selectedType.isViewingField(),
|
||||
amountField = AmountStateConverterV2(
|
||||
amountField = AmountStateConverter(
|
||||
clickIntents = clickIntents,
|
||||
appCurrency = appCurrency,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
|
|
|
|||
|
|
@ -46,9 +46,7 @@ internal class SwapAmountBalanceHiddenTransformer(
|
|||
val newData = recalculatedPrimary.amountField
|
||||
newData.copy(
|
||||
amountTextField = oldData.amountTextField,
|
||||
selectedButton = oldData.selectedButton,
|
||||
isPrimaryButtonEnabled = oldData.isPrimaryButtonEnabled,
|
||||
isSegmentedButtonsEnabled = oldData.isSegmentedButtonsEnabled,
|
||||
isEditingDisabled = oldData.isEditingDisabled,
|
||||
reduceAmountBy = oldData.reduceAmountBy,
|
||||
isIgnoreReduce = oldData.isIgnoreReduce,
|
||||
|
|
|
|||
|
|
@ -127,7 +127,6 @@ private fun ConstraintLayoutScope.SwapAmountBlock(
|
|||
AmountBlockV2(
|
||||
amountState = (amountUM.secondaryAmount.amountField as? AmountState.Data)?.copy(
|
||||
title = resourceReference(R.string.send_with_swap_recipient_amount_title),
|
||||
availableBalance = TextReference.EMPTY,
|
||||
availableBalanceCrypto = TextReference.EMPTY,
|
||||
) ?: amountUM.secondaryAmount.amountField,
|
||||
isClickDisabled = true,
|
||||
|
|
|
|||
|
|
@ -97,9 +97,7 @@ internal data object SwapAmountContentPreview {
|
|||
val defaultState = SwapAmountUM.Content(
|
||||
primaryAmount = SwapAmountFieldUM.Content(
|
||||
amountType = SwapAmountType.From,
|
||||
amountField = AmountStatePreviewData.amountState.copy(
|
||||
availableBalance = stringReference("Balance: 100 BTC"),
|
||||
),
|
||||
amountField = AmountStatePreviewData.amountState,
|
||||
title = stringReference("Tether"),
|
||||
subtitleLeft = stringReference("11 101,123123456 BTC"),
|
||||
subtitleRight = stringReference(" ${StringsSigns.DOT} 1 212,12 $"),
|
||||
|
|
@ -112,7 +110,6 @@ internal data object SwapAmountContentPreview {
|
|||
amountType = SwapAmountType.To,
|
||||
amountField = AmountStatePreviewData.amountState.copy(
|
||||
title = stringReference("Amount to receive"),
|
||||
availableBalance = TextReference.EMPTY,
|
||||
),
|
||||
title = stringReference("Shiba Inu"),
|
||||
priceImpact = stringReference("(-10%)"),
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ internal class PreviewTangemPayTxHistoryComponent(txHistoryUM: TangemPayTxHistor
|
|||
companion object {
|
||||
val loadingUM = TangemPayTxHistoryUM.Loading(isBalanceHidden = true)
|
||||
val emptyUM = TangemPayTxHistoryUM.Empty(isBalanceHidden = true)
|
||||
val errorUM = TangemPayTxHistoryUM.Error(isBalanceHidden = true, onReload = {})
|
||||
val contentUM = TangemPayTxHistoryUM.Content(
|
||||
isBalanceHidden = false,
|
||||
loadMore = { false },
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.features.tangempay.entity
|
||||
|
||||
import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.features.tangempay.details.impl.R
|
||||
|
||||
internal sealed class TangemPayEmptyTransactionHistoryState {
|
||||
|
||||
abstract val iconRes: Int
|
||||
abstract val text: TextReference
|
||||
|
||||
data class FailedToLoad(
|
||||
private val onReload: () -> Unit,
|
||||
) : TangemPayEmptyTransactionHistoryState() {
|
||||
override val iconRes: Int = R.drawable.ic_alert_history_64
|
||||
override val text: TextReference = resourceReference(R.string.transaction_history_error_failed_to_load)
|
||||
val actionButtonConfig = ActionButtonConfig(
|
||||
text = resourceReference(R.string.common_reload),
|
||||
iconResId = R.drawable.ic_refresh_24,
|
||||
onClick = onReload,
|
||||
)
|
||||
}
|
||||
|
||||
data object Empty : TangemPayEmptyTransactionHistoryState() {
|
||||
override val iconRes: Int = R.drawable.ic_empty_token_64
|
||||
override val text: TextReference = resourceReference(R.string.transaction_history_empty_transactions)
|
||||
}
|
||||
}
|
||||
|
|
@ -54,11 +54,16 @@ internal class TangemPayTxHistoryModel @Inject constructor(
|
|||
.onEach(::updateState)
|
||||
.launchIn(modelScope)
|
||||
listManager.paginationStatus
|
||||
.onEach { paginationStatus -> handlePaginationStatus(paginationStatus) }
|
||||
.onEach(::handlePaginationStatus)
|
||||
.launchIn(modelScope)
|
||||
listManager.emptyStatus
|
||||
.onEach(::handleEmptyState)
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun updateState(items: ImmutableList<TangemPayTxHistoryUM.TangemPayTxHistoryItemUM>) {
|
||||
if (items.isEmpty()) return // fast exit. If items is empty, no need to update ui items
|
||||
|
||||
uiState.update { state ->
|
||||
if (state is TangemPayTxHistoryUM.Content) {
|
||||
state.copy(items = items)
|
||||
|
|
@ -72,6 +77,12 @@ internal class TangemPayTxHistoryModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleEmptyState(isEmpty: Boolean) {
|
||||
if (isEmpty) {
|
||||
uiState.update { getEmptyState(it.isBalanceHidden) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePaginationStatus(status: PaginationStatus<*>) {
|
||||
uiState.update { state ->
|
||||
when (status) {
|
||||
|
|
@ -108,6 +119,10 @@ internal class TangemPayTxHistoryModel @Inject constructor(
|
|||
Timber.d("onTransactionClick: $item")
|
||||
}
|
||||
|
||||
private fun getEmptyState(isBalanceHidden: Boolean): TangemPayTxHistoryUM.Empty {
|
||||
return TangemPayTxHistoryUM.Empty(isBalanceHidden = isBalanceHidden)
|
||||
}
|
||||
|
||||
private fun getErrorState(isBalanceHidden: Boolean): TangemPayTxHistoryUM.Error {
|
||||
return TangemPayTxHistoryUM.Error(isBalanceHidden = isBalanceHidden, onReload = ::reload)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,7 @@ package com.tangem.features.tangempay.ui
|
|||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.RepeatMode
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.infiniteRepeatable
|
||||
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -15,12 +10,8 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
|
|
@ -44,21 +35,14 @@ import com.tangem.core.ui.components.dropdownmenu.TangemDropdownItem
|
|||
import com.tangem.core.ui.components.dropdownmenu.TangemDropdownMenu
|
||||
import com.tangem.core.ui.components.snackbar.TangemSnackbarHost
|
||||
import com.tangem.core.ui.components.text.applyBladeBrush
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.TokenDetailsTopBarTestTags
|
||||
import com.tangem.features.tangempay.components.txHistory.PreviewTangemPayTxHistoryComponent
|
||||
import com.tangem.features.tangempay.components.txHistory.TangemPayTxHistoryComponent
|
||||
import com.tangem.features.tangempay.details.impl.R
|
||||
import com.tangem.features.tangempay.entity.TangemPayCardDetailsUM
|
||||
import com.tangem.features.tangempay.entity.TangemPayDetailsBalanceBlockState
|
||||
import com.tangem.features.tangempay.entity.TangemPayDetailsTopBarConfig
|
||||
import com.tangem.features.tangempay.entity.TangemPayDetailsUM
|
||||
import com.tangem.features.tangempay.entity.*
|
||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
|
|
@ -415,8 +399,8 @@ private fun TangemPayDetailsTopAppBar(config: TangemPayDetailsTopBarConfig, modi
|
|||
)
|
||||
}
|
||||
|
||||
@Preview(device = Devices.PIXEL_7_PRO, group = "day")
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, device = Devices.PIXEL_7_PRO, group = "night")
|
||||
@Preview(device = Devices.PIXEL_7_PRO)
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, device = Devices.PIXEL_7_PRO)
|
||||
@Composable
|
||||
private fun TangemPayDetailsScreenPreview(
|
||||
@PreviewParameter(TangemPayDetailsUMProvider::class) state: TangemPayDetailsUM,
|
||||
|
|
@ -473,4 +457,26 @@ private class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider<Ta
|
|||
isBalanceHidden = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@Preview(device = Devices.PIXEL_7_PRO)
|
||||
@Composable
|
||||
private fun TangemPayDetailsTxHistoryScreenPreview(
|
||||
@PreviewParameter(TangemPayDetailsTxHistoryProvider::class) state: TangemPayTxHistoryUM,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
TangemPayDetailsScreen(
|
||||
state = TangemPayDetailsUMProvider().values.first(),
|
||||
txHistoryComponent = PreviewTangemPayTxHistoryComponent(txHistoryUM = state),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class TangemPayDetailsTxHistoryProvider : CollectionPreviewParameterProvider<TangemPayTxHistoryUM>(
|
||||
collection = listOf(
|
||||
PreviewTangemPayTxHistoryComponent.loadingUM,
|
||||
PreviewTangemPayTxHistoryComponent.contentUM,
|
||||
PreviewTangemPayTxHistoryComponent.emptyUM,
|
||||
PreviewTangemPayTxHistoryComponent.errorUM,
|
||||
),
|
||||
)
|
||||
|
|
@ -20,6 +20,7 @@ import androidx.compose.ui.platform.testTag
|
|||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.constraintlayout.compose.ChainStyle
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import androidx.constraintlayout.compose.Dimension
|
||||
|
|
@ -27,6 +28,7 @@ import coil.compose.rememberAsyncImagePainter
|
|||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.CircleShimmer
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.buttons.actions.ActionButton
|
||||
import com.tangem.core.ui.components.list.InfiniteListHandler
|
||||
import com.tangem.core.ui.components.transactions.TxHistoryGroupTitle
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
|
|
@ -34,7 +36,9 @@ import com.tangem.core.ui.extensions.orMaskWithStars
|
|||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.test.EmptyTransactionBlockTestTags
|
||||
import com.tangem.core.ui.test.TransactionHistoryBlockTestTags
|
||||
import com.tangem.features.tangempay.entity.TangemPayEmptyTransactionHistoryState
|
||||
import com.tangem.features.tangempay.entity.TangemPayTransactionState
|
||||
import com.tangem.features.tangempay.entity.TangemPayTxHistoryUM
|
||||
|
||||
|
|
@ -43,12 +47,25 @@ private const val LOAD_ITEMS_BUFFER = 20
|
|||
internal fun LazyListScope.tangemPayTxHistoryItems(listState: LazyListState, state: TangemPayTxHistoryUM) {
|
||||
when (state) {
|
||||
is TangemPayTxHistoryUM.Content -> contentItems(listState = listState, state = state)
|
||||
is TangemPayTxHistoryUM.Empty -> TODO("[REDACTED_JIRA]")
|
||||
is TangemPayTxHistoryUM.Error -> TODO("[REDACTED_JIRA]")
|
||||
is TangemPayTxHistoryUM.Empty -> nonContentItem(state = TangemPayEmptyTransactionHistoryState.Empty)
|
||||
is TangemPayTxHistoryUM.Error -> nonContentItem(
|
||||
state = TangemPayEmptyTransactionHistoryState.FailedToLoad(onReload = state.onReload),
|
||||
)
|
||||
is TangemPayTxHistoryUM.Loading -> loadingItems(state = state)
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.nonContentItem(state: TangemPayEmptyTransactionHistoryState, modifier: Modifier = Modifier) {
|
||||
item(key = state::class.java, contentType = state::class.java) {
|
||||
TangemPayEmptyTransactionBlock(
|
||||
state = state,
|
||||
modifier = modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16, vertical = TangemTheme.dimens.spacing12)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.contentItems(listState: LazyListState, state: TangemPayTxHistoryUM.Content) {
|
||||
itemsIndexed(
|
||||
items = state.items,
|
||||
|
|
@ -370,4 +387,49 @@ private fun Timestamp(state: TangemPayTransactionState, modifier: Modifier = Mod
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TangemPayEmptyTransactionBlock(
|
||||
state: TangemPayEmptyTransactionHistoryState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(color = TangemTheme.colors.background.primary)
|
||||
.padding(vertical = TangemTheme.dimens.spacing24)
|
||||
.testTag(EmptyTransactionBlockTestTags.BLOCK),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size64)
|
||||
.testTag(EmptyTransactionBlockTestTags.ICON),
|
||||
painter = painterResource(id = state.iconRes),
|
||||
tint = TangemTheme.colors.icon.inactive,
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing32)
|
||||
.testTag(EmptyTransactionBlockTestTags.TEXT),
|
||||
textAlign = TextAlign.Center,
|
||||
text = state.text.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
|
||||
when (state) {
|
||||
is TangemPayEmptyTransactionHistoryState.Empty -> Unit
|
||||
is TangemPayEmptyTransactionHistoryState.FailedToLoad -> ActionButton(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 24.dp)
|
||||
.fillMaxWidth(),
|
||||
config = state.actionButtonConfig,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@ internal class TangemPayTxHistoryListManager(
|
|||
private val uiManager = TangemPayTxHistoryUiManager(state = state, txHistoryUiActions = txHistoryUiActions)
|
||||
|
||||
val uiItems: Flow<ImmutableList<TangemPayTxHistoryUM.TangemPayTxHistoryItemUM>> = uiManager.items
|
||||
val emptyStatus: Flow<Boolean> = state.map { it.isEmpty }.distinctUntilChanged()
|
||||
val paginationStatus: Flow<PaginationStatus<*>> = state.map { it.status }.distinctUntilChanged()
|
||||
|
||||
suspend fun launchPagination() = coroutineScope {
|
||||
|
|
@ -80,6 +81,7 @@ internal class TangemPayTxHistoryListManager(
|
|||
newCurrencyBatches = batchListState.data,
|
||||
clearUiBatches = clearUiBatches,
|
||||
),
|
||||
isEmpty = batchListState.status is PaginationStatus.EndOfPagination && batchListState.data.isEmpty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,4 +7,5 @@ import com.tangem.pagination.PaginationStatus
|
|||
internal data class TangemPayTxHistoryState(
|
||||
val status: PaginationStatus<*> = PaginationStatus.None,
|
||||
val uiBatches: List<Batch<Int, List<TangemPayTxHistoryUM.TangemPayTxHistoryItemUM>>> = listOf(),
|
||||
val isEmpty: Boolean = false,
|
||||
)
|
||||
|
|
@ -56,7 +56,7 @@ internal class TangemPayOnboardingModel @Inject constructor(
|
|||
repository.getCustomerInfo()
|
||||
.onRight { customerInfo ->
|
||||
when {
|
||||
!customerInfo.isKycApproved() -> {
|
||||
!customerInfo.isKycApproved -> {
|
||||
when (params) {
|
||||
is TangemPayOnboardingComponent.Params.Deeplink ->
|
||||
screenState.value = screenState.value.copy(fullScreenLoading = false)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import com.tangem.core.ui.UiDependencies
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeActivity
|
||||
import com.tangem.feature.tester.presentation.accounts.ui.AccountsScreen
|
||||
import com.tangem.feature.tester.presentation.accounts.viewmodel.AccountsViewModel
|
||||
import com.tangem.feature.tester.presentation.accounts.viewmodel.TesterAccountsViewModel
|
||||
import com.tangem.feature.tester.presentation.actions.TesterActionsScreen
|
||||
import com.tangem.feature.tester.presentation.actions.TesterActionsViewModel
|
||||
import com.tangem.feature.tester.presentation.environments.ui.EnvironmentTogglesScreen
|
||||
|
|
@ -155,7 +155,7 @@ internal class TesterActivity : ComposeActivity() {
|
|||
}
|
||||
|
||||
composable(route = TesterScreen.ACCOUNTS.name) {
|
||||
val viewModel = hiltViewModel<AccountsViewModel>().apply {
|
||||
val viewModel = hiltViewModel<TesterAccountsViewModel>().apply {
|
||||
setupNavigation(innerTesterRouter)
|
||||
}
|
||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
|
|
|
|||
|
|
@ -9,9 +9,8 @@ internal data class AccountsUM(
|
|||
val onBackClick: () -> Unit,
|
||||
val walletSelector: WalletSelector,
|
||||
val accountListBottomSheetConfig: AccountListBottomSheetConfig,
|
||||
val onAccountsClick: () -> Unit,
|
||||
val onAccountsClick: () -> Boolean,
|
||||
val onFetchAccountsClick: () -> Unit,
|
||||
val onCreateMainAccountClick: () -> Unit,
|
||||
val onClearETagClick: () -> Unit,
|
||||
) {
|
||||
|
||||
|
|
|
|||
|
|
@ -62,8 +62,9 @@ internal fun AccountsScreen(state: AccountsUM, modifier: Modifier = Modifier) {
|
|||
ManageAccountsButtons(
|
||||
state = state,
|
||||
onAccountsClick = { context ->
|
||||
if (state.accountListBottomSheetConfig.accounts.isNotEmpty()) {
|
||||
state.onAccountsClick()
|
||||
val isEmpty = state.onAccountsClick()
|
||||
|
||||
if (!isEmpty) {
|
||||
isAccountListShown = true
|
||||
} else {
|
||||
Toast.makeText(context, "No accounts found", Toast.LENGTH_SHORT).show()
|
||||
|
|
@ -242,16 +243,4 @@ private fun LazyListScope.ManageAccountsButtons(state: AccountsUM, onAccountsCli
|
|||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
if (state.accountListBottomSheetConfig.accounts.none { it.isMainAccount }) {
|
||||
item {
|
||||
PrimaryButton(
|
||||
text = "Create Main account",
|
||||
onClick = state.onCreateMainAccountClick,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,13 +6,9 @@ import com.tangem.data.common.cache.etag.ETagsStore
|
|||
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.producer.SingleAccountListProducer
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.TokensGroupType
|
||||
import com.tangem.domain.models.TokensSortType
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountName
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.feature.tester.presentation.accounts.entity.AccountsUM
|
||||
|
|
@ -28,11 +24,10 @@ import javax.inject.Inject
|
|||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@HiltViewModel
|
||||
internal class AccountsViewModel @Inject constructor(
|
||||
internal class TesterAccountsViewModel @Inject constructor(
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val singleAccountListFetcher: SingleAccountListFetcher,
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier,
|
||||
private val accountsCRUDRepository: AccountsCRUDRepository,
|
||||
private val eTagsStore: ETagsStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : ViewModel() {
|
||||
|
|
@ -88,7 +83,6 @@ internal class AccountsViewModel @Inject constructor(
|
|||
),
|
||||
onAccountsClick = ::updateAccountsList,
|
||||
onFetchAccountsClick = ::fetchAccounts,
|
||||
onCreateMainAccountClick = ::createMainAccount,
|
||||
onClearETagClick = ::clearETag,
|
||||
)
|
||||
}
|
||||
|
|
@ -107,8 +101,8 @@ internal class AccountsViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateAccountsList() {
|
||||
val userWalletId = uiState.value.walletSelector.selected?.walletId ?: return
|
||||
private fun updateAccountsList(): Boolean {
|
||||
val userWalletId = uiState.value.walletSelector.selected?.walletId ?: return false
|
||||
|
||||
val accounts = walletAccounts.value[userWalletId]?.accounts
|
||||
?.filterIsInstance<Account.CryptoPortfolio>()
|
||||
|
|
@ -122,6 +116,8 @@ internal class AccountsViewModel @Inject constructor(
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
return accounts.isEmpty()
|
||||
}
|
||||
|
||||
private fun fetchAccounts() {
|
||||
|
|
@ -134,28 +130,6 @@ internal class AccountsViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun createMainAccount() {
|
||||
viewModelScope.launch {
|
||||
val userWallet = uiState.value.walletSelector.selected ?: return@launch
|
||||
|
||||
// It's temporary solution to create main account for testing purposes
|
||||
val accountList = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(
|
||||
Account.CryptoPortfolio.createMainAccount(userWallet.walletId).copy(
|
||||
accountName = AccountName.invoke(value = "Main Account").getOrNull()!!,
|
||||
),
|
||||
),
|
||||
totalAccounts = 1,
|
||||
sortType = TokensSortType.NONE,
|
||||
groupType = TokensGroupType.NONE,
|
||||
)
|
||||
.getOrNull()!!
|
||||
|
||||
accountsCRUDRepository.saveAccounts(accountList)
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearETag() {
|
||||
viewModelScope.launch {
|
||||
val userWallet = uiState.value.walletSelector.selected ?: return@launch
|
||||
|
|
@ -22,6 +22,7 @@ import com.tangem.core.ui.message.DialogMessage
|
|||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.core.ui.message.bottomSheetMessage
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
|
|
@ -81,6 +82,7 @@ internal class WalletSettingsModel @Inject constructor(
|
|||
private val isUpgradeWalletNotificationEnabledUseCase: IsUpgradeWalletNotificationEnabledUseCase,
|
||||
private val dismissUpgradeWalletNotificationUseCase: DismissUpgradeWalletNotificationUseCase,
|
||||
private val unlockHotWalletContextualUseCase: UnlockHotWalletContextualUseCase,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
) : Model() {
|
||||
|
||||
val params: WalletSettingsComponent.Params = paramsContainer.require()
|
||||
|
|
@ -173,6 +175,7 @@ internal class WalletSettingsModel @Inject constructor(
|
|||
isUpgradeNotificationEnabled: Boolean,
|
||||
accountList: List<WalletSettingsAccountsUM>,
|
||||
): PersistentList<WalletSettingsItemUM> {
|
||||
val accountsFeatureEnabled = accountsFeatureToggles.isFeatureEnabled
|
||||
val isMultiCurrency = when (userWallet) {
|
||||
is UserWallet.Cold -> userWallet.isMultiCurrency
|
||||
is UserWallet.Hot -> true
|
||||
|
|
@ -188,7 +191,7 @@ internal class WalletSettingsModel @Inject constructor(
|
|||
is UserWallet.Cold -> userWallet.scanResponse.card.backupStatus == CardDTO.BackupStatus.NoBackup
|
||||
is UserWallet.Hot -> false
|
||||
},
|
||||
isManageTokensAvailable = isMultiCurrency,
|
||||
isManageTokensAvailable = !accountsFeatureEnabled && isMultiCurrency,
|
||||
isNFTFeatureEnabled = isMultiCurrency,
|
||||
isNFTEnabled = isNFTEnabled,
|
||||
onCheckedNFTChange = ::onCheckedNFTChange,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.tangem.core.ui.components.block.model.BlockUM
|
|||
import com.tangem.core.ui.components.label.entity.LabelStyle
|
||||
import com.tangem.core.ui.components.label.entity.LabelUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.feature.walletsettings.analytics.Settings
|
||||
import com.tangem.feature.walletsettings.entity.WalletSettingsAccountsUM
|
||||
|
|
@ -207,7 +208,7 @@ internal class ItemsBuilder @Inject constructor(
|
|||
iconRes = R.drawable.ic_tether_24,
|
||||
onClick = {
|
||||
analyticsEventHandler.send(Settings.ButtonManageTokens)
|
||||
router.push(AppRoute.ManageTokens(Source.SETTINGS, userWalletId))
|
||||
router.push(AppRoute.ManageTokens(Source.SETTINGS, PortfolioId(userWalletId)))
|
||||
},
|
||||
).let(::add)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -359,10 +359,10 @@ internal class WalletModel @Inject constructor(
|
|||
val info = tangemPayMainScreenCustomerInfoUseCase()
|
||||
if (info != null) {
|
||||
stateHolder.update(
|
||||
transformer = TangemPayStateTransformer(
|
||||
transformer = TangemPayInitialStateTransformer(
|
||||
value = info,
|
||||
onIssueOrderClick = ::issueOrder,
|
||||
onContinueKycClick = innerWalletRouter::openTangemPayOnboarding,
|
||||
onClickIssue = ::issueOrder,
|
||||
onClickKyc = innerWalletRouter::openTangemPayOnboarding,
|
||||
openDetails = innerWalletRouter::openTangemPayDetails,
|
||||
),
|
||||
)
|
||||
|
|
@ -371,9 +371,9 @@ internal class WalletModel @Inject constructor(
|
|||
|
||||
private fun issueOrder() {
|
||||
modelScope.launch {
|
||||
stateHolder.update(TangemPayStateTransformer(issueProgressState = true))
|
||||
stateHolder.update(TangemPayIssueProgressStateTransformer())
|
||||
tangemPayIssueOrderUseCase().onLeft {
|
||||
stateHolder.update(TangemPayStateTransformer(issueState = true, onIssueOrderClick = ::issueOrder))
|
||||
stateHolder.update(TangemPayIssueAvailableStateTransformer(onClickIssue = ::issueOrder))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,17 +4,15 @@ import arrow.core.getOrElse
|
|||
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.domain.models.account.Account
|
||||
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.isLocked
|
||||
import com.tangem.domain.nft.analytics.NFTAnalyticsEvent
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.settings.ShouldShowMarketsTooltipUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
|
||||
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
|
||||
import com.tangem.domain.tokens.TokensAction
|
||||
import com.tangem.domain.tokens.model.TokenActionsState
|
||||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
|
|
@ -39,15 +37,13 @@ internal interface WalletContentClickIntents {
|
|||
|
||||
fun onDetailsClick()
|
||||
|
||||
fun onManageTokensClick()
|
||||
|
||||
fun onOrganizeTokensClick()
|
||||
|
||||
fun onDismissMarketsOnboarding()
|
||||
|
||||
fun onTokenItemClick(portfolioId: PortfolioId, currencyStatus: CryptoCurrencyStatus)
|
||||
fun onTokenItemClick(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onTokenItemLongClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
fun onTokenItemLongClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onAccountExpandClick(account: Account)
|
||||
|
||||
|
|
@ -81,7 +77,6 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
|
||||
private val shouldShowMarketsTooltipUseCase: ShouldShowMarketsTooltipUseCase,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
private val walletEventSender: WalletEventSender,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
|
|
@ -122,11 +117,6 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onManageTokensClick() {
|
||||
reduxStateHolder.dispatch(action = TokensAction.SetArgs.ManageAccess)
|
||||
router.openManageTokensScreen(userWalletId = stateHolder.getSelectedWalletId())
|
||||
}
|
||||
|
||||
override fun onOrganizeTokensClick() {
|
||||
router.openOrganizeTokensScreen(userWalletId = stateHolder.getSelectedWalletId())
|
||||
}
|
||||
|
|
@ -138,13 +128,12 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onTokenItemClick(portfolioId: PortfolioId, currencyStatus: CryptoCurrencyStatus) {
|
||||
router.openTokenDetails(portfolioId, currencyStatus)
|
||||
override fun onTokenItemClick(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus) {
|
||||
router.openTokenDetails(userWalletId, currencyStatus)
|
||||
}
|
||||
|
||||
override fun onTokenItemLongClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
override fun onTokenItemLongClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
modelScope.launch(dispatchers.main) {
|
||||
val userWalletId = portfolioId.userWalletId
|
||||
val userWallet = getUserWalletUseCase(userWalletId).getOrElse {
|
||||
Timber.e(
|
||||
"""
|
||||
|
|
@ -160,7 +149,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
getCryptoCurrencyActionsUseCase(userWallet = userWallet, cryptoCurrencyStatus = cryptoCurrencyStatus)
|
||||
.take(count = 1)
|
||||
.collectLatest {
|
||||
showActionsBottomSheet(it, userWallet, portfolioId)
|
||||
showActionsBottomSheet(it, userWallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -175,17 +164,12 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
accountDependencies.expandedAccountsHolder.collapseAccount(userWalletId, account.accountId)
|
||||
}
|
||||
|
||||
private fun showActionsBottomSheet(
|
||||
tokenActionsState: TokenActionsState,
|
||||
userWallet: UserWallet,
|
||||
portfolioId: PortfolioId,
|
||||
) {
|
||||
private fun showActionsBottomSheet(tokenActionsState: TokenActionsState, userWallet: UserWallet) {
|
||||
stateHolder.showBottomSheet(
|
||||
ActionsBottomSheetConfig(
|
||||
actions = MultiWalletCurrencyActionsConverter(
|
||||
userWallet = userWallet,
|
||||
clickIntents = currencyActionsClickIntents,
|
||||
portfolioId = portfolioId,
|
||||
).convert(tokenActionsState),
|
||||
),
|
||||
userWallet.walletId,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ import com.tangem.domain.core.utils.lceError
|
|||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.markets.TokenMarketParams
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.domain.models.ReceiveAddressModel
|
||||
import com.tangem.domain.models.TokenReceiveConfig
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
|
|
@ -43,12 +42,7 @@ import com.tangem.domain.promo.GetStoryContentUseCase
|
|||
import com.tangem.domain.promo.models.StoryContentIds
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
|
||||
import com.tangem.domain.tokens.GetViewedTokenReceiveWarningUseCase
|
||||
import com.tangem.domain.tokens.NeedShowYieldSupplyDepositedWarningUseCase
|
||||
import com.tangem.domain.tokens.SaveViewedTokenReceiveWarningUseCase
|
||||
import com.tangem.domain.tokens.SaveViewedYieldSupplyWarningUseCase
|
||||
import com.tangem.domain.tokens.model.details.TokenAction
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||
import com.tangem.domain.tokens.model.analytics.TokenReceiveAnalyticsEvent
|
||||
|
|
@ -57,6 +51,7 @@ import com.tangem.domain.tokens.model.analytics.TokenReceiveNewAnalyticsEvent
|
|||
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent
|
||||
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.Companion.AVAILABLE
|
||||
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.Companion.toReasonAnalyticsText
|
||||
import com.tangem.domain.tokens.model.details.TokenAction
|
||||
import com.tangem.domain.transaction.usecase.GetEnsNameUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase
|
||||
|
|
@ -70,7 +65,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
|||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.utils.WalletFeatureUseCasesFacade
|
||||
import com.tangem.features.tokenreceive.TokenReceiveFeatureToggle
|
||||
import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -84,7 +78,7 @@ import javax.inject.Inject
|
|||
interface WalletCurrencyActionsClickIntents {
|
||||
|
||||
fun onSendClick(
|
||||
portfolioId: PortfolioId,
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
unavailabilityReason: ScenarioUnavailabilityReason,
|
||||
)
|
||||
|
|
@ -92,32 +86,32 @@ interface WalletCurrencyActionsClickIntents {
|
|||
fun onSellClick(cryptoCurrencyStatus: CryptoCurrencyStatus, unavailabilityReason: ScenarioUnavailabilityReason)
|
||||
|
||||
fun onBuyClick(
|
||||
portfolioId: PortfolioId,
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
unavailabilityReason: ScenarioUnavailabilityReason,
|
||||
)
|
||||
|
||||
fun onSwapClick(
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
portfolioId: PortfolioId,
|
||||
userWalletId: UserWalletId,
|
||||
unavailabilityReason: ScenarioUnavailabilityReason,
|
||||
)
|
||||
|
||||
fun onReceiveClick(
|
||||
portfolioId: PortfolioId,
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
event: AnalyticsEvent? = null,
|
||||
)
|
||||
|
||||
fun onStakeClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus, yield: Yield?)
|
||||
fun onStakeClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus, yield: Yield?)
|
||||
|
||||
fun onCopyAddressLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus): TextReference?
|
||||
|
||||
fun onCopyAddressClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
fun onCopyAddressClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onHideTokensClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
fun onHideTokensClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onPerformHideToken(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
fun onPerformHideToken(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onExploreClick()
|
||||
|
||||
|
|
@ -139,7 +133,6 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val isDemoCardUseCase: IsDemoCardUseCase,
|
||||
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
|
||||
private val useCasesFacade: WalletFeatureUseCasesFacade,
|
||||
private val getExploreUrlUseCase: GetExploreUrlUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getStoryContentUseCase: GetStoryContentUseCase,
|
||||
|
|
@ -158,10 +151,12 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles,
|
||||
private val needShowYieldSupplyDepositedWarningUseCase: NeedShowYieldSupplyDepositedWarningUseCase,
|
||||
private val saveViewedYieldSupplyWarningUseCase: SaveViewedYieldSupplyWarningUseCase,
|
||||
private val isCryptoCurrencyCoinCouldHide: IsCryptoCurrencyCoinCouldHideUseCase,
|
||||
private val removeCurrencyUseCase: RemoveCurrencyUseCase,
|
||||
) : BaseWalletClickIntents(), WalletCurrencyActionsClickIntents {
|
||||
|
||||
override fun onSendClick(
|
||||
portfolioId: PortfolioId,
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
unavailabilityReason: ScenarioUnavailabilityReason,
|
||||
) {
|
||||
|
|
@ -185,27 +180,21 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
modelScope.launch {
|
||||
saveViewedYieldSupplyWarningUseCase(cryptoCurrencyStatus.currency.name)
|
||||
stateHolder.hideBottomSheet()
|
||||
navigateToSend(cryptoCurrencyStatus, portfolioId)
|
||||
navigateToSend(cryptoCurrencyStatus, userWalletId)
|
||||
}
|
||||
},
|
||||
)
|
||||
} else {
|
||||
navigateToSend(cryptoCurrencyStatus, portfolioId)
|
||||
navigateToSend(cryptoCurrencyStatus, userWalletId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReceiveClick(
|
||||
portfolioId: PortfolioId,
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
event: AnalyticsEvent?,
|
||||
) {
|
||||
val userWalletId = portfolioId.userWalletId
|
||||
if (portfolioId is PortfolioId.Account) {
|
||||
// todo account find address
|
||||
TODO("account")
|
||||
}
|
||||
|
||||
analyticsEventHandler.send(
|
||||
event = TokenScreenAnalyticsEvent.ButtonWithParams.ButtonReceive(
|
||||
token = cryptoCurrencyStatus.currency.symbol,
|
||||
|
|
@ -278,12 +267,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
override fun onCopyAddressClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
val userWalletId = portfolioId.userWalletId
|
||||
if (portfolioId is PortfolioId.Account) {
|
||||
// todo account find address
|
||||
TODO("account")
|
||||
}
|
||||
override fun onCopyAddressClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
analyticsEventHandler.send(
|
||||
event = TokenReceiveNewAnalyticsEvent.ButtonCopyAddress(
|
||||
token = cryptoCurrencyStatus.currency.symbol,
|
||||
|
|
@ -305,7 +289,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onHideTokensClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
override fun onHideTokensClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
analyticsEventHandler.send(
|
||||
event = TokenScreenAnalyticsEvent.ButtonRemoveToken(cryptoCurrencyStatus.currency.symbol),
|
||||
)
|
||||
|
|
@ -313,19 +297,19 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
modelScope.launch(dispatchers.main) {
|
||||
walletEventSender.send(
|
||||
event = WalletEvent.ShowAlert(
|
||||
state = getHideTokeAlertConfig(portfolioId, cryptoCurrencyStatus),
|
||||
state = getHideTokeAlertConfig(userWalletId, cryptoCurrencyStatus),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getHideTokeAlertConfig(
|
||||
portfolioId: PortfolioId,
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
): WalletAlertState.DefaultAlert {
|
||||
val currency = cryptoCurrencyStatus.currency
|
||||
val isCryptoCurrencyCoinCouldHide = currency is CryptoCurrency.Coin &&
|
||||
!useCasesFacade.isCryptoCurrencyCoinCouldHide(portfolioId = portfolioId, cryptoCurrencyCoin = currency)
|
||||
!isCryptoCurrencyCoinCouldHide(userWalletId = userWalletId, cryptoCurrencyCoin = currency)
|
||||
return if (isCryptoCurrencyCoinCouldHide) {
|
||||
WalletAlertState.DefaultAlert(
|
||||
title = resourceReference(
|
||||
|
|
@ -351,14 +335,14 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
formatArgs = WrappedList(listOf(cryptoCurrencyStatus.currency.name)),
|
||||
),
|
||||
message = resourceReference(R.string.token_details_hide_alert_message),
|
||||
onConfirmClick = { onPerformHideToken(portfolioId, cryptoCurrencyStatus) },
|
||||
onConfirmClick = { onPerformHideToken(userWalletId, cryptoCurrencyStatus) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPerformHideToken(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
override fun onPerformHideToken(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
modelScope.launch(dispatchers.io) {
|
||||
useCasesFacade.removeCurrencyUseCase(portfolioId, cryptoCurrencyStatus.currency)
|
||||
removeCurrencyUseCase(userWalletId, cryptoCurrencyStatus.currency)
|
||||
.fold(
|
||||
ifLeft = {
|
||||
walletEventSender.send(
|
||||
|
|
@ -366,7 +350,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
)
|
||||
},
|
||||
ifRight = {
|
||||
stateHolder.update(CloseBottomSheetTransformer(userWalletId = portfolioId.userWalletId))
|
||||
stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWalletId))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -399,7 +383,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onBuyClick(
|
||||
portfolioId: PortfolioId,
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
unavailabilityReason: ScenarioUnavailabilityReason,
|
||||
) {
|
||||
|
|
@ -415,7 +399,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
|
||||
appRouter.push(
|
||||
AppRoute.Onramp(
|
||||
portfolioId = portfolioId,
|
||||
userWalletId = userWalletId,
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
source = OnrampSource.TOKEN_LONG_TAP,
|
||||
),
|
||||
|
|
@ -424,7 +408,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
|
||||
override fun onSwapClick(
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
portfolioId: PortfolioId,
|
||||
userWalletId: UserWalletId,
|
||||
unavailabilityReason: ScenarioUnavailabilityReason,
|
||||
) {
|
||||
analyticsEventHandler.send(
|
||||
|
|
@ -447,12 +431,12 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
modelScope.launch {
|
||||
saveViewedYieldSupplyWarningUseCase(cryptoCurrencyStatus.currency.name)
|
||||
stateHolder.hideBottomSheet()
|
||||
navigateToSwap(cryptoCurrencyStatus, portfolioId)
|
||||
navigateToSwap(cryptoCurrencyStatus, userWalletId)
|
||||
}
|
||||
},
|
||||
)
|
||||
} else {
|
||||
navigateToSwap(cryptoCurrencyStatus, portfolioId)
|
||||
navigateToSwap(cryptoCurrencyStatus, userWalletId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -493,15 +477,15 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onStakeClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus, yield: Yield?) {
|
||||
stateHolder.update(CloseBottomSheetTransformer(userWalletId = portfolioId.userWalletId))
|
||||
override fun onStakeClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus, yield: Yield?) {
|
||||
stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWalletId))
|
||||
|
||||
modelScope.launch {
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
|
||||
appRouter.push(
|
||||
AppRoute.Staking(
|
||||
portfolioId = portfolioId,
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyId = cryptoCurrency.id,
|
||||
yieldId = yield?.id ?: return@launch,
|
||||
),
|
||||
|
|
@ -519,15 +503,19 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
|
||||
override fun onMultiWalletSwapClick(userWalletId: UserWalletId) {
|
||||
val selectedWallet = stateHolder.getSelectedWallet() as? WalletState.MultiCurrency.Content ?: return
|
||||
val tokenListState = selectedWallet.tokensListState as? WalletTokensListState.ContentState.Content ?: return
|
||||
val tokenListState = selectedWallet.tokensListState
|
||||
|
||||
if (tokenListState.items.count { it is TokensListItemUM.Token } < 2) {
|
||||
handleError(
|
||||
alertState = WalletAlertState.InsufficientTokensCountForSwapping,
|
||||
eventCreator = MainScreenAnalyticsEvent::ButtonSwap,
|
||||
when (tokenListState) {
|
||||
is WalletTokensListState.ContentState.Content -> checkSwapCryptoAvailability(
|
||||
tokenCount = tokenListState.items.count { it is TokensListItemUM.Token },
|
||||
)
|
||||
|
||||
return
|
||||
is WalletTokensListState.ContentState.PortfolioContent -> checkSwapCryptoAvailability(
|
||||
tokenCount = tokenListState.items.sumOf { it.tokens.count { it is TokensListItemUM.Token } },
|
||||
)
|
||||
WalletTokensListState.ContentState.Loading,
|
||||
WalletTokensListState.ContentState.Locked,
|
||||
WalletTokensListState.Empty,
|
||||
-> return
|
||||
}
|
||||
|
||||
modelScope.launch {
|
||||
|
|
@ -766,21 +754,21 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
needShowYieldSupplyDepositedWarningUseCase(cryptoCurrencyStatus)
|
||||
}
|
||||
|
||||
private fun navigateToSend(cryptoCurrencyStatus: CryptoCurrencyStatus, portfolioId: PortfolioId) {
|
||||
stateHolder.update(CloseBottomSheetTransformer(userWalletId = portfolioId.userWalletId))
|
||||
private fun navigateToSend(cryptoCurrencyStatus: CryptoCurrencyStatus, userWalletId: UserWalletId) {
|
||||
stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWalletId))
|
||||
val route = AppRoute.Send(
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
portfolioId = portfolioId,
|
||||
userWalletId = userWalletId,
|
||||
)
|
||||
|
||||
appRouter.push(route)
|
||||
}
|
||||
|
||||
private fun navigateToSwap(cryptoCurrencyStatus: CryptoCurrencyStatus, portfolioId: PortfolioId) {
|
||||
private fun navigateToSwap(cryptoCurrencyStatus: CryptoCurrencyStatus, userWalletId: UserWalletId) {
|
||||
appRouter.push(
|
||||
AppRoute.Swap(
|
||||
currencyFrom = cryptoCurrencyStatus.currency,
|
||||
portfolioId = portfolioId,
|
||||
userWalletId = userWalletId,
|
||||
screenSource = AnalyticsParam.ScreensSources.LongTap.value,
|
||||
),
|
||||
)
|
||||
|
|
@ -808,4 +796,15 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkSwapCryptoAvailability(tokenCount: Int) {
|
||||
if (tokenCount < 2) {
|
||||
handleError(
|
||||
alertState = WalletAlertState.InsufficientTokensCountForSwapping,
|
||||
eventCreator = MainScreenAnalyticsEvent::ButtonSwap,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -365,7 +365,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
|||
userWalletId = userWallet.walletId,
|
||||
currency = cryptoCurrency,
|
||||
source = OnrampSource.SEPA_BANNER,
|
||||
launchSepa = true,
|
||||
shouldLaunchSepa = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import com.tangem.feature.wallet.presentation.common.WalletPreviewData.topBarCon
|
|||
import com.tangem.feature.wallet.presentation.wallet.state.model.*
|
||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal object WalletScreenPreviewData {
|
||||
private val tokenItemState = TokenItemState.Content(
|
||||
|
|
@ -86,15 +87,17 @@ internal object WalletScreenPreviewData {
|
|||
private val portfolioContentState = WalletTokensListState.ContentState.PortfolioContent(
|
||||
items = persistentListOf(
|
||||
TokensListItemUM.Portfolio(
|
||||
tokens = textContentTokensState.items.filterIsInstance<PortfolioTokensListItemUM>(),
|
||||
tokens = textContentTokensState.items.filterIsInstance<PortfolioTokensListItemUM>().toPersistentList(),
|
||||
isExpanded = false,
|
||||
state = AccountItemPreviewData.accountItem
|
||||
isCollapsable = true,
|
||||
tokenItemUM = AccountItemPreviewData.accountItem
|
||||
.copy(iconState = AccountItemPreviewData.accountLetterIcon),
|
||||
),
|
||||
TokensListItemUM.Portfolio(
|
||||
tokens = textContentTokensState.items.filterIsInstance<PortfolioTokensListItemUM>(),
|
||||
tokens = textContentTokensState.items.filterIsInstance<PortfolioTokensListItemUM>().toPersistentList(),
|
||||
isExpanded = true,
|
||||
state = AccountItemPreviewData.accountItem,
|
||||
isCollapsable = true,
|
||||
tokenItemUM = AccountItemPreviewData.accountItem,
|
||||
),
|
||||
),
|
||||
organizeTokensButtonConfig = WalletTokensListState.OrganizeTokensButtonConfig(
|
||||
|
|
|
|||
|
|
@ -3,11 +3,9 @@ package com.tangem.feature.wallet.presentation.router
|
|||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRoute.ManageTokens.Source
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.domain.models.TokenReceiveConfig
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
|
|
@ -67,12 +65,12 @@ internal class DefaultWalletRouter @Inject constructor(
|
|||
urlOpener.openUrl(url)
|
||||
}
|
||||
|
||||
override fun openTokenDetails(portfolioId: PortfolioId, currencyStatus: CryptoCurrencyStatus) {
|
||||
override fun openTokenDetails(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus) {
|
||||
val networkAddress = currencyStatus.value.networkAddress
|
||||
if (networkAddress != null && networkAddress.defaultAddress.value.isNotEmpty()) {
|
||||
router.push(
|
||||
AppRoute.CurrencyDetails(
|
||||
portfolioId = portfolioId,
|
||||
userWalletId = userWalletId,
|
||||
currency = currencyStatus.currency,
|
||||
),
|
||||
)
|
||||
|
|
@ -87,10 +85,6 @@ internal class DefaultWalletRouter @Inject constructor(
|
|||
return router.stack.lastOrNull() is AppRoute.Wallet
|
||||
}
|
||||
|
||||
override fun openManageTokensScreen(userWalletId: UserWalletId) {
|
||||
router.push(AppRoute.ManageTokens(Source.SETTINGS, userWalletId))
|
||||
}
|
||||
|
||||
override fun openScanFailedDialog(onTryAgain: () -> Unit) {
|
||||
reduxStateHolder.dispatchDialogShow(StateDialog.ScanFailsDialog(StateDialog.ScanFailsSource.MAIN, onTryAgain))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.feature.wallet.presentation.router
|
|||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.domain.models.TokenReceiveConfig
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
|
|
@ -42,7 +41,7 @@ internal interface InnerWalletRouter {
|
|||
fun openUrl(url: String)
|
||||
|
||||
/** Open token details screen */
|
||||
fun openTokenDetails(portfolioId: PortfolioId, currencyStatus: CryptoCurrencyStatus)
|
||||
fun openTokenDetails(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
/** Open stories screen */
|
||||
fun openStoriesScreen()
|
||||
|
|
@ -50,9 +49,6 @@ internal interface InnerWalletRouter {
|
|||
/** Is wallet last screen */
|
||||
fun isWalletLastScreen(): Boolean
|
||||
|
||||
/** Open manage tokens screen */
|
||||
fun openManageTokensScreen(userWalletId: UserWalletId)
|
||||
|
||||
/** Open scan failed dialog */
|
||||
fun openScanFailedDialog(onTryAgain: () -> Unit)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import com.tangem.domain.analytics.model.WalletBalanceState
|
|||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
|
|
@ -38,33 +37,41 @@ internal class TokenListAnalyticsSender @Inject constructor(
|
|||
private val mutex = Mutex()
|
||||
private val loadingTraces = mutableMapOf<UserWalletId, Trace>()
|
||||
|
||||
suspend fun send(displayedUiState: WalletState?, userWallet: UserWallet, tokenList: TokenList) {
|
||||
suspend fun send(
|
||||
displayedUiState: WalletState?,
|
||||
userWallet: UserWallet,
|
||||
totalFiatBalance: TotalFiatBalance,
|
||||
flattenCurrencies: List<CryptoCurrencyStatus>,
|
||||
) {
|
||||
if (screenLifecycleProvider.isBackgroundState.value) return
|
||||
if (displayedUiState == null || displayedUiState.pullToRefreshConfig.isRefreshing) return
|
||||
|
||||
if (tokenList.totalFiatBalance is TotalFiatBalance.Loading) {
|
||||
startLoadingTraceIfNeeded(userWallet.walletId, tokenList)
|
||||
if (totalFiatBalance is TotalFiatBalance.Loading) {
|
||||
startLoadingTraceIfNeeded(userWallet.walletId, flattenCurrencies)
|
||||
return
|
||||
}
|
||||
|
||||
if (isTerminalState(tokenList.totalFiatBalance)) {
|
||||
stopLoadingTraceIfNeeded(userWallet.walletId, tokenList.totalFiatBalance)
|
||||
if (isTerminalState(totalFiatBalance)) {
|
||||
stopLoadingTraceIfNeeded(userWallet.walletId, totalFiatBalance)
|
||||
}
|
||||
|
||||
val currenciesStatuses = tokenList.flattenCurrencies()
|
||||
val currenciesStatuses = flattenCurrencies
|
||||
|
||||
sendBalanceLoadedEventIfNeeded(tokenList.totalFiatBalance, currenciesStatuses)
|
||||
sendToppedUpEventIfNeeded(userWallet, tokenList.totalFiatBalance, currenciesStatuses)
|
||||
sendBalanceLoadedEventIfNeeded(totalFiatBalance, currenciesStatuses)
|
||||
sendToppedUpEventIfNeeded(userWallet, totalFiatBalance, currenciesStatuses)
|
||||
sendUnreachableNetworksEventIfNeeded(currenciesStatuses)
|
||||
sendTokenBalancesIfNeeded(currenciesStatuses)
|
||||
}
|
||||
|
||||
private suspend fun startLoadingTraceIfNeeded(userWalletId: UserWalletId, tokenList: TokenList) {
|
||||
private suspend fun startLoadingTraceIfNeeded(
|
||||
userWalletId: UserWalletId,
|
||||
flattenCurrencies: List<CryptoCurrencyStatus>,
|
||||
) {
|
||||
mutex.withLock {
|
||||
if (!loadingTraces.containsKey(userWalletId)) {
|
||||
val trace = FirebasePerformance.getInstance().newTrace(BALANCE_LOADED_TRACE_NAME)
|
||||
trace.start()
|
||||
trace.putAttribute(TOKENS_COUNT, tokenList.flattenCurrencies().size.toString())
|
||||
trace.putAttribute(TOKENS_COUNT, flattenCurrencies.size.toString())
|
||||
loadingTraces[userWalletId] = trace
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,17 +9,16 @@ import com.tangem.core.decompose.di.ModelScoped
|
|||
import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig.IconTint
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||
import com.tangem.domain.card.CardTypesResolver
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import com.tangem.domain.onramp.GetOnrampCountryUseCase
|
||||
|
|
@ -45,6 +44,7 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -70,72 +70,81 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow<ImmutableList<WalletNotification>> {
|
||||
val cardTypesResolver = (userWallet as? UserWallet.Cold)?.scanResponse?.cardTypesResolver
|
||||
|
||||
val tokenListFlow = if (accountDependencies.accountsFeatureToggles.isFeatureEnabled) {
|
||||
val accountStatusList by lazy {
|
||||
val params = SingleAccountStatusListProducer.Params(userWallet.walletId)
|
||||
accountDependencies.singleAccountStatusListSupplier(params)
|
||||
} else {
|
||||
tokenListStore.getOrThrow(userWallet.walletId)
|
||||
.map { it.totalFiatBalance to it.flattenCurrencies() }
|
||||
.map { Lce.Content(it) }
|
||||
}
|
||||
|
||||
fun tokenListFlow(): LceFlow<TokenListError, Pair<TotalFiatBalance, List<CryptoCurrencyStatus>>> {
|
||||
return if (accountDependencies.accountsFeatureToggles.isFeatureEnabled) {
|
||||
accountStatusList
|
||||
} else {
|
||||
runCatching { tokenListStore.getOrThrow(userWallet.walletId) }
|
||||
.map { result -> result.map { lce -> lce.map { it.totalFiatBalance to it.flattenCurrencies() } } }
|
||||
.getOrNull()
|
||||
// in case of runtime change ft in tester menu
|
||||
?: accountStatusList
|
||||
}
|
||||
}
|
||||
|
||||
// val params = SingleAccountStatusListProducer.Params(userWallet.walletId)
|
||||
// val accountStatusListFlow = accountDependencies.singleAccountStatusListSupplier(params)
|
||||
return combine(
|
||||
tokenListFlow,
|
||||
// todo account just use it, after delete accountsFeatureToggles
|
||||
// accountStatusListFlow,
|
||||
isReadyToShowRateAppUseCase(),
|
||||
isNeedToBackupUseCase(userWallet.walletId),
|
||||
seedPhraseNotificationUseCase(userWalletId = userWallet.walletId),
|
||||
shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.Referral),
|
||||
shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.Sepa),
|
||||
notificationsRepository.getShouldShowNotification(NotificationId.EnablePushesReminderNotification.key),
|
||||
) { array ->
|
||||
val totalFiatBalance: Lce<TokenListError, TotalFiatBalance>
|
||||
val flattenCurrencies: Lce<TokenListError, List<CryptoCurrencyStatus>>
|
||||
if (accountDependencies.accountsFeatureToggles.isFeatureEnabled) {
|
||||
val accountStatusList = array[0] as AccountStatusList
|
||||
totalFiatBalance = Lce.Content(accountStatusList.totalFiatBalance)
|
||||
flattenCurrencies = Lce.Content(accountStatusList.flattenCurrencies())
|
||||
} else {
|
||||
val maybeTokenList = array[0] as Lce<TokenListError, TokenList>
|
||||
totalFiatBalance = maybeTokenList.map { it.totalFiatBalance }
|
||||
flattenCurrencies = maybeTokenList.map { it.flattenCurrencies() }
|
||||
) { array -> array }
|
||||
.combine(tokenListFlow()) { array, any: Any -> arrayOf(any).plus(elements = array) }
|
||||
.map { array ->
|
||||
val lceTokens = array[0] as Lce<TokenListError, Pair<TotalFiatBalance, List<CryptoCurrencyStatus>>>
|
||||
val totalFiatBalance = lceTokens.map { it.first }
|
||||
val flattenCurrencies = lceTokens.map { it.second }
|
||||
val isReadyToShowRating = array[1] as Boolean
|
||||
val isNeedToBackup = array[2] as Boolean
|
||||
val seedPhraseIssueStatus = array[3] as SeedPhraseNotificationsStatus
|
||||
val shouldShowReferralPromo = array[4] as Boolean
|
||||
val shouldShowSepaBanner = array[5] as Boolean
|
||||
val shouldShowEnablePushesReminderNotification = array[6] as Boolean
|
||||
|
||||
buildList {
|
||||
addUsedOutdatedDataNotification(totalFiatBalance)
|
||||
|
||||
addCriticalNotifications(userWallet, seedPhraseIssueStatus, clickIntents)
|
||||
|
||||
addFinishWalletActivationNotification(userWallet, totalFiatBalance, clickIntents)
|
||||
|
||||
addReferralPromoNotification(cardTypesResolver, clickIntents, shouldShowReferralPromo)
|
||||
|
||||
addSepaPromoNotification(userWallet, clickIntents, shouldShowSepaBanner)
|
||||
|
||||
addInformationalNotifications(userWallet, cardTypesResolver, flattenCurrencies, clickIntents)
|
||||
|
||||
addWarningNotifications(cardTypesResolver, flattenCurrencies, isNeedToBackup, clickIntents)
|
||||
|
||||
addPushReminderNotification(
|
||||
clickIntents = clickIntents,
|
||||
shouldShowPushReminderBanner = shouldShowEnablePushesReminderNotification &&
|
||||
!notificationsRepository.isUserAllowToSubscribeOnPushNotifications(),
|
||||
)
|
||||
|
||||
addYieldSupplyNotifications(flattenCurrencies)
|
||||
|
||||
val hasCriticalOrWarning = any { notification ->
|
||||
notification is WalletNotification.Critical || notification is WalletNotification.Warning
|
||||
}
|
||||
|
||||
if (!hasCriticalOrWarning) {
|
||||
addRateTheAppNotification(isReadyToShowRating, clickIntents)
|
||||
}
|
||||
}.toImmutableList()
|
||||
}
|
||||
|
||||
val isReadyToShowRating = array[1] as Boolean
|
||||
val isNeedToBackup = array[2] as Boolean
|
||||
val seedPhraseIssueStatus = array[3] as SeedPhraseNotificationsStatus
|
||||
val shouldShowReferralPromo = array[4] as Boolean
|
||||
val shouldShowSepaBanner = array[5] as Boolean
|
||||
val shouldShowEnablePushesReminderNotification = array[6] as Boolean
|
||||
|
||||
buildList {
|
||||
addUsedOutdatedDataNotification(totalFiatBalance)
|
||||
|
||||
addCriticalNotifications(userWallet, seedPhraseIssueStatus, clickIntents)
|
||||
|
||||
addFinishWalletActivationNotification(userWallet, totalFiatBalance, clickIntents)
|
||||
|
||||
addReferralPromoNotification(cardTypesResolver, clickIntents, shouldShowReferralPromo)
|
||||
|
||||
addSepaPromoNotification(userWallet, clickIntents, shouldShowSepaBanner)
|
||||
|
||||
addInformationalNotifications(userWallet, cardTypesResolver, flattenCurrencies, clickIntents)
|
||||
|
||||
addWarningNotifications(cardTypesResolver, flattenCurrencies, isNeedToBackup, clickIntents)
|
||||
|
||||
addPushReminderNotification(
|
||||
clickIntents = clickIntents,
|
||||
shouldShowPushReminderBanner = shouldShowEnablePushesReminderNotification &&
|
||||
!notificationsRepository.isUserAllowToSubscribeOnPushNotifications(),
|
||||
)
|
||||
|
||||
addYieldSupplyNotifications(flattenCurrencies)
|
||||
|
||||
val hasCriticalOrWarning = any { notification ->
|
||||
notification is WalletNotification.Critical || notification is WalletNotification.Warning
|
||||
}
|
||||
|
||||
if (!hasCriticalOrWarning) {
|
||||
addRateTheAppNotification(isReadyToShowRating, clickIntents)
|
||||
}
|
||||
}.toImmutableList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addUsedOutdatedDataNotification(
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ internal class SetCryptoCurrencyActionsTransformer(
|
|||
dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None,
|
||||
onClick = {
|
||||
clickIntents.onBuyClick(
|
||||
portfolioId = portfolioId,
|
||||
userWalletId = portfolioId.userWalletId,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
unavailabilityReason = action.unavailabilityReason,
|
||||
)
|
||||
|
|
@ -62,7 +62,10 @@ internal class SetCryptoCurrencyActionsTransformer(
|
|||
enabled = true,
|
||||
dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None,
|
||||
onClick = {
|
||||
clickIntents.onReceiveClick(portfolioId, cryptoCurrencyStatus = cryptoCurrencyStatus)
|
||||
clickIntents.onReceiveClick(
|
||||
portfolioId.userWalletId,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
)
|
||||
},
|
||||
onLongClick = {
|
||||
clickIntents.onCopyAddressLongClick(cryptoCurrencyStatus = cryptoCurrencyStatus)
|
||||
|
|
@ -87,7 +90,7 @@ internal class SetCryptoCurrencyActionsTransformer(
|
|||
dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None,
|
||||
onClick = {
|
||||
clickIntents.onSendClick(
|
||||
portfolioId = portfolioId,
|
||||
userWalletId = portfolioId.userWalletId,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
unavailabilityReason = action.unavailabilityReason,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import com.tangem.core.ui.format.bigdecimal.crypto
|
|||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.card.common.util.getCardsCount
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.visa.exception.RefreshTokenExpiredException
|
||||
|
|
@ -153,7 +152,7 @@ internal class SetVisaInfoTransformer(
|
|||
dimContent = false,
|
||||
onClick = {
|
||||
clickIntents.onReceiveClick(
|
||||
portfolioId = PortfolioId(userWalletId), // todo account Visa use Main account?
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
event = MainScreenAnalyticsEvent.ButtonReceive,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,33 +1,28 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
||||
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.pay.model.CustomerInfo.CardInfo
|
||||
import com.tangem.domain.pay.model.MainScreenCustomerInfo
|
||||
import com.tangem.domain.pay.model.OrderStatus.CANCELED
|
||||
import com.tangem.domain.pay.model.OrderStatus.NOT_ISSUED
|
||||
import com.tangem.domain.pay.model.OrderStatus.UNKNOWN
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState.Progress
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createIssueAvailableState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createIssueProgressState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createKycInProgressState
|
||||
import java.util.Currency
|
||||
|
||||
internal class TangemPayStateTransformer(
|
||||
internal class TangemPayInitialStateTransformer(
|
||||
private val value: MainScreenCustomerInfo? = null,
|
||||
private val onIssueOrderClick: () -> Unit = {},
|
||||
private val onContinueKycClick: () -> Unit = {},
|
||||
private val onClickIssue: () -> Unit = {},
|
||||
private val onClickKyc: () -> Unit = {},
|
||||
private val openDetails: (customerWalletAddress: String, cardNumberEnd: String) -> Unit = { _, _ -> },
|
||||
private val issueProgressState: Boolean = false,
|
||||
private val issueState: Boolean = false,
|
||||
) : WalletScreenStateTransformer {
|
||||
|
||||
override fun transform(prevState: WalletScreenState): WalletScreenState {
|
||||
val tangemPayState = when {
|
||||
issueProgressState -> createIssueProgressState()
|
||||
issueState -> createIssueState()
|
||||
else -> createInitialState()
|
||||
}
|
||||
val tangemPayState = createInitialState()
|
||||
return prevState.copy(tangemPayState = tangemPayState)
|
||||
}
|
||||
|
||||
|
|
@ -35,35 +30,13 @@ internal class TangemPayStateTransformer(
|
|||
val cardInfo = value?.info?.cardInfo
|
||||
return when {
|
||||
value == null -> TangemPayState.Empty
|
||||
!value.info.isKycApproved() -> createKycInProgressState(onContinueKycClick)
|
||||
!value.info.isKycApproved -> createKycInProgressState(onClickKyc)
|
||||
cardInfo != null -> getCardInfoState(cardInfo)
|
||||
value.orderStatus == NOT_ISSUED || value.orderStatus == CANCELED -> createIssueState()
|
||||
value.orderStatus == UNKNOWN || value.orderStatus == CANCELED -> createIssueAvailableState(onClickIssue)
|
||||
else -> createIssueProgressState()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createIssueProgressState(): TangemPayState = Progress(
|
||||
title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
|
||||
buttonText = TextReference.EMPTY,
|
||||
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
|
||||
onButtonClick = {},
|
||||
showProgress = true,
|
||||
)
|
||||
|
||||
private fun createIssueState() = Progress(
|
||||
title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
|
||||
buttonText = TextReference.Res(R.string.common_continue),
|
||||
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
|
||||
onButtonClick = onIssueOrderClick,
|
||||
)
|
||||
|
||||
private fun createKycInProgressState(onContinueKycClick: () -> Unit): TangemPayState = Progress(
|
||||
title = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_title),
|
||||
buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button),
|
||||
iconRes = R.drawable.ic_promo_kyc_36,
|
||||
onButtonClick = onContinueKycClick,
|
||||
)
|
||||
|
||||
private fun getCardInfoState(cardInfo: CardInfo): TangemPayState = TangemPayState.Card(
|
||||
lastFourDigits = TextReference.Str("*${cardInfo.lastFourDigits}"),
|
||||
balanceText = TextReference.Str(getBalanceText(cardInfo)),
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
||||
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createIssueAvailableState
|
||||
|
||||
internal class TangemPayIssueAvailableStateTransformer(
|
||||
private val onClickIssue: () -> Unit = {},
|
||||
) : WalletScreenStateTransformer {
|
||||
|
||||
override fun transform(prevState: WalletScreenState): WalletScreenState =
|
||||
prevState.copy(tangemPayState = createIssueAvailableState(onClickIssue))
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
||||
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createIssueProgressState
|
||||
|
||||
internal class TangemPayIssueProgressStateTransformer : WalletScreenStateTransformer {
|
||||
|
||||
override fun transform(prevState: WalletScreenState): WalletScreenState =
|
||||
prevState.copy(tangemPayState = createIssueProgressState())
|
||||
}
|
||||
|
|
@ -3,9 +3,9 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers.convert
|
|||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
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.tokens.model.ScenarioUnavailabilityReason
|
||||
import com.tangem.domain.tokens.model.TokenActionsState
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletCurrencyActionsClickIntents
|
||||
|
|
@ -18,10 +18,11 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
|
||||
internal class MultiWalletCurrencyActionsConverter(
|
||||
private val userWallet: UserWallet,
|
||||
private val portfolioId: PortfolioId,
|
||||
private val clickIntents: WalletCurrencyActionsClickIntents,
|
||||
) : Converter<TokenActionsState, ImmutableList<TokenActionButtonConfig>> {
|
||||
|
||||
private val userWalletId: UserWalletId = userWallet.walletId
|
||||
|
||||
override fun convert(value: TokenActionsState): ImmutableList<TokenActionButtonConfig> {
|
||||
return value.states
|
||||
.filterIfSingleWithToken()
|
||||
|
|
@ -56,17 +57,17 @@ internal class MultiWalletCurrencyActionsConverter(
|
|||
is TokenActionsState.ActionState.Buy -> {
|
||||
title = resourceReference(R.string.common_buy)
|
||||
icon = R.drawable.ic_plus_24
|
||||
action = { clickIntents.onBuyClick(portfolioId, cryptoCurrencyStatus, noneReason) }
|
||||
action = { clickIntents.onBuyClick(userWalletId, cryptoCurrencyStatus, noneReason) }
|
||||
}
|
||||
is TokenActionsState.ActionState.Receive -> {
|
||||
title = resourceReference(R.string.common_receive)
|
||||
icon = R.drawable.ic_arrow_down_24
|
||||
action = { clickIntents.onReceiveClick(portfolioId, cryptoCurrencyStatus) }
|
||||
action = { clickIntents.onReceiveClick(userWalletId, cryptoCurrencyStatus) }
|
||||
}
|
||||
is TokenActionsState.ActionState.Stake -> {
|
||||
title = resourceReference(R.string.common_stake)
|
||||
icon = R.drawable.ic_staking_24
|
||||
action = { clickIntents.onStakeClick(portfolioId, cryptoCurrencyStatus, actionsState.yield) }
|
||||
action = { clickIntents.onStakeClick(userWalletId, cryptoCurrencyStatus, actionsState.yield) }
|
||||
}
|
||||
is TokenActionsState.ActionState.Sell -> {
|
||||
title = resourceReference(R.string.common_sell)
|
||||
|
|
@ -76,7 +77,7 @@ internal class MultiWalletCurrencyActionsConverter(
|
|||
is TokenActionsState.ActionState.Send -> {
|
||||
title = resourceReference(R.string.common_send)
|
||||
icon = R.drawable.ic_arrow_up_24
|
||||
action = { clickIntents.onSendClick(portfolioId, cryptoCurrencyStatus, noneReason) }
|
||||
action = { clickIntents.onSendClick(userWalletId, cryptoCurrencyStatus, noneReason) }
|
||||
}
|
||||
is TokenActionsState.ActionState.Swap -> {
|
||||
title = resourceReference(R.string.swapping_swap_action)
|
||||
|
|
@ -84,7 +85,7 @@ internal class MultiWalletCurrencyActionsConverter(
|
|||
action = {
|
||||
clickIntents.onSwapClick(
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
portfolioId = portfolioId,
|
||||
userWalletId = userWalletId,
|
||||
unavailabilityReason = noneReason,
|
||||
)
|
||||
}
|
||||
|
|
@ -92,12 +93,12 @@ internal class MultiWalletCurrencyActionsConverter(
|
|||
is TokenActionsState.ActionState.CopyAddress -> {
|
||||
title = resourceReference(R.string.common_copy_address)
|
||||
icon = R.drawable.ic_copy_24
|
||||
action = { clickIntents.onCopyAddressClick(portfolioId, cryptoCurrencyStatus) }
|
||||
action = { clickIntents.onCopyAddressClick(userWalletId, cryptoCurrencyStatus) }
|
||||
}
|
||||
is TokenActionsState.ActionState.HideToken -> {
|
||||
title = resourceReference(R.string.token_details_hide_token)
|
||||
icon = R.drawable.ic_hide_24
|
||||
action = { clickIntents.onHideTokensClick(portfolioId, cryptoCurrencyStatus) }
|
||||
action = { clickIntents.onHideTokensClick(userWalletId, cryptoCurrencyStatus) }
|
||||
}
|
||||
is TokenActionsState.ActionState.Analytics -> {
|
||||
title = resourceReference(R.string.common_analytics)
|
||||
|
|
|
|||
|
|
@ -41,14 +41,12 @@ internal class TokenListStateConverter(
|
|||
|
||||
private val onTokenClick: (accountId: AccountId?, currencyStatus: CryptoCurrencyStatus) -> Unit =
|
||||
{ accountId, currencyStatus ->
|
||||
val id = accountId?.let { PortfolioId(accountId) } ?: PortfolioId(selectedWallet.walletId)
|
||||
clickIntents.onTokenItemClick(id, currencyStatus)
|
||||
clickIntents.onTokenItemClick(selectedWallet.walletId, currencyStatus)
|
||||
}
|
||||
|
||||
private val onTokenLongClick: (accountId: AccountId?, currencyStatus: CryptoCurrencyStatus) -> Unit =
|
||||
{ accountId, currencyStatus ->
|
||||
val id = accountId?.let { PortfolioId(accountId) } ?: PortfolioId(selectedWallet.walletId)
|
||||
clickIntents.onTokenItemLongClick(id, currencyStatus)
|
||||
clickIntents.onTokenItemLongClick(selectedWallet.walletId, currencyStatus)
|
||||
}
|
||||
|
||||
private fun tokenStatusConverter(accountId: AccountId? = null) = TokenItemStateConverter(
|
||||
|
|
@ -112,9 +110,10 @@ internal class TokenListStateConverter(
|
|||
is WalletTokensListState.Empty -> listOf()
|
||||
}
|
||||
return TokensListItemUM.Portfolio(
|
||||
state = accountItem,
|
||||
tokenItemUM = accountItem,
|
||||
isExpanded = isExtend,
|
||||
tokens = items.filterIsInstance<PortfolioTokensListItemUM>(),
|
||||
isCollapsable = true,
|
||||
tokens = items.filterIsInstance<PortfolioTokensListItemUM>().toPersistentList(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.util
|
||||
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState.Progress
|
||||
|
||||
internal object TangemPayStateCreator {
|
||||
|
||||
fun createKycInProgressState(onClickKyc: () -> Unit): TangemPayState = Progress(
|
||||
title = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_title),
|
||||
buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button),
|
||||
iconRes = R.drawable.ic_promo_kyc_36,
|
||||
onButtonClick = onClickKyc,
|
||||
)
|
||||
|
||||
fun createIssueAvailableState(onClickIssue: () -> Unit) = Progress(
|
||||
title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
|
||||
buttonText = TextReference.Res(R.string.common_continue),
|
||||
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
|
||||
onButtonClick = onClickIssue,
|
||||
)
|
||||
|
||||
fun createIssueProgressState(): TangemPayState = Progress(
|
||||
title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
|
||||
buttonText = TextReference.EMPTY,
|
||||
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
|
||||
onButtonClick = {},
|
||||
showProgress = true,
|
||||
)
|
||||
}
|
||||
|
|
@ -8,7 +8,9 @@ import com.tangem.domain.core.lce.Lce
|
|||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.core.utils.getOrElse
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
|
|
@ -63,7 +65,10 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() {
|
|||
flow = tokenListFlow(coroutineScope)
|
||||
.onEach { maybeTokenList ->
|
||||
coroutineScope.launch {
|
||||
sendTokenListAnalytics(maybeTokenList)
|
||||
sendTokenListAnalytics(
|
||||
flattenCurrencies = maybeTokenList.getOrNull()?.flattenCurrencies(),
|
||||
totalFiatBalance = maybeTokenList.getOrNull()?.totalFiatBalance,
|
||||
)
|
||||
}.saveIn(sendAnalyticsJobHolder)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
|
|
@ -140,12 +145,14 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() {
|
|||
|
||||
private fun createAccountListFlow(coroutineScope: CoroutineScope): Flow<*> = combine(
|
||||
flow = accountListFlow(coroutineScope)
|
||||
// todo account analytics for account total balance
|
||||
/*.onEach { maybeTokenList ->
|
||||
.onEach { accountStatusList ->
|
||||
coroutineScope.launch {
|
||||
sendTokenListAnalytics(maybeTokenList)
|
||||
sendTokenListAnalytics(
|
||||
flattenCurrencies = accountStatusList.flattenCurrencies(),
|
||||
totalFiatBalance = accountStatusList.totalFiatBalance,
|
||||
)
|
||||
}.saveIn(sendAnalyticsJobHolder)
|
||||
}*/
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.onEach { accountList ->
|
||||
// todo account see[onAccountListReceived]
|
||||
|
|
@ -207,13 +214,17 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() {
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun sendTokenListAnalytics(maybeTokenList: Lce<TokenListError, TokenList>) {
|
||||
private suspend fun sendTokenListAnalytics(
|
||||
flattenCurrencies: List<CryptoCurrencyStatus>?,
|
||||
totalFiatBalance: TotalFiatBalance?,
|
||||
) {
|
||||
val displayedState = stateHolder.getWalletStateIfSelected(userWallet.walletId)
|
||||
|
||||
tokenListAnalyticsSender.send(
|
||||
displayedUiState = displayedState,
|
||||
userWallet = userWallet,
|
||||
tokenList = maybeTokenList.getOrNull() ?: return,
|
||||
flattenCurrencies = flattenCurrencies ?: return,
|
||||
totalFiatBalance = totalFiatBalance ?: return,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.tokenlist.PortfolioListItem
|
||||
import com.tangem.core.ui.components.tokenlist.PortfolioTokensListItem
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
|
|
@ -59,6 +60,7 @@ internal fun LazyListScope.portfolioTokensList(
|
|||
contentType = { _, item -> item::class.java },
|
||||
itemContent = { tokenIndex, token ->
|
||||
val indexWithHeader = tokenIndex.inc()
|
||||
val lastIndex = tokens.lastIndex.inc()
|
||||
val isPreview = LocalInspectionMode.current
|
||||
val appear = remember {
|
||||
MutableTransitionState(isPreview).apply { targetState = true }
|
||||
|
|
@ -69,11 +71,12 @@ internal fun LazyListScope.portfolioTokensList(
|
|||
.animateItem()
|
||||
.roundedShapeItemDecoration(
|
||||
currentIndex = indexWithHeader,
|
||||
lastIndex = tokens.lastIndex.inc(),
|
||||
lastIndex = lastIndex,
|
||||
backgroundColor = TangemTheme.colors.background.primary,
|
||||
),
|
||||
visibleState = appear,
|
||||
) {
|
||||
val modifier = if (indexWithHeader == lastIndex) Modifier.padding(bottom = 8.dp) else Modifier
|
||||
PortfolioTokensListItem(
|
||||
state = token,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
|
|
@ -114,10 +117,15 @@ private fun LazyListScope.portfolioItem(
|
|||
modifier = anchorModifier,
|
||||
visibleState = appear,
|
||||
) {
|
||||
val modifier = if (portfolio.tokens.isEmpty()) {
|
||||
Modifier.padding(vertical = 8.dp)
|
||||
} else {
|
||||
Modifier.padding(top = 8.dp)
|
||||
}
|
||||
PortfolioListItem(
|
||||
state = portfolio,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.utils
|
||||
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.tokens.IsCryptoCurrencyCoinCouldHideUseCase
|
||||
import com.tangem.domain.tokens.RemoveCurrencyUseCase
|
||||
import javax.inject.Inject
|
||||
|
||||
class WalletFeatureUseCasesFacade @Inject constructor(
|
||||
private val isCryptoCurrencyCoinCouldHide: IsCryptoCurrencyCoinCouldHideUseCase,
|
||||
private val removeCurrencyUseCase: RemoveCurrencyUseCase,
|
||||
) {
|
||||
|
||||
suspend fun isCryptoCurrencyCoinCouldHide(portfolioId: PortfolioId, cryptoCurrencyCoin: CryptoCurrency.Coin) =
|
||||
when (portfolioId) {
|
||||
is PortfolioId.Account -> TODO("account")
|
||||
is PortfolioId.Wallet -> isCryptoCurrencyCoinCouldHide(portfolioId.userWalletId, cryptoCurrencyCoin)
|
||||
}
|
||||
|
||||
suspend fun removeCurrencyUseCase(portfolioId: PortfolioId, currency: CryptoCurrency) = when (portfolioId) {
|
||||
is PortfolioId.Account -> TODO("account")
|
||||
is PortfolioId.Wallet -> removeCurrencyUseCase(portfolioId.userWalletId, currency)
|
||||
}
|
||||
}
|
||||
|
|
@ -16,13 +16,16 @@ dependencies {
|
|||
implementation(projects.features.walletconnect.api)
|
||||
implementation(projects.features.sendV2.api)
|
||||
|
||||
/** Core */
|
||||
implementation(projects.core.configToggles)
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
/** Common */
|
||||
implementation(projects.common.routing)
|
||||
implementation(projects.common.ui)
|
||||
|
||||
/** Core */
|
||||
implementation(projects.core.analytics)
|
||||
implementation(projects.core.configToggles)
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.ui)
|
||||
|
||||
/** Domain models */
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
|
|
|
|||
|
|
@ -110,7 +110,9 @@ private fun EmptyConnectionsBlock(onNewConnectionClick: () -> Unit, modifier: Mo
|
|||
Image(
|
||||
painter = painterResource(R.drawable.img_wallet_connect_76),
|
||||
contentDescription = "Wallet Connect",
|
||||
modifier = Modifier.size(76.dp),
|
||||
modifier = Modifier
|
||||
.size(76.dp)
|
||||
.testTag(WalletConnectScreenTestTags.WALLET_CONNECT_IMAGE),
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing24),
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue