Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-14 13:57:57 +03:00
commit f3dfb262c2
527 changed files with 12898 additions and 3301 deletions

View file

@ -0,0 +1,67 @@
package com.tangem.feature.wallet.child.managetokens
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.slot.childSlot
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.child.managetokens.model.AddAndManageModel
import com.tangem.feature.wallet.child.managetokens.ui.AddAndManageBottomSheetContent
import com.tangem.features.account.PortfolioSelectorComponent
import kotlinx.serialization.builtins.serializer
internal class AddAndManageBottomSheetComponent(
appComponentContext: AppComponentContext,
private val params: Params,
private val portfolioSelectorComponentFactory: PortfolioSelectorComponent.Factory,
) : ComposableBottomSheetComponent, AppComponentContext by appComponentContext {
private val model: AddAndManageModel = getOrCreateModel(params)
private val portfolioSelectorSlot = childSlot(
source = model.portfolioSelectorNavigation,
serializer = Unit.serializer(),
handleBackButton = false,
childFactory = { _, context -> portfolioSelectorChild(context) },
)
private fun portfolioSelectorChild(componentContext: ComponentContext): ComposableBottomSheetComponent =
portfolioSelectorComponentFactory.create(
context = childByContext(componentContext),
params = PortfolioSelectorComponent.Params(
portfolioFetcher = model.portfolioFetcher,
controller = model.portfolioSelectorController,
bsCallback = model.portfolioSelectorCallback,
),
)
override fun dismiss() {
params.onDismiss()
}
@Composable
override fun BottomSheet() {
val portfolioSelectorSlot by portfolioSelectorSlot.subscribeAsState()
AddAndManageBottomSheetContent(
onAddTokensClick = model::onAddTokensClick,
onOrganizeTokensClick = model::onOrganizeTokensClick,
onDismiss = ::dismiss,
)
portfolioSelectorSlot.child?.instance?.BottomSheet()
}
data class Params(
val userWalletId: UserWalletId,
val onDismiss: () -> Unit,
val onOrganizeTokensClick: () -> Unit,
val onManageTokensClick: (AccountId) -> Unit,
)
}

View file

@ -0,0 +1,20 @@
package com.tangem.feature.wallet.child.managetokens.di
import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import com.tangem.feature.wallet.child.managetokens.model.AddAndManageModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(ModelComponent::class)
internal interface AddAndManageModule {
@Binds
@IntoMap
@ClassKey(AddAndManageModel::class)
fun bindAddAndManageModel(model: AddAndManageModel): Model
}

View file

@ -0,0 +1,83 @@
package com.tangem.feature.wallet.child.managetokens.model
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.domain.models.account.AccountId
import com.tangem.feature.wallet.child.managetokens.AddAndManageBottomSheetComponent
import com.tangem.features.account.PortfolioFetcher
import com.tangem.features.account.PortfolioSelectorComponent
import com.tangem.features.account.PortfolioSelectorController
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import javax.inject.Inject
@ModelScoped
internal class AddAndManageModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val portfolioFetcherFactory: PortfolioFetcher.Factory,
val portfolioSelectorController: PortfolioSelectorController,
) : Model() {
private val params = paramsContainer.require<AddAndManageBottomSheetComponent.Params>()
val portfolioSelectorNavigation: SlotNavigation<Unit> = SlotNavigation()
val portfolioFetcher: PortfolioFetcher by lazy {
portfolioFetcherFactory.create(
mode = PortfolioFetcher.Mode.Wallet(params.userWalletId),
scope = modelScope,
)
}
val portfolioSelectorCallback = object : PortfolioSelectorComponent.BottomSheetCallback {
override val onDismiss: () -> Unit = { portfolioSelectorNavigation.dismiss() }
override val onBack: () -> Unit = { portfolioSelectorNavigation.dismiss() }
}
init {
observeAccountSelection()
}
fun onAddTokensClick() {
modelScope.launch {
val data = portfolioFetcher.data.first()
val isSingleAccount = data.isSingleChoice(params.userWalletId)
if (isSingleAccount) {
val mainAccountId = data.balances[params.userWalletId]
?.accountsBalance
?.mainAccount
?.accountId
?: AccountId.forMainCryptoPortfolio(params.userWalletId)
params.onDismiss()
params.onManageTokensClick(mainAccountId)
} else {
portfolioSelectorNavigation.activate(Unit)
}
}
}
fun onOrganizeTokensClick() {
params.onDismiss()
params.onOrganizeTokensClick()
}
private fun observeAccountSelection() {
modelScope.launch {
portfolioSelectorController.selectedAccount.collect { accountId ->
if (accountId != null) {
portfolioSelectorNavigation.dismiss()
params.onDismiss()
params.onManageTokensClick(accountId)
}
}
}
}
}

View file

@ -0,0 +1,159 @@
package com.tangem.feature.wallet.child.managetokens.ui
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.res.R as ResR
import com.tangem.core.ui.R
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
@Composable
internal fun AddAndManageBottomSheetContent(
onAddTokensClick: () -> Unit,
onOrganizeTokensClick: () -> Unit,
onDismiss: () -> Unit,
) {
val config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = onDismiss,
content = AddAndManageBottomSheetConfigContent,
)
TangemModalBottomSheet<AddAndManageBottomSheetConfigContent>(
config = config,
containerColor = TangemTheme.colors.background.primary,
title = {
TangemModalBottomSheetTitle(
title = resourceReference(ResR.string.main_add_and_manage_tokens),
endIconRes = R.drawable.ic_close_24,
onEndClick = onDismiss,
)
},
content = {
AddAndManageContent(
onAddTokensClick = onAddTokensClick,
onOrganizeTokensClick = onOrganizeTokensClick,
)
},
)
}
@Composable
private fun AddAndManageContent(onAddTokensClick: () -> Unit, onOrganizeTokensClick: () -> Unit) {
Column(
modifier = Modifier.padding(
start = 16.dp,
end = 16.dp,
bottom = 16.dp,
),
) {
AddAndManageRow(
iconRes = R.drawable.ic_plus_24,
title = ResR.string.add_and_manage_sheet_manage_title,
subtitle = ResR.string.add_and_manage_sheet_manage_subtitle,
onClick = onAddTokensClick,
modifier = Modifier.roundedShapeItemDecoration(
currentIndex = 0,
lastIndex = 1,
addDefaultPadding = false,
backgroundColor = TangemTheme.colors.background.action,
),
)
AddAndManageRow(
iconRes = R.drawable.ic_filter_default_24,
title = ResR.string.add_and_manage_sheet_organize_title,
subtitle = ResR.string.add_and_manage_sheet_organize_subtitle,
onClick = onOrganizeTokensClick,
modifier = Modifier.roundedShapeItemDecoration(
currentIndex = 1,
lastIndex = 1,
addDefaultPadding = false,
backgroundColor = TangemTheme.colors.background.action,
),
)
}
}
@Composable
private fun AddAndManageRow(
iconRes: Int,
title: Int,
subtitle: Int,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(horizontal = 12.dp, vertical = 15.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(36.dp)
.clip(CircleShape)
.background(TangemTheme.colors.icon.accent.copy(alpha = 0.1f)),
) {
Icon(
modifier = Modifier.size(18.dp),
painter = rememberVectorPainter(ImageVector.vectorResource(id = iconRes)),
tint = TangemTheme.colors.icon.accent,
contentDescription = null,
)
}
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
Text(
text = stringResourceSafe(id = title),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.primary1,
)
Text(
text = stringResourceSafe(id = subtitle),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
}
private object AddAndManageBottomSheetConfigContent : TangemBottomSheetConfigContent
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun AddAndManageBottomSheetContent_Preview() {
TangemThemePreview {
AddAndManageContent(
onAddTokensClick = {},
onOrganizeTokensClick = {},
)
}
}
// endregion

View file

@ -23,7 +23,9 @@ import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.decompose.ComposableDialogComponent
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.tokens.model.details.TokenAction
import com.tangem.feature.wallet.child.managetokens.AddAndManageBottomSheetComponent
import com.tangem.feature.wallet.child.organizetokens.OrganizeTokensComponent
import com.tangem.features.account.PortfolioSelectorComponent
import com.tangem.feature.wallet.child.tokenActions.DefaultTokenActionsComponent
import com.tangem.feature.wallet.child.tokenActions.TokenActionsComponent
import com.tangem.feature.wallet.child.wallet.model.WalletModel
@ -64,6 +66,7 @@ internal class WalletComponent @AssistedInject constructor(
private val newPromoBannersFeatureToggles: NewPromoBannersFeatureToggles,
private val networkSelectionComponentFactory: NetworkSelectionComponent.Factory,
private val tokenActionsComponentFactory: TokenActionsComponent.Factory,
private val portfolioSelectorComponentFactory: PortfolioSelectorComponent.Factory,
private val designFeatureToggles: DesignFeatureToggles,
) : ComposableContentComponent, AppComponentContext by appComponentContext {
@ -178,6 +181,25 @@ internal class WalletComponent @AssistedInject constructor(
),
)
}
is WalletDialogConfig.AddAndManage -> {
AddAndManageBottomSheetComponent(
appComponentContext = childByContext(componentContext),
params = AddAndManageBottomSheetComponent.Params(
userWalletId = dialogConfig.userWalletId,
onDismiss = model.innerWalletRouter.dialogNavigation::dismiss,
onOrganizeTokensClick = {
model.innerWalletRouter.openOrganizeTokensScreen(dialogConfig.userWalletId)
},
onManageTokensClick = { accountId ->
model.innerWalletRouter.openManageTokensScreen(
accountId = accountId,
source = AppRoute.ManageTokens.Source.WALLET,
)
},
),
portfolioSelectorComponentFactory = portfolioSelectorComponentFactory,
)
}
is WalletDialogConfig.OrganizeTokens -> {
OrganizeTokensComponent(
appComponentContext = childByContext(componentContext),

View file

@ -38,7 +38,7 @@ import com.tangem.domain.settings.*
import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase
import com.tangem.domain.walletconnect.WcPairService
import com.tangem.domain.walletconnect.model.WcPairRequest
import com.tangem.domain.tokensync.usecase.StartTokenSyncUseCase
import com.tangem.domain.assetsdiscovery.usecase.StartAssetsDiscoveryUseCase
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.domain.wallets.usecase.*
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyUpdateUseCase
@ -126,7 +126,7 @@ internal class WalletModel @Inject constructor(
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
private val uiMessageSender: UiMessageSender,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val startTokenSyncUseCase: StartTokenSyncUseCase,
private val startAssetsDiscoveryUseCase: StartAssetsDiscoveryUseCase,
val screenLifecycleProvider: ScreenLifecycleProvider,
val innerWalletRouter: InnerWalletRouter,
) : Model() {
@ -159,7 +159,7 @@ internal class WalletModel @Inject constructor(
subscribeTangemPayOnWalletState()
subscribeToMainScreenQrScanning()
enableNotificationsIfNeeded()
applyPendingTokenSyncs()
applyPendingAssetsDiscovery()
clickIntents.initialize(innerWalletRouter, modelScope)
@ -840,9 +840,9 @@ internal class WalletModel @Inject constructor(
}
}
private fun applyPendingTokenSyncs() {
if (hotWalletFeatureToggles.isTokenSyncEnabled) {
startTokenSyncUseCase.applyPendingSyncs()
private fun applyPendingAssetsDiscovery() {
if (hotWalletFeatureToggles.isAssetsDiscoveryEnabled) {
startAssetsDiscoveryUseCase.applyPendingAssetsDiscovery()
}
}

View file

@ -36,6 +36,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBot
import com.tangem.feature.wallet.presentation.wallet.state.transformers.OpenBottomSheetTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletCurrencyActionsConverter
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.flow.collectLatest
@ -112,6 +113,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
private val yieldSupplySetShouldShowMainPromoUseCase: YieldSupplySetShouldShowMainPromoUseCase,
private val tokenListAnalyticsSender: TokenListAnalyticsSender,
private val uiMessageSender: UiMessageSender,
private val walletFeatureToggles: WalletFeatureToggles,
) : BaseWalletClickIntents(), WalletContentClickIntents {
override fun onDetailsClick() {
@ -119,7 +121,12 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
}
override fun onOrganizeTokensClick() {
router.openOrganizeTokensScreen(userWalletId = stateHolder.getSelectedWalletId())
val userWalletId = stateHolder.getSelectedWalletId()
if (walletFeatureToggles.isAddAndManageTokensEnabled) {
router.openAddAndManageBottomSheet(userWalletId = userWalletId)
} else {
router.openOrganizeTokensScreen(userWalletId = userWalletId)
}
}
override fun onDismissMarketsTooltip() {

View file

@ -40,7 +40,7 @@ import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent
import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent.Program
import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent.PromotionBannerClicked
import com.tangem.domain.tokens.model.details.NavigationAction
import com.tangem.domain.tokensync.usecase.AcknowledgeTokenSyncCompletionUseCase
import com.tangem.domain.assetsdiscovery.usecase.AcknowledgeAssetsDiscoveryCompletionUseCase
import com.tangem.domain.wallets.usecase.*
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.Basic
@ -97,9 +97,9 @@ internal interface WalletWarningsClickIntents {
fun onCloseUpgradeBannerClick(userWalletId: UserWalletId)
fun onDismissTokenSyncNotification(userWalletId: UserWalletId)
fun onDismissAssetsDiscoveryNotification(userWalletId: UserWalletId)
fun onTokenSyncManageClick(userWalletId: UserWalletId)
fun onAssetsDiscoveryManageClick(userWalletId: UserWalletId)
}
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
@ -132,7 +132,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
private val uiMessageSender: UiMessageSender,
private val reviewManager: ReviewManager,
private val closeHotWalletUpgradeBannerUseCase: CloseHotWalletUpgradeBannerUseCase,
private val acknowledgeTokenSyncCompletionUseCase: AcknowledgeTokenSyncCompletionUseCase,
private val acknowledgeAssetsDiscoveryCompletionUseCase: AcknowledgeAssetsDiscoveryCompletionUseCase,
) : BaseWalletClickIntents(), WalletWarningsClickIntents {
override fun onAddBackupCardClick() {
@ -508,12 +508,12 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
}
}
override fun onDismissTokenSyncNotification(userWalletId: UserWalletId) {
acknowledgeTokenSyncCompletionUseCase(userWalletId)
override fun onDismissAssetsDiscoveryNotification(userWalletId: UserWalletId) {
acknowledgeAssetsDiscoveryCompletionUseCase(userWalletId)
}
override fun onTokenSyncManageClick(userWalletId: UserWalletId) {
acknowledgeTokenSyncCompletionUseCase(userWalletId)
override fun onAssetsDiscoveryManageClick(userWalletId: UserWalletId) {
acknowledgeAssetsDiscoveryCompletionUseCase(userWalletId)
router.openManageTokensScreen(
AccountId.forMainCryptoPortfolio(userWalletId),
)

View file

@ -14,4 +14,7 @@ internal class DefaultWalletFeatureToggles @Inject constructor(
override val isMainScreenQrScanningEnabled: Boolean
get() = featureToggles.isFeatureEnabled(FeatureToggles.MAIN_SCREEN_QR_SCANNING_ENABLED)
override val isAddAndManageTokensEnabled: Boolean
get() = featureToggles.isFeatureEnabled(FeatureToggles.ADD_AND_MANAGE_TOKENS_ENABLED)
}

View file

@ -91,6 +91,8 @@ internal object WalletScreenPreviewDataLegacy {
),
),
organizeTokensButtonConfig = WalletTokensListState.OrganizeTokensButtonConfig(
textRes = R.string.organize_tokens_title,
iconRes = R.drawable.ic_filter_24,
isEnabled = true,
onClick = {},
),
@ -119,6 +121,8 @@ internal object WalletScreenPreviewDataLegacy {
),
),
organizeTokensButtonConfig = WalletTokensListState.OrganizeTokensButtonConfig(
textRes = R.string.organize_tokens_title,
iconRes = R.drawable.ic_filter_24,
isEnabled = true,
onClick = {},
),
@ -149,6 +153,8 @@ internal object WalletScreenPreviewDataLegacy {
),
),
organizeTokensButtonConfig = WalletTokensListState.OrganizeTokensButtonConfig(
textRes = R.string.organize_tokens_title,
iconRes = R.drawable.ic_filter_24,
isEnabled = true,
onClick = {},
),

View file

@ -32,6 +32,7 @@ import javax.inject.Inject
/** Default implementation of wallet feature router */
@ModelScoped
@Suppress("TooManyFunctions")
internal class DefaultWalletRouter @Inject constructor(
private val router: AppRouter,
private val urlOpener: UrlOpener,
@ -66,14 +67,20 @@ internal class DefaultWalletRouter @Inject constructor(
)
}
override fun openManageTokensScreen(accountId: AccountId) {
override fun openManageTokensScreen(accountId: AccountId, source: AppRoute.ManageTokens.Source) {
val route = AppRoute.ManageTokens(
source = AppRoute.ManageTokens.Source.ACCOUNT,
source = source,
accountId = accountId,
)
router.push(route)
}
override fun openAddAndManageBottomSheet(userWalletId: UserWalletId) {
dialogNavigation.activate(
configuration = WalletDialogConfig.AddAndManage(userWalletId = userWalletId),
)
}
override fun openOnboardingScreen(scanResponse: ScanResponse, continueBackup: Boolean) {
router.push(
AppRoute.Onboarding(

View file

@ -32,6 +32,7 @@ import kotlinx.coroutines.flow.SharedFlow
[REDACTED_AUTHOR]
*/
@Stable
@Suppress("TooManyFunctions")
internal interface InnerWalletRouter {
val dialogNavigation: SlotNavigation<WalletDialogConfig>
@ -47,7 +48,13 @@ internal interface InnerWalletRouter {
fun openDetailsScreen(selectedWalletId: UserWalletId)
/** Open manage tokens screen */
fun openManageTokensScreen(accountId: AccountId)
fun openManageTokensScreen(
accountId: AccountId,
source: AppRoute.ManageTokens.Source = AppRoute.ManageTokens.Source.ACCOUNT,
)
/** Open add and manage tokens bottom sheet */
fun openAddAndManageBottomSheet(userWalletId: UserWalletId)
/** Open onboarding screen */
fun openOnboardingScreen(scanResponse: ScanResponse, continueBackup: Boolean = false)

View file

@ -150,7 +150,7 @@ internal class TokenListAnalyticsSender @Inject constructor(
) {
// for now send only for Polkadot ecosystem blockchains
// later dependency on Blockchain will be removed and use token name
when (val blockchain = Blockchain.fromNetworkId(currencyStatus.currency.network.backendId)) {
when (val blockchain = Blockchain.fromNetworkId(currencyStatus.currency.network.rawId)) {
Blockchain.Polkadot,
Blockchain.AlephZero,
Blockchain.Kusama,

View file

@ -120,7 +120,7 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
is WalletNotification.Warning.TangemPayRefreshNeeded -> null
is WalletNotification.Warning.TangemPayUnreachable -> null
is WalletNotification.UpgradeHotWalletPromo -> null
is WalletNotification.TokenSyncCompleted -> null
is WalletNotification.AssetsDiscoveryCompleted -> null
is WalletNotification.CreateTangemPayAccount -> null
}
}

View file

@ -27,8 +27,8 @@ import com.tangem.domain.notifications.repository.NotificationsRepository
import com.tangem.domain.promo.ShouldShowPromoWalletUseCase
import com.tangem.domain.promo.models.PromoId
import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
import com.tangem.domain.tokensync.model.TokenSyncProgress
import com.tangem.domain.tokensync.usecase.ObserveTokenSyncUseCase
import com.tangem.domain.assetsdiscovery.model.AssetsDiscoveryProgress
import com.tangem.domain.assetsdiscovery.usecase.ObserveAssetsDiscoveryUseCase
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
import com.tangem.feature.wallet.child.wallet.model.WalletActivationBannerType
@ -65,7 +65,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
private val shouldShowUpgradeHotWalletBannerUseCase: ShouldShowUpgradeHotWalletBannerUseCase,
private val getUpgradeBannerClosureTimestampUseCase: GetUpgradeBannerClosureTimestampUseCase,
private val checkHotWalletUpgradeBannerUseCase: CheckHotWalletUpgradeBannerUseCase,
private val observeTokenSyncUseCase: ObserveTokenSyncUseCase,
private val observeAssetsDiscoveryUseCase: ObserveAssetsDiscoveryUseCase,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
) {
@ -75,11 +75,12 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
val params = SingleAccountStatusListProducer.Params(userWallet.walletId)
val accountStatusListFlow = accountDependencies.singleAccountStatusListSupplier(params)
val tokenSyncProgressFlow = if (hotWalletFeatureToggles.isTokenSyncEnabled && userWallet is UserWallet.Hot) {
observeTokenSyncUseCase(userWallet.walletId).distinctUntilChanged()
} else {
flowOf(TokenSyncProgress.Idle)
}
val assetsDiscoveryProgressFlow =
if (hotWalletFeatureToggles.isAssetsDiscoveryEnabled && userWallet is UserWallet.Hot) {
observeAssetsDiscoveryUseCase(userWallet.walletId).distinctUntilChanged()
} else {
flowOf(AssetsDiscoveryProgress.Idle)
}
return combine(
accountStatusListFlow,
@ -96,7 +97,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
.distinctUntilChanged(),
getUpgradeBannerClosureTimestampUseCase(userWallet.walletId)
.distinctUntilChanged(),
tokenSyncProgressFlow,
assetsDiscoveryProgressFlow,
) { array -> array }
.map { array ->
val accountStatusList = array[0] as AccountStatusList
@ -108,7 +109,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
val shouldShowYieldPromo = array[6] as Boolean
val shouldShowUpgradeBanner = array[7] as Boolean
val closureTimestamp = array[8] as? Long
val tokenSyncProgress = array[9] as TokenSyncProgress
val assetsDiscoveryProgress = array[9] as AssetsDiscoveryProgress
val flattenCurrencies = accountStatusList.flattenCurrencies()
val paymentAccountStatus = accountStatusList.accountStatuses
@ -153,9 +154,9 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
clickIntents = clickIntents,
)
addTokenSyncCompletedNotification(
addAssetsDiscoveryCompletedNotification(
userWallet = userWallet,
tokenSyncProgress = tokenSyncProgress,
assetsDiscoveryProgress = assetsDiscoveryProgress,
clickIntents = clickIntents,
)
@ -404,17 +405,17 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
// }
// }
private fun MutableList<WalletNotification>.addTokenSyncCompletedNotification(
private fun MutableList<WalletNotification>.addAssetsDiscoveryCompletedNotification(
userWallet: UserWallet,
tokenSyncProgress: TokenSyncProgress,
assetsDiscoveryProgress: AssetsDiscoveryProgress,
clickIntents: WalletClickIntents,
) {
addIf(
element = WalletNotification.TokenSyncCompleted(
onCloseClick = { clickIntents.onDismissTokenSyncNotification(userWallet.walletId) },
onManageTokensClick = { clickIntents.onTokenSyncManageClick(userWallet.walletId) },
element = WalletNotification.AssetsDiscoveryCompleted(
onCloseClick = { clickIntents.onDismissAssetsDiscoveryNotification(userWallet.walletId) },
onManageTokensClick = { clickIntents.onAssetsDiscoveryManageClick(userWallet.walletId) },
),
condition = tokenSyncProgress is TokenSyncProgress.Completed,
condition = assetsDiscoveryProgress is AssetsDiscoveryProgress.Completed,
)
}

View file

@ -10,7 +10,7 @@ import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.card.common.util.getCardsCount
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.TokenSyncProgressUM
import com.tangem.feature.wallet.presentation.wallet.state.model.AssetsDiscoveryProgressUM
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAdditionalInfo
import java.math.BigDecimal
@ -32,7 +32,7 @@ internal object WalletAdditionalInfoFactory {
fun resolve(
wallet: UserWallet,
currencyAmount: BigDecimal? = null,
syncProgress: TokenSyncProgressUM = TokenSyncProgressUM.Idle,
syncProgress: AssetsDiscoveryProgressUM = AssetsDiscoveryProgressUM.Idle,
): WalletAdditionalInfo {
return when (wallet) {
is UserWallet.Cold -> {
@ -46,8 +46,8 @@ internal object WalletAdditionalInfoFactory {
}
}
private fun UserWallet.Hot.resolveAdditionalInfo(syncProgress: TokenSyncProgressUM): WalletAdditionalInfo {
val content = if (syncProgress is TokenSyncProgressUM.InProgress) {
private fun UserWallet.Hot.resolveAdditionalInfo(syncProgress: AssetsDiscoveryProgressUM): WalletAdditionalInfo {
val content = if (syncProgress is AssetsDiscoveryProgressUM.InProgress) {
WalletAdditionalInfo.Content.SyncProgress(syncProgress.progressPercent)
} else {
WalletAdditionalInfo.Content.Text(

View file

@ -18,7 +18,7 @@ internal class MultiWalletContentLoader @AssistedInject constructor(
private val walletNotificationsSubscriberFactory: WalletNotificationsSubscriber.Factory,
private val multiWalletActionButtonsSubscriberFactory: MultiWalletActionButtonsSubscriber.Factory,
private val tangemPayMainSubscriberFactory: TangemPayMainSubscriber.Factory,
private val tokenSyncSubscriberFactory: TokenSyncSubscriber.Factory,
private val assetsDiscoverySubscriberFactory: AssetsDiscoverySubscriber.Factory,
private val tokenListAnalyticsSubscriberFactory: TokenListAnalyticsSubscriber.Factory,
private val designFeatureToggles: DesignFeatureToggles,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
@ -36,8 +36,8 @@ internal class MultiWalletContentLoader @AssistedInject constructor(
multiWalletActionButtonsSubscriberFactory.create(userWallet),
tangemPayMainSubscriberFactory.create(userWallet),
tokenListAnalyticsSubscriberFactory.create(userWallet),
if (hotWalletFeatureToggles.isTokenSyncEnabled && userWallet is UserWallet.Hot) {
tokenSyncSubscriberFactory.create(userWallet)
if (hotWalletFeatureToggles.isAssetsDiscoveryEnabled && userWallet is UserWallet.Hot) {
assetsDiscoverySubscriberFactory.create(userWallet)
} else {
null
},

View file

@ -0,0 +1,13 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import androidx.compose.runtime.Immutable
@Immutable
internal sealed class AssetsDiscoveryProgressUM {
data object Idle : AssetsDiscoveryProgressUM()
data class InProgress(val progressPercent: Int) : AssetsDiscoveryProgressUM()
data object Completed : AssetsDiscoveryProgressUM()
}

View file

@ -1,13 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import androidx.compose.runtime.Immutable
@Immutable
internal sealed class TokenSyncProgressUM {
data object Idle : TokenSyncProgressUM()
data class InProgress(val progressPercent: Int) : TokenSyncProgressUM()
data object Completed : TokenSyncProgressUM()
}

View file

@ -50,6 +50,9 @@ internal sealed interface WalletDialogConfig {
@Serializable
data class KycRejected(val walletId: UserWalletId, val customerId: String) : WalletDialogConfig
@Serializable
data class AddAndManage(val userWalletId: UserWalletId) : WalletDialogConfig
@Serializable
data class OrganizeTokens(val userWalletId: UserWalletId) : WalletDialogConfig

View file

@ -435,7 +435,7 @@ sealed class WalletNotification(val config: NotificationConfig) {
),
)
data class TokenSyncCompleted(
data class AssetsDiscoveryCompleted(
val onCloseClick: () -> Unit,
val onManageTokensClick: () -> Unit,
) : WalletNotification(

View file

@ -27,7 +27,7 @@ internal sealed interface WalletState : WalletStateHolder {
abstract val tangemPayState: TangemPayState
abstract val tangemPayMainUM: TangemPayMainUM
abstract val isTangemPayRefactorEnabled: Boolean // TANGEM_PAY_ACCOUNTS_REFACTOR_ENABLED
abstract val tokenSyncProgressUM: TokenSyncProgressUM
abstract val assetsDiscoveryProgressUM: AssetsDiscoveryProgressUM
data class Content(
override val pullToRefreshConfig: PullToRefreshConfig,
@ -41,7 +41,7 @@ internal sealed interface WalletState : WalletStateHolder {
override val tangemPayState: TangemPayState,
override val tangemPayMainUM: TangemPayMainUM,
override val isTangemPayRefactorEnabled: Boolean,
override val tokenSyncProgressUM: TokenSyncProgressUM = TokenSyncProgressUM.Idle,
override val assetsDiscoveryProgressUM: AssetsDiscoveryProgressUM = AssetsDiscoveryProgressUM.Idle,
) : MultiCurrency()
data class Locked(
@ -63,7 +63,7 @@ internal sealed interface WalletState : WalletStateHolder {
override val tangemPayState: TangemPayState = TangemPayState.Empty
override val tangemPayMainUM: TangemPayMainUM = TangemPayMainUM.Empty
override val isTangemPayRefactorEnabled: Boolean = false
override val tokenSyncProgressUM: TokenSyncProgressUM = TokenSyncProgressUM.Idle
override val assetsDiscoveryProgressUM: AssetsDiscoveryProgressUM = AssetsDiscoveryProgressUM.Idle
}
}

View file

@ -49,5 +49,10 @@ internal sealed class WalletTokensListState {
}
}
data class OrganizeTokensButtonConfig(val isEnabled: Boolean, val onClick: () -> Unit)
data class OrganizeTokensButtonConfig(
val textRes: Int,
val iconRes: Int,
val isEnabled: Boolean,
val onClick: () -> Unit,
)
}

View file

@ -2,21 +2,21 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
import com.tangem.feature.wallet.presentation.wallet.state.model.TokenSyncProgressUM
import com.tangem.feature.wallet.presentation.wallet.state.model.AssetsDiscoveryProgressUM
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
internal class SetTokenSyncProgressTransformer(
internal class SetAssetsDiscoveryProgressTransformer(
private val userWallet: UserWallet,
private val progress: TokenSyncProgressUM,
private val progress: AssetsDiscoveryProgressUM,
) : WalletStateTransformer(userWallet.walletId) {
override fun transform(prevState: WalletState): WalletState {
return when (prevState) {
is WalletState.MultiCurrency.Content -> prevState.copy(
walletCardState = updateCardState(prevState.walletCardState),
tokenSyncProgressUM = progress,
assetsDiscoveryProgressUM = progress,
)
else -> prevState
}

View file

@ -61,7 +61,7 @@ internal class SetTokenListErrorTransformer(
walletsBalanceUM = walletUM.walletsBalanceUM.toLoadedState(),
tokensListUM = WalletTokensListUM.Empty(
onEmptyClick = {
clickIntents.onTokenSyncManageClick(walletUM.walletsBalanceUM.id)
clickIntents.onAssetsDiscoveryManageClick(walletUM.walletsBalanceUM.id)
},
),
buttons = walletUM.disableButtons(),

View file

@ -25,6 +25,7 @@ internal class SetTokenListTransformer(
private val shouldShowMainPromo: Boolean,
private val isAccountsModeEnabled: Boolean,
private val isRedesignEnabled: Boolean,
private val isAddAndManageTokensEnabled: Boolean,
) : WalletStateTransformer(userWallet.walletId) {
private val tangemPayConverter by lazy {
@ -105,6 +106,7 @@ internal class SetTokenListTransformer(
yieldModuleApyMap = yieldSupplyApyMap,
stakingAvailabilityMap = stakingAvailabilityMap,
shouldShowMainPromo = shouldShowMainPromo,
isAddAndManageTokensEnabled = isAddAndManageTokensEnabled,
).convert(value = this)
}
@ -123,7 +125,7 @@ internal class SetTokenListTransformer(
if (params !is TokenConverterParams.Account) {
return WalletTokensListUM.Empty(
onEmptyClick = {
clickIntents.onTokenSyncManageClick(userWallet.walletId)
clickIntents.onAssetsDiscoveryManageClick(userWallet.walletId)
},
)
}
@ -137,6 +139,7 @@ internal class SetTokenListTransformer(
shouldShowMainPromo = shouldShowMainPromo,
isAccountsModeEnabled = isAccountsModeEnabled,
expandedAccounts = params.expandedAccounts,
isAddAndManageTokensEnabled = isAddAndManageTokensEnabled,
).convert(value = params.accountList)
}
}

View file

@ -80,6 +80,7 @@ internal class TangemPayUpdateInfoStateTransformer(
cardFrozenState = cardFrozenState,
cardNumberEnd = cardInfo.lastFourDigits,
chainId = POLYGON_CHAIN_ID,
displayName = productInstance.displayName,
),
)
},

View file

@ -4,7 +4,7 @@ import com.tangem.domain.card.common.util.getCardsCount
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
import com.tangem.feature.wallet.presentation.wallet.state.model.TokenSyncProgressUM
import com.tangem.feature.wallet.presentation.wallet.state.model.AssetsDiscoveryProgressUM
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
@ -19,7 +19,7 @@ internal class UpdateWalletCardsCountTransformer(
return when (prevState) {
is WalletState.MultiCurrency.Content -> {
prevState.copy(
walletCardState = prevState.walletCardState.toUpdatedState(prevState.tokenSyncProgressUM),
walletCardState = prevState.walletCardState.toUpdatedState(prevState.assetsDiscoveryProgressUM),
)
}
is WalletState.SingleCurrency.Content -> {
@ -39,7 +39,7 @@ internal class UpdateWalletCardsCountTransformer(
}
private fun WalletCardState.toUpdatedState(
syncProgress: TokenSyncProgressUM = TokenSyncProgressUM.Idle,
syncProgress: AssetsDiscoveryProgressUM = AssetsDiscoveryProgressUM.Idle,
): WalletCardState {
return when (this) {
is WalletCardState.Content -> copy(

View file

@ -74,6 +74,7 @@ internal class TangemPayMainBlockConverter(
cardFrozenState = TangemPayCardFrozenState.Frozen,
cardNumberEnd = statusValue.lastFourDigits,
chainId = POLYGON_CHAIN_ID,
displayName = statusValue.displayName,
),
)
},
@ -97,6 +98,7 @@ internal class TangemPayMainBlockConverter(
cardFrozenState = TangemPayCardFrozenState.Unfrozen,
cardNumberEnd = statusValue.lastFourDigits,
chainId = POLYGON_CHAIN_ID,
displayName = statusValue.displayName,
),
)
},

View file

@ -41,6 +41,7 @@ internal class TokenListStateConverter(
private val yieldModuleApyMap: Map<String, BigDecimal>,
private val stakingAvailabilityMap: Map<CryptoCurrency, StakingAvailability>,
private val shouldShowMainPromo: Boolean,
private val isAddAndManageTokensEnabled: Boolean,
) : Converter<WalletTokensListState, WalletTokensListState> {
private val yieldSupplyPromoBannerConverter = YieldSupplyPromoBannerConverter(
@ -157,6 +158,8 @@ internal class TokenListStateConverter(
}
return if (currenciesSize > 1 && !isSingleCurrencyWalletWithToken()) {
WalletOrganizeTokensButtonConfig(
textRes = organizeButtonTextRes(),
iconRes = organizeButtonIconRes(),
isEnabled = tokenList.totalFiatBalance !is TotalFiatBalance.Loading,
onClick = clickIntents::onOrganizeTokensClick,
)
@ -168,6 +171,8 @@ internal class TokenListStateConverter(
private fun getOrganizeTokensButtonStateV2(accountList: AccountStatusList): WalletOrganizeTokensButtonConfig? {
return if (accountList.flattenCurrencies().size > 1 && !isSingleCurrencyWalletWithToken()) {
WalletOrganizeTokensButtonConfig(
textRes = organizeButtonTextRes(),
iconRes = organizeButtonIconRes(),
isEnabled = accountList.totalFiatBalance !is TotalFiatBalance.Loading,
onClick = clickIntents::onOrganizeTokensClick,
)
@ -176,6 +181,18 @@ internal class TokenListStateConverter(
}
}
private fun organizeButtonTextRes(): Int = if (isAddAndManageTokensEnabled) {
R.string.main_add_and_manage_tokens
} else {
R.string.organize_tokens_title
}
private fun organizeButtonIconRes(): Int = if (isAddAndManageTokensEnabled) {
R.drawable.ic_filter_default_24
} else {
R.drawable.ic_filter_24
}
private fun isSingleCurrencyWalletWithToken(): Boolean {
return selectedWallet is UserWallet.Cold &&
selectedWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()

View file

@ -38,6 +38,7 @@ internal class WalletTokensListUMConverter(
private val isAccountsModeEnabled: Boolean,
private val expandedAccounts: Set<AccountId>,
private val stakingAvailabilityMap: Map<CryptoCurrency, StakingAvailability>,
private val isAddAndManageTokensEnabled: Boolean,
shouldShowMainPromo: Boolean,
) : Converter<AccountStatusList, WalletTokensListUM> {
@ -164,15 +165,25 @@ internal class WalletTokensListUMConverter(
}
private fun getOrganizeButtonUM(accountList: AccountStatusList): TangemButtonUM? {
val textRes = if (isAddAndManageTokensEnabled) {
R.string.main_add_and_manage_tokens
} else {
R.string.organize_tokens_title
}
val iconRes = if (isAddAndManageTokensEnabled) {
R.drawable.ic_filter_default_24
} else {
R.drawable.ic_filter_24
}
return if (accountList.flattenCurrencies().size > 1 && !selectedWallet.isSingleWalletWithToken()) {
TangemButtonUM(
text = resourceReference(R.string.organize_tokens_title),
text = resourceReference(textRes),
isEnabled = accountList.totalFiatBalance !is TotalFiatBalance.Loading,
size = TangemButtonSize.X9,
shape = TangemButtonShape.Rounded,
type = TangemButtonType.PrimaryInverse,
tangemIconUM = TangemIconUM.Icon(
iconRes = R.drawable.ic_filter_default_24,
iconRes = iconRes,
tintReference = {
if (accountList.totalFiatBalance !is TotalFiatBalance.Loading) {
TangemTheme.colors2.graphic.neutral.primary

View file

@ -12,6 +12,7 @@ import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoU
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.account.AccountDependencies
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import com.tangem.utils.coroutines.combine7
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@ -39,8 +40,12 @@ internal class AccountListSubscriber @AssistedInject constructor(
private val stakingAvailabilityListUseCase: StakingAvailabilityListUseCase,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
private val designFeatureToggles: DesignFeatureToggles,
private val walletFeatureToggles: WalletFeatureToggles,
) : BasicAccountListSubscriber() {
override val isAddAndManageTokensEnabled: Boolean
get() = walletFeatureToggles.isAddAndManageTokensEnabled
override fun create(coroutineScope: CoroutineScope): Flow<*> = combine7(
flow1 = getAccountStatusListFlow(),
flow2 = getAppCurrencyFlow(),

View file

@ -1,11 +1,11 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokensync.model.TokenSyncProgress
import com.tangem.domain.tokensync.usecase.ObserveTokenSyncUseCase
import com.tangem.domain.assetsdiscovery.model.AssetsDiscoveryProgress
import com.tangem.domain.assetsdiscovery.usecase.ObserveAssetsDiscoveryUseCase
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.model.TokenSyncProgressUM
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTokenSyncProgressTransformer
import com.tangem.feature.wallet.presentation.wallet.state.model.AssetsDiscoveryProgressUM
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetAssetsDiscoveryProgressTransformer
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -13,25 +13,25 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onEach
internal class TokenSyncSubscriber @AssistedInject constructor(
internal class AssetsDiscoverySubscriber @AssistedInject constructor(
@Assisted private val userWallet: UserWallet,
private val stateController: WalletStateController,
private val observeTokenSyncUseCase: ObserveTokenSyncUseCase,
private val observeAssetsDiscoveryUseCase: ObserveAssetsDiscoveryUseCase,
) : WalletSubscriber() {
override fun create(coroutineScope: CoroutineScope): Flow<*> {
return observeTokenSyncUseCase(userWallet.walletId)
return observeAssetsDiscoveryUseCase(userWallet.walletId)
.onEach { current -> handleProgress(userWallet, current) }
}
private fun handleProgress(userWallet: UserWallet, current: TokenSyncProgress) {
private fun handleProgress(userWallet: UserWallet, current: AssetsDiscoveryProgress) {
val progressUM = when (current) {
is TokenSyncProgress.InProgress -> TokenSyncProgressUM.InProgress(current.progressPercent)
is TokenSyncProgress.Completed -> TokenSyncProgressUM.Completed
is TokenSyncProgress.Idle -> TokenSyncProgressUM.Idle
is AssetsDiscoveryProgress.InProgress -> AssetsDiscoveryProgressUM.InProgress(current.progressPercent)
is AssetsDiscoveryProgress.Completed -> AssetsDiscoveryProgressUM.Completed
is AssetsDiscoveryProgress.Idle -> AssetsDiscoveryProgressUM.Idle
}
stateController.update(
SetTokenSyncProgressTransformer(
SetAssetsDiscoveryProgressTransformer(
userWallet = userWallet,
progress = progressUM,
),
@ -40,6 +40,6 @@ internal class TokenSyncSubscriber @AssistedInject constructor(
@AssistedFactory
interface Factory {
fun create(userWallet: UserWallet): TokenSyncSubscriber
fun create(userWallet: UserWallet): AssetsDiscoverySubscriber
}
}

View file

@ -34,6 +34,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
abstract val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase
abstract val stateController: WalletStateController
abstract val clickIntents: WalletClickIntents
abstract val isAddAndManageTokensEnabled: Boolean
override val singleAccountStatusListSupplier: SingleAccountStatusListSupplier
get() = accountDependencies.singleAccountStatusListSupplier
@ -105,6 +106,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
shouldShowMainPromo = shouldShowMainPromo,
isAccountsModeEnabled = isAccountMode,
isRedesignEnabled = true,
isAddAndManageTokensEnabled = isAddAndManageTokensEnabled,
),
)
}
@ -168,6 +170,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
shouldShowMainPromo = shouldShowMainPromo,
isAccountsModeEnabled = false,
isRedesignEnabled = false,
isAddAndManageTokensEnabled = isAddAndManageTokensEnabled,
),
)
}

View file

@ -5,6 +5,7 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.account.AccountDependencies
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -18,8 +19,12 @@ internal class SingleWalletSubscriber @AssistedInject constructor(
override val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
override val stateController: WalletStateController,
override val clickIntents: WalletClickIntents,
private val walletFeatureToggles: WalletFeatureToggles,
) : BasicAccountListSubscriber() {
override val isAddAndManageTokensEnabled: Boolean
get() = walletFeatureToggles.isAddAndManageTokensEnabled
override fun create(coroutineScope: CoroutineScope): Flow<Unit> = combine(
flow = getAccountStatusListFlow(),
flow2 = getAppCurrencyFlow(),

View file

@ -5,6 +5,7 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.account.AccountDependencies
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -19,8 +20,12 @@ internal class SingleWalletWithTokenSubscriberLegacy @AssistedInject constructor
override val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
override val stateController: WalletStateController,
override val clickIntents: WalletClickIntents,
private val walletFeatureToggles: WalletFeatureToggles,
) : BasicAccountListSubscriber() {
override val isAddAndManageTokensEnabled: Boolean
get() = walletFeatureToggles.isAddAndManageTokensEnabled
override fun create(coroutineScope: CoroutineScope): Flow<Unit> = combine(
flow = getAccountStatusListFlow(),
flow2 = getAppCurrencyFlow(),

View file

@ -726,17 +726,13 @@ private fun WalletSnackbarHost(
}
internal fun LazyListScope.organizeTokens(state: WalletState, itemModifier: Modifier) {
(state as? WalletState.MultiCurrency)?.let {
(state.tokensListState as? WalletTokensListState.ContentState)?.let {
it.organizeTokensButtonConfig?.let { config ->
organizeTokensButton(
modifier = itemModifier,
isEnabled = config.isEnabled,
onClick = config.onClick,
)
}
}
}
val multiCurrencyState = state as? WalletState.MultiCurrency ?: return
val contentState = multiCurrencyState.tokensListState as? WalletTokensListState.ContentState ?: return
val config = contentState.organizeTokensButtonConfig ?: return
organizeTokensButton(
modifier = itemModifier,
config = config,
)
}
internal fun LazyListScope.nftCollections(state: WalletState, itemModifier: Modifier) {

View file

@ -7,7 +7,7 @@ import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
import com.tangem.core.ui.components.buttons.actions.RoundedActionButton
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.test.MainScreenTestTags
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
private const val ORGANIZE_BUTTON_CONTENT_TYPE = "OrganizeTokensButton"
@ -20,18 +20,17 @@ private const val ORGANIZE_BUTTON_CONTENT_TYPE = "OrganizeTokensButton"
[REDACTED_AUTHOR]
*/
internal fun LazyListScope.organizeTokensButton(
isEnabled: Boolean,
onClick: () -> Unit,
config: WalletTokensListState.OrganizeTokensButtonConfig,
modifier: Modifier = Modifier,
) {
item(key = ORGANIZE_BUTTON_CONTENT_TYPE, contentType = ORGANIZE_BUTTON_CONTENT_TYPE) {
RoundedActionButton(
modifier = modifier.testTag(MainScreenTestTags.ORGANIZE_TOKENS_BUTTON),
config = ActionButtonConfig(
text = resourceReference(id = R.string.organize_tokens_title),
iconResId = R.drawable.ic_filter_24,
onClick = onClick,
isEnabled = isEnabled,
text = resourceReference(id = config.textRes),
iconResId = config.iconRes,
onClick = config.onClick,
isEnabled = config.isEnabled,
),
)
}

View file

@ -27,7 +27,7 @@ class YieldSupplyPromoBannerConverterTest {
@Test
fun `GIVEN promo disabled WHEN convert THEN return null`() {
val token = createToken(networkId = "ethereum", backendId = "ethereum", contract = "0xABCDEF")
val token = createToken(networkId = "ethereum", rawId = "ethereum", contract = "0xABCDEF")
val status = createLoadedStatus(token = token, amount = BigDecimal.ONE, isYieldActive = false)
val tokenList = ungroupedTokenList(status)
val params = TokenConverterParams.Wallet(
@ -35,7 +35,7 @@ class YieldSupplyPromoBannerConverterTest {
tokenList = tokenList,
)
val converter = YieldSupplyPromoBannerConverter(
yieldModuleApyMap = mapOf("${token.network.backendId}_${token.contractAddress}" to BigDecimal("0.10")),
yieldModuleApyMap = mapOf("${token.network.rawId}_${token.contractAddress}" to BigDecimal("0.10")),
shouldShowMainPromo = false,
)
@ -46,7 +46,7 @@ class YieldSupplyPromoBannerConverterTest {
@Test
fun `GIVEN empty apy map WHEN convert THEN return null`() {
val token = createToken(networkId = "ethereum", backendId = "ethereum", contract = "0xA1")
val token = createToken(networkId = "ethereum", rawId = "ethereum", contract = "0xA1")
val status = createLoadedStatus(token = token, amount = BigDecimal("2.0"), isYieldActive = false)
val params = TokenConverterParams.Wallet(
mainAccount = account,
@ -64,14 +64,14 @@ class YieldSupplyPromoBannerConverterTest {
@Test
fun `GIVEN active yield token present WHEN convert THEN return null`() {
val token = createToken(networkId = "ethereum", backendId = "ethereum", contract = "0xAA")
val token = createToken(networkId = "ethereum", rawId = "ethereum", contract = "0xAA")
val statusActive = createLoadedStatus(token = token, amount = BigDecimal("5"), isYieldActive = true)
val params = TokenConverterParams.Wallet(
mainAccount = account,
tokenList = ungroupedTokenList(statusActive),
)
val converter = YieldSupplyPromoBannerConverter(
yieldModuleApyMap = mapOf("${token.network.backendId}_${token.contractAddress}" to BigDecimal("0.12")),
yieldModuleApyMap = mapOf("${token.network.rawId}_${token.contractAddress}" to BigDecimal("0.12")),
shouldShowMainPromo = true,
)
@ -82,16 +82,16 @@ class YieldSupplyPromoBannerConverterTest {
@Test
fun `GIVEN multiple candidates EVM case insensitive WHEN convert THEN return status of max amount`() {
val evmNetworkId = "ETH"
val tokenSmall = createToken(networkId = evmNetworkId, backendId = evmNetworkId, contract = "0xAbCd")
val tokenBig = createToken(networkId = evmNetworkId, backendId = evmNetworkId, contract = "0xBEEF")
val evmNetworkId = "ethereum"
val tokenSmall = createToken(networkId = evmNetworkId, rawId = evmNetworkId, contract = "0xAbCd")
val tokenBig = createToken(networkId = evmNetworkId, rawId = evmNetworkId, contract = "0xBEEF")
val statusSmall = createLoadedStatus(token = tokenSmall, amount = BigDecimal("1.00"), isYieldActive = false)
val statusBig = createLoadedStatus(token = tokenBig, amount = BigDecimal("10.00"), isYieldActive = false)
val apyMap = mapOf(
"${tokenSmall.network.backendId}_${tokenSmall.contractAddress.lowercase()}" to BigDecimal("0.05"),
"${tokenBig.network.backendId}_${tokenBig.contractAddress.uppercase()}" to BigDecimal("0.15"),
"${tokenSmall.network.rawId}_${tokenSmall.contractAddress.lowercase()}" to BigDecimal("0.05"),
"${tokenBig.network.rawId}_${tokenBig.contractAddress.uppercase()}" to BigDecimal("0.15"),
)
val params = TokenConverterParams.Wallet(
@ -111,10 +111,10 @@ class YieldSupplyPromoBannerConverterTest {
@Test
fun `GIVEN non evm case sensitive mismatch WHEN convert THEN return null`() {
val nonEvmId = "xrp"
val token = createToken(networkId = nonEvmId, backendId = nonEvmId, contract = "rAbC123")
val token = createToken(networkId = nonEvmId, rawId = nonEvmId, contract = "rAbC123")
val status = createLoadedStatus(token = token, amount = BigDecimal("3"), isYieldActive = false)
val mismatchedKey = "${token.network.backendId}_${token.contractAddress.lowercase()}"
val mismatchedKey = "${token.network.rawId}_${token.contractAddress.lowercase()}"
val apyMap = mapOf(mismatchedKey to BigDecimal("0.07"))
val params = TokenConverterParams.Wallet(
@ -133,14 +133,14 @@ class YieldSupplyPromoBannerConverterTest {
@Test
fun `GIVEN custom status WHEN convert THEN return null`() {
val token = createToken(networkId = "ethereum", backendId = "ethereum", contract = "0xCUSTOM")
val token = createToken(networkId = "ethereum", rawId = "ethereum", contract = "0xCUSTOM")
val status = createCustomStatus(token = token, amount = BigDecimal("5.0"), isYieldActive = false)
val params = TokenConverterParams.Wallet(
mainAccount = account,
tokenList = ungroupedTokenList(status),
)
val converter = YieldSupplyPromoBannerConverter(
yieldModuleApyMap = mapOf("${token.network.backendId}_${token.contractAddress}" to BigDecimal("0.10")),
yieldModuleApyMap = mapOf("${token.network.rawId}_${token.contractAddress}" to BigDecimal("0.10")),
shouldShowMainPromo = true,
)
@ -236,15 +236,14 @@ class YieldSupplyPromoBannerConverterTest {
)
}
private fun createToken(networkId: String, backendId: String, contract: String): CryptoCurrency.Token {
private fun createToken(networkId: String, rawId: String, contract: String): CryptoCurrency.Token {
val network = Network(
id = Network.ID(value = networkId, derivationPath = Network.DerivationPath.None),
backendId = backendId,
name = backendId,
id = Network.ID(value = rawId, derivationPath = Network.DerivationPath.None),
name = rawId,
currencySymbol = "SYM",
derivationPath = Network.DerivationPath.None,
isTestnet = false,
standardType = when (backendId) {
standardType = when (rawId) {
"ethereum" -> Network.StandardType.ERC20
else -> Network.StandardType.Unspecified("UNSPEC")
},

View file

@ -834,10 +834,9 @@ class DefaultPromoDeeplinkHandlerTest {
address: String,
derivationPath: Network.DerivationPath = Network.DerivationPath.None,
): NetworkStatus {
val networkId = Network.ID(Network.RawID(rawNetworkId), derivationPath)
val networkId = Network.ID(value = rawNetworkId, derivationPath = derivationPath)
val network = Network(
id = networkId,
backendId = rawNetworkId,
name = rawNetworkId,
currencySymbol = rawNetworkId.take(3).uppercase(),
derivationPath = derivationPath,
@ -866,10 +865,9 @@ class DefaultPromoDeeplinkHandlerTest {
}
private fun buildUnreachableNetworkStatus(rawNetworkId: String): NetworkStatus {
val networkId = Network.ID(Network.RawID(rawNetworkId), Network.DerivationPath.None)
val networkId = Network.ID(value = rawNetworkId, derivationPath = Network.DerivationPath.None)
val network = Network(
id = networkId,
backendId = rawNetworkId,
name = rawNetworkId,
currencySymbol = rawNetworkId.take(3).uppercase(),
derivationPath = Network.DerivationPath.None,
@ -890,10 +888,9 @@ class DefaultPromoDeeplinkHandlerTest {
rawNetworkId: String,
derivationPath: Network.DerivationPath = Network.DerivationPath.None,
): CryptoCurrency.Coin {
val networkId = Network.ID(Network.RawID(rawNetworkId), derivationPath)
val networkId = Network.ID(value = rawNetworkId, derivationPath = derivationPath)
val network = Network(
id = networkId,
backendId = rawNetworkId,
name = rawNetworkId,
currencySymbol = rawNetworkId.take(3).uppercase(),
derivationPath = derivationPath,

View file

@ -285,8 +285,7 @@ internal class QrContentClassifierTest {
private fun buildNetwork(rawNetworkId: String): Network {
return Network(
id = Network.ID(Network.RawID(rawNetworkId), Network.DerivationPath.None),
backendId = rawNetworkId,
id = Network.ID(value = rawNetworkId, derivationPath = Network.DerivationPath.None),
name = rawNetworkId,
currencySymbol = rawNetworkId.take(3).uppercase(),
derivationPath = Network.DerivationPath.None,