Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-19 11:44:37 +03:00
commit 1146554992
1461 changed files with 62266 additions and 17096 deletions

View file

@ -7,7 +7,5 @@ package com.tangem.features.wallet.featuretoggles
*/
interface WalletFeatureToggles {
val isWalletReorderFeatureEnabled: Boolean
val isMainScreenQrScanningEnabled: Boolean
val isAddAndManageTokensEnabled: Boolean
}

View file

@ -41,7 +41,6 @@ dependencies {
implementation(deps.googlePlay.review)
implementation(deps.jodatime)
implementation(deps.kotlin.immutable.collections)
implementation(deps.reKotlin)
implementation(tangemDeps.hot.core)
implementation(tangemDeps.card.core)
implementation(tangemDeps.blockchain)
@ -125,9 +124,11 @@ dependencies {
implementation(projects.domain.yieldSupply.models)
implementation(projects.domain.appTheme)
implementation(projects.domain.appTheme.models)
implementation(projects.domain.tokensync)
implementation(projects.domain.assetsdiscovery)
/** Feature Apis */
implementation(projects.features.commonFeatures.api)
implementation(projects.features.account.api)
implementation(projects.features.details.api)
implementation(projects.features.hotWallet.api)
implementation(projects.features.manageTokens.api)
@ -146,7 +147,6 @@ dependencies {
implementation(projects.features.kyc.api)
implementation(projects.features.tokenRecieve.api)
implementation(projects.features.yieldSupply.api)
implementation(projects.features.tangempay.details.api)
implementation(projects.features.feed.api)
implementation(projects.features.promoBanners.api)
implementation(projects.features.tangempay.main.api)

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.commonfeatures.api.portfolioselector.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,14 @@
package com.tangem.feature.wallet.child.managetokens.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
internal sealed class PortfolioAnalyticsEvent(
event: String,
) : AnalyticsEvent(category = "Portfolio", event = event) {
class ButtonAddManage : PortfolioAnalyticsEvent(event = "Button - Add Manage")
class ButtonAddTokens : PortfolioAnalyticsEvent(event = "Button - Add tokens")
class ButtonOrganizeTokens : PortfolioAnalyticsEvent(event = "Button - Organize Tokens")
}

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,88 @@
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.analytics.api.AnalyticsEventHandler
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.feature.wallet.child.managetokens.analytics.PortfolioAnalyticsEvent
import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioFetcher
import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioSelectorComponent
import com.tangem.features.commonfeatures.api.portfolioselector.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,
private val analyticsEventHandler: AnalyticsEventHandler,
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() {
analyticsEventHandler.send(PortfolioAnalyticsEvent.ButtonAddTokens())
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() {
analyticsEventHandler.send(PortfolioAnalyticsEvent.ButtonOrganizeTokens())
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

@ -1,6 +1,6 @@
package com.tangem.feature.wallet.child.organizetokens.model.converter.items
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants

View file

@ -3,7 +3,7 @@ package com.tangem.feature.wallet.child.organizetokens.model.converter.items
import androidx.compose.ui.text.SpanStyle
import com.tangem.common.getTotalCryptoAmount
import com.tangem.common.getTotalFiatAmount
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.row.internal.TangemRowTailUM
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM

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.commonfeatures.api.portfolioselector.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),
@ -238,10 +260,11 @@ internal class WalletComponent @AssistedInject constructor(
WalletScreen2(
state = uiState,
tangemPayComponent = tangemPayMainBlockComponent,
bottomSheetContent = {
bottomSheetContent = { onExpandSheet ->
BottomSheetContent(
bottomSheetState = bottomSheetState,
onHeaderSizeChange = { headerSize = it },
onExpandSheet = onExpandSheet,
modifier = modifier,
)
},
@ -253,10 +276,11 @@ internal class WalletComponent @AssistedInject constructor(
state = uiState,
promoBannersBlockComponent = promoBannersBlockComponent,
tangemPayComponent = tangemPayMainBlockComponent,
bottomSheetContent = {
bottomSheetContent = { onExpandSheet ->
BottomSheetContent(
bottomSheetState = bottomSheetState,
onHeaderSizeChange = { headerSize = it },
onExpandSheet = onExpandSheet,
modifier = modifier,
)
},
@ -283,11 +307,13 @@ internal class WalletComponent @AssistedInject constructor(
private fun BottomSheetContent(
bottomSheetState: State<BottomSheetState>,
onHeaderSizeChange: (Dp) -> Unit,
onExpandSheet: () -> Unit,
modifier: Modifier = Modifier,
) {
feedEntryComponent.BottomSheetContent(
bottomSheetState = bottomSheetState,
onHeaderSizeChange = onHeaderSizeChange,
onExpandSheet = onExpandSheet,
modifier = modifier,
)
}

View file

@ -14,12 +14,13 @@ import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.datasource.local.appsflyer.AppsFlyerStore
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.apptheme.GetAppThemeModeUseCase
import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.assetsdiscovery.usecase.StartAssetsDiscoveryUseCase
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.wallet.*
@ -27,7 +28,6 @@ import com.tangem.domain.notifications.GetIsHuaweiDeviceWithoutGoogleServicesUse
import com.tangem.domain.notifications.repository.NotificationsRepository
import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher
import com.tangem.domain.pay.repository.OnboardingRepository
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
import com.tangem.domain.qrscanning.models.ClassifiedQrContent
import com.tangem.domain.qrscanning.models.QrResultSource
import com.tangem.domain.qrscanning.models.QrSendTarget
@ -38,8 +38,6 @@ 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.features.hotwallet.HotWalletFeatureToggles
import com.tangem.domain.wallets.usecase.*
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyUpdateUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
@ -62,16 +60,15 @@ import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSend
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.KycRejectedCallbacks
import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider
import com.tangem.features.biometry.AskBiometryComponent
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks
import com.tangem.features.tangempay.TangemPayFeatureToggles
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionListener
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.*
import com.tangem.utils.logging.TangemLogger
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.*
import com.tangem.utils.logging.TangemLogger
import javax.inject.Inject
private const val TANGEM_PAY_UPDATE_INTERVAL = 60_000L
@ -109,7 +106,6 @@ internal class WalletModel @Inject constructor(
private val userWalletsListRepository: UserWalletsListRepository,
private val yieldSupplyApyUpdateUseCase: YieldSupplyApyUpdateUseCase,
private val tangemPayOnboardingRepository: OnboardingRepository,
private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase,
private val getAppThemeModeUseCase: GetAppThemeModeUseCase,
private val trackingContextProxy: TrackingContextProxy,
private val singleAccountListSupplier: SingleAccountListSupplier,
@ -118,15 +114,13 @@ internal class WalletModel @Inject constructor(
private val appsFlyerStore: AppsFlyerStore,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getWalletIconUseCase: GetWalletIconUseCase,
private val walletFeatureToggles: WalletFeatureToggles,
private val listenToQrScanningUseCase: ListenToQrScanningUseCase,
private val wcPairService: WcPairService,
private val resolveQrSendTargetsUseCase: ResolveQrSendTargetsUseCase,
private val paymentAccountStatusFetcher: PaymentAccountStatusFetcher,
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 +153,7 @@ internal class WalletModel @Inject constructor(
subscribeTangemPayOnWalletState()
subscribeToMainScreenQrScanning()
enableNotificationsIfNeeded()
applyPendingTokenSyncs()
applyPendingAssetsDiscovery()
clickIntents.initialize(innerWalletRouter, modelScope)
@ -438,17 +432,14 @@ internal class WalletModel @Inject constructor(
if (isShouldLaunchPeriodicUpdate) {
updateTangemPayJobHolder.cancel()
modelScope.launch {
tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId)
paymentAccountStatusFetcher.invoke(PaymentAccountStatusFetcher.Params(userWalletId))
while (isActive) {
delay(TANGEM_PAY_UPDATE_INTERVAL)
tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId)
paymentAccountStatusFetcher.invoke(PaymentAccountStatusFetcher.Params(userWalletId))
}
}.saveIn(updateTangemPayJobHolder)
} else {
// Don't refresh customer info periodically if the card was already issued, only update on swipe to refresh
tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId)
paymentAccountStatusFetcher.invoke(PaymentAccountStatusFetcher.Params(userWalletId))
}
}.launchIn(modelScope)
@ -556,9 +547,7 @@ internal class WalletModel @Inject constructor(
wallets = action.wallets,
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
isMainScreenQrScanningEnabled = walletFeatureToggles.isMainScreenQrScanningEnabled,
getWalletIconUseCase = getWalletIconUseCase,
isTangemPayRefactorEnabled = tangemPayFeatureToggles.isTangemPayAccountsRefactorEnabled,
),
)
@ -605,7 +594,6 @@ internal class WalletModel @Inject constructor(
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
getWalletIconUseCase = getWalletIconUseCase,
isTangemPayRefactorEnabled = tangemPayFeatureToggles.isTangemPayAccountsRefactorEnabled,
),
)
}
@ -627,7 +615,6 @@ internal class WalletModel @Inject constructor(
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
getWalletIconUseCase = getWalletIconUseCase,
isTangemPayRefactorEnabled = tangemPayFeatureToggles.isTangemPayAccountsRefactorEnabled,
),
)
}
@ -642,7 +629,6 @@ internal class WalletModel @Inject constructor(
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
getWalletIconUseCase = getWalletIconUseCase,
isTangemPayRefactorEnabled = tangemPayFeatureToggles.isTangemPayAccountsRefactorEnabled,
),
)
@ -704,7 +690,6 @@ internal class WalletModel @Inject constructor(
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
getWalletIconUseCase = getWalletIconUseCase,
isTangemPayRefactorEnabled = tangemPayFeatureToggles.isTangemPayAccountsRefactorEnabled,
),
)
@ -844,9 +829,9 @@ internal class WalletModel @Inject constructor(
}
}
private fun applyPendingTokenSyncs() {
if (hotWalletFeatureToggles.isTokenSyncEnabled) {
startTokenSyncUseCase.applyPendingSyncs()
private fun applyPendingAssetsDiscovery() {
if (hotWalletFeatureToggles.isAssetsDiscoveryEnabled) {
startAssetsDiscoveryUseCase.applyPendingAssetsDiscovery()
}
}

View file

@ -22,11 +22,10 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.TangemPayDetailsConfig
import com.tangem.domain.pay.TangemPayEligibilityManager
import com.tangem.domain.pay.model.TangemPayEntryPoint
import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher
import com.tangem.domain.pay.model.TangemPayEntryPoint
import com.tangem.domain.pay.repository.OnboardingRepository
import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDialogConfig
@ -72,7 +71,6 @@ internal class TangemPayClickIntentsImplementor @Inject constructor(
private val produceInitialDataTangemPay: ProduceTangemPayInitialDataUseCase,
private val getWalletMetainfoUseCase: GetWalletMetaInfoUseCase,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase,
private val tangemPayOnboardingRepository: OnboardingRepository,
private val tangemPayEligibilityManager: TangemPayEligibilityManager,
private val uiMessageSender: UiMessageSender,
@ -85,7 +83,6 @@ internal class TangemPayClickIntentsImplementor @Inject constructor(
if (!onboardingRepository.isTangemPayInitialDataProduced(userWalletId)) {
return
}
tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId)
paymentAccountStatusFetcher.invoke(PaymentAccountStatusFetcher.Params(userWalletId))
}
@ -100,7 +97,6 @@ internal class TangemPayClickIntentsImplementor @Inject constructor(
modelScope.launch {
produceInitialDataTangemPay.invoke(userWallet.walletId)
.onRight {
tangemPayMainScreenCustomerInfoUseCase.fetch(userWallet.walletId)
paymentAccountStatusFetcher.invoke(PaymentAccountStatusFetcher.Params(userWallet.walletId))
}
.onLeft {
@ -277,7 +273,6 @@ internal class TangemPayClickIntentsImplementor @Inject constructor(
modelScope.launch {
tangemPayOnboardingRepository.disableTangemPay(userWalletId)
.onRight {
tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId)
paymentAccountStatusFetcher.invoke(PaymentAccountStatusFetcher.Params(userWalletId))
}
.onLeft { uiMessageSender.send(ToastMessage(resourceReference(R.string.common_something_went_wrong))) }

View file

@ -23,6 +23,7 @@ import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplySetShouldShowMainPromoUseCase
import com.tangem.feature.wallet.child.managetokens.analytics.PortfolioAnalyticsEvent
import com.tangem.feature.wallet.presentation.account.AccountDependencies
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory
@ -36,6 +37,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 +114,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 +122,13 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
}
override fun onOrganizeTokensClick() {
router.openOrganizeTokensScreen(userWalletId = stateHolder.getSelectedWalletId())
val userWalletId = stateHolder.getSelectedWalletId()
if (walletFeatureToggles.isAddAndManageTokensEnabled) {
analyticsEventHandler.send(PortfolioAnalyticsEvent.ButtonAddManage())
router.openAddAndManageBottomSheet(userWalletId = userWalletId)
} else {
router.openOrganizeTokensScreen(userWalletId = userWalletId)
}
}
override fun onDismissMarketsTooltip() {

View file

@ -17,13 +17,13 @@ import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.haptic.VibratorHapticManager
import com.tangem.core.ui.message.DialogMessage
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.account.status.usecase.IsCryptoCurrencyCouldHideUseCase
import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.extenstions.unwrap
@ -44,7 +44,6 @@ import com.tangem.domain.onramp.model.OnrampSource
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.promo.models.StoryContentIds
import com.tangem.domain.staking.model.StakingOption
import com.tangem.domain.account.status.usecase.IsCryptoCurrencyCouldHideUseCase
import com.tangem.domain.tokens.NeedShowYieldSupplyDepositedWarningUseCase
import com.tangem.domain.tokens.SaveViewedTokenReceiveWarningUseCase
import com.tangem.domain.tokens.SaveViewedYieldSupplyWarningUseCase
@ -455,13 +454,10 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
override fun onMultiWalletSwapClick(userWalletId: UserWalletId) {
val selectedWallet = stateHolder.getSelectedWallet() as? WalletState.MultiCurrency.Content ?: return
when (val tokenListState = selectedWallet.tokensListState) {
is WalletTokensListState.ContentState.Content -> checkSwapCryptoAvailability(
tokenCount = tokenListState.items.count { it is TokensListItemUM.Token },
)
is WalletTokensListState.ContentState.PortfolioContent -> checkSwapCryptoAvailability(
tokenCount = tokenListState.items.sumOf { it.tokens.count { it is TokensListItemUM.Token } },
)
when (selectedWallet.tokensListState) {
is WalletTokensListState.ContentState.Content,
is WalletTokensListState.ContentState.PortfolioContent,
-> Unit
WalletTokensListState.ContentState.Loading,
WalletTokensListState.ContentState.Locked,
WalletTokensListState.Empty,
@ -470,7 +466,10 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
modelScope.launch {
val swapRoute = getSwapRoute(
AppRoute.SwapCrypto(userWalletId = userWalletId),
AppRoute.Swap(
userWalletId = userWalletId,
screenSource = AnalyticsParam.ScreensSources.Main.value,
),
)
onMultiWalletActionClick(
statusFlow = rampStateManager.getExpressInitializationStatus(userWalletId),
@ -660,7 +659,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
private fun navigateToSwap(cryptoCurrencyStatus: CryptoCurrencyStatus, userWalletId: UserWalletId) {
appRouter.push(
AppRoute.Swap(
currencyFrom = cryptoCurrencyStatus.currency,
cryptoCurrency = cryptoCurrencyStatus.currency,
userWalletId = userWalletId,
screenSource = AnalyticsParam.ScreensSources.LongTap.value,
),
@ -675,11 +674,4 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
}
}
}
private fun checkSwapCryptoAvailability(tokenCount: Int) {
if (tokenCount < 2) {
analyticsEventHandler.send(event = MainScreenAnalyticsEvent.ButtonSwap(AnalyticsParam.Status.Error))
uiMessageSender.send(WalletAlertUM.insufficientTokensCountForSwapping())
}
}
}

View file

@ -1,7 +1,6 @@
package com.tangem.feature.wallet.child.wallet.model.intents
import arrow.core.getOrElse
import com.tangem.utils.logging.TangemLogger
import com.tangem.common.routing.AppRoute.*
import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.notifications.NotificationId
@ -9,10 +8,12 @@ import com.tangem.common.ui.userwallet.handle
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic.ButtonSupport
import com.tangem.core.analytics.models.event.AssetsDiscoveryAnalyticsEvent
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.navigation.review.ReviewManager
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.domain.assetsdiscovery.usecase.AcknowledgeAssetsDiscoveryCompletionUseCase
import com.tangem.domain.card.SetCardWasScannedUseCase
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
@ -40,16 +41,15 @@ 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.wallets.usecase.*
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.Basic
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
import com.tangem.features.pushnotifications.api.analytics.PushNotificationAnalyticEvents
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.logging.TangemLogger
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
@ -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() {
@ -173,7 +173,6 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
}
override fun onGenerateMissedAddressesClick(missedAddressCurrencies: List<CryptoCurrency>) {
analyticsEventHandler.send(Basic.CardWasScanned(AnalyticsParam.ScreensSources.Main))
analyticsEventHandler.send(MainScreen.NoticeScanYourCardTapped())
modelScope.launch {
@ -198,7 +197,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
userWalletsListRepository.unlockAllWallets()
.onLeft {
val selectedUserWalletId = stateHolder.getSelectedWalletId()
nonBiometricUnlockWalletUseCase(selectedUserWalletId)
nonBiometricUnlockWalletUseCase(selectedUserWalletId, AnalyticsParam.ScreensSources.Main)
.onLeft { error ->
error.handle(
onAlreadyUnlocked = {},
@ -508,12 +507,14 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
}
}
override fun onDismissTokenSyncNotification(userWalletId: UserWalletId) {
acknowledgeTokenSyncCompletionUseCase(userWalletId)
override fun onDismissAssetsDiscoveryNotification(userWalletId: UserWalletId) {
analyticsEventHandler.send(AssetsDiscoveryAnalyticsEvent.ButtonCloseBanner())
acknowledgeAssetsDiscoveryCompletionUseCase(userWalletId)
}
override fun onTokenSyncManageClick(userWalletId: UserWalletId) {
acknowledgeTokenSyncCompletionUseCase(userWalletId)
override fun onAssetsDiscoveryManageClick(userWalletId: UserWalletId) {
analyticsEventHandler.send(AssetsDiscoveryAnalyticsEvent.ButtonManageTokens())
acknowledgeAssetsDiscoveryCompletionUseCase(userWalletId)
router.openManageTokensScreen(
AccountId.forMainCryptoPortfolio(userWalletId),
)

View file

@ -9,9 +9,6 @@ internal class DefaultWalletFeatureToggles @Inject constructor(
private val featureToggles: FeatureTogglesManager,
) : WalletFeatureToggles {
override val isWalletReorderFeatureEnabled: Boolean
get() = featureToggles.isFeatureEnabled(FeatureToggles.WALLET_REORDER_FEATURE_ENABLED)
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

@ -3,14 +3,14 @@ package com.tangem.feature.wallet.presentation.account
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.account.status.supplier.SingleAccountStatusSupplier
import com.tangem.domain.account.status.utils.ExpandedAccountsHolder
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
import com.tangem.domain.account.status.utils.MainExpandedAccountsHolder
import javax.inject.Inject
@ModelScoped
internal class AccountDependencies @Inject constructor(
val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
val expandedAccountsHolder: ExpandedAccountsHolder,
val expandedAccountsHolder: MainExpandedAccountsHolder,
val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
val singleAccountStatusSupplier: SingleAccountStatusSupplier,
)

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 = {},
),
@ -211,15 +217,8 @@ internal object WalletScreenPreviewDataLegacy {
isFlickering = false,
onItemClick = { },
),
tangemPayState = TangemPayState.Card(
lastFourDigits = stringReference("*1234"),
balanceText = stringReference("$10"),
balanceSymbol = stringReference("USDC"),
onClick = {},
),
type = WalletType.Cold,
tangemPayMainUM = TangemPayMainUM.Empty,
isTangemPayRefactorEnabled = false,
)
}

View file

@ -8,6 +8,7 @@ import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.styledStringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAdditionalInfo
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBalanceUM
internal object WalletBalancePreview {
@ -38,6 +39,36 @@ internal object WalletBalancePreview {
isZeroBalance = false,
)
val syncProgress = WalletBalanceUM.Content(
id = UserWalletId("0"),
name = "My Wallet",
balanceInAppBar = combinedReference(
stringReference("1,234"),
styledStringReference(
".56",
{ SpanStyle(color = TangemTheme.colors2.text.neutral.secondary) },
),
stringReference(" $"),
),
balance = combinedReference(
stringReference("1,234"),
styledStringReference(
".56",
{
TangemTheme.typography2.headingRegular28.toSpanStyle()
},
),
stringReference(" $"),
),
deviceIcon = DeviceIconUM.Stub(cardsCount = 3),
isBalanceFlickering = false,
isZeroBalance = false,
additionalInfo = WalletAdditionalInfo(
hideable = false,
content = WalletAdditionalInfo.Content.SyncProgress(37),
),
)
val hiddenBalanceContent = content.copy(balance = content.balance.orMaskWithStars(true))
val loading: WalletBalanceUM.Loading = WalletBalanceUM.Loading(

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

@ -2,9 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.analytics.utils
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.models.kyc.KycStatus
import com.tangem.domain.pay.model.MainScreenCustomerInfo
import com.tangem.domain.pay.model.OrderStatus
import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider
import javax.inject.Inject
@ -15,26 +13,24 @@ internal class WalletTangemPayAnalyticsEventSender @Inject constructor(
private val screenLifecycleProvider: ScreenLifecycleProvider,
) {
private val sentEvents = mutableSetOf<TangemPayAnalyticsEvents>()
private val sentEvents = mutableSetOf<String>()
fun send(customerInfo: MainScreenCustomerInfo) {
fun send(statusValue: PaymentAccountStatusValue) {
if (screenLifecycleProvider.isBackgroundState.value) return
val cardInfo = customerInfo.info.cardInfo
val productInstance = customerInfo.info.productInstance
// TODO: TangemPay refactor analytics
// when statement copied from TangemPayUpdateInfoStateTransformer. Be careful when editing
val event = when {
// ignore cancelled state on analytics
customerInfo.orderStatus == OrderStatus.CANCELED -> return
// ignore kyc not approved state on analytics
customerInfo.info.kycStatus != KycStatus.APPROVED -> return
cardInfo != null && productInstance != null -> return
else -> TangemPayAnalyticsEvents.IssuingBannerDisplayed()
val event = when (statusValue) {
is PaymentAccountStatusValue.IssuingCard -> TangemPayAnalyticsEvents.IssuingBannerDisplayed()
PaymentAccountStatusValue.Empty,
is PaymentAccountStatusValue.Error,
is PaymentAccountStatusValue.Loaded,
PaymentAccountStatusValue.Loading,
PaymentAccountStatusValue.NotCreated,
is PaymentAccountStatusValue.UnderReview,
is PaymentAccountStatusValue.Deactivated,
-> return
}
if (sentEvents.add(event)) {
if (sentEvents.add(event.id)) {
analyticsEventHandler.send(event)
}
}

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.domain.wallets.usecase.IsNeedToBackupUseCase
import com.tangem.feature.wallet.child.wallet.model.WalletActivationBannerType
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
@ -61,7 +61,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,
) {
@ -71,11 +71,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,
@ -92,7 +93,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
.distinctUntilChanged(),
getUpgradeBannerClosureTimestampUseCase(userWallet.walletId)
.distinctUntilChanged(),
tokenSyncProgressFlow,
assetsDiscoveryProgressFlow,
) { array -> array }
.map { array ->
val accountStatusList = array[0] as AccountStatusList
@ -104,7 +105,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
@ -149,9 +150,9 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
clickIntents = clickIntents,
)
addTokenSyncCompletedNotification(
addAssetsDiscoveryCompletedNotification(
userWallet = userWallet,
tokenSyncProgress = tokenSyncProgress,
assetsDiscoveryProgress = assetsDiscoveryProgress,
clickIntents = clickIntents,
)
@ -208,7 +209,6 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
is PaymentAccountStatusValue.IssuingCard,
is PaymentAccountStatusValue.Loaded,
is PaymentAccountStatusValue.Loading,
is PaymentAccountStatusValue.Locked,
is PaymentAccountStatusValue.UnderReview,
is PaymentAccountStatusValue.Empty,
is PaymentAccountStatusValue.Deactivated,
@ -402,17 +402,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

@ -237,7 +237,6 @@ internal class GetWalletNotificationsFactory @Inject constructor(
is PaymentAccountStatusValue.IssuingCard,
is PaymentAccountStatusValue.Loaded,
is PaymentAccountStatusValue.Loading,
is PaymentAccountStatusValue.Locked,
is PaymentAccountStatusValue.UnderReview,
is PaymentAccountStatusValue.Empty,
is PaymentAccountStatusValue.Deactivated,

View file

@ -354,7 +354,7 @@ internal enum class Wallet2CobrandImage(
WinterSakura(
cards2ResId = R.drawable.ill_winter_sakura_card2_120_106,
cards3ResId = R.drawable.ill_winter_sakura_card3_120_106,
batchIds = setOf("AF990053", "AF990054", "AF990055"),
batchIds = setOf("AF990053", "AF990054", "AF990055", "AF990074", "AF990075", "AF990076"),
),
LockedMoney(

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

@ -2,16 +2,15 @@ package com.tangem.feature.wallet.presentation.wallet.domain
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
import com.tangem.features.tangempay.TangemPayFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveInAndJoin
import com.tangem.utils.logging.TangemLogger
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import com.tangem.utils.logging.TangemLogger
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
import javax.inject.Singleton
@ -28,7 +27,6 @@ import javax.inject.Singleton
internal class WalletContentFetcher @Inject constructor(
private val walletBalanceFetcher: WalletBalanceFetcher,
private val dispatchers: CoroutineDispatcherProvider,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
) {
private val fetchingJobMap = ConcurrentHashMap<UserWalletId, JobHolder>()
@ -66,12 +64,8 @@ internal class WalletContentFetcher @Inject constructor(
TangemLogger.d("Start fetching for $userWalletId")
val maybeResult = launch {
walletBalanceFetcher(
params = WalletBalanceFetcher.Params(
userWalletId = userWalletId,
isPaymentAccountRefactorEnabled = tangemPayFeatureToggles.isTangemPayAccountsRefactorEnabled,
),
).onLeft { TangemLogger.e("Error", it) }
walletBalanceFetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId))
.onLeft { TangemLogger.e("Error", it) }
}
.saveInAndJoin(jobHolder)

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,49 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.extensions.TextReference
@Immutable
internal sealed class TangemPayState {
object Empty : TangemPayState()
data object Loading : TangemPayState()
data class OnboardingBanner(
val onClick: () -> Unit,
val closeOnClick: () -> Unit,
) : TangemPayState()
data class Progress(
val title: TextReference,
val description: TextReference,
val buttonText: TextReference,
@DrawableRes val iconRes: Int,
val onButtonClick: () -> Unit,
val showProgress: Boolean = false,
) : TangemPayState()
data class FailedIssue(
val title: TextReference,
val description: TextReference,
@DrawableRes val iconRes: Int,
val onButtonClick: () -> Unit,
) : TangemPayState()
data class Card(
val lastFourDigits: TextReference,
val balanceText: TextReference,
val balanceSymbol: TextReference,
val onClick: () -> Unit,
) : TangemPayState()
data class RefreshNeeded(
val notification: WalletNotification,
) : TangemPayState()
data class TemporaryUnavailable(val notification: WalletNotification) : TangemPayState()
data object ExposedDevice : TangemPayState()
}

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

@ -28,6 +28,9 @@ internal sealed interface WalletBalanceUM {
/** Wallet Icon */
val deviceIcon: DeviceIconUM
/** Wallet additional info (e.g. card count, sync progress) */
val additionalInfo: WalletAdditionalInfo?
/**
* Wallet card content state
*
@ -39,6 +42,7 @@ internal sealed interface WalletBalanceUM {
override val id: UserWalletId,
override val name: String,
override val deviceIcon: DeviceIconUM,
override val additionalInfo: WalletAdditionalInfo? = null,
val balance: TextReference,
val balanceInAppBar: TextReference,
val isBalanceFlickering: Boolean,
@ -55,6 +59,7 @@ internal sealed interface WalletBalanceUM {
override val id: UserWalletId,
override val name: String,
override val deviceIcon: DeviceIconUM,
override val additionalInfo: WalletAdditionalInfo? = null,
) : WalletBalanceUM
/**
@ -67,6 +72,7 @@ internal sealed interface WalletBalanceUM {
override val id: UserWalletId,
override val name: String,
override val deviceIcon: DeviceIconUM,
override val additionalInfo: WalletAdditionalInfo? = null,
) : WalletBalanceUM
/**
@ -79,14 +85,18 @@ internal sealed interface WalletBalanceUM {
override val id: UserWalletId,
override val name: String,
override val deviceIcon: DeviceIconUM,
override val additionalInfo: WalletAdditionalInfo? = null,
) : WalletBalanceUM
fun copySealed(name: String): WalletBalanceUM {
fun copySealed(
name: String = this.name,
additionalInfo: WalletAdditionalInfo? = this.additionalInfo,
): WalletBalanceUM {
return when (this) {
is Content -> copy(name = name)
is Error -> copy(name = name)
is Loading -> copy(name = name)
is Empty -> copy(name = name)
is Content -> copy(name = name, additionalInfo = additionalInfo)
is Error -> copy(name = name, additionalInfo = additionalInfo)
is Loading -> copy(name = name, additionalInfo = additionalInfo)
is Empty -> copy(name = name, additionalInfo = additionalInfo)
}
}
}

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

@ -24,10 +24,8 @@ internal sealed interface WalletState : WalletStateHolder {
abstract val tokensListState: WalletTokensListState
abstract val nftState: WalletNFTItemUM
abstract val type: WalletType
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,
@ -38,10 +36,8 @@ internal sealed interface WalletState : WalletStateHolder {
override val tokensListState: WalletTokensListState,
override val nftState: WalletNFTItemUM,
override val type: WalletType,
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(
@ -60,10 +56,8 @@ internal sealed interface WalletState : WalletStateHolder {
override val tokensListState = WalletTokensListState.ContentState.Locked
override val nftState: WalletNFTItemUM = WalletNFTItemUM.Hidden
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

@ -13,7 +13,6 @@ internal class AddWalletTransformer(
private val clickIntents: WalletClickIntents,
private val walletImageResolver: WalletImageResolver,
private val getWalletIconUseCase: GetWalletIconUseCase,
private val isTangemPayRefactorEnabled: Boolean,
) : WalletScreenStateTransformer {
private val walletLoadingStateFactory by lazy {
@ -21,7 +20,6 @@ internal class AddWalletTransformer(
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
getWalletIconUseCase = getWalletIconUseCase,
isTangemPayRefactorEnabled = isTangemPayRefactorEnabled,
)
}

View file

@ -27,8 +27,6 @@ internal class InitializeWalletsTransformer(
private val clickIntents: WalletClickIntents,
private val walletImageResolver: WalletImageResolver,
private val getWalletIconUseCase: GetWalletIconUseCase,
private val isMainScreenQrScanningEnabled: Boolean = false,
private val isTangemPayRefactorEnabled: Boolean,
) : WalletScreenStateTransformer {
private val walletLoadingStateFactory by lazy {
@ -36,7 +34,6 @@ internal class InitializeWalletsTransformer(
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
getWalletIconUseCase = getWalletIconUseCase,
isTangemPayRefactorEnabled = isTangemPayRefactorEnabled,
)
}
@ -65,15 +62,11 @@ internal class InitializeWalletsTransformer(
private fun createTopBarConfig(): WalletTopBarConfig {
return WalletTopBarConfig(
endActions = listOfNotNull(
if (isMainScreenQrScanningEnabled) {
TangemTopBarActionUM(
iconRes = CoreUiR.drawable.ic_qrcode_scaner_24,
onClick = clickIntents::onScanQrClick,
)
} else {
null
},
endActions = listOf(
TangemTopBarActionUM(
iconRes = CoreUiR.drawable.ic_qrcode_scaner_24,
onClick = clickIntents::onScanQrClick,
),
TangemTopBarActionUM(
iconRes = CoreUiR.drawable.ic_more_default_24,
onClick = clickIntents::onDetailsClick,

View file

@ -24,7 +24,6 @@ internal class ReinitializeNewWalletTransformer(
private val clickIntents: WalletClickIntents,
private val walletImageResolver: WalletImageResolver,
private val getWalletIconUseCase: GetWalletIconUseCase,
private val isTangemPayRefactorEnabled: Boolean,
) : WalletScreenStateTransformer {
private val walletLoadingStateFactory by lazy {
@ -32,7 +31,6 @@ internal class ReinitializeNewWalletTransformer(
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
getWalletIconUseCase = getWalletIconUseCase,
isTangemPayRefactorEnabled = isTangemPayRefactorEnabled,
)
}

View file

@ -20,7 +20,6 @@ internal class ReinitializeWalletTransformer(
private val clickIntents: WalletClickIntents,
private val walletImageResolver: WalletImageResolver,
private val getWalletIconUseCase: GetWalletIconUseCase,
private val isTangemPayRefactorEnabled: Boolean,
) : WalletStateTransformer(userWalletId = userWallet.walletId) {
private val walletLoadingStateFactory by lazy {
@ -28,7 +27,6 @@ internal class ReinitializeWalletTransformer(
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
getWalletIconUseCase = getWalletIconUseCase,
isTangemPayRefactorEnabled = isTangemPayRefactorEnabled,
)
}

View file

@ -2,27 +2,35 @@ 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
}
}
override fun transform(walletUM: WalletUM): WalletUM = walletUM
override fun transform(walletUM: WalletUM): WalletUM {
val additionalInfo = WalletAdditionalInfoFactory.resolve(wallet = userWallet, syncProgress = progress)
return when (walletUM) {
is WalletUM.Content -> walletUM.copy(
walletsBalanceUM = walletUM.walletsBalanceUM.copySealed(additionalInfo = additionalInfo),
)
is WalletUM.Locked -> walletUM
}
}
private fun updateCardState(cardState: WalletCardState): WalletCardState {
val additionalInfo = WalletAdditionalInfoFactory.resolve(wallet = userWallet, syncProgress = progress)

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(),
@ -98,6 +98,7 @@ internal class SetTokenListErrorTransformer(
id = id,
name = name,
deviceIcon = deviceIcon,
additionalInfo = additionalInfo,
balanceInAppBar = BigDecimal.ZERO.formatStyled {
fiat(
fiatCurrencyCode = appCurrency.code,

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

@ -1,22 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
internal class TangemPayExposedDeviceTransformer(
userWalletId: UserWalletId,
) : WalletStateTransformer(userWalletId) {
override fun transform(prevState: WalletState): WalletState {
return if (prevState is WalletState.MultiCurrency.Content) {
prevState.copy(tangemPayState = TangemPayState.ExposedDevice)
} else {
prevState
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
}

View file

@ -1,23 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
internal class TangemPayHiddenStateTransformer(
userWalletId: UserWalletId,
) : WalletStateTransformer(userWalletId) {
override fun transform(prevState: WalletState): WalletState {
return if (prevState is WalletState.MultiCurrency.Content) {
prevState.copy(tangemPayState = TangemPayState.Empty)
} else {
prevState
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
}

View file

@ -1,7 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import com.tangem.features.tangempay.entity.TangemPayMainUM
@ -12,7 +11,7 @@ internal class TangemPayHideOnboardingStateTransformer(
override fun transform(prevState: WalletState): WalletState {
return if (prevState is WalletState.MultiCurrency.Content) {
prevState.copy(tangemPayState = TangemPayState.Empty, tangemPayMainUM = TangemPayMainUM.Empty)
prevState.copy(tangemPayMainUM = TangemPayMainUM.Empty)
} else {
prevState
}

View file

@ -1,20 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
internal class TangemPayLoadingStateTransformer(userWalletId: UserWalletId) : WalletStateTransformer(userWalletId) {
override fun transform(prevState: WalletState): WalletState {
return if (prevState is WalletState.MultiCurrency.Content) {
prevState.copy(tangemPayState = TangemPayState.Loading)
} else {
prevState
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
}

View file

@ -1,30 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
internal class TangemPayOnboardingBannerStateTransformer(
userWalletId: UserWalletId,
private val onClick: (UserWalletId) -> Unit,
private val closeOnClick: (UserWalletId) -> Unit,
) : WalletStateTransformer(userWalletId) {
override fun transform(prevState: WalletState): WalletState {
return if (prevState is WalletState.MultiCurrency.Content) {
prevState.copy(
tangemPayState = TangemPayState.OnboardingBanner(
onClick = { onClick(userWalletId) },
closeOnClick = { closeOnClick(userWalletId) },
),
)
} else {
prevState
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
}

View file

@ -1,39 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification.Warning.TangemPayRefreshNeeded
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
internal class TangemPayRefreshNeededStateTransformer(
userWalletId: UserWalletId,
private val userWallet: UserWallet,
private val onRefreshClick: () -> Unit,
) : WalletStateTransformer(userWalletId = userWalletId) {
override fun transform(prevState: WalletState): WalletState {
val tangemPayState = TangemPayState.RefreshNeeded(
notification = TangemPayRefreshNeeded(
buttonText = when (userWallet) {
is UserWallet.Cold -> resourceReference(id = R.string.home_button_scan)
is UserWallet.Hot -> resourceReference(id = R.string.tangempay_sync_needed_restore_access)
},
onRefreshClick = onRefreshClick,
shouldShowProgress = false,
),
)
return if (prevState is WalletState.MultiCurrency.Content) {
prevState.copy(tangemPayState = tangemPayState)
} else {
prevState
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
}

View file

@ -1,7 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
@ -14,9 +13,6 @@ internal class TangemPayRefreshShowProgressTransformer(
override fun transform(prevState: WalletState): WalletState {
val multiContentState = prevState as? WalletState.MultiCurrency.Content ?: return prevState
val refreshNeededState = multiContentState.tangemPayState as? TangemPayState.RefreshNeeded ?: return prevState
val refreshNotification =
refreshNeededState.notification as? WalletNotification.Warning.TangemPayRefreshNeeded ?: return prevState
val newWarnings = prevState.warnings.map { warning ->
if (warning is WalletNotification.Warning.TangemPayRefreshNeeded) {
warning.copy(shouldShowProgress = shouldShowProgress)
@ -25,12 +21,7 @@ internal class TangemPayRefreshShowProgressTransformer(
}
}
return multiContentState.copy(
tangemPayState = refreshNeededState.copy(
notification = refreshNotification.copy(shouldShowProgress = shouldShowProgress),
),
warnings = newWarnings.toImmutableList(),
)
return multiContentState.copy(warnings = newWarnings.toImmutableList())
}
override fun transform(walletUM: WalletUM): WalletUM {

View file

@ -1,28 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
internal class TangemPayUnavailableStateTransformer(
userWalletId: UserWalletId,
) : WalletStateTransformer(userWalletId) {
override fun transform(prevState: WalletState): WalletState {
return if (prevState is WalletState.MultiCurrency.Content) {
prevState.copy(
tangemPayState = TangemPayState.TemporaryUnavailable(
notification = WalletNotification.Warning.TangemPayUnreachable,
),
)
} else {
prevState
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
}

View file

@ -1,130 +0,0 @@
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.extensions.stringReference
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.models.kyc.KycStatus
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.TangemPayDetailsConfig
import com.tangem.domain.pay.model.CustomerInfo.CardInfo
import com.tangem.domain.pay.model.CustomerInfo.ProductInstance
import com.tangem.domain.pay.model.MainScreenCustomerInfo
import com.tangem.domain.pay.model.OrderStatus
import com.tangem.domain.visa.model.TangemPayCardFrozenState
import com.tangem.feature.wallet.child.wallet.model.intents.TangemPayIntents
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.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import java.util.Currency
/**
* Hardcode Polygon chain id only for F&F.
* Later chain id will be fetched from BFF.
*/
private const val POLYGON_CHAIN_ID = 137
internal class TangemPayUpdateInfoStateTransformer(
userWalletId: UserWalletId,
private val value: MainScreenCustomerInfo,
private val cardFrozenState: TangemPayCardFrozenState,
private val tangemPayClickIntents: TangemPayIntents,
) : WalletStateTransformer(userWalletId = userWalletId) {
override fun transform(prevState: WalletState): WalletState {
val tangemPayState = createInitialState()
return if (prevState is WalletState.MultiCurrency.Content) {
prevState.copy(tangemPayState = tangemPayState)
} else {
prevState
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
private fun createInitialState(): TangemPayState {
val cardInfo = value.info.cardInfo
val productInstance = value.info.productInstance
val customerId = value.info.customerId ?: "Unknown"
// when statement copied to WalletTangemPayAnalyticsEventSender. Be careful when editing.
return when {
value.info.kycStatus != KycStatus.APPROVED && !value.info.customerId.isNullOrEmpty() ->
createKycInProgressState(kycStatus = value.info.kycStatus, customerId = customerId)
value.orderStatus == OrderStatus.CANCELED -> createCancelledState(customerId)
cardInfo != null && productInstance != null && value.orderStatus == OrderStatus.COMPLETED ->
getCardInfoState(customerId, cardInfo, productInstance)
else -> createIssueProgressState()
}
}
private fun getCardInfoState(
customerId: String,
cardInfo: CardInfo,
productInstance: ProductInstance,
): TangemPayState = TangemPayState.Card(
lastFourDigits = TextReference.Str("*${cardInfo.lastFourDigits}"),
balanceText = TextReference.Str(getBalanceText(cardInfo)),
balanceSymbol = stringReference("USDC"), // TODO hardcode for now
onClick = {
tangemPayClickIntents.openDetails(
userWalletId,
TangemPayDetailsConfig(
customerId = customerId,
cardId = productInstance.cardId,
isPinSet = cardInfo.isPinSet,
cardFrozenState = cardFrozenState,
cardNumberEnd = cardInfo.lastFourDigits,
chainId = POLYGON_CHAIN_ID,
isTangemPayDeactivated = false,
),
)
},
)
private fun getBalanceText(cardInfo: CardInfo): String {
val currency = Currency.getInstance(cardInfo.currencyCode)
return cardInfo.balance.format {
fiat(fiatCurrencyCode = currency.currencyCode, fiatCurrencySymbol = currency.symbol)
}
}
private fun createKycInProgressState(kycStatus: KycStatus, customerId: String): TangemPayState = Progress(
title = TextReference.Res(R.string.tangempay_payment_account),
description = when (kycStatus) {
KycStatus.REJECTED -> TextReference.Res(R.string.tangempay_kyc_has_failed)
else -> TextReference.Res(R.string.tangempay_kyc_in_progress)
},
buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button),
iconRes = R.drawable.ic_promo_kyc_36,
onButtonClick = {
when (kycStatus) {
KycStatus.REJECTED -> tangemPayClickIntents.onKycRejectedClicked(
userWalletId = userWalletId,
customerId = customerId,
)
else -> tangemPayClickIntents.onKycProgressClicked(userWalletId)
}
},
)
private fun createIssueProgressState(): TangemPayState = Progress(
title = TextReference.Res(R.string.tangempay_payment_account),
description = TextReference.Res(R.string.tangempay_issuing_your_card),
buttonText = TextReference.EMPTY,
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
onButtonClick = tangemPayClickIntents::onIssuingCardClicked,
showProgress = true,
)
private fun createCancelledState(customerId: String): TangemPayState = TangemPayState.FailedIssue(
title = TextReference.Res(R.string.tangempay_payment_account),
description = TextReference.Res(R.string.tangempay_failed_to_issue_card),
iconRes = R.drawable.ic_alert_24,
onButtonClick = { tangemPayClickIntents.onIssuingFailedClicked(customerId) },
)
}

View file

@ -9,16 +9,15 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenSta
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory
import com.tangem.utils.logging.TangemLogger
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentList
import com.tangem.utils.logging.TangemLogger
internal class UnlockWalletTransformer(
private val unlockedWallets: List<UserWallet>,
private val clickIntents: WalletClickIntents,
private val walletImageResolver: WalletImageResolver,
private val getWalletIconUseCase: GetWalletIconUseCase,
private val isTangemPayRefactorEnabled: Boolean,
) : WalletScreenStateTransformer {
private val walletLoadingStateFactory by lazy {
@ -26,7 +25,6 @@ internal class UnlockWalletTransformer(
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
getWalletIconUseCase = getWalletIconUseCase,
isTangemPayRefactorEnabled = isTangemPayRefactorEnabled,
)
}

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

@ -29,6 +29,7 @@ internal class MultiWalletBalanceUMTransformer(
id = id,
name = name,
deviceIcon = deviceIcon,
additionalInfo = additionalInfo,
)
}
@ -37,6 +38,7 @@ internal class MultiWalletBalanceUMTransformer(
id = id,
name = name,
deviceIcon = deviceIcon,
additionalInfo = additionalInfo,
)
}
@ -45,6 +47,7 @@ internal class MultiWalletBalanceUMTransformer(
id = id,
name = name,
deviceIcon = deviceIcon,
additionalInfo = additionalInfo,
balanceInAppBar = fiatBalance.amount.formatStyled {
fiat(
fiatCurrencyCode = appCurrency.code,

View file

@ -1,17 +1,20 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
import com.tangem.common.ui.expressStatus.state.*
import com.tangem.common.ui.expressStatus.toActiveStatusText
import com.tangem.common.ui.expressStatus.toIconState
import com.tangem.common.ui.notifications.ExpressNotificationsUM
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
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.core.ui.utils.mapFormattedDate
import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday
import com.tangem.core.ui.utils.toTimeFormat
import com.tangem.domain.appcurrency.model.AppCurrency
@ -36,6 +39,7 @@ internal class SingleWalletOnrampTransactionConverter(
private val currency = cryptoCurrencyStatus.currency
private val status = cryptoCurrencyStatus.value
@Suppress("LongMethod")
override fun convert(value: OnrampTransaction): ExpressTransactionStateUM.OnrampUM {
return ExpressTransactionStateUM.OnrampUM(
info = ExpressTransactionStateInfoUM(
@ -56,6 +60,8 @@ internal class SingleWalletOnrampTransactionConverter(
value.timestamp.toTimeFormat(),
),
),
timestampAgoFormatted = mapFormattedDate(value.timestamp),
activeStatus = value.status.toActiveStatusText(currency.name),
toAmount = stringReference(value.toAmount.format { crypto(currency) }),
toFiatAmount = stringReference(
status.fiatRate?.multiply(value.toAmount).format {
@ -81,7 +87,7 @@ internal class SingleWalletOnrampTransactionConverter(
url = value.fromCurrency.image,
fallbackResId = R.drawable.ic_currency_24,
),
iconState = getIconState(value.status),
iconState = value.status.toIconState(),
onGoToProviderClick = { url ->
analyticsEventHandler.send(TokenOnrampAnalyticsEvent.GoToProvider())
clickIntents.onGoToProviderClick(url)
@ -134,18 +140,6 @@ internal class SingleWalletOnrampTransactionConverter(
null
}
private fun getIconState(status: OnrampStatus.Status): ExpressTransactionStateIconUM {
return when (status) {
OnrampStatus.Status.Verifying,
OnrampStatus.Status.RefundInProgress,
-> ExpressTransactionStateIconUM.Warning
OnrampStatus.Status.Refunded,
OnrampStatus.Status.Failed,
-> ExpressTransactionStateIconUM.Error
else -> ExpressTransactionStateIconUM.None
}
}
private fun convertStatuses(status: OnrampStatus.Status, externalTxUrl: String?): ExpressStatusUM {
val statuses = with(status) {
persistentListOf(

View file

@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers.convert
import androidx.compose.ui.text.SpanStyle
import com.tangem.common.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
@ -76,59 +77,50 @@ internal class TangemPayMainBlockConverter(
cardFrozenState = TangemPayCardFrozenState.Unfrozen,
cardNumberEnd = "",
chainId = POLYGON_CHAIN_ID,
displayName = null,
isTangemPayDeactivated = true,
isReissuing = false,
),
)
},
)
is PaymentAccountStatusValue.Locked -> TangemPayMainUM.Content(
subtitle = stringReference("*${statusValue.lastFourDigits}"),
isBalanceFlickering = statusValue.source == StatusSource.CACHE,
balance = getBalanceText(
currencyCode = statusValue.currencyCode,
balance = statusValue.fiatBalance.availableBalance,
),
balanceSubtitle = stringReference("USDC"), // TODO hardcode for now
shouldShowOnlyCacheWarning = statusValue.source == StatusSource.ONLY_CACHE,
onClick = {
tangemPayClickIntents.openDetails(
value.account.userWalletId,
TangemPayDetailsConfig(
customerId = statusValue.customerId,
cardId = statusValue.cardId,
isPinSet = statusValue.isPinSet,
cardFrozenState = TangemPayCardFrozenState.Frozen,
cardNumberEnd = statusValue.lastFourDigits,
chainId = POLYGON_CHAIN_ID,
isTangemPayDeactivated = false,
),
)
},
)
is PaymentAccountStatusValue.Loaded -> TangemPayMainUM.Content(
subtitle = stringReference("*${statusValue.lastFourDigits}"),
isBalanceFlickering = statusValue.source == StatusSource.CACHE,
balance = getBalanceText(
currencyCode = statusValue.currencyCode,
balance = statusValue.fiatBalance.availableBalance,
),
balanceSubtitle = stringReference("USDC"), // TODO hardcode for now
shouldShowOnlyCacheWarning = statusValue.source == StatusSource.ONLY_CACHE,
onClick = {
tangemPayClickIntents.openDetails(
value.account.userWalletId,
TangemPayDetailsConfig(
customerId = statusValue.customerId,
cardId = statusValue.cardId,
isPinSet = statusValue.isPinSet,
cardFrozenState = TangemPayCardFrozenState.Unfrozen,
cardNumberEnd = statusValue.lastFourDigits,
chainId = POLYGON_CHAIN_ID,
isTangemPayDeactivated = false,
),
)
},
)
is PaymentAccountStatusValue.Loaded -> {
val card = statusValue.cards.firstOrNull() ?: return TangemPayMainUM.TemporaryUnavailable
TangemPayMainUM.Content(
subtitle = if (card.isReissuing) {
resourceReference(R.string.tangempay_status_replacing)
} else {
stringReference("*${card.lastDigits}")
},
isBalanceFlickering = statusValue.source == StatusSource.CACHE,
balance = getBalanceText(
currencyCode = statusValue.currencyCode,
balance = statusValue.fiatBalance.availableBalance,
),
balanceSubtitle = stringReference("USDC"), // TODO hardcode for now
shouldShowOnlyCacheWarning = statusValue.source == StatusSource.ONLY_CACHE,
onClick = {
tangemPayClickIntents.openDetails(
value.account.userWalletId,
TangemPayDetailsConfig(
customerId = statusValue.customerId,
cardId = card.id,
isPinSet = card.hasPinCode,
cardFrozenState = if (card.isFrozen) {
TangemPayCardFrozenState.Frozen
} else {
TangemPayCardFrozenState.Unfrozen
},
cardNumberEnd = card.lastDigits,
chainId = POLYGON_CHAIN_ID,
displayName = card.displayName,
isTangemPayDeactivated = false,
isReissuing = card.isReissuing,
),
)
},
)
}
}
}

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

@ -3,7 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers.convert
import androidx.compose.ui.text.SpanStyle
import com.tangem.common.getTotalCryptoAmount
import com.tangem.common.getTotalFiatAmount
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.components.marketprice.PriceChangeState
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.ds.badge.*

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

@ -34,7 +34,6 @@ internal class WalletLoadingStateFactory(
private val clickIntents: WalletClickIntents,
private val walletImageResolver: WalletImageResolver,
private val getWalletIconUseCase: GetWalletIconUseCase,
private val isTangemPayRefactorEnabled: Boolean,
) {
fun create(userWallet: UserWallet): WalletState {
@ -83,9 +82,7 @@ internal class WalletLoadingStateFactory(
tokensListState = WalletTokensListState.ContentState.Loading,
nftState = WalletNFTItemUM.Hidden,
type = WalletType.Hot,
tangemPayState = TangemPayState.Empty,
tangemPayMainUM = TangemPayMainUM.Empty,
isTangemPayRefactorEnabled = isTangemPayRefactorEnabled,
)
}
@ -99,9 +96,7 @@ internal class WalletLoadingStateFactory(
tokensListState = WalletTokensListState.ContentState.Loading,
nftState = WalletNFTItemUM.Hidden,
type = WalletType.Cold,
tangemPayState = TangemPayState.Empty,
tangemPayMainUM = TangemPayMainUM.Empty,
isTangemPayRefactorEnabled = isTangemPayRefactorEnabled,
)
}

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 com.tangem.utils.logging.TangemLogger
import dagger.assisted.Assisted
@ -42,8 +43,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<*> {
val walletId = userWallet.walletId.stringValue
TangemLogger.i("$TAG[$walletId]: create() called, building combine7")

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

@ -1,117 +1,38 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.model.MainCustomerInfoContentState
import com.tangem.domain.pay.model.MainScreenCustomerInfo
import com.tangem.domain.pay.model.TangemPayCustomerInfoError
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
import com.tangem.domain.pay.repository.TangemPayWithdrawRepository
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
import com.tangem.domain.visa.model.TangemPayCardFrozenState
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletTangemPayAnalyticsEventSender
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.transformers.*
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import com.tangem.utils.logging.TangemLogger
@Suppress("LongParameterList")
internal class TangemPayMainSubscriber @AssistedInject constructor(
@Assisted private val userWallet: UserWallet,
private val stateController: WalletStateController,
private val clickIntents: WalletClickIntents,
private val cardDetailsRepository: TangemPayCardDetailsRepository,
private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase,
private val tangemPayWithdrawRepository: TangemPayWithdrawRepository,
private val paymentAccountStatusSupplier: PaymentAccountStatusSupplier,
private val analytics: WalletTangemPayAnalyticsEventSender,
) : WalletSubscriber() {
override fun create(coroutineScope: CoroutineScope): Flow<*> {
coroutineScope.launch {
// TODO: Doston move this logic to proper place(e.g. WalletBalanceFetcher)
tangemPayWithdrawRepository.pollWithdrawOrdersIfNeeds(userWallet)
}
return subscribeOnTangemPayInfoUpdates()
subscribeToStatus(coroutineScope)
return emptyFlow<Any>()
}
private fun subscribeOnTangemPayInfoUpdates(): Flow<*> {
return tangemPayMainScreenCustomerInfoUseCase(userWalletId = userWallet.walletId)
private fun subscribeToStatus(coroutineScope: CoroutineScope) {
paymentAccountStatusSupplier.invoke(userWalletId = userWallet.walletId)
.map { it.value }
.distinctUntilChanged()
.onEach { mainInfoData ->
val userWalletId = userWallet.walletId
mainInfoData.onLeft { tangemPayError ->
when (tangemPayError) {
TangemPayCustomerInfoError.RefreshNeededError -> {
stateController.update(
transformer = TangemPayRefreshNeededStateTransformer(
userWalletId = userWalletId,
userWallet = userWallet,
onRefreshClick = { clickIntents.onRefreshPayToken(userWallet) },
),
)
}
TangemPayCustomerInfoError.UnavailableError -> {
stateController.update(
transformer = TangemPayUnavailableStateTransformer(userWalletId),
)
}
TangemPayCustomerInfoError.ExposedDeviceError -> {
stateController.update(TangemPayExposedDeviceTransformer(userWalletId))
}
TangemPayCustomerInfoError.UnknownError -> {
// hide TangemPay block
TangemLogger.e("Failed when loading main screen TangemPay info: $tangemPayError")
stateController.update(
transformer = TangemPayHiddenStateTransformer(userWalletId),
)
}
}
}.onRight { contentState -> handleContentState(state = contentState) }
}
}
private suspend fun handleContentState(state: MainCustomerInfoContentState) {
val userWalletId = userWallet.walletId
when (state) {
MainCustomerInfoContentState.Loading -> stateController.update(
transformer = TangemPayLoadingStateTransformer(userWalletId),
)
is MainCustomerInfoContentState.Content -> {
updateTangemPay(data = state.info, userWalletId = userWalletId)
analytics.send(customerInfo = state.info)
}
is MainCustomerInfoContentState.OnboardingBanner -> stateController.update(
transformer = TangemPayOnboardingBannerStateTransformer(
userWalletId = userWalletId,
onClick = clickIntents::onOnboardingBannerClick,
closeOnClick = clickIntents::onOnboardingBannerCloseClick,
),
)
is MainCustomerInfoContentState.Empty -> stateController.update(
transformer = TangemPayHiddenStateTransformer(userWalletId),
)
}
}
private suspend fun updateTangemPay(data: MainScreenCustomerInfo, userWalletId: UserWalletId) {
val cardFrozenState =
data.info.productInstance?.cardId?.let { cardDetailsRepository.cardFrozenStateSync(it) }
?: TangemPayCardFrozenState.Unfrozen
stateController.update(
transformer = TangemPayUpdateInfoStateTransformer(
userWalletId = userWalletId,
value = data,
cardFrozenState = cardFrozenState,
tangemPayClickIntents = clickIntents,
),
)
.onEach(analytics::send)
.launchIn(coroutineScope)
}
@AssistedFactory

View file

@ -82,7 +82,6 @@ import com.tangem.feature.wallet.presentation.wallet.ui.components.common.*
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.nftCollections
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.organizeTokensButton
import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.marketPriceBlock
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.TangemPayMainScreenBlock
import com.tangem.feature.wallet.presentation.wallet.ui.utils.changeWalletAnimator
import com.tangem.features.tangempay.component.TangemPayMainBlockComponent
import com.tangem.features.tangempay.entity.TangemPayMainUM
@ -96,7 +95,7 @@ internal fun WalletScreen(
state: WalletScreenState,
tangemPayComponent: TangemPayMainBlockComponent,
promoBannersBlockComponent: ComposableContentComponent? = null,
bottomSheetContent: @Composable (() -> Unit),
bottomSheetContent: @Composable (onExpandSheet: () -> Unit) -> Unit,
bottomSheetHeaderHeightProvider: () -> Dp,
onBottomSheetStateChange: (BottomSheetState) -> Unit,
) {
@ -140,7 +139,7 @@ private fun WalletContent(
promoBannersBlockComponent: ComposableContentComponent? = null,
bottomSheetHeaderHeightProvider: () -> Dp,
onBottomSheetStateChange: (BottomSheetState) -> Unit,
bottomSheetContent: @Composable (() -> Unit),
bottomSheetContent: @Composable (onExpandSheet: () -> Unit) -> Unit,
) {
/*
* Don't pass key to remember, because it will brake scroll animation.
@ -296,7 +295,7 @@ private inline fun BaseScaffoldWithMarkets(
snackbarHostState: SnackbarHostState,
bottomSheetHeaderHeightProvider: () -> Dp,
noinline onBottomSheetStateChange: (BottomSheetState) -> Unit,
crossinline bottomSheetContent: @Composable () -> Unit,
crossinline bottomSheetContent: @Composable (onExpandSheet: () -> Unit) -> Unit,
crossinline content: @Composable (PaddingValues) -> Unit,
) {
val isKeyboardVisible by rememberIsKeyboardVisible()
@ -383,7 +382,7 @@ private inline fun BaseScaffoldWithMarkets(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
// expand bottom sheet when clicked on the header
// expand bottom sheet when clicked on the drag handle
.clickable(
enabled = bottomSheetState.currentValue == TangemSheetValue.PartiallyExpanded,
indication = null,
@ -401,7 +400,9 @@ private inline fun BaseScaffoldWithMarkets(
isSearchFieldFocused = it.isFocused
},
) {
bottomSheetContent()
bottomSheetContent {
coroutineScope.launch { bottomSheetState.expand() }
}
}
}
},
@ -726,17 +727,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) {
@ -756,14 +753,8 @@ internal fun LazyListScope.tangemPayItem(
) {
if (state !is WalletState.MultiCurrency) return
if (state.isTangemPayRefactorEnabled) {
with(tangemPayComponent) {
tangemPayMainContent(modifier = modifier, state = state.tangemPayMainUM, isBalanceHidden = isHidingMode)
}
} else {
item(key = "TangemPayMainScreenBlock", contentType = state.tangemPayState::class.java) {
TangemPayMainScreenBlock(modifier = modifier, state = state.tangemPayState, isBalanceHidden = isHidingMode)
}
with(tangemPayComponent) {
tangemPayMainContent(modifier = modifier, state = state.tangemPayMainUM, isBalanceHidden = isHidingMode)
}
}

View file

@ -86,7 +86,7 @@ internal fun WalletScreen2(
state: WalletScreenState,
tangemPayComponent: TangemPayMainBlockComponent,
modifier: Modifier = Modifier,
bottomSheetContent: @Composable (() -> Unit),
bottomSheetContent: @Composable (onExpandSheet: () -> Unit) -> Unit,
bottomSheetHeaderHeightProvider: () -> Dp,
onBottomSheetStateChange: (BottomSheetState) -> Unit,
) {
@ -162,7 +162,7 @@ private fun WalletContent2(
modifier: Modifier = Modifier,
bottomSheetHeaderHeightProvider: () -> Dp,
onBottomSheetStateChange: (BottomSheetState) -> Unit,
bottomSheetContent: @Composable (() -> Unit),
bottomSheetContent: @Composable (onExpandSheet: () -> Unit) -> Unit,
) {
val density = LocalDensity.current
val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(this).toDp() }
@ -343,7 +343,7 @@ private inline fun BaseScaffoldWithMarkets(
modifier: Modifier = Modifier,
noinline onBottomSheetStateChange: (BottomSheetState) -> Unit,
crossinline appBarContent: @Composable () -> Unit,
crossinline bottomSheetContent: @Composable () -> Unit,
crossinline bottomSheetContent: @Composable (onExpandSheet: () -> Unit) -> Unit,
crossinline content: @Composable (PaddingValues, TangemSheetState) -> Unit,
) {
val density = LocalDensity.current
@ -384,7 +384,9 @@ private inline fun BaseScaffoldWithMarkets(
isSearchFieldFocused = focusState.isFocused
},
) {
bottomSheetContent()
bottomSheetContent {
coroutineScope.launch { bottomSheetState.expand() }
}
}
},
content = { paddingValues ->
@ -456,7 +458,7 @@ private fun BottomSheet(
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
// expand bottom sheet when clicked on the header
// expand bottom sheet when clicked on the drag handle
.clickable(
enabled = bottomSheetState.currentValue == TangemSheetValue.PartiallyExpanded,
indication = null,

View file

@ -9,9 +9,9 @@ import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
@ -25,25 +25,26 @@ import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.text.applyBladeBrush
import com.tangem.core.ui.ds.button.SecondaryTangemButton
import com.tangem.core.ui.ds.button.TangemButtonShape
import com.tangem.core.ui.ds.button.TangemButtonUM
import com.tangem.core.ui.ds.button.action.ActionButtons
import com.tangem.core.ui.ds.image.TangemDeviceIcon
import com.tangem.core.ui.ds.placeholder.TextPlaceholder
import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingAppBarBehavior
import com.tangem.core.ui.ds.topbar.collapsing.rememberTangemExitUntilCollapsedScrollBehavior
import com.tangem.core.ui.ds.topbar.collapsing.snapToExitUntilCollapsed
import com.tangem.core.ui.extensions.orEmpty
import com.tangem.core.ui.extensions.orMaskWithStars
import com.tangem.core.ui.extensions.resolveAnnotatedReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.test.MainScreenTestTags
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.preview.WalletBalancePreview
import com.tangem.feature.wallet.presentation.preview.WalletPreviewData
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAdditionalInfo
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBalanceUM
import com.tangem.feature.wallet.presentation.wallet.ui.components.fastForEach
import com.tangem.utils.StringsSigns
import kotlinx.collections.immutable.ImmutableList
@ -84,17 +85,7 @@ internal fun WalletBalance(
isBalanceHidden = isBalanceHidden,
)
SpacerH(TangemTheme.dimens2.x3)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),
) {
Text(
text = walletBalanceUM.name,
style = TangemTheme.typography2.bodyRegular14,
color = TangemTheme.colors2.text.neutral.tertiary,
)
TangemDeviceIcon(state = walletBalanceUM.deviceIcon)
}
SubtitleRow(walletBalanceUM = walletBalanceUM)
}
SpacerH(TangemTheme.dimens2.x2)
ActionButtons(buttons)
@ -102,6 +93,61 @@ internal fun WalletBalance(
}
}
@Composable
private fun SubtitleRow(walletBalanceUM: WalletBalanceUM, modifier: Modifier = Modifier) {
AnimatedContent(
targetState = walletBalanceUM.additionalInfo?.content,
contentKey = { content ->
when (content) {
is WalletAdditionalInfo.Content.SyncProgress -> WalletAdditionalInfo.Content.SyncProgress::class
else -> content
}
},
label = "Update subtitle",
modifier = modifier,
transitionSpec = {
fadeIn(animationSpec = tween(durationMillis = 220, delayMillis = 90)) togetherWith
fadeOut(animationSpec = tween(durationMillis = 90))
},
) { content ->
when (content) {
is WalletAdditionalInfo.Content.SyncProgress -> {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1_5),
) {
Text(
text = resourceReference(
id = R.string.initial_wallet_sync_restore_progress,
formatArgs = wrappedList(content.progressPercent),
).resolveReference(),
style = TangemTheme.typography2.bodyRegular14,
color = TangemTheme.colors2.text.neutral.tertiary,
)
CircularProgressIndicator(
modifier = Modifier.size(19.dp),
color = TangemTheme.colors2.graphic.neutral.primary,
strokeWidth = 2.dp,
)
}
}
else -> {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),
) {
Text(
text = walletBalanceUM.name,
style = TangemTheme.typography2.bodyRegular14,
color = TangemTheme.colors2.text.neutral.tertiary,
)
TangemDeviceIcon(state = walletBalanceUM.deviceIcon)
}
}
}
}
}
@Composable
private fun Balance(walletBalanceUM: WalletBalanceUM, isBalanceHidden: Boolean, modifier: Modifier = Modifier) {
AnimatedContent(
@ -152,41 +198,6 @@ private fun Balance(walletBalanceUM: WalletBalanceUM, isBalanceHidden: Boolean,
}
}
@Composable
private fun ActionButtons(buttons: ImmutableList<TangemButtonUM>) {
Row(
horizontalArrangement = Arrangement.spacedBy(14.dp),
verticalAlignment = Alignment.CenterVertically,
) {
buttons.fastForEach { button ->
key(button.text) {
val textColor = if (button.isEnabled) {
TangemTheme.colors2.text.neutral.primary
} else {
TangemTheme.colors2.text.status.disabled
}
Column(
modifier = Modifier.padding(horizontal = TangemTheme.dimens2.x2_5),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
horizontalAlignment = Alignment.CenterHorizontally,
) {
SecondaryTangemButton(
tangemIconUM = button.tangemIconUM,
onClick = button.onClick,
isEnabled = button.isEnabled,
shape = TangemButtonShape.Rounded,
)
Text(
text = button.text.orEmpty().resolveReference(),
style = TangemTheme.typography2.bodySemibold15,
color = textColor,
)
}
}
}
}
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 360)
@ -215,6 +226,7 @@ private class WalletBalancePreviewProvider : PreviewParameterProvider<WalletBala
override val values: Sequence<WalletBalancePreviewData>
get() = sequenceOf(
WalletBalancePreviewData(WalletBalancePreview.content, WalletPreviewData.actionButtons),
WalletBalancePreviewData(WalletBalancePreview.syncProgress, WalletPreviewData.actionButtons),
WalletBalancePreviewData(WalletBalancePreview.hiddenBalanceContent, WalletPreviewData.actionButtons),
WalletBalancePreviewData(WalletBalancePreview.loading, WalletPreviewData.disabledActionButtons),
WalletBalancePreviewData(WalletBalancePreview.error, WalletPreviewData.disabledActionButtons),

View file

@ -8,6 +8,7 @@ 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 +21,22 @@ 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) {
val testTag = if (config.textRes == R.string.main_add_and_manage_tokens) {
MainScreenTestTags.ADD_AND_MANAGE_BUTTON
} else {
MainScreenTestTags.ORGANIZE_TOKENS_BUTTON
}
RoundedActionButton(
modifier = modifier.testTag(MainScreenTestTags.ORGANIZE_TOKENS_BUTTON),
modifier = modifier.testTag(testTag),
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

@ -1,103 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency
import android.content.res.Configuration
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.R
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.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.TangemPayMainScreenBlock
@Composable
internal fun TangemPayCardMainBlock(
state: TangemPayState.Card,
isBalanceHidden: Boolean,
modifier: Modifier = Modifier,
) {
Surface(
modifier = modifier.fillMaxWidth(),
shape = TangemTheme.shapes.roundedCornersXMedium,
color = TangemTheme.colors.background.primary,
onClick = state.onClick,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(IntrinsicSize.Min)
.padding(horizontal = 12.dp, vertical = 16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
Image(
painter = painterResource(R.drawable.img_visa_36),
contentDescription = null,
modifier = Modifier.size(36.dp),
)
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
Text(
text = stringResourceSafe(R.string.tangempay_payment_account),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.primary1,
)
Text(
text = state.lastFourDigits.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
Column(
modifier = Modifier.fillMaxHeight(),
verticalArrangement = Arrangement.spacedBy(2.dp),
horizontalAlignment = Alignment.End,
) {
Text(
text = state.balanceText.resolveReference().orMaskWithStars(isBalanceHidden),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.End,
)
Text(
text = state.balanceSymbol.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.End,
)
}
}
}
}
@Preview(showBackground = true)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun TangemPayCardMainBlockPreview() {
TangemThemePreview {
TangemPayMainScreenBlock(
TangemPayState.Card(
lastFourDigits = TextReference.Str("*1234"),
balanceText = TextReference.Str("$ 0.00"),
balanceSymbol = TextReference.Str("USDC"),
onClick = {},
),
isBalanceHidden = false,
)
}
}

View file

@ -1,41 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import com.tangem.core.ui.R
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.inputrow.InputRowImageBase
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
private const val DISABLED_ALPHA = 0.6F
@Composable
internal fun TangemPayExposedDeviceState(modifier: Modifier = Modifier) {
BlockCard(
modifier = modifier
.clip(RoundedCornerShape(size = TangemTheme.dimens.radius14))
.background(TangemTheme.colors.background.primary)
.alpha(DISABLED_ALPHA),
enabled = false,
onClick = {},
) {
InputRowImageBase(
modifier = Modifier
.padding(
all = TangemTheme.dimens.spacing12,
),
subtitle = resourceReference(R.string.tangempay_payment_account),
caption = resourceReference(R.string.tangem_pay_rooted_device_subtitle),
subtitleColor = TangemTheme.colors.text.primary1,
captionColor = TangemTheme.colors.text.tertiary,
iconResWebp = R.drawable.img_visa_36,
endIconTint = TangemTheme.colors.icon.warning,
)
}
}

View file

@ -1,36 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.inputrow.InputRowImageBase
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
@Composable
internal fun TangemPayFailedIssueState(state: TangemPayState.FailedIssue, modifier: Modifier = Modifier) {
BlockCard(
modifier = modifier
.clip(RoundedCornerShape(size = TangemTheme.dimens.radius14))
.background(TangemTheme.colors.background.primary),
onClick = state.onButtonClick,
) {
InputRowImageBase(
modifier = Modifier
.padding(
all = TangemTheme.dimens.spacing12,
),
subtitle = state.title,
caption = state.description,
subtitleColor = TangemTheme.colors.text.primary1,
captionColor = TangemTheme.colors.text.tertiary,
iconResWebp = com.tangem.core.ui.R.drawable.img_visa_36,
iconEndRes = state.iconRes,
endIconTint = TangemTheme.colors.icon.warning,
)
}
}

View file

@ -1,37 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.CircleShimmer
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.res.TangemTheme
@Composable
internal fun TangemPayLoadingScreenBlock(modifier: Modifier = Modifier) {
Row(
modifier = modifier
.fillMaxWidth()
.background(color = TangemTheme.colors.background.primary, shape = TangemTheme.shapes.roundedCornersXMedium)
.padding(horizontal = 12.dp, vertical = 16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
CircleShimmer(modifier = Modifier.size(36.dp))
Column(
modifier = Modifier.padding(start = 12.dp),
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
RectangleShimmer(modifier = Modifier.padding(vertical = 4.dp).sizeIn(minWidth = 70.dp, minHeight = 12.dp))
RectangleShimmer(modifier = Modifier.padding(vertical = 2.dp).sizeIn(minWidth = 52.dp, minHeight = 12.dp))
}
SpacerWMax()
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
RectangleShimmer(modifier = Modifier.padding(vertical = 4.dp).sizeIn(minWidth = 40.dp, minHeight = 12.dp))
RectangleShimmer(modifier = Modifier.padding(vertical = 2.dp).sizeIn(minWidth = 40.dp, minHeight = 12.dp))
}
}
}

View file

@ -1,106 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
import android.content.res.Configuration
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.common.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
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.WalletNotification
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification.Warning.TangemPayRefreshNeeded
import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.TangemPayCardMainBlock
@Composable
internal fun TangemPayMainScreenBlock(state: TangemPayState, isBalanceHidden: Boolean, modifier: Modifier = Modifier) {
when (state) {
is Progress -> TangemPayProgressState(state, modifier)
is TangemPayState.Card -> TangemPayCardMainBlock(state, isBalanceHidden, modifier)
is TangemPayState.Empty -> Unit
is TangemPayState.RefreshNeeded -> TangemPayRefreshBlock(state, modifier)
is TangemPayState.TemporaryUnavailable -> TangemPayUnavailableBlock(state, modifier)
is TangemPayState.FailedIssue -> TangemPayFailedIssueState(state, modifier)
is TangemPayState.OnboardingBanner -> TangemPayOnboardingBanner(state, modifier)
is TangemPayState.ExposedDevice -> TangemPayExposedDeviceState(modifier)
is TangemPayState.Loading -> TangemPayLoadingScreenBlock(modifier)
}
}
@Preview(showBackground = true)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun TangemPayMainScreenBlockPreview() {
TangemThemePreview {
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
TangemPayMainScreenBlock(state = TangemPayState.Loading, isBalanceHidden = false)
TangemPayMainScreenBlock(
state = TangemPayState.RefreshNeeded(
TangemPayRefreshNeeded(
buttonText = resourceReference(id = R.string.home_button_scan),
onRefreshClick = {},
shouldShowProgress = false,
),
),
isBalanceHidden = false,
)
TangemPayMainScreenBlock(
state = TangemPayState.TemporaryUnavailable(WalletNotification.Warning.TangemPayUnreachable),
isBalanceHidden = false,
)
TangemPayMainScreenBlock(
state = TangemPayState.OnboardingBanner(onClick = {}, closeOnClick = {}),
isBalanceHidden = false,
)
TangemPayMainScreenBlock(state = TangemPayState.ExposedDevice, isBalanceHidden = false)
TangemPayMainScreenBlock(
state = TangemPayState.FailedIssue(
title = TextReference.Res(R.string.tangempay_payment_account),
description = TextReference.Res(R.string.tangempay_failed_to_issue_card),
iconRes = R.drawable.ic_alert_24,
onButtonClick = { },
),
isBalanceHidden = false,
)
TangemPayMainScreenBlock(
Progress(
title = TextReference.Res(R.string.tangempay_payment_account),
description = TextReference.Res(R.string.tangempay_kyc_in_progress),
buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button),
iconRes = R.drawable.ic_promo_kyc_36,
onButtonClick = {},
),
isBalanceHidden = false,
)
TangemPayMainScreenBlock(
Progress(
title = TextReference.Res(R.string.tangempay_payment_account),
description = TextReference.Res(R.string.tangempay_issuing_your_card),
buttonText = TextReference.EMPTY,
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
onButtonClick = {},
showProgress = true,
),
isBalanceHidden = false,
)
TangemPayMainScreenBlock(
TangemPayState.Card(
lastFourDigits = TextReference.Str("*1234"),
balanceText = TextReference.Str("$ 0.00"),
balanceSymbol = TextReference.Str("USDC"),
onClick = {},
),
isBalanceHidden = false,
)
}
}
}

View file

@ -1,120 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
import android.content.res.Configuration
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
private const val GRADIENT_START_COLOR = 0xFF252934
private const val GRADIENT_END_COLOR = 0xFF12141E
private const val GRADIENT_OFFSET_X = 0f
private const val GRADIENT_OFFSET_Y = 80F
private const val GRADIENT_RADIUS = 200F
@Composable
internal fun TangemPayOnboardingBanner(state: TangemPayState.OnboardingBanner, modifier: Modifier = Modifier) {
Box(
modifier = modifier
.fillMaxWidth()
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(
brush = Brush.radialGradient(
colors = listOf(Color(GRADIENT_START_COLOR), Color(GRADIENT_END_COLOR)),
center = Offset(GRADIENT_OFFSET_X, GRADIENT_OFFSET_Y),
radius = GRADIENT_RADIUS,
),
)
.clickable(onClick = state.onClick),
) {
ConstraintLayout(
modifier = Modifier.fillMaxWidth(),
) {
val (image, text, close) = createRefs()
Image(
painter = painterResource(R.drawable.ic_close_24),
contentDescription = null,
modifier = Modifier
.size(16.dp)
.clickable(onClick = state.closeOnClick)
.constrainAs(close) {
top.linkTo(parent.top, margin = 16.dp)
end.linkTo(parent.end, margin = 16.dp)
},
colorFilter = ColorFilter.tint(TangemTheme.colors.icon.inactive),
)
Column(
modifier = Modifier
.constrainAs(text) {
top.linkTo(parent.top)
start.linkTo(image.end, margin = 12.dp)
end.linkTo(close.start, margin = 12.dp)
width = Dimension.fillToConstraints
}
.padding(top = 16.dp, bottom = 16.dp),
) {
Text(
text = stringResourceSafe(R.string.tangempay_onboarding_banner_title),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.constantWhite,
)
SpacerH(6.dp)
Text(
text = stringResourceSafe(R.string.tangempay_onboarding_banner_description),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
Image(
painter = painterResource(R.drawable.img_tangem_pay_onboarding_banner),
contentDescription = null,
modifier = Modifier
.padding(top = 8.dp, start = 24.dp)
.constrainAs(image) {
start.linkTo(parent.start)
top.linkTo(text.top)
bottom.linkTo(text.bottom)
height = Dimension.fillToConstraints
},
)
}
}
}
@Preview(showBackground = true)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PreviewTangemOnboardingBanner() {
TangemThemePreview {
TangemPayOnboardingBanner(
TangemPayState.OnboardingBanner(
onClick = {},
closeOnClick = {},
),
)
}
}

View file

@ -1,35 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import com.tangem.core.ui.R
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.inputrow.InputRowImageBase
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
@Composable
internal fun TangemPayProgressState(state: TangemPayState.Progress, modifier: Modifier = Modifier) {
BlockCard(
modifier = modifier
.clip(RoundedCornerShape(size = TangemTheme.dimens.radius14))
.background(TangemTheme.colors.background.primary),
onClick = state.onButtonClick,
) {
InputRowImageBase(
modifier = Modifier
.padding(
all = TangemTheme.dimens.spacing12,
),
subtitle = state.title,
caption = state.description,
subtitleColor = TangemTheme.colors.text.primary1,
captionColor = TangemTheme.colors.text.tertiary,
iconResWebp = R.drawable.img_visa_36,
)
}
}

View file

@ -1,79 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
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.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.inputrow.InputRowImageBase
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification.Warning.TangemPayRefreshNeeded
@Composable
internal fun TangemPayRefreshBlock(state: TangemPayState.RefreshNeeded, modifier: Modifier = Modifier) {
Column(modifier) {
Notification(
config = state.notification.config,
iconTint = when (state.notification) {
is WalletNotification.Critical -> TangemTheme.colors.icon.warning
is WalletNotification.Informational -> TangemTheme.colors.icon.accent
is WalletNotification.RateApp -> TangemTheme.colors.icon.attention
is WalletNotification.UnlockWallets -> TangemTheme.colors.icon.primary1
is WalletNotification.UsedOutdatedData -> TangemTheme.colors.text.attention
else -> null
},
)
SpacerH12()
BlockCard(
modifier = Modifier
.clip(RoundedCornerShape(size = TangemTheme.dimens.radius14))
.background(TangemTheme.colors.background.primary),
enabled = false,
) {
InputRowImageBase(
modifier = Modifier.padding(
all = TangemTheme.dimens.spacing12,
),
subtitle = resourceReference(R.string.tangempay_payment_account),
caption = resourceReference(R.string.tangempay_payment_account_sync_needed),
subtitleColor = TangemTheme.colors.text.tertiary,
captionColor = TangemTheme.colors.text.tertiary,
iconResWebp = R.drawable.img_visa_36,
)
}
}
}
@Preview(showBackground = true)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun TangemPayRefreshBlockPreview() {
TangemThemePreview {
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
TangemPayRefreshBlock(
state = TangemPayState.RefreshNeeded(
TangemPayRefreshNeeded(
buttonText = resourceReference(id = R.string.tangempay_sync_needed_restore_access),
onRefreshClick = {},
shouldShowProgress = true,
),
),
modifier = Modifier,
)
}
}
}

View file

@ -1,76 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
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.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.inputrow.InputRowImageBase
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import com.tangem.utils.StringsSigns.DASH_SIGN
@Composable
internal fun TangemPayUnavailableBlock(state: TangemPayState.TemporaryUnavailable, modifier: Modifier = Modifier) {
Column(modifier) {
Notification(
config = state.notification.config,
iconTint = when (state.notification) {
is WalletNotification.Critical -> TangemTheme.colors.icon.warning
is WalletNotification.Informational -> TangemTheme.colors.icon.accent
is WalletNotification.RateApp -> TangemTheme.colors.icon.attention
is WalletNotification.UnlockWallets -> TangemTheme.colors.icon.primary1
is WalletNotification.UsedOutdatedData -> TangemTheme.colors.text.attention
else -> null
},
)
SpacerH12()
BlockCard(
modifier = Modifier
.clip(RoundedCornerShape(size = TangemTheme.dimens.radius14))
.background(TangemTheme.colors.background.primary),
enabled = false,
) {
InputRowImageBase(
modifier = Modifier.padding(
all = TangemTheme.dimens.spacing12,
),
subtitle = resourceReference(R.string.tangempay_payment_account),
caption = TextReference.Str(DASH_SIGN),
subtitleColor = TangemTheme.colors.text.tertiary,
captionColor = TangemTheme.colors.text.tertiary,
iconResWebp = R.drawable.img_visa_36,
)
}
}
}
@Preview(showBackground = true)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun TangemPayUnavailableBlockPreview() {
TangemThemePreview {
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
TangemPayUnavailableBlock(
state = TangemPayState.TemporaryUnavailable(
WalletNotification.Warning.TangemPayUnreachable,
),
modifier = Modifier,
)
}
}
}

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

@ -0,0 +1,101 @@
package com.tangem.feature.wallet.child.managetokens.model
import com.google.common.truth.Truth.assertThat
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.decompose.model.MutableParamsContainer
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.child.managetokens.AddAndManageBottomSheetComponent
import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioFetcher
import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioSelectorController
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.every
import io.mockk.mockk
import io.mockk.slot
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
internal class AddAndManageModelTest {
private val analyticsEventHandler: AnalyticsEventHandler = mockk(relaxed = true)
private val portfolioFetcher: PortfolioFetcher = mockk(relaxed = true) {
every { data } returns flowOf(
PortfolioFetcher.Data(
appCurrency = mockk(relaxed = true),
isBalanceHidden = false,
balances = emptyMap(),
),
)
}
private val portfolioFetcherFactory: PortfolioFetcher.Factory = mockk(relaxed = true) {
every { create(any(), any()) } returns portfolioFetcher
}
private val portfolioSelectorController: PortfolioSelectorController = mockk(relaxed = true) {
every { selectedAccount } returns flowOf(null)
}
private val onDismiss: () -> Unit = mockk(relaxed = true)
private val onOrganizeTokensClick: () -> Unit = mockk(relaxed = true)
private val onManageTokensClick: (AccountId) -> Unit = mockk(relaxed = true)
private val userWalletId = UserWalletId(stringValue = "0123456789ABCDEF")
private val params = AddAndManageBottomSheetComponent.Params(
userWalletId = userWalletId,
onDismiss = onDismiss,
onOrganizeTokensClick = onOrganizeTokensClick,
onManageTokensClick = onManageTokensClick,
)
private fun createModel(): AddAndManageModel = AddAndManageModel(
paramsContainer = MutableParamsContainer(params),
dispatchers = TestingCoroutineDispatcherProvider(),
portfolioFetcherFactory = portfolioFetcherFactory,
analyticsEventHandler = analyticsEventHandler,
portfolioSelectorController = portfolioSelectorController,
)
@Test
fun `GIVEN bottom sheet model WHEN onAddTokensClick THEN sends ButtonAddTokens event with correct payload`() =
runTest {
val model = createModel()
val captured = slot<AnalyticsEvent>()
model.onAddTokensClick()
verify(exactly = 1) { analyticsEventHandler.send(capture(captured)) }
assertThat(captured.captured.category).isEqualTo("Portfolio")
assertThat(captured.captured.event).isEqualTo("Button - Add tokens")
assertThat(captured.captured.params).isEmpty()
}
@Test
fun `GIVEN bottom sheet model WHEN onOrganizeTokensClick THEN sends ButtonOrganizeTokens event with correct payload`() =
runTest {
val model = createModel()
val captured = slot<AnalyticsEvent>()
model.onOrganizeTokensClick()
verify(exactly = 1) { analyticsEventHandler.send(capture(captured)) }
assertThat(captured.captured.category).isEqualTo("Portfolio")
assertThat(captured.captured.event).isEqualTo("Button - Organize Tokens")
assertThat(captured.captured.params).isEmpty()
}
@Test
fun `GIVEN bottom sheet model WHEN onOrganizeTokensClick THEN dismisses bottom sheet and forwards to params callback`() =
runTest {
val model = createModel()
model.onOrganizeTokensClick()
verify(exactly = 1) { onDismiss() }
verify(exactly = 1) { onOrganizeTokensClick() }
}
}

View file

@ -0,0 +1,83 @@
package com.tangem.feature.wallet.child.wallet.model.intents
import com.google.common.truth.Truth.assertThat
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import io.mockk.every
import io.mockk.mockk
import io.mockk.slot
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
internal class WalletContentClickIntentsAnalyticsTest {
private val stateHolder: WalletStateController = mockk(relaxed = true)
private val analyticsEventHandler: AnalyticsEventHandler = mockk(relaxed = true)
private val walletFeatureToggles: WalletFeatureToggles = mockk(relaxed = true)
private val router: InnerWalletRouter = mockk(relaxed = true)
private val userWalletId = UserWalletId(stringValue = "0123456789ABCDEF")
private fun createImplementor(): WalletContentClickIntentsImplementor {
every { stateHolder.getSelectedWalletId() } returns userWalletId
val implementor = WalletContentClickIntentsImplementor(
stateHolder = stateHolder,
currencyActionsClickIntents = mockk(relaxed = true),
onrampStatusFactory = mockk(relaxed = true),
getUserWalletUseCase = mockk(relaxed = true),
singleAccountStatusListSupplier = mockk(relaxed = true),
getCryptoCurrencyActionsUseCase = mockk(relaxed = true),
getExplorerTransactionUrlUseCase = mockk(relaxed = true),
shouldShowMarketsTooltipUseCase = mockk(relaxed = true),
dispatchers = mockk(relaxed = true),
walletEventSender = mockk(relaxed = true),
analyticsEventHandler = analyticsEventHandler,
accountDependencies = mockk(relaxed = true),
yieldSupplySetShouldShowMainPromoUseCase = mockk(relaxed = true),
tokenListAnalyticsSender = mockk(relaxed = true),
uiMessageSender = mockk(relaxed = true),
walletFeatureToggles = walletFeatureToggles,
)
implementor.initialize(router = router, coroutineScope = TestScope())
return implementor
}
@Test
fun `GIVEN add and manage toggle enabled WHEN onOrganizeTokensClick THEN sends ButtonAddManage event and opens bottom sheet`() =
runTest {
every { walletFeatureToggles.isAddAndManageTokensEnabled } returns true
val implementor = createImplementor()
val captured = slot<AnalyticsEvent>()
implementor.onOrganizeTokensClick()
verify(exactly = 1) { analyticsEventHandler.send(capture(captured)) }
assertThat(captured.captured.category).isEqualTo("Portfolio")
assertThat(captured.captured.event).isEqualTo("Button - Add Manage")
assertThat(captured.captured.params).isEmpty()
verify(exactly = 1) { router.openAddAndManageBottomSheet(userWalletId = userWalletId) }
verify(exactly = 0) { router.openOrganizeTokensScreen(any()) }
}
@Test
fun `GIVEN add and manage toggle disabled WHEN onOrganizeTokensClick THEN does not send analytics and opens organize screen`() =
runTest {
every { walletFeatureToggles.isAddAndManageTokensEnabled } returns false
val implementor = createImplementor()
implementor.onOrganizeTokensClick()
verify(exactly = 0) { analyticsEventHandler.send(any<AnalyticsEvent>()) }
verify(exactly = 1) { router.openOrganizeTokensScreen(userWalletId = userWalletId) }
verify(exactly = 0) { router.openAddAndManageBottomSheet(any()) }
}
}

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,