Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-05 14:27:19 +03:00
commit 94a6b98f54
237 changed files with 2879 additions and 1365 deletions

View file

@ -1,10 +0,0 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>BooleanPropertyNaming:PortfolioFetcher.kt$PortfolioFetcher.Mode.All$val onlyMultiCurrency: Boolean</ID>
<ID>NonBooleanPropertyPrefixedWithIs:PortfolioSelectorComponent.kt$PortfolioSelectorController$/** * for some Feature specific filtering * combine and update with your Feature data and [PortfolioFetcher.data] */ val isEnabled: MutableStateFlow&lt;(UserWallet, AccountStatus) -&gt; Boolean&gt;</ID>
<ID>NonBooleanPropertyPrefixedWithIs:PortfolioSelectorComponent.kt$PortfolioSelectorController$val isAccountMode: Flow&lt;Boolean&gt;</ID>
<ID>UseSumOfInsteadOfFlatMapSize:PortfolioFetcher.kt$PortfolioFetcher.Data$flatten()</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -26,7 +26,7 @@ interface PortfolioFetcher {
val isSingleChoice: Boolean = balances.values
.map { it.accountsBalance.accountStatuses }
.flatten().size == 1
.sumOf { it.size } == 1
fun isSingleChoice(walletId: UserWalletId): Boolean = balances[walletId]
?.accountsBalance
@ -43,7 +43,7 @@ interface PortfolioFetcher {
}
sealed interface Mode {
data class All(val onlyMultiCurrency: Boolean) : Mode
data class All(val isOnlyMultiCurrency: Boolean) : Mode
data class Wallet(val walletId: UserWalletId) : Mode
}

View file

@ -1,10 +1,5 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>NonBooleanPropertyPrefixedWithIs:DefaultPortfolioSelectorController.kt$DefaultPortfolioSelectorController$override val isAccountMode: Flow&lt;Boolean&gt; by lazy { isAccountsModeEnabledUseCase() }</ID>
<ID>NonBooleanPropertyPrefixedWithIs:DefaultPortfolioSelectorController.kt$DefaultPortfolioSelectorController$override val isEnabled: MutableStateFlow&lt;(UserWallet, AccountStatus) -&gt; Boolean&gt; = MutableStateFlow { _, _ -&gt; true }</ID>
<ID>NonBooleanPropertyPrefixedWithIs:DefaultPortfolioSelectorController.kt$DefaultPortfolioSelectorController$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:PortfolioSelectorModel.kt$PortfolioSelectorModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
</CurrentIssues>
<CurrentIssues/>
</SmellBaseline>

View file

@ -120,7 +120,7 @@ internal class ArchivedAccountListModel @Inject constructor(
messageSender.send(
DialogMessage(
title = resourceReference(R.string.account_recover_limit_dialog_title),
title = resourceReference(R.string.common_something_went_wrong),
message = resourceReference(
id = R.string.account_recover_limit_dialog_description,
formatArgs = wrappedList(AccountList.MAX_ACCOUNTS_COUNT.toString()),

View file

@ -274,7 +274,7 @@ internal class AccountCreateEditModel @Inject constructor(
private fun showSomethingWrong() {
val dialogMessage = DialogMessage(
title = resourceReference(R.string.common_something_went_wrong),
message = resourceReference(R.string.account_could_not_create),
message = resourceReference(R.string.account_generic_error_dialog_message),
)
messageSender.send(dialogMessage)
}

View file

@ -1,15 +1,10 @@
package com.tangem.features.account.createedit.ui
import android.content.res.Configuration
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -30,6 +25,8 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachIndexed
import com.tangem.common.ui.R
import com.tangem.common.ui.account.*
import com.tangem.core.ui.components.PrimaryButton
@ -186,9 +183,9 @@ private fun AccountColor(colorsState: AccountCreateEditUM.Colors) {
modifier = Modifier
.fillMaxWidth()
.padding(contentPadding),
horizontalArrangement = Arrangement.spacedBy(4.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterHorizontally),
) {
colorsState.list.forEach { color ->
colorsState.list.fastForEach { color ->
val isSelected = color == colorsState.selected
Box(
contentAlignment = Alignment.Center,
@ -236,8 +233,9 @@ private fun AccountIcons(iconsState: AccountCreateEditUM.Icons) {
FlowRow(
maxItemsInEachRow = 6,
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
) {
iconsState.list.forEachIndexed { index, icon ->
iconsState.list.fastForEachIndexed { index, icon ->
val isSelected = icon == iconsState.selected
Box(
contentAlignment = Alignment.Center,

View file

@ -104,21 +104,13 @@ internal class AccountDetailsModel @Inject constructor(
}
private fun failedArchiveDialog(error: ArchiveCryptoPortfolioUseCase.Error) {
val titleRes = when (error) {
is ArchiveCryptoPortfolioUseCase.Error.CriticalTechError.AccountListRequirementsNotMet,
is ArchiveCryptoPortfolioUseCase.Error.CriticalTechError.AccountNotFound,
is ArchiveCryptoPortfolioUseCase.Error.CriticalTechError.AccountsNotCreated,
is ArchiveCryptoPortfolioUseCase.Error.DataOperationFailed,
-> R.string.common_something_went_wrong
is ArchiveCryptoPortfolioUseCase.Error.ActiveReferralStatus,
-> R.string.account_could_not_archive_referral_program_title
}
val titleRes = R.string.common_something_went_wrong
val messageRes = when (error) {
is ArchiveCryptoPortfolioUseCase.Error.CriticalTechError.AccountListRequirementsNotMet,
is ArchiveCryptoPortfolioUseCase.Error.CriticalTechError.AccountNotFound,
is ArchiveCryptoPortfolioUseCase.Error.CriticalTechError.AccountsNotCreated,
is ArchiveCryptoPortfolioUseCase.Error.DataOperationFailed,
-> R.string.account_could_not_archive
-> R.string.account_generic_error_dialog_message
is ArchiveCryptoPortfolioUseCase.Error.ActiveReferralStatus,
-> R.string.account_could_not_archive_referral_program_message
}

View file

@ -72,7 +72,7 @@ internal class DefaultPortfolioFetcher @AssistedInject constructor(
private fun List<UserWallet>.filterWallets(mode: Mode): List<UserWallet> = this.filter { wallet ->
when (mode) {
is Mode.All -> if (mode.onlyMultiCurrency) wallet.isMultiCurrency else true
is Mode.All -> if (mode.isOnlyMultiCurrency) wallet.isMultiCurrency else true
is Mode.Wallet -> wallet.walletId == mode.walletId
}
}

View file

@ -1,10 +0,0 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>BooleanPropertyNaming:AskBiometryUM.kt$AskBiometryUM$val bottomSheetVariant: Boolean = false</ID>
<ID>BooleanPropertyNaming:AskBiometryUM.kt$AskBiometryUM$val showProgress: Boolean = false</ID>
<ID>BooleanPropertyNaming:DefaultAskBiometryComponent.kt$DefaultAskBiometryComponent$val bsShown by bsShown.collectAsStateWithLifecycle()</ID>
<ID>MultilineLambdaItParameter:AskBiometryModel.kt$AskBiometryModel${ uiMessageSender.send( SnackbarMessage(stringReference("Something went wrong. Please contact support: $it")), ) }</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -41,10 +41,10 @@ internal class DefaultAskBiometryComponent @AssistedInject constructor(
@Composable
override fun BottomSheet() {
val state by model.uiState.collectAsStateWithLifecycle()
val bsShown by bsShown.collectAsStateWithLifecycle()
val bsConfig = remember(this, bsShown) {
val isBSShown by bsShown.collectAsStateWithLifecycle()
val bsConfig = remember(this, isBSShown) {
TangemBottomSheetConfig(
isShown = bsShown,
isShown = isBSShown,
onDismissRequest = ::dismiss,
content = TangemBottomSheetConfigContent.Empty,
)

View file

@ -55,7 +55,7 @@ internal class AskBiometryModel @Inject constructor(
private val _uiState = MutableStateFlow(
AskBiometryUM(
bottomSheetVariant = params.isBottomSheetVariant,
isBottomSheetVariant = params.isBottomSheetVariant,
onAllowClick = ::onAllowClick,
onDontAllowClick = ::dontAllow,
onDismiss = ::dismiss,
@ -85,7 +85,7 @@ internal class AskBiometryModel @Inject constructor(
return@launch
}
_uiState.update { it.copy(showProgress = true) }
_uiState.update { it.copy(shouldShowProgress = true) }
/*
@ -96,7 +96,7 @@ internal class AskBiometryModel @Inject constructor(
uiMessageSender.send(
SnackbarMessage(stringReference("No selected user wallet")),
)
_uiState.update { it.copy(showProgress = false) }
_uiState.update { it.copy(shouldShowProgress = false) }
return@launch
}
@ -128,7 +128,7 @@ internal class AskBiometryModel @Inject constructor(
}
}
if (_uiState.value.bottomSheetVariant) {
if (_uiState.value.isBottomSheetVariant) {
dismissBSFlow.emit(Unit)
delay(timeMillis = 500)
}
@ -142,9 +142,9 @@ internal class AskBiometryModel @Inject constructor(
userWalletId = userWallet.walletId,
lockMethod = UserWalletsListRepository.LockMethod.Biometric,
changeUnsecured = false,
).onLeft {
).onLeft { error ->
uiMessageSender.send(
SnackbarMessage(stringReference("Something went wrong. Please contact support: $it")),
SnackbarMessage(stringReference("Something went wrong. Please contact support: $error")),
)
}
}

View file

@ -29,7 +29,7 @@ internal fun AskBiometry(state: AskBiometryUM, modifier: Modifier = Modifier) {
Column(
modifier = Modifier.weight(1f),
) {
if (state.bottomSheetVariant) {
if (state.isBottomSheetVariant) {
Header(onCloseClick = state.onDismiss)
}
@ -128,12 +128,12 @@ private fun Footer(state: AskBiometryUM, modifier: Modifier = Modifier) {
) {
PrimaryButton(
modifier = Modifier.fillMaxWidth(),
showProgress = state.showProgress,
showProgress = state.shouldShowProgress,
text = stringResourceSafe(id = R.string.save_user_wallet_agreement_allow_biometrics),
onClick = state.onAllowClick,
)
if (state.bottomSheetVariant.not()) {
if (state.isBottomSheetVariant.not()) {
SpacerH12()
SecondaryButton(
@ -199,7 +199,7 @@ private fun Preview() {
private fun PreviewBS() {
TangemThemePreview {
AskBiometry(
state = AskBiometryUM(bottomSheetVariant = true),
state = AskBiometryUM(isBottomSheetVariant = true),
)
}
}

View file

@ -3,8 +3,8 @@ package com.tangem.features.biometry.impl.ui.state
import com.tangem.core.ui.extensions.TextReference
internal data class AskBiometryUM(
val bottomSheetVariant: Boolean = false,
val showProgress: Boolean = false,
val isBottomSheetVariant: Boolean = false,
val shouldShowProgress: Boolean = false,
val error: TextReference? = null,
val onAllowClick: () -> Unit = {},
val onDontAllowClick: () -> Unit = {},

View file

@ -2,6 +2,8 @@ package com.tangem.features.createwalletselection
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.navigation.Router
@ -9,8 +11,6 @@ import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.components.label.entity.LabelStyle
import com.tangem.core.ui.components.label.entity.LabelUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.card.analytics.IntroductionProcess
import com.tangem.domain.card.analytics.Shop
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
import com.tangem.features.createwalletselection.entity.CreateWalletSelectionUM
import com.tangem.features.createwalletselection.impl.R
@ -101,8 +101,7 @@ internal class CreateWalletSelectionModel @Inject constructor(
}
private fun onBuyClick() {
analyticsEventHandler.send(IntroductionProcess.ButtonBuyCards)
analyticsEventHandler.send(Shop.ScreenOpened)
analyticsEventHandler.send(Basic.ButtonBuy(source = AnalyticsParam.ScreensSources.AddNewWallet))
modelScope.launch {
generateBuyTangemCardLinkUseCase.invoke().let { urlOpener.openUrl(it) }
}

View file

@ -1,9 +0,0 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>BooleanPropertyNaming:CreateWalletStartUM.kt$CreateWalletStartUM$val showScanSecondaryButton: Boolean</ID>
<ID>MultilineLambdaItParameter:CreateWalletStartContent.kt${ FeatureItem( iconResId = it.iconResId, text = it.text, ) }</ID>
<ID>MultilineLambdaItParameter:CreateWalletStartModel.kt$CreateWalletStartModel${ delay(HIDE_PROGRESS_DELAY) setLoading(false) when (it) { is SaveWalletError.DataError -&gt; Timber.e(it.toString(), "Unable to save user wallet") is SaveWalletError.WalletAlreadySaved -&gt; { userWalletsListRepository.unlock( userWalletId = userWallet.walletId, unlockMethod = UserWalletsListRepository.UnlockMethod.Scan(scanResponse), ).onRight { appRouter.replaceAll(AppRoute.Wallet) } } } }</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -6,8 +6,7 @@ import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic.SignedIn
import com.tangem.core.analytics.models.Basic.SignedIn.SignInType
import com.tangem.core.analytics.models.Basic
import com.tangem.core.decompose.di.GlobalUiMessageSender
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
@ -19,8 +18,7 @@ import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.DialogMessage
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.analytics.ParamCardCurrencyConverter
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.card.analytics.IntroductionProcess
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.common.wallets.error.SaveWalletError
@ -51,7 +49,6 @@ internal class CreateWalletStartModel @Inject constructor(
private val scanCardProcessor: ScanCardProcessor,
private val cardSdkConfigRepository: CardSdkConfigRepository,
private val settingsRepository: SettingsRepository,
private val analyticsEventHandler: AnalyticsEventHandler,
private val appRouter: AppRouter,
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
private val saveWalletUseCase: SaveWalletUseCase,
@ -59,6 +56,7 @@ internal class CreateWalletStartModel @Inject constructor(
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase,
private val urlOpener: UrlOpener,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() {
private val params = paramsContainer.require<CreateWalletStartComponent.Params>()
@ -84,7 +82,7 @@ internal class CreateWalletStartModel @Inject constructor(
),
),
imageResId = R.drawable.img_hardware_wallet,
showScanSecondaryButton = true,
shouldShowScanSecondaryButton = true,
onPrimaryButtonClick = ::onBuyClick,
primaryButtonText = resourceReference(R.string.details_buy_wallet),
otherMethodTitle = resourceReference(R.string.welcome_create_wallet_mobile_title),
@ -112,7 +110,7 @@ internal class CreateWalletStartModel @Inject constructor(
),
),
imageResId = R.drawable.img_mobile_wallet,
showScanSecondaryButton = false,
shouldShowScanSecondaryButton = false,
onPrimaryButtonClick = ::onStartWithMobileWalletClick,
primaryButtonText = resourceReference(R.string.welcome_create_wallet_mobile_title),
otherMethodTitle = resourceReference(R.string.welcome_create_wallet_use_hardware_title),
@ -126,6 +124,9 @@ internal class CreateWalletStartModel @Inject constructor(
)
private fun onScanClick() {
analyticsEventHandler.send(
event = IntroductionProcess.ButtonScanCard(AnalyticsParam.ScreensSources.CreateNewWallet),
)
scanCard()
}
@ -134,6 +135,7 @@ internal class CreateWalletStartModel @Inject constructor(
}
private fun onBuyClick() {
analyticsEventHandler.send(Basic.ButtonBuy(source = AnalyticsParam.ScreensSources.CreateNewWallet))
modelScope.launch {
generateBuyTangemCardLinkUseCase.invoke().let { urlOpener.openUrl(it) }
}
@ -182,11 +184,11 @@ internal class CreateWalletStartModel @Inject constructor(
}
saveWalletUseCase(userWallet = userWallet).fold(
ifLeft = {
ifLeft = { error ->
delay(HIDE_PROGRESS_DELAY)
setLoading(false)
when (it) {
is SaveWalletError.DataError -> Timber.e(it.toString(), "Unable to save user wallet")
when (error) {
is SaveWalletError.DataError -> Timber.e(error.toString(), "Unable to save user wallet")
is SaveWalletError.WalletAlreadySaved -> {
userWalletsListRepository.unlock(
userWalletId = userWallet.walletId,
@ -199,28 +201,11 @@ internal class CreateWalletStartModel @Inject constructor(
},
ifRight = {
setLoading(false)
sendSignedInCardAnalyticsEvent(scanResponse = scanResponse, isImported = userWallet.isImported)
appRouter.replaceAll(AppRoute.Wallet)
},
)
}
private suspend fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse, isImported: Boolean) {
val currency = ParamCardCurrencyConverter().convert(value = scanResponse.cardTypesResolver)
if (currency != null) {
analyticsEventHandler.send(
SignedIn(
currency = currency,
batch = scanResponse.card.batchId,
signInType = SignInType.Card,
walletsCount = userWalletsListRepository.userWalletsSync().size.toString(),
isImported = isImported,
hasBackup = scanResponse.card.backupStatus?.isActive,
),
)
}
}
private fun setLoading(isLoading: Boolean) {
uiState.update { it.copy(isScanInProgress = isLoading) }
}

View file

@ -9,7 +9,7 @@ internal data class CreateWalletStartUM(
val featureItems: ImmutableList<FeatureItem>,
val imageResId: Int,
val isScanInProgress: Boolean,
val showScanSecondaryButton: Boolean,
val shouldShowScanSecondaryButton: Boolean,
val primaryButtonText: TextReference,
val onPrimaryButtonClick: () -> Unit,
val otherMethodDescription: TextReference,

View file

@ -120,10 +120,10 @@ internal fun CreateWalletStartContent(state: CreateWalletStartUM, modifier: Modi
horizontalArrangement = Arrangement.Center,
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
state.featureItems.forEach {
state.featureItems.forEach { item ->
FeatureItem(
iconResId = it.iconResId,
text = it.text,
iconResId = item.iconResId,
text = item.text,
)
}
}
@ -143,7 +143,7 @@ internal fun CreateWalletStartContent(state: CreateWalletStartUM, modifier: Modi
)
},
bottomContent = {
if (state.showScanSecondaryButton) {
if (state.shouldShowScanSecondaryButton) {
SecondaryButtonIconEnd(
modifier = Modifier
.fillMaxWidth()
@ -232,7 +232,7 @@ internal fun CreateWalletStartContent(state: CreateWalletStartUM, modifier: Modi
minImageHeight = 160.dp,
)
}
if (!state.showScanSecondaryButton) {
if (!state.shouldShowScanSecondaryButton) {
FlowRow(
modifier = Modifier
.wrapContentWidth()
@ -425,7 +425,7 @@ private class CreateWalletStartStateProvider : CollectionPreviewParameterProvide
),
),
imageResId = R.drawable.img_hardware_wallet,
showScanSecondaryButton = true,
shouldShowScanSecondaryButton = true,
onPrimaryButtonClick = { },
primaryButtonText = resourceReference(R.string.details_buy_wallet),
otherMethodTitle = resourceReference(R.string.welcome_create_wallet_mobile_title),
@ -455,7 +455,7 @@ private class CreateWalletStartStateProvider : CollectionPreviewParameterProvide
),
),
imageResId = R.drawable.img_mobile_wallet,
showScanSecondaryButton = false,
shouldShowScanSecondaryButton = false,
onPrimaryButtonClick = { },
primaryButtonText = resourceReference(R.string.welcome_create_wallet_mobile_title),
otherMethodTitle = resourceReference(R.string.welcome_create_wallet_use_hardware_title),

View file

@ -9,7 +9,6 @@
<ID>MultilineLambdaItParameter:DetailsModel.kt$DetailsModel${ it.copy( selectFeedbackEmailTypeBSConfig = it.selectFeedbackEmailTypeBSConfig.copy(isShown = false), ) }</ID>
<ID>MultilineLambdaItParameter:PreviewUserWalletListComponent.kt$PreviewUserWalletListComponent${ it.copy( balance = UserWalletItemUM.Balance.Loaded( value = "1.000 BTC", isFlickering = true, ), ) }</ID>
<ID>MultilineLambdaItParameter:UserWalletSaver.kt$UserWalletSaver${ val message = it.message if (!message.isNullOrEmpty()) { messageSender.send(SnackbarMessage(message)) } }</ID>
<ID>NonBooleanPropertyPrefixedWithIs:UserWalletListModel.kt$UserWalletListModel$private val isWalletSavingInProgress: MutableStateFlow&lt;Boolean&gt; = MutableStateFlow(value = false)</ID>
<ID>RedundantSuspendModifier:UserWalletSaver.kt$UserWalletSaver$suspend</ID>
<ID>UnnecessaryLet:ItemsBuilder.kt$ItemsBuilder$let(::add)</ID>
</CurrentIssues>

View file

@ -4,6 +4,9 @@ import android.content.res.Resources
import arrow.core.getOrElse
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.AppInstanceIdProvider
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -13,13 +16,14 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.domain.card.common.TapWorkarounds.isVisa
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.WalletMetaInfo
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.feedback.models.WalletMetaInfo
import com.tangem.domain.feedback.repository.FeedbackFeatureToggles
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.redux.LegacyAction
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.walletconnect.CheckIsWalletConnectAvailableUseCase
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.features.details.component.DetailsComponent
@ -29,6 +33,7 @@ import com.tangem.features.details.entity.DetailsUM
import com.tangem.features.details.entity.SelectEmailFeedbackTypeBS
import com.tangem.features.details.utils.ItemsBuilder
import com.tangem.features.details.utils.SocialsBuilder
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.version.AppVersionProvider
import kotlinx.collections.immutable.ImmutableList
@ -60,6 +65,9 @@ internal class DetailsModel @Inject constructor(
private val getWalletsUseCase: GetWalletsUseCase,
private val feedbackFeatureToggles: FeedbackFeatureToggles,
override val dispatchers: CoroutineDispatcherProvider,
private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() {
private val params: DetailsComponent.Params = paramsContainer.require()
@ -216,7 +224,12 @@ internal class DetailsModel @Inject constructor(
private fun onBuyClick() {
modelScope.launch {
urlOpener.openUrl(buildBuyLink())
if (hotWalletFeatureToggles.isHotWalletEnabled) {
analyticsEventHandler.send(Basic.ButtonBuy(source = AnalyticsParam.ScreensSources.Settings))
generateBuyTangemCardLinkUseCase.invoke().let { urlOpener.openUrl(it) }
} else {
urlOpener.openUrl(buildBuyLink())
}
}
}

View file

@ -6,9 +6,6 @@
<ID>BooleanPropertyNaming:HomeUM.kt$HomeUM$val scanInProgress: Boolean</ID>
<ID>MultilineLambdaItParameter:HomeModel.kt$HomeModel${ delay(HIDE_PROGRESS_DELAY) setLoading(false) when (it) { is SaveWalletError.DataError -&gt; Timber.e(it.toString(), "Unable to save user wallet") is SaveWalletError.WalletAlreadySaved -&gt; appRouter.replaceAll(AppRoute.Wallet) } }</ID>
<ID>MultilineLambdaItParameter:StoriesProgressBar.kt${ when (index) { currentStep -&gt; it.fillMaxWidth(progress.value) in 0 until currentStep -&gt; it.fillMaxWidth(fraction = 1f) else -&gt; it } }</ID>
<ID>NonBooleanPropertyPrefixedWithIs:StoriesAnimation.kt$val isFirstStepLaunched = remember { mutableStateOf(false) }</ID>
<ID>NonBooleanPropertyPrefixedWithIs:StoriesAnimation.kt$val isLaunched = remember { mutableStateOf(false) }</ID>
<ID>NonBooleanPropertyPrefixedWithIs:StoriesAnimation.kt$val isSecondStepLaunched = remember { mutableStateOf(false) }</ID>
<ID>ReusedModifierInstance:HomeButtonsV2.kt$StoriesButton( modifier = modifier, text = stringResourceSafe(id = R.string.common_get_started), useDarkerColors = false, onClick = onGetStartedClick, )</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -8,8 +8,8 @@ import com.tangem.common.routing.AppRouter
import com.tangem.common.routing.entity.InitScreenLaunchMode
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic.SignedIn
import com.tangem.core.analytics.models.Basic.SignedIn.SignInType
import com.tangem.core.analytics.models.Basic.SignedInLegacy
import com.tangem.core.analytics.models.Basic.SignedInLegacy.SignInType
import com.tangem.core.decompose.di.GlobalUiMessageSender
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
@ -127,7 +127,7 @@ internal class HomeModel @Inject constructor(
}
private fun onScanClick() {
analyticsEventHandler.send(IntroductionProcess.ButtonScanCard)
analyticsEventHandler.send(IntroductionProcess.ButtonScanCardLegacy)
scanCard()
}
@ -212,7 +212,7 @@ internal class HomeModel @Inject constructor(
val currency = ParamCardCurrencyConverter().convert(value = scanResponse.cardTypesResolver)
if (currency != null) {
analyticsEventHandler.send(
SignedIn(
SignedInLegacy(
currency = currency,
batch = scanResponse.card.batchId,
signInType = SignInType.Card,

View file

@ -17,11 +17,8 @@
<ID>BooleanPropertyNaming:ManualBackupCheckUM.kt$ManualBackupCheckUM$val completeButtonProgress: Boolean</ID>
<ID>BooleanPropertyNaming:ManualBackupCheckUM.kt$ManualBackupCheckUM.WordField$val error: Boolean</ID>
<ID>BooleanPropertyNaming:MobileWalletSetupFinishedContent.kt$var showConfetti by remember { mutableStateOf(false) }</ID>
<ID>BooleanPropertyNaming:UpgradeWalletModel.kt$UpgradeWalletModel$val otherWalletAndAlreadyCreated by lazy { userWallet?.walletId != params.userWalletId &amp;&amp; it.card.wallets.map { it.curve }.toSet().isNotEmpty() }</ID>
<ID>BooleanPropertyNaming:UpgradeWalletModel.kt$UpgradeWalletModel$val sameWalletButNotFinishedBackup by lazy { userWallet?.walletId == params.userWalletId &amp;&amp; BackupValidator.isValidFull(it.card).not() }</ID>
<ID>BooleanPropertyNaming:WalletBackupUM.kt$WalletBackupUM$val backedUp: Boolean</ID>
<ID>BooleanPropertyNaming:WalletHardwareBackupUM.kt$WalletHardwareBackupUM$val showPurchaseBlock: Boolean = false</ID>
<ID>MaxChainedCallsOnSameLine:UpgradeWalletModel.kt$UpgradeWalletModel$it.card.wallets.map { it.curve }.toSet().isNotEmpty()</ID>
<ID>MultilineLambdaItParameter:AddExistingWalletImportModel.kt$AddExistingWalletImportModel${ Timber.e(it) setImportProgress(false) }</ID>
<ID>MultilineLambdaItParameter:AddExistingWalletImportModel.kt$AddExistingWalletImportModel${ setImportProgress(false) when (it) { is SaveWalletError.DataError -&gt; Timber.e(it.toString(), "Unable to save user wallet") is SaveWalletError.WalletAlreadySaved -&gt; { uiMessageSender.send( SnackbarMessage(resourceReference(R.string.hw_import_seed_phrase_already_imported)), ) } } }</ID>
<ID>MultilineLambdaItParameter:CreateHardwareWalletModel.kt$CreateHardwareWalletModel${ delay(HIDE_PROGRESS_DELAY) setLoading(false) when (it) { is SaveWalletError.DataError -&gt; Timber.e(it.toString(), "Unable to save user wallet") is SaveWalletError.WalletAlreadySaved -&gt; { userWalletsListRepository.unlock( userWalletId = userWallet.walletId, unlockMethod = UserWalletsListRepository.UnlockMethod.Scan(scanResponse), ).onRight { router.replaceAll(AppRoute.Wallet) } } } }</ID>
@ -48,7 +45,6 @@
<ID>MultilineLambdaItParameter:ManualBackupCheckModel.kt$ManualBackupCheckModel${ it.copy( words = seedPhrasePrivateInfo.mnemonic.mnemonicComponents.filterIndexed { index, _ -&gt; WORD_FIELD_INDICES.contains(index + 1) }.toImmutableList(), ) }</ID>
<ID>MultilineLambdaItParameter:ManualBackupPhraseContent.kt${ EnumeratedTwoColumnGridItem( index = it + 1, mnemonic = "word${it + 1}", ) }</ID>
<ID>MultilineLambdaItParameter:ManualBackupPhraseModel.kt$ManualBackupPhraseModel${ it.copy( words = seedPhrasePrivateInfo.mnemonic.mnemonicComponents.mapIndexed { index, s -&gt; EnumeratedTwoColumnGridItem(index + 1, s) }.toImmutableList(), ) }</ID>
<ID>MultilineLambdaItParameter:UpgradeWalletModel.kt$UpgradeWalletModel${ // Check if user attempted to upgrade before but something went wrong and a full reset is required val userWallet = coldUserWalletBuilderFactory.create(it).build() val sameWalletButNotFinishedBackup by lazy { userWallet?.walletId == params.userWalletId &amp;&amp; BackupValidator.isValidFull(it.card).not() } val otherWalletAndAlreadyCreated by lazy { userWallet?.walletId != params.userWalletId &amp;&amp; it.card.wallets.map { it.curve }.toSet().isNotEmpty() } if (userWallet != null &amp;&amp; (sameWalletButNotFinishedBackup || otherWalletAndAlreadyCreated)) { startResetCardsFlow.emit(userWallet) return@doOnSuccess } delay(DELAY_SDK_DIALOG_CLOSE) tangemSdkManager.changeDisplayedCardIdNumbersCount(it) navigateToUpgradeFlow(it) }</ID>
<ID>MultilineLambdaItParameter:ViewPhraseContent.kt${ EnumeratedTwoColumnGridItem( index = it + 1, mnemonic = "word${it + 1}", ) }</ID>
<ID>MultilineLambdaItParameter:ViewPhraseModel.kt$ViewPhraseModel${ it.copy( words = words.mapIndexed { index, s -&gt; EnumeratedTwoColumnGridItem(index + 1, s) }.toImmutableList(), ) }</ID>
<ID>NoNameShadowing:ManualBackupCheckModel.kt$ManualBackupCheckModel${ it.copy(completeButtonProgress = false) }</ID>

View file

@ -28,7 +28,6 @@ import com.tangem.hot.sdk.model.HotAuth
import com.tangem.hot.sdk.model.HotWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -152,11 +151,6 @@ internal class AccessCodeModel @Inject constructor(
tryToAskForBiometry()
userWalletsListRepository.saveWithoutLock(
userWallet.copy(backedUp = true),
canOverride = true,
)
userWalletsListRepository.setLock(
userWallet.walletId,
UserWalletsListRepository.LockMethod.AccessCode(accessCode.toCharArray()),
@ -179,30 +173,25 @@ internal class AccessCodeModel @Inject constructor(
hotWalletAccessor.unlockContextual(userWallet.hotWalletId)
}
launch(NonCancellable) {
var updatedHotWalletId = tangemHotSdk.changeAuth(
var updatedHotWalletId = tangemHotSdk.changeAuth(
unlockHotWallet = unlockHotWallet,
auth = HotAuth.Password(accessCode.toCharArray()),
)
if (walletsRepository.requireAccessCode().not()) {
updatedHotWalletId = tangemHotSdk.changeAuth(
unlockHotWallet = unlockHotWallet,
auth = HotAuth.Password(accessCode.toCharArray()),
auth = HotAuth.Biometry,
)
if (walletsRepository.requireAccessCode().not()) {
updatedHotWalletId = tangemHotSdk.changeAuth(
unlockHotWallet = unlockHotWallet,
auth = HotAuth.Biometry,
)
}
userWalletsListRepository.saveWithoutLock(
userWallet.copy(
hotWalletId = updatedHotWalletId,
backedUp = true,
),
canOverride = true,
)
clearHotWalletContextualUnlockUseCase.invoke(params.userWalletId)
}
userWalletsListRepository.saveWithoutLock(
userWallet.copy(hotWalletId = updatedHotWalletId),
canOverride = true,
)
clearHotWalletContextualUnlockUseCase.invoke(params.userWalletId)
params.callbacks.onAccessCodeUpdated(params.userWalletId)
}
}

View file

@ -15,7 +15,6 @@ internal class AddExistingWalletStepperStateManager {
title = resourceReference(R.string.wallet_import_seed_navtitle),
showBackButton = true,
showSkipButton = false,
showFeedbackButton = true,
)
is AddExistingWalletRoute.BackupCompleted -> HotWalletStepperComponent.StepperUM(
@ -24,7 +23,6 @@ internal class AddExistingWalletStepperStateManager {
title = resourceReference(R.string.wallet_import_title),
showBackButton = false,
showSkipButton = false,
showFeedbackButton = false,
)
is AddExistingWalletRoute.SetAccessCode -> HotWalletStepperComponent.StepperUM(
@ -33,7 +31,6 @@ internal class AddExistingWalletStepperStateManager {
title = resourceReference(R.string.access_code_navtitle),
showBackButton = false,
showSkipButton = true,
showFeedbackButton = false,
)
is AddExistingWalletRoute.ConfirmAccessCode -> HotWalletStepperComponent.StepperUM(
@ -42,7 +39,6 @@ internal class AddExistingWalletStepperStateManager {
title = resourceReference(R.string.access_code_navtitle),
showBackButton = true,
showSkipButton = true,
showFeedbackButton = false,
)
is AddExistingWalletRoute.PushNotifications -> HotWalletStepperComponent.StepperUM(
@ -51,7 +47,6 @@ internal class AddExistingWalletStepperStateManager {
title = resourceReference(R.string.onboarding_title_notifications),
showBackButton = false,
showSkipButton = false,
showFeedbackButton = false,
)
is AddExistingWalletRoute.SetupFinished -> HotWalletStepperComponent.StepperUM(
@ -60,7 +55,6 @@ internal class AddExistingWalletStepperStateManager {
title = resourceReference(R.string.common_done),
showBackButton = false,
showSkipButton = false,
showFeedbackButton = false,
)
}
}

View file

@ -5,8 +5,7 @@ import com.tangem.common.core.TangemSdkError
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic.SignedIn
import com.tangem.core.analytics.models.Basic.SignedIn.SignInType
import com.tangem.core.analytics.models.Basic
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.navigation.Router
@ -16,8 +15,7 @@ import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.DialogMessage
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.analytics.ParamCardCurrencyConverter
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.card.analytics.IntroductionProcess
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.common.wallets.error.SaveWalletError
@ -74,12 +72,16 @@ internal class CreateHardwareWalletModel @Inject constructor(
}
private fun onBuyTangemWalletClick() {
analyticsEventHandler.send(Basic.ButtonBuy(source = AnalyticsParam.ScreensSources.CreateWallet))
modelScope.launch {
generateBuyTangemCardLinkUseCase.invoke().let { urlOpener.openUrl(it) }
}
}
private fun onScanDeviceClick() {
analyticsEventHandler.send(
event = IntroductionProcess.ButtonScanCard(AnalyticsParam.ScreensSources.CreateWallet),
)
scanCard()
}
@ -143,28 +145,11 @@ internal class CreateHardwareWalletModel @Inject constructor(
},
ifRight = {
setLoading(false)
sendSignedInCardAnalyticsEvent(scanResponse = scanResponse, isImported = userWallet.isImported)
router.replaceAll(AppRoute.Wallet)
},
)
}
private suspend fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse, isImported: Boolean) {
val currency = ParamCardCurrencyConverter().convert(value = scanResponse.cardTypesResolver)
if (currency != null) {
analyticsEventHandler.send(
SignedIn(
currency = currency,
batch = scanResponse.card.batchId,
signInType = SignInType.Card,
walletsCount = userWalletsListRepository.userWalletsSync().size.toString(),
isImported = isImported,
hasBackup = scanResponse.card.backupStatus?.isActive,
),
)
}
}
private fun setLoading(isLoading: Boolean) {
uiState.update { it.copy(isScanInProgress = isLoading) }
}

View file

@ -3,9 +3,12 @@ package com.tangem.features.hotwallet.manualbackup.start.ui
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
@ -22,8 +25,8 @@ import com.tangem.features.hotwallet.manualbackup.start.entity.ManualBackupStart
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun ManualBackupStartContent(state: ManualBackupStartUM, modifier: Modifier = Modifier) {
Column(
modifier = modifier
Box(
modifier
.background(TangemTheme.colors.background.primary)
.fillMaxSize()
.padding(
@ -33,53 +36,57 @@ internal fun ManualBackupStartContent(state: ManualBackupStartUM, modifier: Modi
bottom = 16.dp,
),
) {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = 16.dp,
vertical = 8.dp,
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = 16.dp,
vertical = 8.dp,
),
text = stringResourceSafe(R.string.backup_info_title),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
)
Text(
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = 16.dp,
vertical = 8.dp,
),
text = stringResourceSafe(
R.string.backup_info_description,
state.seepPhraseLength.toString(),
),
text = stringResourceSafe(R.string.backup_info_title),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
)
Text(
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = 16.dp,
vertical = 8.dp,
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
)
FeatureBlock(
modifier = Modifier
.padding(top = 24.dp),
title = stringResourceSafe(R.string.backup_info_save_title),
description = stringResourceSafe(
R.string.backup_info_save_description,
state.seepPhraseLength.toString(),
),
text = stringResourceSafe(
R.string.backup_info_description,
state.seepPhraseLength.toString(),
),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
)
FeatureBlock(
modifier = Modifier
.padding(top = 24.dp),
title = stringResourceSafe(R.string.backup_info_save_title),
description = stringResourceSafe(
R.string.backup_info_save_description,
state.seepPhraseLength.toString(),
),
iconRes = R.drawable.ic_lock_24,
)
FeatureBlock(
modifier = Modifier
.padding(top = 24.dp),
title = stringResourceSafe(R.string.backup_info_keep_title),
description = stringResourceSafe(R.string.backup_info_keep_description),
iconRes = R.drawable.ic_settings_24,
)
Spacer(modifier = Modifier.weight(1f))
iconRes = R.drawable.ic_lock_24,
)
FeatureBlock(
modifier = Modifier
.padding(top = 24.dp),
title = stringResourceSafe(R.string.backup_info_keep_title),
description = stringResourceSafe(R.string.backup_info_keep_description),
iconRes = R.drawable.ic_settings_24,
)
Spacer(modifier = Modifier.weight(1f))
}
PrimaryButton(
modifier = Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth()
.padding(top = 16.dp),
text = stringResourceSafe(R.string.common_continue),

View file

@ -14,7 +14,6 @@ interface HotWalletStepperComponent : ComposableContentComponent {
val title: TextReference,
val showBackButton: Boolean,
val showSkipButton: Boolean,
val showFeedbackButton: Boolean,
) {
companion object {
fun initialState() = StepperUM(
@ -23,7 +22,6 @@ interface HotWalletStepperComponent : ComposableContentComponent {
title = TextReference.EMPTY,
showBackButton = false,
showSkipButton = false,
showFeedbackButton = false,
)
}
}

View file

@ -34,7 +34,6 @@ internal class DefaultHotWalletStepperComponent @AssistedInject constructor(
modifier = modifier,
onBackClick = model::onBackClick,
onSkipClick = model::onSkipClick,
onFeedbackClick = model::onFeedbackClick,
)
}

View file

@ -34,9 +34,4 @@ internal class HotWalletStepperModel @Inject constructor(
// TODO send analytics
params.callback.onSkipClick()
}
fun onFeedbackClick() {
// TODO send analytics
// openFeedback()
}
}

View file

@ -31,7 +31,6 @@ internal fun HotWalletStepper(
state: HotWalletStepperComponent.StepperUM,
onBackClick: () -> Unit,
onSkipClick: () -> Unit,
onFeedbackClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val fraction = state.currentStep.toFloat() / state.steps.coerceAtLeast(1)
@ -47,20 +46,16 @@ internal fun HotWalletStepper(
} else {
null
},
endButton = when {
state.showSkipButton -> TopAppBarButtonUM.Text(
endButton = if (state.showSkipButton) {
TopAppBarButtonUM.Text(
text = resourceReference(R.string.common_skip),
onClicked = onSkipClick,
)
state.showFeedbackButton -> TopAppBarButtonUM.Icon(
iconRes = R.drawable.ic_chat_24,
onClicked = onFeedbackClick,
)
else -> null
} else {
null
},
title = state.title,
containerColor = TangemTheme.colors.background.primary,
modifier = modifier,
titleAlignment = Alignment.CenterHorizontally,
)
@ -95,11 +90,9 @@ private fun HotWalletStepper_Preview() {
title = resourceReference(R.string.common_done),
showBackButton = true,
showSkipButton = false,
showFeedbackButton = true,
),
onBackClick = {},
onSkipClick = {},
onFeedbackClick = {},
)
}
}

View file

@ -7,6 +7,8 @@ import com.tangem.common.doOnResult
import com.tangem.common.doOnSuccess
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -18,7 +20,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.toWrappedList
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessageAction
import com.tangem.domain.card.BackupValidator
import com.tangem.domain.card.analytics.IntroductionProcess
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
@ -86,12 +88,14 @@ internal class UpgradeWalletModel @Inject constructor(
}
private fun onBuyTangemWalletClick() {
analyticsEventHandler.send(Basic.ButtonBuy(source = AnalyticsParam.ScreensSources.Upgrade))
modelScope.launch {
generateBuyTangemCardLinkUseCase.invoke().let { urlOpener.openUrl(it) }
}
}
private fun onContinueClick() {
analyticsEventHandler.send(IntroductionProcess.ButtonScanCard(AnalyticsParam.ScreensSources.Upgrade))
analyticsEventHandler.send(WalletSettingsAnalyticEvents.ButtonStartUpgrade)
scanCard()
}
@ -131,12 +135,9 @@ internal class UpgradeWalletModel @Inject constructor(
scanResponse: ScanResponse,
onSuccess: suspend () -> Unit,
) {
// Check if user attempted to upgrade before but something went wrong and a full reset is required
val userWallet = coldUserWalletBuilderFactory.create(scanResponse).build()
val isSameWalletButNotFinishedBackup = userWallet?.walletId == params.userWalletId &&
BackupValidator.isValidFull(scanResponse.card).not()
if (userWallet != null && isSameWalletButNotFinishedBackup) {
if (userWallet?.walletId == params.userWalletId) {
startResetCardsFlow.emit(userWallet)
return
}

View file

@ -16,7 +16,6 @@ internal class WalletActivationStepperStateManager @Inject constructor() {
title = resourceReference(R.string.common_backup),
showBackButton = true,
showSkipButton = false,
showFeedbackButton = true,
)
is WalletActivationRoute.ManualBackupPhrase -> HotWalletStepperComponent.StepperUM(
currentStep = STEP_BACKUP_PHRASE,
@ -24,7 +23,6 @@ internal class WalletActivationStepperStateManager @Inject constructor() {
title = resourceReference(R.string.common_backup),
showBackButton = true,
showSkipButton = false,
showFeedbackButton = true,
)
is WalletActivationRoute.ManualBackupCheck -> HotWalletStepperComponent.StepperUM(
currentStep = STEP_BACKUP_CHECK,
@ -32,7 +30,6 @@ internal class WalletActivationStepperStateManager @Inject constructor() {
title = resourceReference(R.string.common_backup),
showBackButton = true,
showSkipButton = false,
showFeedbackButton = true,
)
is WalletActivationRoute.ManualBackupCompleted -> HotWalletStepperComponent.StepperUM(
currentStep = STEP_BACKUP_COMPLETED,
@ -40,7 +37,6 @@ internal class WalletActivationStepperStateManager @Inject constructor() {
title = resourceReference(R.string.common_backup),
showBackButton = false,
showSkipButton = false,
showFeedbackButton = false,
)
is WalletActivationRoute.SetAccessCode -> HotWalletStepperComponent.StepperUM(
currentStep = STEP_ACCESS_CODE,
@ -48,7 +44,6 @@ internal class WalletActivationStepperStateManager @Inject constructor() {
title = resourceReference(R.string.access_code_navtitle),
showBackButton = false,
showSkipButton = true,
showFeedbackButton = false,
)
is WalletActivationRoute.ConfirmAccessCode -> HotWalletStepperComponent.StepperUM(
currentStep = STEP_ACCESS_CODE,
@ -56,7 +51,6 @@ internal class WalletActivationStepperStateManager @Inject constructor() {
title = resourceReference(R.string.access_code_navtitle),
showBackButton = true,
showSkipButton = true,
showFeedbackButton = false,
)
is WalletActivationRoute.PushNotifications -> HotWalletStepperComponent.StepperUM(
currentStep = STEP_NOTIFICATIONS,
@ -64,7 +58,6 @@ internal class WalletActivationStepperStateManager @Inject constructor() {
title = resourceReference(R.string.onboarding_title_notifications),
showBackButton = false,
showSkipButton = false,
showFeedbackButton = false,
)
is WalletActivationRoute.SetupFinished -> HotWalletStepperComponent.StepperUM(
currentStep = STEP_DONE,
@ -72,7 +65,6 @@ internal class WalletActivationStepperStateManager @Inject constructor() {
title = resourceReference(R.string.common_done),
showBackButton = false,
showSkipButton = false,
showFeedbackButton = false,
)
}
}

View file

@ -3,6 +3,7 @@ package com.tangem.features.hotwallet.walletbackup.model
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -111,6 +112,7 @@ internal class WalletBackupModel @Inject constructor(
)
private fun onBuyClick() {
analyticsEventHandler.send(Basic.ButtonBuy(source = AnalyticsParam.ScreensSources.Backup))
modelScope.launch {
generateBuyTangemCardLinkUseCase.invoke().let { urlOpener.openUrl(it) }
}

View file

@ -4,6 +4,7 @@ import arrow.core.getOrElse
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -160,6 +161,7 @@ internal class WalletHardwareBackupModel @Inject constructor(
}
private fun onBuyClick() {
analyticsEventHandler.send(Basic.ButtonBuy(source = AnalyticsParam.ScreensSources.HardwareWallet))
modelScope.launch {
generateBuyTangemCardLinkUseCase.invoke().let { urlOpener.openUrl(it) }
}

View file

@ -37,7 +37,6 @@
<ID>MultilineLambdaItParameter:AddToPortfolioBSContentUMFactory.kt$AddToPortfolioBSContentUMFactory${ if (it != selectedWalletId) { onAnotherWalletSelect(it) onWalletSelectorVisibilityChange(false) } }</ID>
<ID>MultilineLambdaItParameter:AddToPortfolioBottomSheet.kt${ Content( modifier = Modifier.fillMaxWidth(), state = it, ) WalletSelectorBottomSheet(it.walletSelectorConfig) }</ID>
<ID>MultilineLambdaItParameter:AddToPortfolioManager.kt$AddToPortfolioManager${ it.toMutableMap().apply { this[userWalletId] = if (isAddAction) { this[userWalletId].orEmpty() + network } else { this[userWalletId].orEmpty() - network } } }</ID>
<ID>MultilineLambdaItParameter:AddToPortfolioModel.kt$AddToPortfolioModel${ PortfolioData.CryptoCurrencyData( userWallet = selectedPortfolio.userWallet, status = addedToken, actions = it.states, ) }</ID>
<ID>MultilineLambdaItParameter:AddToPortfolioModel.kt$AddToPortfolioModel${ Timber.e(it) params.callback.onDismiss() }</ID>
<ID>MultilineLambdaItParameter:AddToPortfolioModel.kt$AddToPortfolioModel${ tokenActionsData.emit(it) navigation.replaceAll(AddToPortfolioRoutes.TokenActions) }</ID>
<ID>MultilineLambdaItParameter:AddTokenModel.kt$AddTokenModel${ processError(error = it) uiState.value = um.toggleProgress(false) return@launch }</ID>
@ -118,12 +117,6 @@
<ID>NoNameShadowing:MarketsTokenDetailsModel.kt$MarketsTokenDetailsModel${ it.copy( chartState = it.chartState.copy( status = MarketsTokenDetailsUM.ChartState.Status.ERROR, ), body = if (it.body is MarketsTokenDetailsUM.Body.Error) { MarketsTokenDetailsUM.Body.Nothing } else { it.body }, ) }</ID>
<ID>NoNameShadowing:MyPortfolioUMFactory.kt$MyPortfolioUMFactory${ networkIds.contains(it.status.currency.network.backendId) }</ID>
<ID>NoNameShadowing:NewMarketsPortfolioDelegate.kt$NewMarketsPortfolioDelegate$portfolio</ID>
<ID>NonBooleanPropertyPrefixedWithIs:MarketsListBatchFlowManager.kt$MarketsListBatchFlowManager$val isInInitialLoadingErrorState = batchFlow.state .map { it.status is PaginationStatus.InitialLoadingError } .distinctUntilChanged() .stateIn( scope = modelScope, started = SharingStarted.Eagerly, initialValue = false, )</ID>
<ID>NonBooleanPropertyPrefixedWithIs:MarketsListBatchFlowManager.kt$MarketsListBatchFlowManager$val isSearchNotFoundState = batchFlow.state .map { currentSearchText().isNullOrEmpty().not() &amp;&amp; it.status is PaginationStatus.EndOfPagination &amp;&amp; it.data.isEmpty() } .distinctUntilChanged() .stateIn( scope = modelScope, started = SharingStarted.Eagerly, initialValue = false, )</ID>
<ID>NonBooleanPropertyPrefixedWithIs:MarketsListModel.kt$MarketsListModel$val isVisibleOnScreen = MutableStateFlow(false)</ID>
<ID>NonBooleanPropertyPrefixedWithIs:MarketsListUMStateManager.kt$MarketsListUMStateManager$val isInSearchStateFlow = state.map { it.searchBar.isActive }.distinctUntilChanged()</ID>
<ID>NonBooleanPropertyPrefixedWithIs:MarketsTokenDetailsModel.kt$MarketsTokenDetailsModel$val isVisibleOnScreen = MutableStateFlow(false)</ID>
<ID>NonBooleanPropertyPrefixedWithIs:TokenActionsHandler.kt$TokenActionsHandler$private val isDemoCardUseCase: IsDemoCardUseCase</ID>
<ID>NullableToStringCall:MarketsListItemUM.kt$MarketsListItemUM$marketCap.toString()</ID>
<ID>PropertyUsedBeforeDeclaration:MarketsListModel.kt$MarketsListModel$activeListManager</ID>
<ID>PropertyUsedBeforeDeclaration:MarketsListUMStateManager.kt$MarketsListUMStateManager$state</ID>

View file

@ -31,7 +31,7 @@ internal class DefaultAddToPortfolioManager @AssistedInject constructor(
override val allAvailableNetworks: Flow<List<TokenMarketInfo.Network>> = _allAvailableNetworks.asSharedFlow()
override val portfolioFetcher: PortfolioFetcher = portfolioFetcherFactory.create(
mode = PortfolioFetcher.Mode.All(onlyMultiCurrency = true),
mode = PortfolioFetcher.Mode.All(isOnlyMultiCurrency = true),
scope = scope,
)

View file

@ -23,8 +23,6 @@
<ID>MultilineLambdaItParameter:NFTReceiveModel.kt$NFTReceiveModel${ it.copy( bottomSheetConfig = it.bottomSheetConfig?.copy(isShown = false), ) }</ID>
<ID>MultilineLambdaItParameter:UpdateDataStateTransformer.kt$UpdateDataStateTransformer${ NFTCollectionUM( id = it.collectionIdProvider(), networkIconId = getActiveIconRes(it.network.rawId), name = it.name.orEmpty(), description = TextReference.PluralRes( R.plurals.nft_collections_count, it.count, wrappedList(it.count), ), logoUrl = it.logoUrl, assets = it.transformAssets(), onExpandClick = { onExpandCollectionClick(it) }, isExpanded = it.isExpanded(state), ) }</ID>
<ID>NoNameShadowing:NFTCollectionsModel.kt$NFTCollectionsModel${ val assetsFulfillQuery = if (query.isEmpty()) { true } else { when (val assets = it.assets) { is NFTCollection.Assets.Empty, is NFTCollection.Assets.Failed, is NFTCollection.Assets.Loading, -&gt; false is NFTCollection.Assets.Value -&gt; { assets.items.any { asset -&gt; asset.name?.lowercase()?.contains(query.lowercase()) == true } } } } val collectionFulfillQuery = query.isEmpty() || it.name?.lowercase()?.contains(query.lowercase()) == true collectionFulfillQuery || assetsFulfillQuery }</ID>
<ID>NonBooleanPropertyPrefixedWithIs:NFTCollectionsModel.kt$NFTCollectionsModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:NFTReceiveModel.kt$NFTReceiveModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
<ID>NullableBooleanCheck:UpdateDataStateTransformer.kt$UpdateDataStateTransformer$(state.content as? NFTCollectionsUM.Content) ?.collections ?.filterIsInstance&lt;NFTCollectionUM&gt;() ?.firstOrNull { it.id == this.collectionIdProvider() } ?.isExpanded ?: false</ID>
<ID>NullableToStringCall:NFTCollectionsContent.kt$${item2?.id}</ID>
<ID>NullableToStringCall:NFTCollectionsModel.kt$NFTCollectionsModel$${network.derivationPath.value}</ID>

View file

@ -107,7 +107,6 @@
<ID>NoNameShadowing:MultiWalletCreateWalletModel.kt$MultiWalletCreateWalletModel${ it.copy(resultUserWallet = userWallet) }</ID>
<ID>NoNameShadowing:MultiWalletUpgradeWalletModel.kt$MultiWalletUpgradeWalletModel${ it.copy(resultUserWallet = userWallet) }</ID>
<ID>NoNameShadowing:Wallet1ChooseOptionModel.kt$Wallet1ChooseOptionModel${ it.copy(resultUserWallet = userWallet) }</ID>
<ID>NonBooleanPropertyPrefixedWithIs:MultiWalletSeedPhraseModel.kt$MultiWalletSeedPhraseModel$private val isWalletAlreadySavedUseCase: IsWalletAlreadySavedUseCase</ID>
<ID>PropertyUsedBeforeDeclaration:MultiWalletCreateWalletModel.kt$MultiWalletCreateWalletModel$onDone</ID>
<ID>RedundantSuspendModifier:MultiWalletCreateWalletModel.kt$MultiWalletCreateWalletModel$suspend</ID>
<ID>RedundantSuspendModifier:OnboardingNoteCreateWalletModel.kt$OnboardingNoteCreateWalletModel$suspend</ID>

View file

@ -41,6 +41,7 @@ import com.tangem.sdk.api.BackupServiceHolder
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.utils.StringsSigns
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runSuspendCatching
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@ -91,7 +92,8 @@ internal class MultiWalletFinalizeModel @Inject constructor(
// sets proper artwork state for initial step
// (if we start from backup cards, we need to show proper artwork) ([REDACTED_TASK_KEY])
when (getInitialStep()) {
MultiWalletFinalizeUM.Step.Primary -> { /* state is already set */ }
MultiWalletFinalizeUM.Step.Primary -> { /* state is already set */
}
MultiWalletFinalizeUM.Step.BackupDevice1 -> {
onEvent.emit(MultiWalletFinalizeComponent.Event.OneBackupCardAdded)
}
@ -299,7 +301,11 @@ internal class MultiWalletFinalizeModel @Inject constructor(
.updateWithHotWallet(wallet),
)
},
).getOrElse {
).onRight {
launch(NonCancellable) {
runSuspendCatching { walletsRepository.upgradeWallet(userWalletCreated.walletId) }
}
}.getOrElse {
error("Failed to upgrade to cold wallet. Error: $it")
}
}

View file

@ -1,13 +1,5 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>NonBooleanPropertyPrefixedWithIs:AvailableSwapPairsModel.kt$AvailableSwapPairsModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:HotCryptoModel.kt$HotCryptoModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:OnrampAddTokenUiBuilder.kt$OnrampAddTokenUiBuilder$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:OnrampMainComponentModel.kt$OnrampMainComponentModel$private val isDemoCardUseCase: IsDemoCardUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:OnrampOperationModel.kt$OnrampOperationModel$private val isDemoCardUseCase: IsDemoCardUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:OnrampTokenListModel.kt$OnrampTokenListModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SwapSelectTokensModel.kt$SwapSelectTokensModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
</CurrentIssues>
<CurrentIssues/>
</SmellBaseline>

View file

@ -1,10 +0,0 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>MultilineLambdaItParameter:ReferralInteractorImpl.kt$ReferralInteractorImpl${ Timber.e("Failed to derive public keys: $it") throw it.mapToDomainError() }</ID>
<ID>ObjectExtendsThrowable:ReferralError.kt$ReferralError$SdkError : ReferralError</ID>
<ID>ObjectExtendsThrowable:ReferralError.kt$ReferralError$UserCancelledException : ReferralError</ID>
<ID>UselessCallOnNotNull:ReferralInteractorImpl.kt$ReferralInteractorImpl$listOfNotNull(cryptoCurrency)</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -72,9 +72,9 @@ internal class ReferralInteractorImpl(
manageCryptoCurrenciesUseCase(accountId = portfolioId.accountId, add = cryptoCurrency)
}
is PortfolioId.Wallet -> {
derivePublicKeysUseCase(userWallet.walletId, listOfNotNull(cryptoCurrency)).getOrElse {
Timber.e("Failed to derive public keys: $it")
throw it.mapToDomainError()
derivePublicKeysUseCase(userWallet.walletId, listOf(cryptoCurrency)).getOrElse { throwable ->
Timber.e("Failed to derive public keys: $throwable")
throw throwable.mapToDomainError()
}
addCryptoCurrenciesUseCase(
@ -118,9 +118,9 @@ internal class ReferralInteractorImpl(
private fun Throwable.mapToDomainError(): ReferralError {
if (this !is TangemSdkError) return ReferralError.DataError(this)
return if (this is TangemSdkError.UserCancelled) {
ReferralError.UserCancelledException
ReferralError.UserCancelledException()
} else {
ReferralError.SdkError
ReferralError.SdkError()
}
}
}

View file

@ -1,8 +1,8 @@
package com.tangem.feature.referral.domain.errors
sealed class ReferralError : Exception() {
data object UserCancelledException : ReferralError()
data object SdkError : ReferralError()
class UserCancelledException : ReferralError()
class SdkError : ReferralError()
data class DataError(val throwable: Throwable) : ReferralError()
class DataError(val throwable: Throwable) : ReferralError()
}

View file

@ -7,8 +7,6 @@
<ID>MultilineLambdaItParameter:ReferralScreen.kt${ // TODO: use StateEvent if (stateHolder.errorSnackbar != null) { TangemSnackbar(data = it, actionOnNewLine = true) } else { CopiedTextSnackbar(it) } }</ID>
<ID>MultilineLambdaItParameter:ReferralScreen.kt${ ReferralContent( stateHolder = stateHolder, snackbarHostState = snackbarHostState, onAgreementClick = stateHolder.analytics.onAgreementClicked, modifier = Modifier.padding(it), ) }</ID>
<ID>NamedArguments:ReferralScreen.kt$Text( formatAwardConditionsString( quantity = award, network = networkName, address = if (!address.isNullOrBlank()) " $address" else "", ), color = TangemTheme.colors.text.tertiary, style = TangemTheme.typography.body2, modifier = Modifier.testTag(ReferralProgramScreenTestTags.INFO_FOR_YOU_TEXT), )</ID>
<ID>NonBooleanPropertyPrefixedWithIs:ParticipateBottomBlock.kt$val isExpanded = remember { mutableStateOf(false) }</ID>
<ID>NonBooleanPropertyPrefixedWithIs:ReferralModel.kt$ReferralModel$private val isDemoCardUseCase: IsDemoCardUseCase</ID>
<ID>SuspendFunSwallowedCancellation:ReferralModel.kt$ReferralModel$runCatching</ID>
<ID>UseOrEmpty:ReferralScreen.kt$matchResult.groups[1]?.value ?: ""</ID>
<ID>VarCouldBeVal:ReferralModel.kt$ReferralModel$private var referralData: MutableStateFlow&lt;ReferralData?&gt; = MutableStateFlow(null)</ID>

View file

@ -4,7 +4,6 @@
<CurrentIssues>
<ID>BooleanPropertyNaming:FeeSelectorData.kt$FeeSelectorData$val removeSuggestedFee: Boolean = false</ID>
<ID>BooleanPropertyNaming:SendEntryRoute.kt$SendEntryRoute.ChooseToken$val showSendViaSwapNotification: Boolean</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendDestinationComponentParams.kt$SendDestinationComponentParams.DestinationParams$val isBalanceHidingFlow: StateFlow&lt;Boolean&gt;</ID>
<ID>UseEmptyCounterpart:CommonSendAmountAnalyticEvents.kt$CommonSendAmountAnalyticEvents$mapOf()</ID>
<ID>UseEmptyCounterpart:CommonSendAnalyticEvents.kt$CommonSendAnalyticEvents$mapOf()</ID>
<ID>UseEmptyCounterpart:CommonSendFeeAnalyticEvents.kt$CommonSendFeeAnalyticEvents$mapOf()</ID>

View file

@ -46,29 +46,6 @@
<ID>NestedScopeFunctions:KaspaCustomFeeConverter.kt$KaspaCustomFeeConverter$let { val valueDecimal = it.value.parseToBigDecimal(it.decimals) // krc-20 transaction will be failed if custom fee value is less than minimum, // so we set value to minimum in this case if (valueDecimal &lt; minimumFee.amount.value) { val fixedValue = minimumFeeAmountValue.parseBigDecimal(it.decimals) set( FEE_AMOUNT_INDEX, it.copy( value = fixedValue, label = getFiatReference( rate = currencyStatus.fiatRate, value = valueDecimal, appCurrency = appCurrency, ), ), ) } }</ID>
<ID>NoNameShadowing:FeeSelectorAlertFactory.kt$FeeSelectorAlertFactory$newFee</ID>
<ID>NoNameShadowing:SendContent.kt$navigationUM</ID>
<ID>NonBooleanPropertyPrefixedWithIs:FeeSelectorModel.kt$FeeSelectorModel$private val isFeeApproximateUseCase: IsFeeApproximateUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:NFTSendConfirmComponent.kt$NFTSendConfirmComponent.Params$val isBalanceHidingFlow: StateFlow&lt;Boolean&gt;</ID>
<ID>NonBooleanPropertyPrefixedWithIs:NFTSendConfirmModel.kt$NFTSendConfirmModel$private val isSendTapHelpEnabledUseCase: IsSendTapHelpEnabledUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:NFTSendModel.kt$NFTSendModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:NFTSendModel.kt$NFTSendModel$val isBalanceHiddenFlow: StateFlow&lt;Boolean&gt; field = MutableStateFlow(false)</ID>
<ID>NonBooleanPropertyPrefixedWithIs:NotificationsModel.kt$NotificationsModel$private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendAmountComponentParams.kt$SendAmountComponentParams$abstract val isAccountModeFlow: StateFlow&lt;Boolean&gt;</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendAmountComponentParams.kt$SendAmountComponentParams$abstract val isBalanceHidingFlow: StateFlow&lt;Boolean&gt;</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendAmountComponentParams.kt$SendAmountComponentParams.AmountBlockParams$override val isAccountModeFlow: StateFlow&lt;Boolean&gt;</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendAmountComponentParams.kt$SendAmountComponentParams.AmountBlockParams$override val isBalanceHidingFlow: StateFlow&lt;Boolean&gt;</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendAmountComponentParams.kt$SendAmountComponentParams.AmountParams$override val isAccountModeFlow: StateFlow&lt;Boolean&gt;</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendAmountComponentParams.kt$SendAmountComponentParams.AmountParams$override val isBalanceHidingFlow: StateFlow&lt;Boolean&gt;</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendAmountModel.kt$SendAmountModel$val isSendWithSwapAvailable: StateFlow&lt;Boolean&gt; field = MutableStateFlow(false)</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendConfirmComponent.kt$SendConfirmComponent.Params$val isAccountModeFlow: StateFlow&lt;Boolean&gt;</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendConfirmComponent.kt$SendConfirmComponent.Params$val isBalanceHidingFlow: StateFlow&lt;Boolean&gt;</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendConfirmModel.kt$SendConfirmModel$private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendConfirmModel.kt$SendConfirmModel$private val isSendTapHelpEnabledUseCase: IsSendTapHelpEnabledUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendConfirmModel.kt$SendConfirmModel$val isBalanceHiddenFlow: StateFlow&lt;Boolean&gt; field = MutableStateFlow(false)</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendDestinationModel.kt$SendDestinationModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendDestinationModel.kt$SendDestinationModel$private val isSelfSendAvailableUseCase: IsSelfSendAvailableUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendModel.kt$SendModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendModel.kt$SendModel$val isAccountModeFlow: StateFlow&lt;Boolean&gt; field = MutableStateFlow(false)</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendModel.kt$SendModel$val isBalanceHiddenFlow: StateFlow&lt;Boolean&gt; field = MutableStateFlow(false)</ID>
<ID>NullCheckOnMutableProperty:SendAmountModel.kt$SendAmountModel$if (uiState.value is AmountState.Empty &amp;&amp; userWallet != null) { val isOnlyOneWallet = getWalletsUseCase.invokeSync().size == 1 val walletTitle = if (isOnlyOneWallet) { resourceReference(R.string.send_from_title) } else { resourceReference( R.string.send_from_wallet_name, WrappedList(listOf(userWallet?.name.orEmpty())), // TODO AND-11440 ) } _uiState.update { AmountStateConverter( clickIntents = this, appCurrency = appCurrency, cryptoCurrencyStatus = cryptoCurrencyStatus, maxEnterAmount = maxAmountBoundary, iconStateConverter = CryptoCurrencyToIconStateConverter(), isBalanceHidden = params.isBalanceHidingFlow.value, accountTitleUM = AmountAccountConverter( isAccountsMode = isAccountsMode, walletTitle = walletTitle, prefixText = resourceReference(R.string.common_from), ).convert(account), ).convert( AmountParameters( title = walletTitle, value = "", ), ) } }</ID>
<ID>NullableToStringCall:BitcoinCustomFeeConverter.kt$BitcoinCustomFeeConverter$toSatoshiPerByte( amount = feeValue, decimals = value.amount.decimals, txSize = value.txSize, ).toString()</ID>
<ID>NullableToStringCall:NFTSendConfirmModel.kt$NFTSendConfirmModel$params.nftAsset.amount.toString()</ID>

View file

@ -28,14 +28,6 @@
<ID>MultilineLambdaItParameter:StakingTransactionSender.kt$StakingTransactionSender${ onConstructError(it) return emptyList() }</ID>
<ID>NoNameShadowing:StakingFeeTransactionLoader.kt$StakingFeeTransactionLoader$amount</ID>
<ID>NoNameShadowing:StakingFeeTransactionLoader.kt$StakingFeeTransactionLoader${ if (!it.amount.isZero()) return feeResult }</ID>
<ID>NonBooleanPropertyPrefixedWithIs:AddStakingNotificationsTransformer.kt$AddStakingNotificationsTransformer$private val isAccountInitializedProvider: Provider&lt;Boolean&gt;</ID>
<ID>NonBooleanPropertyPrefixedWithIs:DefaultStakingDeepLinkHandler.kt$DefaultStakingDeepLinkHandler$val isStakingEnabled = getStakingAvailabilityUseCase.invokeSync( userWalletId = selectedUserWalletId, cryptoCurrency = cryptoCurrency, ).getOrNull()</ID>
<ID>NonBooleanPropertyPrefixedWithIs:StakingFeeTransactionLoader.kt$StakingFeeTransactionLoader$private val isFeeApproximateUseCase: IsFeeApproximateUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:StakingModel.kt$StakingModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:StakingModel.kt$StakingModel$private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:StakingModel.kt$StakingModel$private val isAnyTokenStakedUseCase: IsAnyTokenStakedUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:StakingModel.kt$StakingModel$private val isBalanceHiddenFlow: StateFlow&lt;Boolean&gt; field = MutableStateFlow(false)</ID>
<ID>NonBooleanPropertyPrefixedWithIs:StakingTransactionSender.kt$StakingTransactionSender$private val isFeeApproximateUseCase: IsFeeApproximateUseCase</ID>
<ID>NullCheckOnMutableProperty:StakingModel.kt$StakingModel$if (feeCryptoCurrencyStatus != null &amp;&amp; fee != null) { getBalanceNotEnoughForFeeWarningUseCase( fee = fee, userWalletId = userWalletId, tokenStatus = cryptoCurrencyStatus, coinStatus = feeCryptoCurrencyStatus ?: cryptoCurrencyStatus, ).getOrNull() } else { null }</ID>
<ID>NullableToStringCall:DefaultStakingDeepLinkHandler.kt$DefaultStakingDeepLinkHandler$$networkId</ID>
<ID>NullableToStringCall:DefaultStakingDeepLinkHandler.kt$DefaultStakingDeepLinkHandler$$tokenId</ID>

View file

@ -32,19 +32,6 @@
<ID>NamedArguments:SwapTransactionSender.kt$SwapTransactionSender$getSwapDataUseCase( userWallet = userWallet, fromCryptoCurrencyStatus = fromStatus, fromAmount = fromAmount.toStringWithRightOffset(fromStatus.currency.decimals), toCryptoCurrency = toStatus.currency, toAddress = destination, expressProvider = provider, rateType = rateType, expressOperationType, )</ID>
<ID>NoNameShadowing:SendWithSwapContent.kt$navigationUM</ID>
<ID>NoNameShadowing:SwapAmountContent.kt$amountFieldUM</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendWithSwapConfirmComponent.kt$SendWithSwapConfirmComponent.Params$val isAccountModeFlow: StateFlow&lt;Boolean&gt;</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendWithSwapConfirmComponent.kt$SendWithSwapConfirmComponent.Params$val isBalanceHidingFlow: StateFlow&lt;Boolean&gt;</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendWithSwapConfirmModel.kt$SendWithSwapConfirmModel$private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendWithSwapConfirmModel.kt$SendWithSwapConfirmModel$private val isSendTapHelpEnabledUseCase: IsSendTapHelpEnabledUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendWithSwapModel.kt$SendWithSwapModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendWithSwapModel.kt$SendWithSwapModel$val isAccountModeFlow: StateFlow&lt;Boolean&gt; field = MutableStateFlow(false)</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SendWithSwapModel.kt$SendWithSwapModel$val isBalanceHiddenFlow: StateFlow&lt;Boolean&gt; field = MutableStateFlow(false)</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SwapAmountComponentParams.kt$SwapAmountComponentParams$abstract val isAccountModeFlow: StateFlow&lt;Boolean&gt;</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SwapAmountComponentParams.kt$SwapAmountComponentParams$abstract val isBalanceHidingFlow: StateFlow&lt;Boolean&gt;</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SwapAmountComponentParams.kt$SwapAmountComponentParams.AmountBlockParams$override val isAccountModeFlow: StateFlow&lt;Boolean&gt;</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SwapAmountComponentParams.kt$SwapAmountComponentParams.AmountBlockParams$override val isBalanceHidingFlow: StateFlow&lt;Boolean&gt;</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SwapAmountComponentParams.kt$SwapAmountComponentParams.AmountParams$override val isAccountModeFlow: StateFlow&lt;Boolean&gt;</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SwapAmountComponentParams.kt$SwapAmountComponentParams.AmountParams$override val isBalanceHidingFlow: StateFlow&lt;Boolean&gt;</ID>
<ID>NullableToStringCall:SendWithSwapConfirmModel.kt$SendWithSwapConfirmModel$error.toString()</ID>
<ID>NullableToStringCall:SwapAmountModel.kt$SwapAmountModel$$primaryStatus</ID>
<ID>NullableToStringCall:SwapAmountModel.kt$SwapAmountModel$$secondaryStatus</ID>

View file

@ -37,7 +37,6 @@ dependencies {
implementation(projects.domain.tokens.models)
implementation(projects.domain.legacy)
implementation(projects.domain.walletManager)
implementation(projects.libs.blockchainSdk)
implementation(projects.domain.models)
implementation(projects.domain.wallets)
implementation(projects.domain.wallets.models)
@ -45,6 +44,9 @@ dependencies {
implementation(projects.domain.express.models)
implementation(projects.domain.account.status)
implementation(projects.libs.blockchainSdk)
implementation(projects.libs.crypto)
/** Data */
implementation(projects.data.common)

View file

@ -17,6 +17,7 @@ import com.tangem.feature.swap.domain.models.domain.ExchangeStatusModel
import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListModel
import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListModelInner
import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionModel
import com.tangem.lib.crypto.derivation.AccountNodeRecognizer
import com.tangem.utils.converter.Converter
internal class SavedSwapTransactionListConverter(
@ -75,9 +76,23 @@ internal class SavedSwapTransactionListConverter(
.map { tx ->
val status = txStatuses[tx.txId]
val refundCurrency = status?.refundTokensResponse?.let { id ->
val blockchain = Blockchain.fromNetworkId(id.networkId) ?: return@let null
val derivationPath = id.derivationPath ?: return@let null
val accountIndex = if (blockchain == Blockchain.Chia) {
DerivationIndex.Main
} else {
val recognizer = AccountNodeRecognizer(blockchain = blockchain)
val index = recognizer.recognize(derivationPathValue = derivationPath)?.toInt()
?: return@let null
DerivationIndex(index).getOrNull() ?: return@let null
}
responseCryptoCurrenciesFactory.createCurrency(
responseToken = id,
userWallet = userWallet,
accountIndex = accountIndex,
)
}
val statusWithRefundCurrency = status?.copy(refundCurrency = refundCurrency)

View file

@ -25,8 +25,6 @@
<ID>NamedArguments:SwapInteractorImpl.kt$SwapInteractorImpl$tryGetFromCacheV2(userWallet, initialCryptoCurrency, state, isReverseFromTo)</ID>
<ID>NoNameShadowing:SwapInteractorImpl.kt$SwapInteractorImpl$account</ID>
<ID>NoNameShadowing:SwapInteractorImpl.kt$SwapInteractorImpl${ it.isAvailable }</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SwapInteractorImpl.kt$SwapInteractorImpl$private val isDemoCardUseCase: IsDemoCardUseCase</ID>
<ID>NullableToStringCall:SwapInteractorImpl.kt$SwapInteractorImpl$$swapData</ID>
<ID>NullableToStringCall:SwapInteractorImpl.kt$SwapInteractorImpl$${e.message}</ID>
<ID>SuspendFunSwallowedCancellation:SwapInteractorImpl.kt$SwapInteractorImpl$runCatching</ID>
</CurrentIssues>

View file

@ -48,8 +48,6 @@
<ID>MultilineLambdaItParameter:SwapModel.kt$SwapModel${ uiState = stateBuilder.dismissBottomSheet(uiState) dataState = dataState.copy(selectedFee = it) modelScope.launch(dispatchers.io) { startLoadingQuotesFromLastState(false) } }</ID>
<ID>MultilineLambdaItParameter:SwapModel.kt$SwapModel${ val balance = swapInteractor.getTokenBalance(it) onAmountChanged(balance.formatToUIRepresentation()) }</ID>
<ID>MultilineLambdaItParameter:SwapModel.kt$SwapModel${ val provider = findAndSelectProvider(it) val swapState = dataState.lastLoadedSwapStates[provider] val fromToken = dataState.fromCryptoCurrency if (provider != null &amp;&amp; swapState != null &amp;&amp; fromToken != null) { analyticsEventHandler.send(SwapEvents.ProviderChosen(provider)) uiState = stateBuilder.dismissBottomSheet(uiState) setupLoadedState( provider = provider, state = swapState, fromToken = fromToken, ) } }</ID>
<ID>MultilineLambdaItParameter:SwapModel.kt$SwapModel${ when (it) { is SwapTransactionState.TxSent -&gt; { sendApproveSuccessEvent(fromToken, feeForPermission.feeType, approveType) updateWalletBalance() uiState = stateBuilder.loadingPermissionState(uiState) uiState = stateBuilder.dismissBottomSheet(uiState) startLoadingQuotesFromLastState(isSilent = true) } is SwapTransactionState.Error -&gt; { uiState = stateBuilder.createErrorTransactionAlert( uiState = uiState, error = it, onDismiss = { uiState = stateBuilder.clearAlert(uiState) }, onSupportClick = ::onFailedTxEmailClick, isReverseSwapPossible = isReverseSwapPossible(), ) } SwapTransactionState.DemoMode -&gt; { uiState = stateBuilder.createDemoModeAlert( uiState = uiState, onDismiss = { uiState = stateBuilder.clearAlert(uiState) }, isReverseSwapPossible = isReverseSwapPossible(), ) } } }</ID>
<ID>MultilineLambdaItParameter:SwapModel.kt$SwapModel${ when (it) { is SwapTransactionState.TxSent -&gt; { sendSuccessSwapEvent(fromCurrency.currency, fee.feeType) val url = getExplorerTransactionUrlUseCase( txHash = it.txHash, networkId = fromCurrency.currency.network.id, ).getOrElse { Timber.i("tx hash explore not supported") "" } updateWalletBalance() uiState = stateBuilder.createSuccessState( uiState = uiState, swapTransactionState = it, dataState = dataState, txUrl = url, onExploreClick = { if (it.txHash.isNotEmpty()) { urlOpener.openUrl(url) } analyticsEventHandler.send( event = SwapEvents.ButtonExplore(initialCurrencyFrom.symbol), ) }, onStatusClick = { val txExternalUrl = it.txExternalUrl if (!txExternalUrl.isNullOrBlank()) { urlOpener.openUrl(txExternalUrl) analyticsEventHandler.send( event = SwapEvents.ButtonStatus(initialCurrencyFrom.symbol), ) } }, ) sendSuccessEvent() swapRouter.openScreen(SwapNavScreen.Success) } SwapTransactionState.DemoMode -&gt; { uiState = stateBuilder.createDemoModeAlert( uiState = uiState, onDismiss = { uiState = stateBuilder.clearAlert(uiState) }, isReverseSwapPossible = isReverseSwapPossible(), ) } is SwapTransactionState.Error -&gt; { startLoadingQuotesFromLastState() uiState = stateBuilder.createErrorTransactionAlert( uiState = uiState, error = it, onDismiss = { uiState = stateBuilder.clearAlert(uiState) }, onSupportClick = ::onFailedTxEmailClick, isReverseSwapPossible = isReverseSwapPossible(), ) } } }</ID>
<ID>MultilineLambdaItParameter:TransactionCard.kt${ Text( text = it, color = TangemTheme.colors.text.tertiary, style = TangemTheme.typography.body2, ) }</ID>
<ID>MultilineLambdaItParameter:TransactionCard.kt${ Text( text = it, color = TangemTheme.colors.text.tertiary, style = TangemTheme.typography.body2, modifier = Modifier .align(Alignment.CenterVertically) .testTag(SwapTokenScreenTestTags.BALANCE), ) }</ID>
<ID>MultilineLambdaItParameter:TransactionCard.kt${ Text( text = it, color = TangemTheme.colors.text.tertiary, style = TangemTheme.typography.body2, modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size20), ) }</ID>
@ -57,10 +55,6 @@
<ID>NoNameShadowing:SwapModel.kt$SwapModel${ it.cryptoCurrencyStatus }</ID>
<ID>NoNameShadowing:SwapModel.kt$SwapModel${ it.cryptoCurrencyStatus.currency.id.value == id }</ID>
<ID>NoNameShadowing:SwapModel.kt$SwapModel${ it.key == selectedSwapProvider }</ID>
<ID>NonBooleanPropertyPrefixedWithIs:StateBuilder.kt$StateBuilder$private val isAccountsModeProvider: Provider&lt;Boolean&gt;</ID>
<ID>NonBooleanPropertyPrefixedWithIs:StateBuilder.kt$StateBuilder$private val isBalanceHiddenProvider: Provider&lt;Boolean&gt;</ID>
<ID>NonBooleanPropertyPrefixedWithIs:SwapModel.kt$SwapModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:TokensDataConverter.kt$TokensDataConverter$private val isBalanceHiddenProvider: Provider&lt;Boolean&gt;</ID>
<ID>NullableToStringCall:SwapModel.kt$SwapModel$${currencyStatus.value.amount}</ID>
<ID>NullableToStringCall:SwapModel.kt$SwapModel$${it.value.amount}</ID>
<ID>NullableToStringCall:TransactionCard.kt$data.toString()</ID>

View file

@ -16,7 +16,6 @@
<ID>MultilineLambdaItParameter:TangemPayDetailsScreen.kt${ TangemDropdownItem( item = it.dropdownItem, dismissParent = { showDropdownMenu = false }, ) }</ID>
<ID>MultilineLambdaItParameter:TangemPayTxHistoryUiManager.kt$TangemPayTxHistoryUiManager${ it.status !is PaginationStatus.None &amp;&amp; it.status !is PaginationStatus.InitialLoading &amp;&amp; it.status !is PaginationStatus.InitialLoadingError }</ID>
<ID>NullCheckOnMutableProperty:GoogleWalletUtil.kt$GoogleWalletUtil$if (walletIntent != null) { walletIntent } else { try { context.packageManager.getLaunchIntentForPackage(WALLET_PACKAGE_NAME) ?.apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } .also { walletIntent = it } } catch (exception: Exception) { Timber.tag(TAG).e(exception) null } }</ID>
<ID>NullableBooleanCheck:TangemPayDetailsModel.kt$TangemPayDetailsModel$cardDetailsRepository.isAddToWalletDone().getOrNull() ?: false</ID>
<ID>ReusedModifierInstance:DefaultTangemPayDetailsContainerComponent.kt$DefaultTangemPayDetailsContainerComponent$Content(modifier = modifier)</ID>
<ID>ReusedModifierInstance:TangemPayChangePinCodeSuccessScreen.kt$Column( modifier .fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, ) { SuccessContent( modifier = Modifier .fillMaxWidth() .weight(1f), ) PrimaryButton( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp) .padding(bottom = 16.dp) .navigationBarsPadding(), text = stringResourceSafe(R.string.common_done), onClick = onClick, ) }</ID>
<ID>ReusedModifierInstance:TangemPayChangePinScreen.kt$Column( modifier = modifier .fillMaxWidth() .padding(top = 48.dp) .padding(horizontal = 36.dp) .weight(1f), horizontalAlignment = Alignment.CenterHorizontally, ) { Text( text = stringResourceSafe(R.string.visa_onboarding_pin_code_title), style = TangemTheme.typography.h2, color = TangemTheme.colors.text.primary1, textAlign = TextAlign.Center, ) SpacerH16() Text( text = stringResourceSafe(R.string.visa_onboarding_pin_code_description), style = TangemTheme.typography.body1, color = TangemTheme.colors.text.secondary, textAlign = TextAlign.Center, ) SpacerH(26.dp) PinCodeSection(state) }</ID>

View file

@ -1,9 +0,0 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>MultilineLambdaItParameter:TangemPayOnboardingModel.kt$TangemPayOnboardingModel${ TangemPayOnboardingScreenState.Content( onBack = it.onBack, onTermsClick = ::onTermsClick, buttonConfig = TangemPayOnboardingScreenState.Content.ButtonConfig( isLoading = false, onClick = ::onGetCardClick, ), ) }</ID>
<ID>MultilineLambdaItParameter:TangemPayOnboardingModel.kt$TangemPayOnboardingModel${ Timber.e("Error getCustomerInfo: ${it.errorCode}") uiState.transformerUpdate(TangemPayOnboardingButtonLoadingTransformer(isLoading = false)) }</ID>
<ID>NullableToStringCall:TangemPayOnboardingModel.kt$TangemPayOnboardingModel$${result.leftOrNull()?.message}</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -60,9 +60,9 @@ internal class TangemPayOnboardingModel @Inject constructor(
}
private fun showOnboarding() {
uiState.update {
uiState.update { state ->
TangemPayOnboardingScreenState.Content(
onBack = it.onBack,
onBack = state.onBack,
onTermsClick = ::onTermsClick,
buttonConfig = TangemPayOnboardingScreenState.Content.ButtonConfig(
isLoading = false,
@ -97,6 +97,7 @@ internal class TangemPayOnboardingModel @Inject constructor(
urlOpener.openUrl(TangemPayConstants.TERMS_AND_LIMITS_LINK)
}
@Suppress("NullableToStringCall")
private fun onGetCardClick() {
analytics.send(TangemPayAnalyticsEvents.GetCardClicked)
uiState.transformerUpdate(TangemPayOnboardingButtonLoadingTransformer(isLoading = true))
@ -114,8 +115,8 @@ internal class TangemPayOnboardingModel @Inject constructor(
repository.getCustomerInfo(
userWalletId = userWalletId,
).fold(
ifLeft = {
Timber.e("Error getCustomerInfo: ${it.errorCode}")
ifLeft = { error ->
Timber.e("Error getCustomerInfo: ${error.errorCode}")
uiState.transformerUpdate(TangemPayOnboardingButtonLoadingTransformer(isLoading = false))
},
ifRight = { customerInfo ->

View file

@ -9,7 +9,6 @@
<ID>ExplicitCollectionElementAccessMethod:TestPushAddKeyDataTransformer.kt$TestPushAddKeyDataTransformer$mutableData.set(index = index, updated)</ID>
<ID>ExplicitCollectionElementAccessMethod:TestPushAddValueDataTransformer.kt$TestPushAddValueDataTransformer$mutableData.set(index = index, updated)</ID>
<ID>MaxChainedCallsOnSameLine:BlockchainProvidersScreen.kt$ProvidersDnDTarget$event.toAndroidDragEvent().clipData.getItemAt(0).text.toString().toInt()</ID>
<ID>MultilineLambdaItParameter:ApiEnvironmentComparator.kt$ApiEnvironmentComparator${ when (it) { ApiEnvironment.DEV -&gt; 0 ApiEnvironment.DEV_2 -&gt; 1 ApiEnvironment.DEV_3 -&gt; 2 ApiEnvironment.STAGE -&gt; 3 ApiEnvironment.MOCK -&gt; 4 ApiEnvironment.PROD -&gt; 5 } }</ID>
<ID>MultilineLambdaItParameter:BlockchainProvidersScreen.kt${ value = it state.onValueChange(it.text) }</ID>
<ID>MultilineLambdaItParameter:BlockchainProvidersViewModel.kt$BlockchainProvidersViewModel${ if (it.blockchainId == blockchainId) { it.update() } else { it } }</ID>
<ID>MultilineLambdaItParameter:BlockchainProvidersViewModel.kt$BlockchainProvidersViewModel${ it.copyProvidersUM(blockchainId = id) { copy( addPublicProviderDialog = addPublicProviderDialog.copy( hasError = !PatternsCompat.WEB_URL.matcher(url).matches(), ), ) } }</ID>

View file

@ -10,14 +10,15 @@ import com.tangem.datasource.api.common.config.ApiEnvironmentConfig
*/
internal object ApiEnvironmentComparator : Comparator<ApiEnvironmentConfig> {
private val apiEnvironmentPriorityMap = ApiEnvironment.entries.associateWith {
when (it) {
private val apiEnvironmentPriorityMap = ApiEnvironment.entries.associateWith { environment ->
when (environment) {
ApiEnvironment.DEV -> 0
ApiEnvironment.DEV_2 -> 1
ApiEnvironment.DEV_3 -> 2
ApiEnvironment.STAGE -> 3
ApiEnvironment.MOCK -> 4
ApiEnvironment.PROD -> 5
ApiEnvironment.STAGE_2 -> 4
ApiEnvironment.MOCK -> 5
ApiEnvironment.PROD -> 6
}
}

View file

@ -31,7 +31,6 @@
<ID>NamedArguments:TokenDetailsLoadedBalanceConverter.kt$TokenDetailsLoadedBalanceConverter$formatFiatAmount( status.value, stakingFiatAmount, currentState.selectedBalanceType, appCurrencyProvider(), )</ID>
<ID>NamedArguments:TokenDetailsSwapTransactionsStateConverter.kt$TokenDetailsSwapTransactionsStateConverter$createStateInfo( transaction, toCryptoCurrency, fromCryptoCurrency, toFiatAmount, fromFiatAmount, )</ID>
<ID>NestedScopeFunctions:TokenDetailsBalanceSelectStateConverter.kt$TokenDetailsBalanceSelectStateConverter$let { cryptoCurrencyStatus.value.fiatRate?.multiply(it) }</ID>
<ID>NonBooleanPropertyPrefixedWithIs:TokenDetailsModel.kt$TokenDetailsModel$private val isDemoCardUseCase: IsDemoCardUseCase</ID>
<ID>NullableBooleanCheck:TokenDetailsSwapTransactionsStateConverter.kt$TokenDetailsSwapTransactionsStateConverter$transaction.status?.hasLongTime ?: false</ID>
<ID>NullableToStringCall:DefaultTokenDetailsDeepLinkHandler.kt$DefaultTokenDetailsDeepLinkHandler$$networkId</ID>
<ID>NullableToStringCall:DefaultTokenDetailsDeepLinkHandler.kt$DefaultTokenDetailsDeepLinkHandler$$tokenId</ID>

View file

@ -77,7 +77,7 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor(
params = YieldSupplyComponent.Params(
userWalletId = params.userWalletId,
cryptoCurrency = params.currency,
handleNavigation = (params.navigationAction as? NavigationAction.YieldSupply)
shouldHandleNavigation = (params.navigationAction as? NavigationAction.YieldSupply)
?.isActive,
),
)

View file

@ -1,9 +0,0 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>BooleanPropertyNaming:TxHistoryListManager.kt$TxHistoryListManager$val clearUiBatches = state.status is PaginationStatus.InitialLoading &amp;&amp; batchListState.status is PaginationStatus.Paginating</ID>
<ID>MultilineLambdaItParameter:TxHistoryUiManager.kt$TxHistoryUiManager${ it.status !is PaginationStatus.None &amp;&amp; it.status !is PaginationStatus.InitialLoading &amp;&amp; it.status !is PaginationStatus.InitialLoadingError }</ID>
<ID>UseEmptyCounterpart:TxHistoryListState.kt$TxHistoryListState$listOf()</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -88,13 +88,13 @@ internal class TxHistoryListManager(
private fun updateState(batchListState: BatchListState<Int, PaginationWrapper<TxInfo>>) {
state.update { state ->
val clearUiBatches =
val shouldClearUiBatches =
state.status is PaginationStatus.InitialLoading && batchListState.status is PaginationStatus.Paginating
state.copy(
status = batchListState.status,
uiBatches = uiManager.createOrUpdateUiBatches(
newCurrencyBatches = batchListState.data,
clearUiBatches = clearUiBatches,
shouldClearUiBatches = shouldClearUiBatches,
),
)
}

View file

@ -6,5 +6,5 @@ import com.tangem.pagination.PaginationStatus
data class TxHistoryListState(
val status: PaginationStatus<*> = PaginationStatus.None,
val uiBatches: List<Batch<Int, List<TxHistoryUM.TxHistoryItemUM>>> = listOf(),
val uiBatches: List<Batch<Int, List<TxHistoryUM.TxHistoryItemUM>>> = emptyList(),
)

View file

@ -22,10 +22,10 @@ internal class TxHistoryUiManager(
@OptIn(ExperimentalCoroutinesApi::class)
val items: Flow<ImmutableList<TxHistoryUM.TxHistoryItemUM>> = state
// filter initial states, since we dont emit loading items as UI items
.filter {
it.status !is PaginationStatus.None &&
it.status !is PaginationStatus.InitialLoading &&
it.status !is PaginationStatus.InitialLoadingError
.filter { state ->
state.status !is PaginationStatus.None &&
state.status !is PaginationStatus.InitialLoading &&
state.status !is PaginationStatus.InitialLoadingError
}
.mapLatest { state ->
state.uiBatches.asSequence()
@ -36,10 +36,10 @@ internal class TxHistoryUiManager(
fun createOrUpdateUiBatches(
newCurrencyBatches: List<Batch<Int, PaginationWrapper<TxInfo>>>,
clearUiBatches: Boolean,
shouldClearUiBatches: Boolean,
): List<Batch<Int, List<TxHistoryUM.TxHistoryItemUM>>> {
val currentUiBatches = state.value.uiBatches
val batches = if (clearUiBatches) mutableListOf() else currentUiBatches.toMutableList()
val batches = if (shouldClearUiBatches) mutableListOf() else currentUiBatches.toMutableList()
for ((key, data) in newCurrencyBatches) {
// Find if batch with same key exists

View file

@ -1,9 +1,5 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>NonBooleanPropertyPrefixedWithIs:AccountItemsDelegate.kt$AccountItemsDelegate$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:WalletSettingsModel.kt$WalletSettingsModel$private val isDemoCardUseCase: IsDemoCardUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:WalletSettingsModel.kt$WalletSettingsModel$private val isUpgradeWalletNotificationEnabledUseCase: IsUpgradeWalletNotificationEnabledUseCase</ID>
</CurrentIssues>
<CurrentIssues/>
</SmellBaseline>

View file

@ -438,7 +438,7 @@ internal class WalletSettingsModel @Inject constructor(
AppRoute.CreateWalletBackup(
userWalletId = params.userWalletId,
isUpgradeFlow = isUpgradeFlow,
setAccessCode = true,
shouldSetAccessCode = true,
analyticsSource = AnalyticsParam.ScreensSources.WalletSettings.value,
analyticsAction = if (isUpgradeFlow) {
RecoveryPhraseScreenAction.Backup.value

View file

@ -5,7 +5,6 @@
<ID>BooleanPropertyNaming:DefaultUserWalletsFetcher.kt$DefaultUserWalletsFetcher$@Assisted("onlyMultiCurrency") private val onlyMultiCurrency: Boolean</ID>
<ID>BooleanPropertyNaming:DraggableItem.kt$DraggableItem$abstract val showShadow: Boolean</ID>
<ID>BooleanPropertyNaming:DraggableItem.kt$DraggableItem.RoundingMode$abstract val showGap: Boolean</ID>
<ID>BooleanPropertyNaming:GetMultiWalletWarningsFactory.kt$GetMultiWalletWarningsFactory$val accessCodeSkipped = array[7] as Boolean</ID>
<ID>BooleanPropertyNaming:GetSingleWalletWarningsFactory.kt$GetSingleWalletWarningsFactory$private var readyForRateAppNotification = false</ID>
<ID>BooleanPropertyNaming:GetSingleWalletWarningsFactory.kt$GetSingleWalletWarningsFactory$val userHasWalletOrWallet2 = userWallets.filterIsInstance&lt;UserWallet.Cold&gt;().any { val typesResolver = it.scanResponse.cardTypesResolver typesResolver.isTangemWallet() || typesResolver.isWallet2() }</ID>
<ID>BooleanPropertyNaming:OrganizeTokensState.kt$OrganizeTokensState.ActionsConfig$val showApplyProgress: Boolean = false</ID>
@ -20,7 +19,6 @@
<ID>BooleanPropertyNaming:WalletManageButton.kt$WalletManageButton$/** Whether to dim content */ abstract val dimContent: Boolean</ID>
<ID>BooleanPropertyNaming:WalletManageButton.kt$WalletManageButton.Swap$val showBadge: Boolean = false</ID>
<ID>BooleanPropertyNaming:WalletModel.kt$WalletModel$private var needToRefreshWallet = false</ID>
<ID>BooleanPropertyNaming:WalletModel.kt$WalletModel$val initialDataProduced = tangemPayOnboardingRepository.isTangemPayInitialDataProduced()</ID>
<ID>BooleanPropertyNaming:WalletNameMigrationUseCase.kt$WalletNameMigrationUseCase$private val useNewListRepository: Boolean</ID>
<ID>BooleanPropertyNaming:WalletScreen.kt$val portfolioContent = state is WalletState.MultiCurrency.Content &amp;&amp; state.tokensListState is WalletTokensListState.ContentState.PortfolioContent</ID>
<ID>BooleanPropertyNaming:WalletScreen.kt$val showMarketsHint by remember { derivedStateOf { // Show hint only when there are items in the list // and when there a no items to scroll listState.layoutInfo.totalItemsCount &gt; 0 &amp;&amp; !listState.canScrollBackward &amp;&amp; !listState.canScrollForward || listState.canScrollBackward &amp;&amp; !listState.canScrollForward } }</ID>
@ -73,12 +71,10 @@
<ID>MultilineLambdaItParameter:WalletCurrencyActionsClickIntents.kt$WalletCurrencyActionsClickIntentsImplementor${ analyticsEventHandler.send(TokenReceiveAnalyticsEvent.ButtonShareAddress(currency.symbol)) shareManager.shareText(text = it) }</ID>
<ID>MultilineLambdaItParameter:WalletCurrencyActionsClickIntents.kt$WalletCurrencyActionsClickIntentsImplementor${ onAddressTypeSelected( userWalletId = userWalletId, currency = currency, addressModel = it, ) }</ID>
<ID>MultilineLambdaItParameter:WalletLoaderStorage.kt$WalletLoaderStorage${ it.forEach(Job::cancel) loaders.remove(id) }</ID>
<ID>MultilineLambdaItParameter:WalletModel.kt$WalletModel${ it .conflate() .distinctUntilChanged() .onEach { selectedWallet -&gt; if (selectedWallet.isMultiCurrency) { selectedWalletAnalyticsSender.send(selectedWallet) } subscribeOnExpressTransactionsUpdates(selectedWallet) observeAndClearNFTCacheIfNeedUseCase(selectedWallet) } .flowOn(dispatchers.main) .launchIn(modelScope) }</ID>
<ID>MultilineLambdaItParameter:WalletModel.kt$WalletModel${ walletScreenContentLoader.load( userWallet = it, clickIntents = clickIntents, coroutineScope = modelScope, isRefresh = true, ) }</ID>
<ID>MultilineLambdaItParameter:WalletModel.kt$WalletModel${ walletsUpdateActionResolver.resolve( wallets = it, currentState = stateHolder.value, ) }</ID>
<ID>MultilineLambdaItParameter:WalletNFTListSubscriber.kt$WalletNFTListSubscriber${ stateHolder.update( SetNFTCollectionsTransformer( userWalletId = userWallet.walletId, nftCollections = it, onItemClick = { clickIntents.onNFTClick(userWallet) }, ), ) }</ID>
<ID>MultilineLambdaItParameter:WalletNameMigrationUseCase.kt$WalletNameMigrationUseCase${ val defaultName = it.name val suggestedWalletName = suggestedWalletName(defaultName, existingNames) if (defaultName != suggestedWalletName) { userWalletsListRepository.saveWithoutLock(it.copy(name = suggestedWalletName), canOverride = true) } Timber.tag("Migrated names").e(it.walletId.toString() + " " + suggestedWalletName) }</ID>
<ID>MultilineLambdaItParameter:WalletNotifications.kt${ // TODO develop promo banner general component when (it) { is WalletNotification.SwapPromo -&gt; { // Use it on new promo action } is WalletNotification.NoteMigration -&gt; { NoteMigrationNotification( config = it.config, modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null), ) } is WalletNotification.FinishWalletActivation -&gt; { Notification( config = it.config, modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null), ) } else -&gt; { Notification( config = it.config, modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null), iconTint = when (it) { is WalletNotification.Critical -&gt; TangemTheme.colors.icon.warning is WalletNotification.Informational -&gt; TangemTheme.colors.icon.accent is WalletNotification.RateApp -&gt; TangemTheme.colors.icon.attention is WalletNotification.UnlockWallets -&gt; TangemTheme.colors.icon.primary1 is WalletNotification.UsedOutdatedData -&gt; TangemTheme.colors.text.attention else -&gt; null }, ) } } }</ID>
<ID>MultilineLambdaItParameter:WalletScreen.kt${ PaddingValues( bottom = it.calculateBottomPadding() + marketHintAproxHeight + 52.dp, ) }</ID>
<ID>MultilineLambdaItParameter:WalletScreen.kt${ WalletSnackbarHost( snackbarHostState = it, event = state.event, modifier = Modifier .padding(bottom = TangemTheme.dimens.spacing4) .navigationBarsPadding(), ) }</ID>
<ID>MultilineLambdaItParameter:WalletScreen.kt${ balancesAndLimitsBlock( modifier = itemModifier, state = it.balancesAndLimitBlockState, ) }</ID>
@ -87,11 +83,8 @@
<ID>MultilineLambdaItParameter:WalletScreen.kt${ nftCollections( modifier = itemModifier, state = it.nftState, ) }</ID>
<ID>MultilineLambdaItParameter:WalletWarningsClickIntents.kt$WalletWarningsClickIntentsImplementor${ Timber.e( """ Unable to get user wallet |- ID: $userWalletId |- Exception: $it """.trimIndent(), ) null }</ID>
<ID>MultilineLambdaItParameter:WalletWarningsClickIntents.kt$WalletWarningsClickIntentsImplementor${ router.openOnboardingScreen( scanResponse = it.scanResponse, continueBackup = true, ) }</ID>
<ID>MultilineLambdaItParameter:WalletWarningsClickIntents.kt$WalletWarningsClickIntentsImplementor${ when (it) { UnlockWalletError.AlreadyUnlocked -&gt; Unit UnlockWalletError.ScannedCardWalletNotMatched -&gt; { uiMessageSender.send( message = DialogMessage( title = resourceReference(R.string.common_warning), message = resourceReference(R.string.error_wrong_wallet_tapped), ), ) } UnlockWalletError.UnableToUnlock -&gt; { Timber.e("Unable to unlock wallet with id: $selectedUserWalletId") uiMessageSender.send( SnackbarMessage(TextReference.Res(R.string.generic_error)), ) } UnlockWalletError.UserCancelled -&gt; Unit UnlockWalletError.UserWalletNotFound -&gt; { // This should never happen in this flow Timber.e("User wallet not found for unlock: $selectedUserWalletId") uiMessageSender.send( SnackbarMessage(TextReference.Res(R.string.generic_error)), ) } } }</ID>
<ID>MultilineLambdaItParameter:WalletWithFundsChecker.kt$WalletWithFundsChecker${ val amount = it.value.amount ?: return@any false !amount.isZero() }</ID>
<ID>MultilineLambdaItParameter:WalletsUpdateActionResolver.kt$WalletsUpdateActionResolver${ if (it.warnings.any { it is WalletNotification.FinishWalletActivation }) { it.walletCardState.id } else { null } }</ID>
<ID>NamedArguments:BasicAccountListSubscriber.kt$BasicAccountListSubscriber$updateContent(convertParams, appCurrency, yieldSupplyApyMap, stakingApyMap)</ID>
<ID>NamedArguments:GetMultiWalletWarningsFactory.kt$GetMultiWalletWarningsFactory$addFinishWalletActivationNotification(userWallet, totalFiatBalance, clickIntents, accessCodeSkipped)</ID>
<ID>NamedArguments:GetMultiWalletWarningsFactory.kt$GetMultiWalletWarningsFactory$addInformationalNotifications(userWallet, cardTypesResolver, flattenCurrencies, clickIntents)</ID>
<ID>NamedArguments:GetMultiWalletWarningsFactory.kt$GetMultiWalletWarningsFactory$addWarningNotifications(cardTypesResolver, flattenCurrencies, isNeedToBackup, clickIntents)</ID>
<ID>NamedArguments:TangemSnapFlingBehavior.kt$HighVelocityApproachAnimation$animateDecay(offset, animationState, decayAnimationSpec, onAnimationStep)</ID>
@ -114,23 +107,6 @@
<ID>NoNameShadowing:WalletCurrencyActionsClickIntents.kt$WalletCurrencyActionsClickIntentsImplementor${ it is TokensListItemUM.Token }</ID>
<ID>NoNameShadowing:WalletNFTItem.kt$modifier</ID>
<ID>NoNameShadowing:WalletScreen.kt${ it.organizeTokensButtonConfig?.let { config -&gt; organizeTokensButton( modifier = itemModifier, isEnabled = config.isEnabled, onClick = config.onClick, ) } }</ID>
<ID>NoNameShadowing:WalletWarningsClickIntents.kt$WalletWarningsClickIntentsImplementor${ when (it) { UnlockWalletError.AlreadyUnlocked -&gt; Unit UnlockWalletError.ScannedCardWalletNotMatched -&gt; { uiMessageSender.send( message = DialogMessage( title = resourceReference(R.string.common_warning), message = resourceReference(R.string.error_wrong_wallet_tapped), ), ) } UnlockWalletError.UnableToUnlock -&gt; { Timber.e("Unable to unlock wallet with id: $selectedUserWalletId") uiMessageSender.send( SnackbarMessage(TextReference.Res(R.string.generic_error)), ) } UnlockWalletError.UserCancelled -&gt; Unit UnlockWalletError.UserWalletNotFound -&gt; { // This should never happen in this flow Timber.e("User wallet not found for unlock: $selectedUserWalletId") uiMessageSender.send( SnackbarMessage(TextReference.Res(R.string.generic_error)), ) } } }</ID>
<ID>NoNameShadowing:WalletsUpdateActionResolver.kt$WalletsUpdateActionResolver${ it is WalletNotification.FinishWalletActivation }</ID>
<ID>NonBooleanPropertyPrefixedWithIs:AccountDependencies.kt$AccountDependencies$val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:ExpandedAccountsHolder.kt$ExpandedAccountsHolder$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:GetMultiWalletWarningsFactory.kt$GetMultiWalletWarningsFactory$private val isDemoCardUseCase: IsDemoCardUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:GetMultiWalletWarningsFactory.kt$GetMultiWalletWarningsFactory$private val isNeedToBackupUseCase: IsNeedToBackupUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:GetMultiWalletWarningsFactory.kt$GetMultiWalletWarningsFactory$private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:GetSingleWalletWarningsFactory.kt$GetSingleWalletWarningsFactory$private val isDemoCardUseCase: IsDemoCardUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:GetSingleWalletWarningsFactory.kt$GetSingleWalletWarningsFactory$private val isNeedToBackupUseCase: IsNeedToBackupUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:GetSingleWalletWarningsFactory.kt$GetSingleWalletWarningsFactory$private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:OrganizeTokensModel.kt$OrganizeTokensModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:ScreenLifecycleProvider.kt$ScreenLifecycleProvider$val isBackgroundState: StateFlow&lt;Boolean&gt; = _isBackgroundState</ID>
<ID>NonBooleanPropertyPrefixedWithIs:WalletCurrencyActionsClickIntents.kt$WalletCurrencyActionsClickIntentsImplementor$private val isCryptoCurrencyCoinCouldHide: IsCryptoCurrencyCoinCouldHideUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:WalletCurrencyActionsClickIntents.kt$WalletCurrencyActionsClickIntentsImplementor$private val isDemoCardUseCase: IsDemoCardUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:WalletModel.kt$WalletModel$private val isWalletsScrollPreviewEnabled: IsWalletsScrollPreviewEnabled</ID>
<ID>NonBooleanPropertyPrefixedWithIs:WalletScreen.kt$val isAutoScroll = remember { mutableStateOf(value = false) }</ID>
<ID>NonBooleanPropertyPrefixedWithIs:WalletScreen.kt$val isNavBarVisible = remember { mutableStateOf(true) }</ID>
<ID>NullableToStringCall:DefaultPromoDeeplinkHandler.kt$DefaultPromoDeeplinkHandler$$bitcoinCurrency</ID>
<ID>NullableToStringCall:DefaultPromoDeeplinkHandler.kt$DefaultPromoDeeplinkHandler$$bitcoinStatus</ID>
<ID>NullableToStringCall:DefaultPromoDeeplinkHandler.kt$DefaultPromoDeeplinkHandler$${cryptoCurrencies?.size}</ID>

View file

@ -7,6 +7,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.slot.childSlot
import com.arkivanov.decompose.router.slot.dismiss
import com.arkivanov.essenty.lifecycle.doOnResume
import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
@ -48,6 +49,7 @@ internal class WalletComponent @AssistedInject constructor(
init {
lifecycle.subscribe(model.screenLifecycleProvider)
doOnResume { model.onResume() }
componentScope.launch { model.innerWalletRouter.navigateToFlow.collect { navigate(it) } }
}

View file

@ -4,9 +4,11 @@ import androidx.compose.runtime.Stable
import arrow.core.getOrElse
import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss
import com.squareup.sqldelight.internal.AtomicBoolean
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.core.analytics.utils.TrackingContextProxy
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
@ -95,6 +97,7 @@ internal class WalletModel @Inject constructor(
private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val tangemPayMainInfoManager: TangemPayMainInfoManager,
private val trackingContextProxy: TrackingContextProxy,
val screenLifecycleProvider: ScreenLifecycleProvider,
val innerWalletRouter: InnerWalletRouter,
) : Model() {
@ -110,16 +113,12 @@ internal class WalletModel @Inject constructor(
private val updateTangemPayJobHolder = JobHolder()
private var expressTxStatusTaskScheduler = SingleTaskScheduler<Unit>()
private val hasMainScreenOpenedEventSent = AtomicBoolean(false)
init {
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.MainScreen.ScreenOpened)
screenLifecycleProvider.isBackgroundState
.onEach { isBackground ->
if (isBackground.not()) {
suggestToEnableBiometrics()
}
}.launchIn(modelScope)
if (!hotWalletFeatureToggles.isHotWalletEnabled) {
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.MainScreen.ScreenOpenedLegacy)
}
suggestToOpenMarkets()
@ -137,6 +136,12 @@ internal class WalletModel @Inject constructor(
clickIntents.initialize(innerWalletRouter, modelScope)
}
fun onResume() {
modelScope.launch(dispatchers.main) {
suggestToEnableBiometrics()
}
}
private fun updateYieldSupplyApy() {
if (yieldSupplyFeatureToggles.isYieldSupplyFeatureEnabled) {
modelScope.launch(dispatchers.default) {
@ -192,7 +197,6 @@ internal class WalletModel @Inject constructor(
private suspend fun shouldShowAskBiometryBottomSheet(): Boolean {
return if (hotWalletFeatureToggles.isHotWalletEnabled) {
userWalletsListRepository.userWalletsSync().any { it is UserWallet.Cold } &&
innerWalletRouter.isWalletLastScreen() &&
shouldShowAskBiometryUseCase() &&
canUseBiometryUseCase()
} else {
@ -270,11 +274,25 @@ internal class WalletModel @Inject constructor(
// It's okay here because we need to be able to observe the selected wallet changes
@Suppress("DEPRECATION")
private fun subscribeOnSelectedWalletFlow() {
getSelectedWalletUseCase().onRight {
it
getSelectedWalletUseCase().onRight { walletFlow ->
walletFlow
.conflate()
.distinctUntilChanged()
.onEach { selectedWallet ->
trackingContextProxy.setContext(selectedWallet)
if (hotWalletFeatureToggles.isHotWalletEnabled && !hasMainScreenOpenedEventSent.get()) {
// send it here because we need context to be set
modelScope.launch {
val hasMobileWallet = userWalletsListRepository.userWalletsSync()
.any { it is UserWallet.Hot }
analyticsEventsHandler.send(
WalletScreenAnalyticsEvent.MainScreen.ScreenOpened(hasMobileWallet),
)
hasMainScreenOpenedEventSent.set(true)
}
}
if (selectedWallet.isMultiCurrency) {
selectedWalletAnalyticsSender.send(selectedWallet)
}
@ -412,6 +430,7 @@ internal class WalletModel @Inject constructor(
when (action) {
is WalletsUpdateActionResolver.Action.InitializeWallets -> initializeWallets(action)
is WalletsUpdateActionResolver.Action.ReinitializeWallet -> reinitializeWallet(action)
is WalletsUpdateActionResolver.Action.ReinitializeWallets -> reinitializeWallets(action)
is WalletsUpdateActionResolver.Action.AddWallet -> addWallet(action)
is WalletsUpdateActionResolver.Action.DeleteWallet -> deleteWallet(action)
is WalletsUpdateActionResolver.Action.UnlockWallet -> unlockWallet(action)
@ -516,6 +535,32 @@ internal class WalletModel @Inject constructor(
)
}
private fun reinitializeWallets(action: WalletsUpdateActionResolver.Action.ReinitializeWallets) {
action.wallets.forEach { userWallet ->
walletScreenContentLoader.cancel(userWallet.walletId)
tokenListStore.remove(userWallet.walletId)
walletScreenContentLoader.load(
userWallet = userWallet,
clickIntents = clickIntents,
coroutineScope = modelScope,
)
modelScope.launch(dispatchers.main) {
fetchWalletContent(userWallet = userWallet)
}
stateHolder.update(
ReinitializeWalletTransformer(
prevWalletId = userWallet.walletId,
newUserWallet = userWallet,
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
),
)
}
}
private suspend fun addWallet(action: WalletsUpdateActionResolver.Action.AddWallet) {
if (accountsFeatureToggles.isFeatureEnabled) {
fetchWalletContent(userWallet = action.selectedWallet)

View file

@ -64,8 +64,11 @@ internal class WalletsUpdateActionResolver @Inject constructor(
selectedWallet: UserWallet,
): Action {
return when {
isHotWalletUpgraded(state, wallets) -> {
getHotWalletsUpgradedAction(state, wallets)
isAnyHotWalletUpgraded(state, wallets) -> {
getHotWalletsUpgradedAction(state, wallets, selectedWallet)
}
isAnyHotWalletBackedUpChange(state, wallets) -> {
getHotWalletsBackedUpAction(state, wallets)
}
isWalletsCountChanged(state, wallets) -> {
getChangeWalletsListAction(state, wallets, selectedWallet)
@ -79,31 +82,35 @@ internal class WalletsUpdateActionResolver @Inject constructor(
isAnyWalletNameChanged(state, wallets) -> {
getRenameWalletsAction(state, wallets)
}
isAnyHotWalletBackedUpChange(state, wallets) -> {
getHotWalletsBackedUpAction(state, wallets)
isAnyWalletUnlocked(state, wallets) -> {
Action.UnlockWallet(
selectedWallet = selectedWallet,
unlockedWallets = wallets.filterNot(UserWallet::isLocked),
)
}
else -> getUpdateSelectedWalletAction(state, wallets, selectedWallet)
isSelectedWalletCardsCountChanged(state, selectedWallet) -> {
Action.UpdateWalletCardCount(selectedWallet)
}
else -> Action.Unknown
}
}
private fun isAnyHotWalletBackedUpChange(state: WalletScreenState, wallets: List<UserWallet>): Boolean {
val incompleteActivationWalletIds = state.incompleteActivationWalletIds()
val walletsToUpdate = wallets.filter {
it is UserWallet.Hot && it.backedUp == incompleteActivationWalletIds.contains(it.walletId)
return wallets.any {
it is UserWallet.Hot && it.backedUp && incompleteActivationWalletIds.contains(it.walletId)
}
return walletsToUpdate.isNotEmpty()
}
private fun isHotWalletUpgraded(state: WalletScreenState, wallets: List<UserWallet>): Boolean {
val previousWallet = state
.wallets
.getOrNull(state.selectedWalletIndex)
return when (previousWallet) {
is WalletState.MultiCurrency -> {
val wallet = wallets.firstOrNull { it.walletId == previousWallet.walletCardState.id }
previousWallet.type == WalletState.MultiCurrency.WalletType.Hot && wallet is UserWallet.Cold
private fun isAnyHotWalletUpgraded(state: WalletScreenState, wallets: List<UserWallet>): Boolean {
return state.wallets.any { walletState ->
when (walletState) {
is WalletState.MultiCurrency -> {
val wallet = wallets.firstOrNull { it.walletId == walletState.walletCardState.id }
walletState.type == WalletState.MultiCurrency.WalletType.Hot && wallet is UserWallet.Cold
}
else -> false
}
else -> false
}
}
@ -189,9 +196,15 @@ internal class WalletsUpdateActionResolver @Inject constructor(
private fun getHotWalletsUpgradedAction(
state: WalletScreenState,
wallets: List<UserWallet>,
): Action.ReloadWallets {
val walletsToUpdate = wallets.filter { it.walletId == state.getPrevSelectedWallet().id }
return Action.ReloadWallets(walletsToUpdate)
selectedWallet: UserWallet,
): Action.ReinitializeWallets {
val walletsToUpdate = wallets.filter { wallet ->
val previousState = state.wallets.firstOrNull { it.walletCardState.id == wallet.walletId }
?: return@filter false
wallet is UserWallet.Cold && previousState is WalletState.MultiCurrency &&
previousState.type == WalletState.MultiCurrency.WalletType.Hot
}
return Action.ReinitializeWallets(selectedWallet, walletsToUpdate)
}
private fun getRenameWalletsAction(state: WalletScreenState, wallets: List<UserWallet>): Action.RenameWallets {
@ -205,29 +218,14 @@ internal class WalletsUpdateActionResolver @Inject constructor(
)
}
private fun getUpdateSelectedWalletAction(
state: WalletScreenState,
wallets: List<UserWallet>,
selectedWallet: UserWallet,
): Action {
return when {
isSelectedWalletUnlocked(state, selectedWallet) -> {
Action.UnlockWallet(
selectedWallet = selectedWallet,
unlockedWallets = wallets.filterNot(UserWallet::isLocked),
)
}
isSelectedWalletCardsCountChanged(state, selectedWallet) -> {
Action.UpdateWalletCardCount(selectedWallet)
}
else -> Action.Unknown
private fun isAnyWalletUnlocked(state: WalletScreenState, wallets: List<UserWallet>): Boolean {
return state.wallets.any { walletState ->
val wallet = wallets.firstOrNull { it.walletId == walletState.walletCardState.id } ?: return@any false
!wallet.isLocked &&
(walletState is WalletState.MultiCurrency.Locked || walletState is WalletState.SingleCurrency.Locked)
}
}
private fun isSelectedWalletUnlocked(state: WalletScreenState, selectedWallet: UserWallet): Boolean {
return state.isSelectedWalletLocked() && !selectedWallet.isLocked
}
private fun isSelectedWalletCardsCountChanged(state: WalletScreenState, selectedWallet: UserWallet): Boolean {
if (selectedWallet !is UserWallet.Cold) return false
val prevSelectedWallet = state.getPrevSelectedWallet()
@ -235,12 +233,6 @@ internal class WalletsUpdateActionResolver @Inject constructor(
prevSelectedWallet.cardCount != selectedWallet.getCardsCount()
}
private fun WalletScreenState.isSelectedWalletLocked(): Boolean {
val selectedWalletState = wallets.getOrNull(selectedWalletIndex) ?: error("Selected wallet is not found")
return selectedWalletState is WalletState.MultiCurrency.Locked ||
selectedWalletState is WalletState.SingleCurrency.Locked
}
private fun WalletScreenState.getPrevSelectedWallet(): WalletCardState {
return wallets
.map(WalletState::walletCardState)
@ -249,9 +241,12 @@ internal class WalletsUpdateActionResolver @Inject constructor(
}
private fun WalletScreenState.incompleteActivationWalletIds(): List<UserWalletId> {
return wallets.mapNotNull {
if (it.warnings.any { it is WalletNotification.FinishWalletActivation }) {
it.walletCardState.id
return wallets.mapNotNull { wallet ->
if (wallet.warnings.any { it is WalletNotification.FinishWalletActivation } ||
wallet.walletCardState is WalletState.MultiCurrency &&
wallet.walletCardState.additionalInfo?.isHotBackedUp == false
) {
wallet.walletCardState.id
} else {
null
}
@ -306,6 +301,19 @@ internal class WalletsUpdateActionResolver @Inject constructor(
}
}
/**
* Reinitialize wallets
*/
data class ReinitializeWallets(
val selectedWallet: UserWallet,
val wallets: List<UserWallet>,
) : Action() {
override fun toString(): String {
return "ReinitializeWallets(wallets = ${wallets.joinToString { it.walletId.toString() }}"
}
}
/**
* Rename wallets
*

View file

@ -11,9 +11,7 @@ import com.tangem.core.analytics.models.AnalyticsParam
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.components.bottomsheets.message.*
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.bottomSheetMessage
import com.tangem.domain.card.SetCardWasScannedUseCase
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
@ -40,7 +38,6 @@ import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent.PromotionBan
import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType
import com.tangem.domain.wallets.models.UnlockWalletsError
import com.tangem.domain.wallets.usecase.*
import com.tangem.feature.wallet.child.wallet.model.WalletActivationBannerType
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.Basic
@ -106,7 +103,7 @@ internal interface WalletWarningsClickIntents {
fun onDenyPermissions()
fun onFinishWalletActivationClick(bannerType: WalletActivationBannerType, isBackupExists: Boolean)
fun onFinishWalletActivationClick(isBackupExists: Boolean)
}
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
@ -460,40 +457,10 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
}
}
override fun onFinishWalletActivationClick(bannerType: WalletActivationBannerType, isBackupExists: Boolean) {
when (bannerType) {
WalletActivationBannerType.Attention -> {
val userWallet = getSelectedUserWallet() ?: return
appRouter.push(WalletActivation(userWallet.walletId, isBackupExists))
}
WalletActivationBannerType.Warning -> {
val message = bottomSheetMessage {
infoBlock {
icon(R.drawable.img_knight_shield_32) {
type = MessageBottomSheetUMV2.Icon.Type.Warning
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint
}
title = resourceReference(R.string.hw_activation_need_title)
body = resourceReference(R.string.hw_activation_need_description)
}
secondaryButton {
text = resourceReference(R.string.common_later)
onClick {
closeBs()
}
}
primaryButton {
text = resourceReference(R.string.hw_activation_need_backup)
onClick {
val userWallet = getSelectedUserWallet() ?: return@onClick
appRouter.push(WalletActivation(userWallet.walletId, isBackupExists))
closeBs()
}
}
}
uiMessageSender.send(message)
}
}
override fun onFinishWalletActivationClick(isBackupExists: Boolean) {
analyticsEventHandler.send(MainScreen.ButtonFinalizeActivation)
val userWalletId = stateHolder.getSelectedWalletId()
appRouter.push(WalletActivation(userWalletId, isBackupExists))
}
override fun onAllowPermissions() {

View file

@ -3,7 +3,6 @@ package com.tangem.feature.wallet.presentation.common.preview
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState
import com.tangem.core.ui.components.token.AccountItemPreviewData
import com.tangem.core.ui.components.token.state.TokenItemState
@ -14,6 +13,7 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.child.wallet.model.WalletActivationBannerType
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.WalletPreviewData.topBarConfig
import com.tangem.feature.wallet.presentation.wallet.state.model.*
@ -167,11 +167,12 @@ internal object WalletScreenPreviewData {
warnings = persistentListOf(
WalletNotification.Warning.SomeNetworksUnreachable,
WalletNotification.FinishWalletActivation(
iconTint = NotificationConfig.IconTint.Attention,
type = WalletActivationBannerType.Attention,
buttonsState = ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.hw_activation_need_finish),
onClick = { },
),
isBackupExists = false,
),
),
bottomSheetConfig = null,

View file

@ -51,7 +51,30 @@ sealed class WalletScreenAnalyticsEvent {
params: Map<String, String> = mapOf(),
) : AnalyticsEvent(category = "Main Screen", event = event, params = params) {
data object ScreenOpened : MainScreen(event = "Screen opened")
data object ScreenOpenedLegacy : MainScreen(
event = "Screen opened",
)
data class ScreenOpened(
private val hasMobileWallet: Boolean,
) : MainScreen(
event = "Screen opened",
params = mapOf("Mobile Wallet" to if (hasMobileWallet) "Yes" else "No"),
)
data class NoticeFinishActivation(private val activationState: ActivationState) : MainScreen(
event = "Notice - Finish Activation",
params = mapOf("Activation State" to activationState.value),
) {
enum class ActivationState(val value: String) {
NotStarted("Not Started"),
Unfinished("Unfinished"),
}
}
data object ButtonFinalizeActivation : MainScreen(
event = "Button - Finalize Activation",
)
class WalletSelected(val isImported: Boolean) : MainScreen(
event = "Wallet Selected",

View file

@ -70,9 +70,16 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
is WalletNotification.Warning.NetworksUnreachable,
is WalletNotification.UsedOutdatedData,
is WalletNotification.UnlockVisaAccess,
is WalletNotification.FinishWalletActivation,
is WalletNotification.Warning.YeildSupplyApprove, // TODO apply correct event
-> null
is WalletNotification.FinishWalletActivation -> {
val activationState = if (warning.isBackupExists) {
MainScreen.NoticeFinishActivation.ActivationState.Unfinished
} else {
MainScreen.NoticeFinishActivation.ActivationState.NotStarted
}
MainScreen.NoticeFinishActivation(activationState)
}
is WalletNotification.Critical.SeedPhraseNotification -> MainScreen.NoticeSeedPhraseSupport
is WalletNotification.Critical.SeedPhraseSecondNotification -> MainScreen.NoticeSeedPhraseSupportSecond
is WalletNotification.PushNotifications -> WalletScreenAnalyticsEvent.PushBannerPromo.PushBanner

View file

@ -1,8 +1,24 @@
package com.tangem.feature.wallet.presentation.wallet.analytics.utils
import arrow.atomic.AtomicBoolean
import com.tangem.common.routing.AppRoute.WalletActivation
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2
import com.tangem.core.ui.components.bottomsheets.message.icon
import com.tangem.core.ui.components.bottomsheets.message.infoBlock
import com.tangem.core.ui.components.bottomsheets.message.onClick
import com.tangem.core.ui.components.bottomsheets.message.primaryButton
import com.tangem.core.ui.components.bottomsheets.message.secondaryButton
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.bottomSheetMessage
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.wallets.usecase.SeedPhraseNotificationUseCase
import com.tangem.feature.wallet.child.wallet.model.WalletActivationBannerType
import com.tangem.feature.wallet.impl.R
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.utils.ScreenLifecycleProvider
@ -12,8 +28,13 @@ import javax.inject.Inject
internal class WalletWarningsSingleEventSender @Inject constructor(
private val seedPhraseNotificationUseCase: SeedPhraseNotificationUseCase,
private val screenLifecycleProvider: ScreenLifecycleProvider,
private val uiMessageSender: UiMessageSender,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val router: Router,
) {
private val isActivationBottomSheetShown: AtomicBoolean = AtomicBoolean(false)
suspend fun send(
userWalletId: UserWalletId,
displayedUiState: WalletState?,
@ -26,9 +47,49 @@ internal class WalletWarningsSingleEventSender @Inject constructor(
val events = newWarnings.filter { it !in displayedUiState.warnings }
events.forEach { event ->
if (event is WalletNotification.Critical.SeedPhraseNotification) {
seedPhraseNotificationUseCase.notified(userWalletId = userWalletId)
when (event) {
is WalletNotification.Critical.SeedPhraseNotification -> {
seedPhraseNotificationUseCase.notified(userWalletId = userWalletId)
}
is WalletNotification.FinishWalletActivation -> {
if (event.type == WalletActivationBannerType.Warning && !isActivationBottomSheetShown.get()) {
showFinishActivationBottomSheet(userWalletId)
isActivationBottomSheetShown.set(true)
}
}
else -> Unit
}
}
}
private fun showFinishActivationBottomSheet(userWalletId: UserWalletId) {
val userWallet = getUserWalletUseCase(userWalletId).getOrNull() ?: return
if (userWallet !is UserWallet.Hot) return
val message = bottomSheetMessage {
infoBlock {
icon(R.drawable.img_knight_shield_32) {
type = MessageBottomSheetUMV2.Icon.Type.Warning
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint
}
title = resourceReference(R.string.hw_activation_need_title)
body = resourceReference(R.string.hw_activation_need_description)
}
secondaryButton {
text = resourceReference(R.string.common_later)
onClick {
closeBs()
}
}
primaryButton {
text = resourceReference(R.string.hw_activation_need_backup)
onClick {
router.push(WalletActivation(userWallet.walletId, userWallet.backedUp))
closeBs()
}
}
}
uiMessageSender.send(message)
}
}

View file

@ -7,7 +7,6 @@ import com.tangem.common.ui.notifications.NotificationId
import com.tangem.common.ui.userwallet.ext.walletInterationIcon
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState
import com.tangem.core.ui.components.notifications.NotificationConfig.IconTint
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
import com.tangem.domain.card.CardTypesResolver
@ -465,24 +464,20 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
ifError = { WalletActivationBannerType.Attention },
)
val tint = when (type) {
WalletActivationBannerType.Attention -> IconTint.Attention
WalletActivationBannerType.Warning -> IconTint.Warning
}
addIf(
element = WalletNotification.FinishWalletActivation(
iconTint = tint,
type = type,
buttonsState = when (type) {
WalletActivationBannerType.Warning -> ButtonsState.PrimaryButtonConfig(
text = resourceReference(R.string.hw_activation_need_finish),
onClick = { clickIntents.onFinishWalletActivationClick(type, isBackupExists) },
onClick = { clickIntents.onFinishWalletActivationClick(isBackupExists) },
)
else -> ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.hw_activation_need_finish),
onClick = { clickIntents.onFinishWalletActivationClick(type, isBackupExists) },
onClick = { clickIntents.onFinishWalletActivationClick(isBackupExists) },
)
},
isBackupExists = isBackupExists,
),
condition = shouldShowFinishActivation,
)

View file

@ -50,6 +50,7 @@ internal object WalletAdditionalInfoFactory {
backedUp.not() -> DIVIDER + TextReference.Res(R.string.hw_backup_no_backup)
else -> TextReference.Str("")
},
isHotBackedUp = backedUp,
)
}

View file

@ -7,4 +7,5 @@ import com.tangem.core.ui.extensions.TextReference
data class WalletAdditionalInfo(
val hideable: Boolean,
val content: TextReference,
val isHotBackedUp: Boolean = false,
)

View file

@ -10,6 +10,7 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.pluralReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.feature.wallet.child.wallet.model.WalletActivationBannerType
import com.tangem.feature.wallet.impl.R
import org.joda.time.DateTime
@ -287,14 +288,18 @@ sealed class WalletNotification(val config: NotificationConfig) {
)
data class FinishWalletActivation(
val iconTint: IconTint,
val type: WalletActivationBannerType,
val buttonsState: ButtonsState,
val isBackupExists: Boolean,
) : WalletNotification(
config = NotificationConfig(
title = resourceReference(R.string.hw_activation_need_title),
subtitle = resourceReference(R.string.hw_activation_need_description),
iconResId = R.drawable.img_knight_shield_32,
iconTint = iconTint,
iconTint = when (type) {
WalletActivationBannerType.Attention -> IconTint.Attention
WalletActivationBannerType.Warning -> IconTint.Warning
},
buttonsState = buttonsState,
),
)

View file

@ -49,7 +49,6 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
yieldSupplyApyMap: Map<String, String> = emptyMap(),
stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
) {
val accountFlattenCurrencies = accountList.flattenCurrencies()
val mainAccount = accountList.mainAccount
when {
@ -70,20 +69,8 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
)
}
isAccountMode -> {
val isAllAccountsEmpty = accountFlattenCurrencies.isEmpty()
if (isAllAccountsEmpty) {
stateController.update(
SetTokenListErrorTransformer(
selectedWallet = userWallet,
error = TokenListError.EmptyTokens,
appCurrency = appCurrency,
),
)
} else {
val convertParams = TokenConverterParams.Account(accountList, expandedAccounts)
updateContent(convertParams, appCurrency, yieldSupplyApyMap, stakingApyMap)
}
val convertParams = TokenConverterParams.Account(accountList, expandedAccounts)
updateContent(convertParams, appCurrency, yieldSupplyApyMap, stakingApyMap)
}
}
}

View file

@ -31,10 +31,6 @@
<ID>NamedArguments:WcSendTransactionModel.kt$WcSendTransactionModel$buildUiState(securityCheck, useCase, signState, isApprovalMethod)</ID>
<ID>NestedScopeFunctions:WcSendAndReceiveBlockAidUiConverter.kt$WcSendAndReceiveBlockAidUiConverter$let { spendAllowanceUMConverter.convert( WcSpendAllowanceUMConverter.Input( approvedAmount = it, onLearnMoreClick = value.onApproveLearnMoreClick, ), ) }</ID>
<ID>NoNameShadowing:WcNavigationUtils.kt$model</ID>
<ID>NonBooleanPropertyPrefixedWithIs:WcConnectionsModel.kt$WcConnectionsModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:WcPairModel.kt$WcPairModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
<ID>NonBooleanPropertyPrefixedWithIs:WcPortfolioNameDelegate.kt$WcPortfolioNameDelegate$val isAccountMode = isAccountsModeEnabledUseCase.invoke() .stateIn(scope = scope, started = SharingStarted.Eagerly, initialValue = false)</ID>
<ID>NonBooleanPropertyPrefixedWithIs:WcRoutingModel.kt$WcRoutingModel$private val isSlotEmpty = MutableStateFlow(true)</ID>
<ID>NullCheckOnMutableProperty:WcCommonTransactionComponentDelegate.kt$WcCommonTransactionComponentDelegate$if (contentStack != null) { val content by contentStack!!.subscribeAsState() BackHandler(onBack = ::onChildBack) content.active.instance.BottomSheet() }</ID>
<ID>NullableBooleanCheck:WcPairModel.kt$WcPairModel$isAccountMode ?: false</ID>
<ID>NullableToStringCall:WcEstimatedWalletChangeUMConverter.kt$WcEstimatedWalletChangeUMConverter$${value.sign}</ID>

View file

@ -118,7 +118,7 @@ internal class WcPairModel @Inject constructor(
init {
if (accountsFeatureToggles.isFeatureEnabled) {
portfolioFetcher = portfolioFetcherFactory.create(
mode = PortfolioFetcher.Mode.All(onlyMultiCurrency = true),
mode = PortfolioFetcher.Mode.All(isOnlyMultiCurrency = true),
scope = modelScope,
)
modelScope.launch {

View file

@ -4,10 +4,8 @@
<CurrentIssues>
<ID>BooleanPropertyNaming:WelcomeModel.kt$WelcomeModel$private var routedOut = false</ID>
<ID>BooleanPropertyNaming:WelcomeUM.kt$WelcomeUM.SelectWallet$val showUnlockWithBiometricButton: Boolean = false</ID>
<ID>CanBeNonNullable:WelcomeModel.kt$WelcomeModel$specificWalletId: UserWalletId?</ID>
<ID>MultilineLambdaItParameter:WelcomeModel.kt$WelcomeModel${ if (it.isEmpty()) { router.replaceAll(AppRoute.Home()) } wallets.value = it }</ID>
<ID>MultilineLambdaItParameter:WelcomeModel.kt$WelcomeModel${ it.handle( specificWalletId = null, onUserCancelled = { tryToUnlockWithAccessCodeRightAway() }, ) setSelectWalletState() }</ID>
<ID>NullableToStringCall:WelcomeModel.kt$WelcomeModel$$specificWalletId</ID>
<ID>ReusedModifierInstance:Welcome.kt$WelcomePlain(modifier = modifier)</ID>
<ID>ReusedModifierInstance:Welcome.kt$WelcomeSelectWallet( state = st, modifier = modifier, )</ID>
</CurrentIssues>

View file

@ -3,6 +3,11 @@ package com.tangem.features.welcome.impl.model
import com.tangem.common.routing.AppRoute
import com.tangem.common.ui.userwallet.handle
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.analytics.models.event.SignIn
import com.tangem.core.analytics.utils.TrackingContextProxy
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.navigation.Router
@ -36,6 +41,8 @@ internal class WelcomeModel @Inject constructor(
private val nonBiometricUnlockWalletUseCase: NonBiometricUnlockWalletUseCase,
private val canUseBiometryUseCase: CanUseBiometryUseCase,
private val walletsRepository: WalletsRepository,
private val trackingContextProxy: TrackingContextProxy,
private val analyticsEventHandler: AnalyticsEventHandler,
userWalletsFetcherFactory: UserWalletsFetcher.Factory,
) : Model() {
@ -51,6 +58,14 @@ internal class WelcomeModel @Inject constructor(
modelScope.launch {
val userWallets = userWalletsListRepository.userWalletsSync()
val userWallet = userWallets.first { it.walletId == walletId }
trackingContextProxy.proceedWithContext(userWallet) {
val signInType = when {
!userWallet.isLocked -> SignIn.ButtonWallet.SignInType.NoSecurity
userWallet is UserWallet.Cold -> SignIn.ButtonWallet.SignInType.Card
else -> SignIn.ButtonWallet.SignInType.AccessCode
}
analyticsEventHandler.send(SignIn.ButtonWallet(signInType))
}
onUserWalletClick(userWallet)
}
},
@ -64,6 +79,8 @@ internal class WelcomeModel @Inject constructor(
userWalletsListRepository.load()
wallets.value = walletsFetcher.userWallets.first()
analyticsEventHandler.send(SignIn.ScreenOpened(wallets.value.size))
launch {
walletsFetcher.userWallets
.collectLatest {
@ -119,6 +136,7 @@ internal class WelcomeModel @Inject constructor(
showUnlockWithBiometricButton = canUnlockWithBiometrics(),
addWalletClick = ::addWalletClick,
onUnlockWithBiometricClick = {
analyticsEventHandler.send(SignIn.ButtonUnlockAllWithBiometric())
modelScope.launch {
userWalletsListRepository.unlockAllWallets()
.onRight {
@ -140,6 +158,7 @@ internal class WelcomeModel @Inject constructor(
}
private fun addWalletClick() {
analyticsEventHandler.send(SignIn.ButtonAddWallet(AnalyticsParam.ScreensSources.SignIn))
router.push(AppRoute.CreateWalletSelection)
}
@ -154,6 +173,7 @@ internal class WelcomeModel @Inject constructor(
if (userWallet.isLocked.not()) {
// If the wallet is not locked, we can proceed to the wallet screen directly
userWalletsListRepository.select(userWallet.walletId)
trackSignInEvent(userWallet, Basic.SignedIn.SignInType.NoSecurity)
router.replaceAll(AppRoute.Wallet)
return@launch
}
@ -203,4 +223,16 @@ internal class WelcomeModel @Inject constructor(
}
}
}
private suspend fun trackSignInEvent(userWallet: UserWallet, type: Basic.SignedIn.SignInType) {
val walletsCount = userWalletsListRepository.userWalletsSync().size
trackingContextProxy.proceedWithContext(userWallet) {
analyticsEventHandler.send(
event = Basic.SignedIn(
signInType = type,
walletsCount = walletsCount,
),
)
}
}
}

View file

@ -1,8 +0,0 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>BooleanPropertyNaming:YieldSupplyComponent.kt$YieldSupplyComponent.Params$val handleNavigation: Boolean? = null</ID>
<ID>UseEmptyCounterpart:YieldSupplyAnalytics.kt$YieldSupplyAnalytics$mapOf()</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -10,7 +10,7 @@ interface YieldSupplyComponent : ComposableContentComponent {
data class Params(
val userWalletId: UserWalletId,
val cryptoCurrency: CryptoCurrency,
val handleNavigation: Boolean? = null,
val shouldHandleNavigation: Boolean? = null,
)
interface Factory : ComponentFactory<Params, YieldSupplyComponent>

View file

@ -8,7 +8,7 @@ import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
sealed class YieldSupplyAnalytics(
event: String,
params: Map<String, String> = mapOf(),
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent(category = "Earning", event = event, params = params) {
data class EarningScreenInfoOpened(

View file

@ -3,7 +3,6 @@
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>BooleanPropertyNaming:YieldSupplyApyComponent.kt$YieldSupplyApyComponent$val state by loadingState.collectAsState()</ID>
<ID>BooleanPropertyNaming:YieldSupplyModel.kt$YieldSupplyModel$private val handleNavigation = params.handleNavigation</ID>
<ID>BooleanPropertyNaming:YieldSupplyModel.kt$YieldSupplyModel$val processing = uiState.value is YieldSupplyUM.Processing</ID>
<ID>BooleanPropertyNaming:YieldSupplyModel.kt$YieldSupplyModel$val showWarningIcon = !yieldSupplyStatus.isAllowedToSpend</ID>
<ID>BooleanPropertyNaming:YieldSupplyUM.kt$YieldSupplyUM.Content$val showInfoIcon: Boolean</ID>
@ -16,10 +15,6 @@
<ID>NamedArguments:YieldSupplyActiveContent.kt$Icon( painterResource(R.drawable.ic_token_info_24), contentDescription = null, modifier = Modifier.size(20.dp), tint = TangemTheme.colors.text.warning, )</ID>
<ID>NamedArguments:YieldSupplyChartUM.kt$YieldSupplyMarketChartDataUM.Companion$YieldSupplyMarketChartDataUM(y = y, x = x, avr = 5.15, "%.1f")</ID>
<ID>NoNameShadowing:YieldSupplyStopEarningModel.kt$YieldSupplyStopEarningModel$fee</ID>
<ID>NonBooleanPropertyPrefixedWithIs:YieldSupplyActiveComponent.kt$YieldSupplyActiveComponent.Params$val isBalanceHiddenFlow: StateFlow&lt;Boolean&gt;</ID>
<ID>NonBooleanPropertyPrefixedWithIs:YieldSupplyActiveEntryComponent.kt$YieldSupplyActiveEntryComponent.Params$val isBalanceHiddenFlow: StateFlow&lt;Boolean&gt;</ID>
<ID>NonBooleanPropertyPrefixedWithIs:YieldSupplyActiveEntryModel.kt$YieldSupplyActiveEntryModel$val isTransactionInProgressFlow: StateFlow&lt;Boolean&gt; field = MutableStateFlow(false)</ID>
<ID>NonBooleanPropertyPrefixedWithIs:YieldSupplyModel.kt$YieldSupplyModel$val isBalanceHiddenFlow: StateFlow&lt;Boolean&gt; field = MutableStateFlow(false)</ID>
<ID>NullableToStringCall:YieldSupplyModel.kt$YieldSupplyModel$$tokenPendingStatus</ID>
<ID>NullableToStringCall:YieldSupplyModel.kt$YieldSupplyModel$$tokenProtocolStatus</ID>
<ID>NullableToStringCall:YieldSupplyModel.kt$YieldSupplyModel$$yieldSupplyStatus</ID>

View file

@ -24,11 +24,7 @@ import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.yield.supply.YieldSupplyRepository
import com.tangem.domain.yield.supply.models.YieldSupplyEnterStatus
import com.tangem.domain.yield.supply.usecase.YieldSupplyActivateUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyDeactivateUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetTokenStatusUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyIsAvailableUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyMinAmountUseCase
import com.tangem.domain.yield.supply.usecase.*
import com.tangem.features.yield.supply.api.YieldSupplyComponent
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
import com.tangem.features.yield.supply.impl.R
@ -274,8 +270,7 @@ internal class YieldSupplyModel @Inject constructor(
private fun loadActiveState(cryptoCurrencyStatus: CryptoCurrencyStatus, yieldSupplyStatus: YieldSupplyStatus) {
val cryptoCurrencyToken = cryptoCurrency as? CryptoCurrency.Token ?: return
val showWarningIcon = !yieldSupplyStatus.isAllowedToSpend
val state = uiState.value
val isShowInfoIconPrevState = when (state) {
val isShowInfoIconPrevState = when (val state = uiState.value) {
is YieldSupplyUM.Content -> state.showInfoIcon
else -> false
}