Updated on 2026-08-14

This commit is contained in:
Tangem 2026-02-03 13:42:07 +03:00
commit d994b7840a
150 changed files with 971 additions and 4621 deletions

View file

@ -16,12 +16,10 @@ import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.settings.SetAskBiometryShownUseCase
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.features.biometry.AskBiometryComponent
import com.tangem.features.biometry.impl.ui.state.AskBiometryUM
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.delay
@ -40,7 +38,6 @@ internal class AskBiometryModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
paramsContainer: ParamsContainer,
private val setAskBiometryShownUseCase: SetAskBiometryShownUseCase,
private val settingsRepository: SettingsRepository,
private val tangemSdkManager: TangemSdkManager,
private val getSelectedWalletUseCase: GetSelectedWalletUseCase,
private val walletsRepository: WalletsRepository,
@ -48,7 +45,6 @@ internal class AskBiometryModel @Inject constructor(
private val settingsManager: SettingsManager,
private val uiMessageSender: UiMessageSender,
private val userWalletsListRepository: UserWalletsListRepository,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
) : Model() {
private val params = paramsContainer.require<AskBiometryComponent.Params>()
@ -110,24 +106,13 @@ internal class AskBiometryModel @Inject constructor(
}
private suspend fun handleSuccessAllowing(userWallet: UserWallet) {
walletsRepository.saveShouldSaveUserWallets(item = true)
if (hotWalletFeatureToggles.isHotWalletEnabled) {
walletsRepository.setUseBiometricAuthentication(value = true)
walletsRepository.setRequireAccessCode(value = false)
setBiometryLockForAllWallets()
if (userWallet is UserWallet.Cold) {
cardSdkConfigRepository.setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = userWallet.hasAccessCode,
)
}
} else {
settingsRepository.setShouldSaveAccessCodes(value = true)
if (userWallet is UserWallet.Cold) {
cardSdkConfigRepository.setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = userWallet.hasAccessCode,
)
}
walletsRepository.setUseBiometricAuthentication(value = true)
walletsRepository.setRequireAccessCode(value = false)
setBiometryLockForAllWallets()
if (userWallet is UserWallet.Cold) {
cardSdkConfigRepository.setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = userWallet.hasAccessCode,
)
}
if (_uiState.value.isBottomSheetVariant) {

View file

@ -32,7 +32,6 @@ import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
import com.tangem.features.createwalletstart.entity.CreateWalletStartUM
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.delay
@ -64,7 +63,6 @@ internal class CreateWalletStartModel @Inject constructor(
private val urlOpener: UrlOpener,
private val trackingContextProxy: TrackingContextProxy,
private val analyticsEventHandler: AnalyticsEventHandler,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
) : Model() {
private val params = paramsContainer.require<CreateWalletStartComponent.Params>()
@ -99,7 +97,6 @@ internal class CreateWalletStartModel @Inject constructor(
onBackClick = { router.pop() },
onScanClick = ::onScanClick,
isScanInProgress = false,
isHotWalletOptionVisible = hotWalletFeatureToggles.isHotWalletVisible,
)
CreateWalletStartComponent.Mode.HotWallet -> CreateWalletStartUM(
title = resourceReference(R.string.hw_mobile_wallet),

View file

@ -17,7 +17,6 @@ internal data class CreateWalletStartUM(
val otherMethodClick: () -> Unit,
val onScanClick: () -> Unit,
val onBackClick: () -> Unit,
val isHotWalletOptionVisible: Boolean = true,
) {
data class FeatureItem(
val iconResId: Int,

View file

@ -6,7 +6,6 @@ import androidx.annotation.DrawableRes
import androidx.compose.foundation.*
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
@ -165,74 +164,72 @@ internal fun CreateWalletStartContent(state: CreateWalletStartUM, modifier: Modi
text = state.primaryButtonText.resolveReference(),
onClick = state.onPrimaryButtonClick,
)
if (state.isHotWalletOptionVisible) {
Row(
Row(
modifier = Modifier
.padding(
start = 16.dp,
top = 24.dp,
end = 16.dp,
),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
DashedGradientLine(
modifier = Modifier
.weight(1f)
.height(16.dp),
)
Text(
text = stringResourceSafe(R.string.common_or),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
)
DashedGradientLine(
modifier = Modifier
.weight(1f)
.height(16.dp)
.scale(scaleX = -1f, scaleY = 1f),
)
}
if (state.otherMethodDescription != null) {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(
start = 16.dp,
top = 24.dp,
top = 16.dp,
end = 16.dp,
),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
DashedGradientLine(
modifier = Modifier
.weight(1f)
.height(16.dp),
)
Text(
text = stringResourceSafe(R.string.common_or),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
)
DashedGradientLine(
modifier = Modifier
.weight(1f)
.height(16.dp)
.scale(scaleX = -1f, scaleY = 1f),
)
}
text = state.otherMethodDescription.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,
)
}
if (state.isHotWalletOptionVisible) {
if (state.otherMethodDescription != null) {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(
start = 16.dp,
top = 16.dp,
end = 16.dp,
),
text = state.otherMethodDescription.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,
)
}
Row(
modifier = Modifier
.wrapContentWidth()
.clickable { state.otherMethodClick() }
.padding(
horizontal = 16.dp,
vertical = 12.dp,
),
horizontalArrangement = Arrangement.Center,
) {
Text(
text = state.otherMethodTitle.resolveReference(),
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
)
Icon(
painter = painterResource(id = R.drawable.ic_chevron_right_18x24),
tint = TangemTheme.colors.icon.primary1,
contentDescription = null,
)
}
Row(
modifier = Modifier
.wrapContentWidth()
.clickable { state.otherMethodClick() }
.padding(
horizontal = 16.dp,
vertical = 12.dp,
),
horizontalArrangement = Arrangement.Center,
) {
Text(
text = state.otherMethodTitle.resolveReference(),
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
)
Icon(
painter = painterResource(id = R.drawable.ic_chevron_right_18x24),
tint = TangemTheme.colors.icon.primary1,
contentDescription = null,
)
}
},
minImageHeight = 160.dp,

View file

@ -21,9 +21,7 @@ internal class PreviewDetailsComponent : DetailsComponent {
ItemsBuilder(
router = DummyRouter(),
hotWalletFeatureToggles = object : HotWalletFeatureToggles {
override val isHotWalletEnabled: Boolean = true
override val isWalletCreationRestrictionEnabled: Boolean = true
override val isHotWalletVisible: Boolean = true
},
).buildAll(
isWalletConnectAvailable = true,

View file

@ -3,7 +3,6 @@ package com.tangem.features.details.model
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
@ -34,7 +33,6 @@ 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
@ -59,7 +57,6 @@ internal class DetailsModel @Inject constructor(
private val checkIsWalletConnectAvailableUseCase: CheckIsWalletConnectAvailableUseCase,
private val router: Router,
private val urlOpener: UrlOpener,
private val appInstanceIdProvider: AppInstanceIdProvider,
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
private val appStateHolder: ReduxStateHolder,
private val getWalletMetaInfoUseCase: GetWalletMetaInfoUseCase,
@ -67,7 +64,6 @@ internal class DetailsModel @Inject constructor(
private val getWalletsUseCase: GetWalletsUseCase,
override val dispatchers: CoroutineDispatcherProvider,
private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val analyticsEventHandler: AnalyticsEventHandler,
private val tangemPayEligibilityManager: TangemPayEligibilityManager,
) : Model() {
@ -229,14 +225,10 @@ internal class DetailsModel @Inject constructor(
private fun onBuyClick() {
modelScope.launch {
if (hotWalletFeatureToggles.isHotWalletEnabled) {
analyticsEventHandler.send(Basic.ButtonBuy(source = AnalyticsParam.ScreensSources.Settings))
generateBuyTangemCardLinkUseCase
.invoke(GenerateBuyTangemCardLinkUseCase.Source.Settings).let { urlOpener.openUrl(it) }
} else {
// This is incorrect implementation of buy link generation, but it is left here
urlOpener.openUrl(buildBuyLink())
}
analyticsEventHandler.send(Basic.ButtonBuy(source = AnalyticsParam.ScreensSources.Settings))
val url = generateBuyTangemCardLinkUseCase(GenerateBuyTangemCardLinkUseCase.Source.Settings)
urlOpener.openUrl(url)
}
}
@ -270,12 +262,6 @@ internal class DetailsModel @Inject constructor(
private fun getAppVersion(): String = "${appVersionProvider.versionName} (${appVersionProvider.versionCode})"
private suspend fun buildBuyLink(): String {
return appInstanceIdProvider.getAppInstanceId()?.let {
"$BUY_TANGEM_URL&app_instance_id=$it"
} ?: BUY_TANGEM_URL
}
private companion object {
val SYSTEM_LANGUAGE = runCatching { Resources.getSystem().configuration.locales[0].language }.getOrElse { "" }
val APP_LANGUAGE = Locale.getDefault().language
@ -283,7 +269,5 @@ internal class DetailsModel @Inject constructor(
"&utm_medium=app" +
"&utm_campaign=users-$SYSTEM_LANGUAGE" +
"&utm_content=devicelang-$APP_LANGUAGE"
val BUY_TANGEM_URL = "https://buy.tangem.com/?$UTM_MARKS"
}
}

View file

@ -13,7 +13,6 @@ import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.domain.wallets.usecase.UnlockWalletUseCase
import com.tangem.features.details.entity.UserWalletListUM
import com.tangem.features.details.impl.R
@ -32,7 +31,6 @@ import javax.inject.Inject
@ModelScoped
internal class UserWalletListModel @Inject constructor(
userWalletsFetcherFactory: UserWalletsFetcher.Factory,
shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val router: Router,
private val messageSender: UiMessageSender,
override val dispatchers: CoroutineDispatcherProvider,
@ -47,7 +45,7 @@ internal class UserWalletListModel @Inject constructor(
messageSender = messageSender,
onlyMultiCurrency = false,
isAuthMode = false,
isClickableIfLocked = hotWalletFeatureToggles.isHotWalletEnabled,
isClickableIfLocked = true,
onWalletClick = ::onWalletClicked,
)
@ -67,67 +65,48 @@ internal class UserWalletListModel @Inject constructor(
combine(
flow = userWalletsFlow,
flow2 = shouldSaveUserWalletsUseCase(),
flow3 = isWalletSavingInProgress,
) { userWallets, shouldSaveUserWallets, isWalletSavingInProgress ->
updateState(userWallets, shouldSaveUserWallets, isWalletSavingInProgress)
flow2 = isWalletSavingInProgress,
) { userWallets, isWalletSavingInProgress ->
updateState(userWallets, isWalletSavingInProgress)
}.collect()
}
}
private fun updateState(
userWallets: ImmutableList<UserWalletItemUM>,
shouldSaveUserWallets: Boolean,
isWalletSavingInProgress: Boolean,
) = state.update { value ->
value.copy(
userWallets = userWallets,
isWalletSavingInProgress = isWalletSavingInProgress,
addNewWalletText = when {
shouldSaveUserWallets || hotWalletFeatureToggles.isHotWalletEnabled -> {
resourceReference(R.string.user_wallet_list_add_button)
}
else -> resourceReference(R.string.scan_card_settings_button)
},
)
}
private fun updateState(userWallets: ImmutableList<UserWalletItemUM>, isWalletSavingInProgress: Boolean) =
state.update { value ->
value.copy(
userWallets = userWallets,
isWalletSavingInProgress = isWalletSavingInProgress,
addNewWalletText = resourceReference(R.string.user_wallet_list_add_button),
)
}
private fun onAddNewWalletClick() {
if (hotWalletFeatureToggles.isHotWalletEnabled) {
analyticsEventHandler.send(SignIn.ButtonAddWallet(AnalyticsParam.ScreensSources.Settings))
analyticsEventHandler.send(SignIn.ButtonAddWallet(AnalyticsParam.ScreensSources.Settings))
if (hotWalletFeatureToggles.isWalletCreationRestrictionEnabled) {
withProgress(isWalletSavingInProgress) {
userWalletSaver.scanAndSaveUserWallet(modelScope)
}
} else {
router.push(AppRoute.CreateWalletSelection)
}
} else {
if (hotWalletFeatureToggles.isWalletCreationRestrictionEnabled) {
withProgress(isWalletSavingInProgress) {
userWalletSaver.scanAndSaveUserWallet(modelScope)
}
} else {
router.push(AppRoute.CreateWalletSelection)
}
}
private fun onWalletClicked(userWalletId: UserWalletId) {
if (hotWalletFeatureToggles.isHotWalletEnabled) {
modelScope.launch {
unlockWalletUseCase(userWalletId)
.onRight { router.push(AppRoute.WalletSettings(userWalletId)) }
.onLeft { error ->
Timber.e("Failed to unlock wallet $userWalletId: $error")
error.handle(
onUserCancelled = {},
isFromUnlockAll = false,
onAlreadyUnlocked = { router.push(AppRoute.WalletSettings(userWalletId)) },
analyticsEventHandler = analyticsEventHandler,
showMessage = messageSender::send,
)
}
}
} else {
router.push(AppRoute.WalletSettings(userWalletId))
modelScope.launch {
unlockWalletUseCase(userWalletId)
.onRight { router.push(AppRoute.WalletSettings(userWalletId)) }
.onLeft { error ->
Timber.e("Failed to unlock wallet $userWalletId: $error")
error.handle(
onUserCancelled = {},
isFromUnlockAll = false,
onAlreadyUnlocked = { router.push(AppRoute.WalletSettings(userWalletId)) },
analyticsEventHandler = analyticsEventHandler,
showMessage = messageSender::send,
)
}
}
}
}

View file

@ -24,7 +24,6 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsSyncUseCase
import com.tangem.features.details.impl.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@ -38,7 +37,6 @@ internal class UserWalletSaver @Inject constructor(
private val scanCardProcessor: ScanCardProcessor,
private val saveWalletUseCase: SaveWalletUseCase,
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
private val shouldSaveUserWalletsSyncUseCase: ShouldSaveUserWalletsSyncUseCase,
private val reduxStateHolder: ReduxStateHolder,
private val messageSender: UiMessageSender,
private val router: Router,
@ -84,11 +82,7 @@ internal class UserWalletSaver @Inject constructor(
recover = { error ->
when (error) {
is SaveWalletError.WalletAlreadySaved -> {
if (shouldSaveUserWalletsSyncUseCase()) {
selectUserWallet()
} else {
router.popTo<AppRoute.Wallet>()
}
selectUserWallet()
}
is SaveWalletError.DataError -> {
val messageRef = ensureNotNull(error.messageId?.let(::resourceReference)) {
@ -116,7 +110,7 @@ internal class UserWalletSaver @Inject constructor(
)
}
private suspend fun Raise<Error>.createUserWallet(response: ScanResponse): UserWallet {
private fun Raise<Error>.createUserWallet(response: ScanResponse): UserWallet {
val userWallet = coldUserWalletBuilderFactory.create(scanResponse = response).build()
return ensureNotNull(userWallet) { Error.Unknown }

View file

@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
@ -17,6 +18,7 @@ import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.res.LocalHapticManager
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.MarketTokenDetailsBottomSheetTestTags
import com.tangem.features.feed.components.market.details.portfolio.impl.ui.preview.PreviewMyPortfolioUMProvider
import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.PortfolioTokenUM
import com.tangem.features.feed.impl.R
@ -49,6 +51,7 @@ internal fun PortfolioItem(state: PortfolioTokenUM, lastInList: Boolean, modifie
start = TangemTheme.dimens.spacing10,
end = TangemTheme.dimens.spacing12,
),
modifier = Modifier.testTag(MarketTokenDetailsBottomSheetTestTags.PORTFOLIO_TOKEN_ITEM),
)
PortfolioQuickActions(

View file

@ -1,45 +1,29 @@
package com.tangem.features.feed.components.market.details.portfolio.impl.ui
import android.content.res.Configuration
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.*
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
@ -51,6 +35,7 @@ import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.res.LocalHapticManager
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.MarketTokenDetailsBottomSheetTestTags
import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.QuickActionUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@ -151,7 +136,8 @@ private fun AnimatedVisibilityScope.QuickActionItem(
onClick()
},
)
.padding(horizontal = TangemTheme.dimens.spacing14, vertical = TangemTheme.dimens.spacing4),
.padding(horizontal = TangemTheme.dimens.spacing14, vertical = TangemTheme.dimens.spacing4)
.testTag(MarketTokenDetailsBottomSheetTestTags.PORTFOLIO_QUICK_ACTION_BUTTON),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing18),
) {
@ -168,6 +154,7 @@ private fun AnimatedVisibilityScope.QuickActionItem(
text = state.title.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
modifier = Modifier.testTag(MarketTokenDetailsBottomSheetTestTags.PORTFOLIO_QUICK_ACTION_BUTTON_TITLE),
)
Text(
text = state.description.resolveReference(),
@ -192,6 +179,13 @@ private fun AnimatedVisibilityScope.QuickActionIcon(state: QuickActionUM) {
shape = CircleShape,
)
.size(TangemTheme.dimens.size32)
.semantics {
contentDescription = if (state is QuickActionUM.Exchange && state.shouldShowBadge) {
"Badge shown"
} else {
"Badge hidden"
}
}
.drawWithContent {
drawContent()
if (state is QuickActionUM.Exchange && state.shouldShowBadge) {
@ -202,7 +196,8 @@ private fun AnimatedVisibilityScope.QuickActionIcon(state: QuickActionUM) {
) {
Icon(
modifier = Modifier
.requiredSize(TangemTheme.dimens.size16),
.requiredSize(TangemTheme.dimens.size16)
.testTag(MarketTokenDetailsBottomSheetTestTags.PORTFOLIO_QUICK_ACTION_BUTTON_ICON),
imageVector = ImageVector.vectorResource(id = state.icon),
contentDescription = null,
tint = TangemTheme.colors.button.primary,

View file

@ -24,6 +24,7 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.onFirstVisible
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
@ -52,6 +53,7 @@ import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.BaseSearchBarTestTags.SEARCH_BAR
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
import com.tangem.features.feed.ui.feed.preview.FeedListPreviewDataProvider.createFeedPreviewState
import com.tangem.features.feed.ui.feed.state.*
@ -69,7 +71,8 @@ internal fun FeedListHeader(
modifier = modifier
.drawBehind { drawRect(background) }
.padding(horizontal = 16.dp)
.padding(bottom = 8.dp),
.padding(bottom = 8.dp)
.testTag(SEARCH_BAR),
)
}

View file

@ -9,7 +9,6 @@ import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.features.home.api.HomeComponent
import com.tangem.features.home.impl.model.HomeModel
import com.tangem.features.home.impl.ui.Home
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -17,7 +16,6 @@ import dagger.assisted.AssistedInject
internal class DefaultHomeComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: HomeComponent.Params,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
) : HomeComponent, AppComponentContext by appComponentContext {
private val model: HomeModel = getOrCreateModel(params)
@ -26,11 +24,7 @@ internal class DefaultHomeComponent @AssistedInject constructor(
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
Home(
state = state,
modifier = modifier,
isV2StoriesEnabled = hotWalletFeatureToggles.isHotWalletEnabled,
)
Home(state = state, modifier = modifier)
}
@AssistedFactory

View file

@ -35,14 +35,12 @@ import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.settings.usercountry.models.UserCountry
import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
import com.tangem.features.home.api.HomeComponent
import com.tangem.features.home.impl.ui.state.HomeUM
import com.tangem.features.home.impl.ui.state.Stories
import com.tangem.features.home.impl.ui.state.getRestrictedStories
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.delay
@ -70,8 +68,6 @@ internal class HomeModel @Inject constructor(
private val saveWalletUseCase: SaveWalletUseCase,
private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase,
private val urlOpener: UrlOpener,
private val userWalletsListManager: UserWalletsListManager,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val userWalletsListRepository: UserWalletsListRepository,
private val reduxStateHolder: ReduxStateHolder,
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
@ -219,7 +215,7 @@ internal class HomeModel @Inject constructor(
currency = currency,
batch = scanResponse.card.batchId,
signInType = SignInType.Card,
walletsCount = getWalletsCount().toString(),
walletsCount = userWalletsListRepository.userWalletsSync().size.toString(),
isImported = isImported,
hasBackup = scanResponse.card.backupStatus?.isActive,
),
@ -227,14 +223,6 @@ internal class HomeModel @Inject constructor(
}
}
private suspend fun getWalletsCount(): Int {
return if (hotWalletFeatureToggles.isHotWalletEnabled) {
userWalletsListRepository.userWalletsSync().size
} else {
userWalletsListManager.walletsCount
}
}
private fun setLoading(isLoading: Boolean) {
_uiState.update { it.copy(scanInProgress = isLoading) }
}

View file

@ -5,29 +5,18 @@ import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.SystemBarsIconsDisposable
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.utils.ChangeRootBackgroundColorEffect
import com.tangem.features.home.impl.ui.compose.StoriesScreen
import com.tangem.features.home.impl.ui.compose.StoriesScreenV2
import com.tangem.features.home.impl.ui.state.HomeUM
@Composable
internal fun Home(state: HomeUM, isV2StoriesEnabled: Boolean, modifier: Modifier = Modifier) {
internal fun Home(state: HomeUM, modifier: Modifier = Modifier) {
SystemBarsIconsDisposable(darkIcons = false)
if (isV2StoriesEnabled) {
StoriesScreenV2(
modifier = modifier,
state = state,
onGetStartedClick = state.onGetStartedClick,
)
} else {
StoriesScreen(
modifier = modifier,
state = state,
onScanButtonClick = state.onScanClick,
onShopButtonClick = state.onShopClick,
onSearchTokensClick = state.onSearchTokensClick,
)
}
StoriesScreenV2(
modifier = modifier,
state = state,
onGetStartedClick = state.onGetStartedClick,
)
ChangeRootBackgroundColorEffect(TangemColorPalette.Black)
}

View file

@ -1,271 +0,0 @@
@file:Suppress("MagicNumber")
package com.tangem.features.home.impl.ui.compose
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.StoriesScreenTestTags
import com.tangem.features.home.impl.ui.compose.content.*
import com.tangem.features.home.impl.ui.compose.views.HomeButtons
import com.tangem.features.home.impl.ui.compose.views.SearchCurrenciesButton
import com.tangem.features.home.impl.ui.compose.views.StoriesProgressBar
import com.tangem.features.home.impl.ui.state.Stories
import com.tangem.core.ui.R
import com.tangem.features.home.impl.ui.state.HomeUM
import kotlin.math.max
@Composable
internal fun StoriesScreen(
state: HomeUM,
onScanButtonClick: () -> Unit,
onShopButtonClick: () -> Unit,
onSearchTokensClick: () -> Unit,
modifier: Modifier = Modifier,
) {
var currentStory by remember { mutableStateOf(state.firstStory) }
val currentStoryIndex by rememberUpdatedState(newValue = state.stepOf(currentStory))
LaunchedEffect(currentStoryIndex) {
if (currentStoryIndex < 0) {
currentStory = state.firstStory
}
}
val goToPreviousStory = remember(currentStory, currentStoryIndex) {
{ currentStory = state.stories[max(0, currentStoryIndex - 1)] }
}
val goToNextStory = remember(currentStory, currentStoryIndex) {
{
currentStory = if (currentStoryIndex >= 0 && currentStoryIndex < state.stories.lastIndex) {
state.stories[currentStoryIndex + 1]
} else {
state.firstStory
}
}
}
// todo refactor [REDACTED_TASK_KEY]
StoriesScreenContent(
modifier = modifier
.fillMaxSize()
.testTag(StoriesScreenTestTags.SCREEN_CONTAINER),
config = StoriesScreenContentConfig(
storiesSize = state.stories.lastIndex,
currentStoryIndex = currentStoryIndex,
currentStory = currentStory,
isScanInProgress = state.scanInProgress,
onGoToPreviousStory = goToPreviousStory,
onGoToNextStory = goToNextStory,
onSearchTokensClick = onSearchTokensClick,
onScanButtonClick = onScanButtonClick,
onShopButtonClick = onShopButtonClick,
),
)
}
@Deprecated("Use StoriesContainer from core/ui")
@Suppress("LongMethod")
@Composable
private fun StoriesScreenContent(config: StoriesScreenContentConfig, modifier: Modifier = Modifier) {
var isPressed by remember { mutableStateOf(value = false) }
val isPaused = isPressed || config.isScanInProgress
val currentStoryDuration = config.currentStory.duration
Box(
modifier = modifier.background(Color(0xFF010101)),
) {
Row(
modifier = Modifier.fillMaxSize(),
) {
Box(
Modifier
.weight(1f)
.fillMaxHeight()
.pointerInput(Unit) {
detectTapGestures(
onPress = {
val pressStartTime = System.currentTimeMillis()
isPressed = true
this.tryAwaitRelease()
val pressEndTime = System.currentTimeMillis()
val totalPressTime = pressEndTime - pressStartTime
if (totalPressTime < 200) config.onGoToPreviousStory()
isPressed = false
},
)
},
)
Box(
Modifier
.weight(1f)
.fillMaxHeight()
.pointerInput(Unit) {
detectTapGestures(
onPress = {
val pressStartTime = System.currentTimeMillis()
isPressed = true
this.tryAwaitRelease()
val pressEndTime = System.currentTimeMillis()
val totalPressTime = pressEndTime - pressStartTime
if (totalPressTime < 200) config.onGoToNextStory()
isPressed = false
},
)
},
)
}
Column(
modifier = Modifier
.statusBarsPadding()
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
StoriesProgressBar(
steps = config.storiesSize,
currentStep = config.currentStoryIndex,
stepDuration = currentStoryDuration,
paused = isPaused,
onStepFinish = config.onGoToNextStory,
)
Image(
painter = painterResource(id = R.drawable.ic_tangem_logo),
contentDescription = null,
contentScale = ContentScale.FillHeight,
modifier = Modifier
.padding(
start = TangemTheme.dimens.spacing16,
top = TangemTheme.dimens.spacing16,
)
.height(TangemTheme.dimens.size18)
.align(Alignment.Start),
)
when (config.currentStory) {
Stories.TangemIntro -> FirstStoriesContent(
isPaused = isPaused,
duration = currentStoryDuration,
)
Stories.RevolutionaryWallet -> StoriesRevolutionaryWallet()
Stories.UltraSecureBackup -> StoriesUltraSecureBackup(
isPaused = isPaused,
stepDuration = currentStoryDuration,
)
Stories.Currencies -> StoriesCurrencies(isPaused, currentStoryDuration)
Stories.Web3 -> StoriesWeb3(isPaused, currentStoryDuration)
Stories.WalletForEveryone -> StoriesWalletForEveryone(currentStoryDuration)
}
}
Column(
modifier = Modifier
.navigationBarsPadding()
.padding(bottom = TangemTheme.dimens.spacing16)
.padding(horizontal = TangemTheme.dimens.spacing16)
.align(Alignment.BottomCenter)
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
AnimatedVisibility(
visible = config.currentStory == Stories.Currencies,
enter = fadeIn(),
exit = fadeOut(),
) {
SearchCurrenciesButton(
modifier = Modifier.fillMaxWidth(),
onClick = config.onSearchTokensClick,
)
}
HomeButtons(
modifier = Modifier.fillMaxWidth(),
btnScanStateInProgress = config.isScanInProgress,
onScanButtonClick = config.onScanButtonClick,
onShopButtonClick = config.onShopButtonClick,
)
}
}
}
private data class StoriesScreenContentConfig(
val storiesSize: Int,
val currentStoryIndex: Int,
val currentStory: Stories,
val isScanInProgress: Boolean,
val onGoToPreviousStory: () -> Unit = {},
val onGoToNextStory: () -> Unit = {},
val onSearchTokensClick: () -> Unit = {},
val onScanButtonClick: () -> Unit = {},
val onShopButtonClick: () -> Unit = {},
)
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun StoriesScreenContentPreview(
@PreviewParameter(StoriesScreenContentConfigProvider::class) config: StoriesScreenContentConfig,
) {
TangemThemePreview {
StoriesScreenContent(config = config)
}
}
private class StoriesScreenContentConfigProvider : CollectionPreviewParameterProvider<StoriesScreenContentConfig>(
collection = listOf(
StoriesScreenContentConfig(
storiesSize = 6,
currentStoryIndex = 0,
currentStory = Stories.TangemIntro,
isScanInProgress = true,
),
StoriesScreenContentConfig(
storiesSize = 6,
currentStoryIndex = 1,
currentStory = Stories.RevolutionaryWallet,
isScanInProgress = false,
),
StoriesScreenContentConfig(
storiesSize = 6,
currentStoryIndex = 2,
currentStory = Stories.UltraSecureBackup,
isScanInProgress = false,
),
StoriesScreenContentConfig(
storiesSize = 6,
currentStoryIndex = 3,
currentStory = Stories.Currencies,
isScanInProgress = false,
),
StoriesScreenContentConfig(
storiesSize = 6,
currentStoryIndex = 4,
currentStory = Stories.Web3,
isScanInProgress = false,
),
StoriesScreenContentConfig(
storiesSize = 6,
currentStoryIndex = 5,
currentStory = Stories.WalletForEveryone,
isScanInProgress = false,
),
),
)
// endregion Preview

View file

@ -1,7 +1,5 @@
package com.tangem.features.hotwallet
interface HotWalletFeatureToggles {
val isHotWalletEnabled: Boolean
val isWalletCreationRestrictionEnabled: Boolean
val isHotWalletVisible: Boolean
}

View file

@ -5,11 +5,7 @@ import com.tangem.core.configtoggle.feature.FeatureTogglesManager
internal class DefaultHotWalletFeatureToggles(
private val featureTogglesManager: FeatureTogglesManager,
) : HotWalletFeatureToggles {
override val isHotWalletEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "HOT_WALLET_ENABLED")
override val isWalletCreationRestrictionEnabled: Boolean
get() = isHotWalletEnabled &&
featureTogglesManager.isFeatureEnabled(name = "HOT_WALLET_CREATION_RESTRICTION_ENABLED")
override val isHotWalletVisible: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "HOT_WALLET_VISIBLE")
get() = featureTogglesManager.isFeatureEnabled(name = "HOT_WALLET_CREATION_RESTRICTION_ENABLED")
}

View file

@ -3,9 +3,6 @@ package com.tangem.features.hotwallet.di
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import com.tangem.features.hotwallet.DefaultHotWalletFeatureToggles
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.features.hotwallet.MnemonicRepository
import com.tangem.features.hotwallet.common.repository.DefaultMnemonicRepository
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -18,15 +15,7 @@ internal object HotWalletFeatureModule {
@Provides
@Singleton
fun provideFeatureToggles(featureTogglesManager: FeatureTogglesManager): HotWalletFeatureToggles {
fun provideHotWalletFeatureToggles(featureTogglesManager: FeatureTogglesManager): HotWalletFeatureToggles {
return DefaultHotWalletFeatureToggles(featureTogglesManager)
}
}
@Module
@InstallIn(SingletonComponent::class)
internal interface HotWalletFeatureModuleBinds {
@Binds
@Singleton
fun provideMnemonicRepository(repository: DefaultMnemonicRepository): MnemonicRepository
}

View file

@ -0,0 +1,18 @@
package com.tangem.features.hotwallet.di
import com.tangem.features.hotwallet.MnemonicRepository
import com.tangem.features.hotwallet.common.repository.DefaultMnemonicRepository
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface MnemonicRepositoryModule {
@Binds
@Singleton
fun provideMnemonicRepository(repository: DefaultMnemonicRepository): MnemonicRepository
}

View file

@ -17,10 +17,7 @@ import com.tangem.domain.models.scan.ProductType
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.asLockable
import com.tangem.features.biometry.AskBiometryComponent
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.features.onboarding.v2.TitleProvider
import com.tangem.features.onboarding.v2.common.ui.CantLeaveBackupDialog
import com.tangem.features.onboarding.v2.done.api.OnboardingDoneComponent
@ -47,8 +44,6 @@ internal class OnboardingEntryModel @Inject constructor(
private val settingsRepository: SettingsRepository,
private val analyticsEventHandler: AnalyticsEventHandler,
private val uiMessageSender: UiMessageSender,
private val userWalletsListManager: UserWalletsListManager,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val userWalletsListRepository: UserWalletsListRepository,
) : Model() {
@ -215,29 +210,12 @@ internal class OnboardingEntryModel @Inject constructor(
}
private fun exitComponentScreen() {
// new flow
if (hotWalletFeatureToggles.isHotWalletEnabled) {
modelScope.launch {
if (userWalletsListRepository.userWalletsSync().isEmpty()) {
router.replaceAll(AppRoute.Home())
} else {
router.replaceAll(AppRoute.Wallet)
}
}
return
}
// legacy flow
if (userWalletsListManager.hasUserWallets) {
val isLocked = runCatching { userWalletsListManager.asLockable()?.isLocked!! }.getOrElse { false }
if (isLocked) {
router.replaceAll(AppRoute.Welcome())
modelScope.launch {
if (userWalletsListRepository.userWalletsSync().isEmpty()) {
router.replaceAll(AppRoute.Home())
} else {
router.replaceAll(AppRoute.Wallet)
}
} else {
router.replaceAll(AppRoute.Home())
}
}

View file

@ -15,6 +15,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@ -30,6 +31,7 @@ import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.SwapSelectTokenScreenTestTags
import com.tangem.core.ui.utils.dashedBorder
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.swap.entity.ExchangeCardUM
@ -50,7 +52,8 @@ internal fun ExchangeCard(state: ExchangeCardUM, isBalanceHidden: Boolean, modif
.fillMaxWidth()
.heightIn(min = 116.dp)
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.primary),
.background(TangemTheme.colors.background.primary)
.testTag(SwapSelectTokenScreenTestTags.YOU_SWAP_BLOCK),
verticalArrangement = Arrangement.SpaceBetween,
) {
Title(
@ -157,6 +160,7 @@ private fun EmptyTokenBlock(text: TextReference, modifier: Modifier = Modifier)
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = TangemTheme.typography.body2,
modifier = Modifier.testTag(SwapSelectTokenScreenTestTags.CHOOSE_TOKEN_TEXT),
)
}
}

View file

@ -18,6 +18,8 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.models.wallet.isColdWallet
import com.tangem.domain.models.wallet.isHotWallet
import javax.inject.Inject
@ModelScoped
@ -35,9 +37,10 @@ internal class StakingStateController @Inject constructor(
private val titleTransformer = SetTitleTransformer
fun initializeWithUserWallet(userWallet: UserWallet) {
mutableUiState.update {
it.copy(
showColdWalletInteractionIcon = userWallet is UserWallet.Cold,
mutableUiState.update { state ->
state.copy(
showColdWalletInteractionIcon = userWallet.isColdWallet,
shouldShowHoldToConfirmButton = userWallet.isHotWallet,
)
}
}
@ -98,6 +101,7 @@ internal class StakingStateController @Inject constructor(
buttonsState = NavigationButtonsState.Empty,
balanceState = null,
showColdWalletInteractionIcon = true,
shouldShowHoldToConfirmButton = false,
)
}
}

View file

@ -43,6 +43,7 @@ internal data class StakingUiState(
val event: StateEvent<StakingEvent>,
val balanceState: BalanceState?,
val showColdWalletInteractionIcon: Boolean,
val shouldShowHoldToConfirmButton: Boolean,
) {
fun copyWrapped(

View file

@ -7,6 +7,7 @@ import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.features.staking.impl.presentation.state.utils.getPendingActionTitle
@ -45,15 +46,17 @@ internal class SetButtonsStateTransformer(
val isInProgress = innerConfirmState == InnerConfirmationStakingState.IN_PROGRESS
val isCompleted = innerConfirmState == InnerConfirmationStakingState.COMPLETED
val isIconVisible = isConfirmation && !isCompleted
val isHoldToConfirm = prevState.shouldShowHoldToConfirmButton && isConfirmation && !isCompleted
val isIconVisible = isConfirmation && !isCompleted && !isHoldToConfirm
val isPrimaryButtonDisabled = prevState.isPrimaryButtonDisabled()
return NavigationButton(
textReference = prevState.getButtonText(),
textReference = prevState.getButtonText(isHoldToConfirm),
iconRes = R.drawable.ic_tangem_24.takeIf { prevState.showColdWalletInteractionIcon },
isDimmed = isPrimaryButtonDisabled,
isIconVisible = isIconVisible,
shouldShowProgress = isInProgress,
isEnabled = prevState.isButtonEnabled(),
isHoldToConfirm = isHoldToConfirm,
onClick = {
if (isPrimaryButtonDisabled) {
prevState.clickIntents.showPrimaryClickAlert()
@ -89,7 +92,7 @@ internal class SetButtonsStateTransformer(
else -> true
}
private fun StakingUiState.getButtonText(): TextReference {
private fun StakingUiState.getButtonText(isHoldToConfirm: Boolean): TextReference {
return when (currentStep) {
StakingStep.InitialInfo -> {
val initialState = initialInfoState as? StakingStates.InitialInfoState.Data
@ -100,7 +103,7 @@ internal class SetButtonsStateTransformer(
}
}
StakingStep.Success -> resourceReference(R.string.common_close)
StakingStep.Confirmation -> getConfirmationButtonText()
StakingStep.Confirmation -> getConfirmationButtonText(isHoldToConfirm)
StakingStep.Validators -> resourceReference(R.string.common_continue)
StakingStep.Amount,
StakingStep.RestakeValidator,
@ -109,25 +112,37 @@ internal class SetButtonsStateTransformer(
}
}
private fun StakingUiState.getConfirmationButtonText(): TextReference {
private fun StakingUiState.getConfirmationButtonText(isHoldToConfirm: Boolean): TextReference {
val confirmationState = confirmationState as? StakingStates.ConfirmationState.Data
?: return resourceReference(R.string.common_close)
val amountState = amountState as? AmountState.Data
return if (confirmationState != null && amountState != null) {
when (actionType) {
is StakingActionCommonType.Enter -> {
val amount = amountState.amountTextField.cryptoAmount.value.orZero()
if (confirmationState.isApprovalNeeded && confirmationState.allowance < amount) {
resourceReference(R.string.give_permission_title)
} else {
resourceReference(R.string.common_stake)
}
}
is StakingActionCommonType.Exit -> resourceReference(R.string.common_unstake)
is StakingActionCommonType.Pending -> confirmationState.pendingAction?.type.getPendingActionTitle()
?: return resourceReference(R.string.common_close)
if (actionType is StakingActionCommonType.Enter) {
val amount = amountState.amountTextField.cryptoAmount.value.orZero()
if (confirmationState.isApprovalNeeded && confirmationState.allowance < amount) {
return resourceReference(R.string.give_permission_title)
}
} else {
resourceReference(R.string.common_close)
}
val baseText = getBaseActionText(confirmationState)
?: return resourceReference(R.string.common_close)
return baseText.wrapWithHoldToIf(isHoldToConfirm)
}
private fun StakingUiState.getBaseActionText(
confirmationState: StakingStates.ConfirmationState.Data,
): TextReference? = when (actionType) {
is StakingActionCommonType.Enter -> resourceReference(R.string.common_stake)
is StakingActionCommonType.Exit -> resourceReference(R.string.common_unstake)
is StakingActionCommonType.Pending -> confirmationState.pendingAction?.type.getPendingActionTitle()
}
private fun TextReference.wrapWithHoldToIf(condition: Boolean): TextReference = if (condition) {
resourceReference(id = R.string.common_hold_to, formatArgs = wrappedList(this))
} else {
this
}
private fun StakingUiState.onPrimaryClick() {

View file

@ -8,11 +8,13 @@ import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.testTag
import com.tangem.common.ui.bottomsheet.permission.GiveTxPermissionBottomSheet
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomSheetConfig
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.SwapTokenScreenTestTags
import com.tangem.core.ui.utils.WindowInsetsZero
import com.tangem.feature.swap.component.SwapFeeSelectorBlockComponent
import com.tangem.feature.swap.models.SwapStateHolder
@ -50,7 +52,9 @@ internal fun SwapScreen(stateHolder: SwapStateHolder, feeSelectorBlockComponent:
} else {
null
},
modifier = Modifier.padding(scaffoldPaddings),
modifier = Modifier
.padding(scaffoldPaddings)
.testTag(SwapTokenScreenTestTags.CONTAINER),
)
if (stateHolder.bottomSheetConfig != null) {

View file

@ -6,49 +6,37 @@ import com.tangem.core.ui.components.block.model.BlockUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsSyncUseCase
import com.tangem.feature.walletsettings.entity.DialogConfig
import com.tangem.feature.walletsettings.entity.WalletSettingsItemUM
import com.tangem.feature.walletsettings.impl.R
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.features.wallet.utils.UserWalletImageFetcher
import com.tangem.operations.attestation.ArtworkSize
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
internal class WalletCardItemDelegate @AssistedInject constructor(
private val getShouldSaveUserWalletsSyncUseCase: ShouldSaveUserWalletsSyncUseCase,
private val walletImageFetcher: UserWalletImageFetcher,
@Assisted private val dialogNavigation: SlotNavigation<DialogConfig>,
@Assisted private val onUpgradeHotWalletClick: () -> Unit,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
) {
fun cardItemFlow(wallet: UserWallet): Flow<WalletSettingsItemUM.CardBlock> = combine(
flow = walletImageFetcher.walletImage(wallet, ArtworkSize.SMALL),
flow2 = if (!hotWalletFeatureToggles.isHotWalletEnabled) {
flow { emit(getShouldSaveUserWalletsSyncUseCase()) }
} else {
flowOf(true)
},
transform = { imageState, isRenameAvailable ->
fun cardItemFlow(wallet: UserWallet): Flow<WalletSettingsItemUM.CardBlock> {
return walletImageFetcher.walletImage(wallet, ArtworkSize.SMALL).map { imageState ->
val walletName = wallet.name
WalletSettingsItemUM.CardBlock(
id = "wallet_name",
title = resourceReference(id = R.string.user_wallet_list_rename_popup_placeholder),
text = stringReference(walletName),
isEnabled = isRenameAvailable,
isEnabled = true,
onClick = { openRenameWalletDialog(wallet) },
imageState = imageState,
additionalBlock = buildUpgradeToHardwareWalletBlockOrNull(wallet),
)
},
)
}
}
private fun buildUpgradeToHardwareWalletBlockOrNull(wallet: UserWallet): BlockUM? {
return BlockUM(

View file

@ -43,7 +43,6 @@ import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.KycRejec
import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider
import com.tangem.features.biometry.AskBiometryComponent
import com.tangem.features.feed.entry.featuretoggle.FeedFeatureToggle
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks
import com.tangem.features.tangempay.TangemPayFeatureToggles
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionListener
@ -88,9 +87,7 @@ internal class WalletModel @Inject constructor(
private val notificationsRepository: NotificationsRepository,
private val getWalletsListForEnablingUseCase: GetWalletsForAutomaticallyPushEnablingUseCase,
private val setNotificationsEnabledUseCase: SetNotificationsEnabledUseCase,
private val shouldSaveUserWalletsSyncUseCase: ShouldSaveUserWalletsSyncUseCase,
private val getIsHuaweiDeviceWithoutGoogleServicesUseCase: GetIsHuaweiDeviceWithoutGoogleServicesUseCase,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val userWalletsListRepository: UserWalletsListRepository,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
private val yieldSupplyApyUpdateUseCase: YieldSupplyApyUpdateUseCase,
@ -210,49 +207,39 @@ internal class WalletModel @Inject constructor(
}
private fun trackScreenOpened() {
if (hotWalletFeatureToggles.isHotWalletEnabled) {
modelScope.launch {
userWalletsListRepository
.selectedUserWalletSync()
?.let { selectedWallet ->
val hasMobileWallet = userWalletsListRepository.userWalletsSync()
.any { it is UserWallet.Hot }
modelScope.launch {
userWalletsListRepository
.selectedUserWalletSync()
?.let { selectedWallet ->
val hasMobileWallet = userWalletsListRepository.userWalletsSync()
.any { it is UserWallet.Hot }
val accountsCount = if (isAccountsModeEnabledUseCase.invokeSync()) {
singleAccountListSupplier(selectedWallet.walletId)
.first()
.accounts
.size
} else {
null
}
val result = getAppThemeModeUseCase().firstOrNull()
val theme = result?.getOrElse { AppThemeMode.FOLLOW_SYSTEM } ?: AppThemeMode.FOLLOW_SYSTEM
analyticsEventsHandler.send(
WalletScreenAnalyticsEvent.MainScreen.ScreenOpened(
hasMobileWallet = hasMobileWallet,
accountsCount = accountsCount,
theme = theme.value,
isImported = selectedWallet.isImported(),
),
)
val accountsCount = if (isAccountsModeEnabledUseCase.invokeSync()) {
singleAccountListSupplier(selectedWallet.walletId)
.first()
.accounts
.size
} else {
null
}
}
} else {
analyticsEventsHandler.send(
WalletScreenAnalyticsEvent.MainScreen.ScreenOpenedLegacy(),
)
val result = getAppThemeModeUseCase().firstOrNull()
val theme = result?.getOrElse { AppThemeMode.FOLLOW_SYSTEM } ?: AppThemeMode.FOLLOW_SYSTEM
analyticsEventsHandler.send(
WalletScreenAnalyticsEvent.MainScreen.ScreenOpened(
hasMobileWallet = hasMobileWallet,
accountsCount = accountsCount,
theme = theme.value,
isImported = selectedWallet.isImported(),
),
)
}
}
}
private suspend fun shouldShowAskBiometryBottomSheet(): Boolean {
return if (hotWalletFeatureToggles.isHotWalletEnabled) {
userWalletsListRepository.userWalletsSync().any { it is UserWallet.Cold } &&
shouldShowAskBiometryUseCase() &&
canUseBiometryUseCase()
} else {
innerWalletRouter.isWalletLastScreen() && shouldShowAskBiometryUseCase() && canUseBiometryUseCase()
}
return userWalletsListRepository.userWalletsSync().any { it is UserWallet.Cold } &&
shouldShowAskBiometryUseCase() &&
canUseBiometryUseCase()
}
private fun subscribeToUserWalletsUpdates() = channelFlow<Unit> {
@ -298,20 +285,16 @@ internal class WalletModel @Inject constructor(
modelScope.launch {
val shouldAskNotificationPermissionsViaBs = notificationsRepository.shouldAskNotificationPermissionsViaBs()
val shouldShow = notificationsRepository.shouldShowSubscribeOnNotificationsAfterUpdate()
val isBiometricsEnabled = shouldSaveUserWalletsSyncUseCase()
val isHuaweiDevice = getIsHuaweiDeviceWithoutGoogleServicesUseCase()
Timber.d(
"push BS afterUpdate: $shouldShow," +
"isBiometricsEnabled $isBiometricsEnabled," +
"isHuaweiDevice $isHuaweiDevice",
)
if (!shouldAskNotificationPermissionsViaBs) {
notificationsRepository.setShouldAskNotificationPermissionsViaBs(true)
return@launch
}
if (!hotWalletFeatureToggles.isHotWalletEnabled && !isBiometricsEnabled) {
return@launch
}
if (!shouldShow) {
return@launch
}

View file

@ -1,56 +1,23 @@
package com.tangem.feature.wallet.child.wallet.model.intents
import arrow.core.getOrElse
import com.arkivanov.decompose.router.slot.activate
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.card.DeleteSavedAccessCodesUseCase
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.usecase.DeleteWalletUseCase
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAlertState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDialogConfig
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
internal interface WalletCardClickIntents {
fun onRenameBeforeConfirmationClick(userWalletId: UserWalletId)
fun onDeleteBeforeConfirmationClick(userWalletId: UserWalletId)
fun onDeleteAfterConfirmationClick(userWalletId: UserWalletId)
}
// TODO: Refactor
@Suppress("LongParameterList")
@ModelScoped
internal class WalletCardClickIntentsImplementor @Inject constructor(
private val stateHolder: WalletStateController,
private val tokenListStore: MultiWalletTokenListStore,
private val walletEventSender: WalletEventSender,
private val walletScreenContentLoader: WalletScreenContentLoader,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
private val deleteWalletUseCase: DeleteWalletUseCase,
private val deleteSavedAccessCodesUseCase: DeleteSavedAccessCodesUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val reduxStateHolder: ReduxStateHolder,
private val appRouter: AppRouter,
private val dispatchers: CoroutineDispatcherProvider,
) : BaseWalletClickIntents(), WalletCardClickIntents {
override fun onRenameBeforeConfirmationClick(userWalletId: UserWalletId) {
@ -63,47 +30,4 @@ internal class WalletCardClickIntentsImplementor @Inject constructor(
),
)
}
override fun onDeleteBeforeConfirmationClick(userWalletId: UserWalletId) {
analyticsEventHandler.send(MainScreen.DeleteWalletTapped())
walletEventSender.send(
event = WalletEvent.ShowAlert(
state = WalletAlertState.RemoveWalletAlert(
onConfirmClick = { onDeleteAfterConfirmationClick(userWalletId) },
),
),
)
}
override fun onDeleteAfterConfirmationClick(userWalletId: UserWalletId) {
modelScope.launch(dispatchers.main) {
walletScreenContentLoader.cancel(userWalletId)
tokenListStore.remove(userWalletId)
val walletToDelete = getUserWalletUseCase(userWalletId).getOrNull() ?: return@launch
val hasUserWallets = deleteWalletUseCase(userWalletId).getOrElse {
Timber.e("Unable to delete user wallet: $it")
return@launch
}
if (walletToDelete is UserWallet.Cold) {
deleteSavedAccessCodesUseCase(cardId = walletToDelete.cardId).onLeft {
Timber.e("Unable to delete user wallet access code: $it")
}
}
if (hasUserWallets) {
val selectedWallet = getSelectedWalletSyncUseCase().getOrElse {
error("Unable to find selected wallet: $it")
}
reduxStateHolder.onUserWalletSelected(selectedWallet)
} else {
tokenListStore.clear()
stateHolder.clear()
appRouter.replaceAll(AppRoute.Home())
}
}
}
}

View file

@ -13,7 +13,6 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.staking.StakingBalance
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.nft.analytics.NFTAnalyticsEvent
import com.tangem.domain.settings.ShouldShowMarketsTooltipUseCase
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
@ -33,7 +32,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBot
import com.tangem.feature.wallet.presentation.wallet.state.transformers.OpenBottomSheetTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletCurrencyActionsConverter
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.take
@ -90,7 +88,6 @@ internal interface WalletContentClickIntents {
internal class WalletContentClickIntentsImplementor @Inject constructor(
private val stateHolder: WalletStateController,
private val currencyActionsClickIntents: WalletCurrencyActionsClickIntentsImplementor,
private val walletWarningsClickIntents: WalletWarningsClickIntentsImplementor,
private val onrampStatusFactory: OnrampStatusFactory,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
@ -100,44 +97,13 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
private val dispatchers: CoroutineDispatcherProvider,
private val walletEventSender: WalletEventSender,
private val analyticsEventHandler: AnalyticsEventHandler,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val accountDependencies: AccountDependencies,
private val yieldSupplySetShouldShowMainPromoUseCase: YieldSupplySetShouldShowMainPromoUseCase,
private val tokenListAnalyticsSender: TokenListAnalyticsSender,
) : BaseWalletClickIntents(), WalletContentClickIntents {
override fun onDetailsClick() {
if (hotWalletFeatureToggles.isHotWalletEnabled) {
router.openDetailsScreen(stateHolder.getSelectedWalletId())
return
}
// Will be removed after Hot Wallet release
modelScope.launch(dispatchers.main) {
val userWalletId = stateHolder.getSelectedWalletId()
val userWallet = getUserWalletUseCase(userWalletId).getOrElse {
Timber.e(
"""
Unable to get user wallet
|- ID: $userWalletId
|- Exception: $it
""".trimIndent(),
)
return@launch
}
if (userWallet.isLocked) {
stateHolder.showBottomSheet(
WalletBottomSheetConfig.UnlockWallets(
onUnlockClick = walletWarningsClickIntents::onUnlockWalletClick,
onScanClick = walletWarningsClickIntents::onScanToUnlockWalletClick,
),
)
} else {
router.openDetailsScreen(stateHolder.getSelectedWalletId())
}
}
router.openDetailsScreen(stateHolder.getSelectedWalletId())
}
override fun onOrganizeTokensClick() {

View file

@ -37,8 +37,6 @@ import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent
import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent.Program
import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent.PromotionBannerClicked
import com.tangem.domain.tokens.model.details.NavigationAction
import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType
import com.tangem.domain.wallets.models.UnlockWalletsError
import com.tangem.domain.wallets.usecase.*
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
@ -48,11 +46,8 @@ import com.tangem.feature.wallet.presentation.wallet.domain.ScanCardToUnlockWall
import com.tangem.feature.wallet.presentation.wallet.domain.ScanCardToUnlockWalletError
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAlertState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBottomSheetConfig
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent
import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.features.pushnotifications.api.analytics.PushNotificationAnalyticEvents
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.async
@ -75,12 +70,8 @@ internal interface WalletWarningsClickIntents {
fun onOpenUnlockWalletsBottomSheetClick()
fun onUnlockWalletClick()
fun onUnlockVisaAccessClick()
fun onScanToUnlockWalletClick()
fun onLikeAppClick()
fun onDislikeAppClick()
@ -123,7 +114,6 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
private val remindToRateAppLaterUseCase: RemindToRateAppLaterUseCase,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val scanCardToUnlockWalletClickHandler: ScanCardToUnlockWalletClickHandler,
private val unlockWalletsUseCase: UnlockWalletsUseCase,
private val nonBiometricUnlockWalletUseCase: NonBiometricUnlockWalletUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val dispatchers: CoroutineDispatcherProvider,
@ -137,7 +127,6 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
private val multiStakingBalanceFetcher: MultiStakingBalanceFetcher,
private val stakingIdFactory: StakingIdFactory,
private val appRouter: AppRouter,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val userWalletsListRepository: UserWalletsListRepository,
private val setShouldShowNotificationUseCase: SetShouldShowNotificationUseCase,
private val notificationsRepository: NotificationsRepository,
@ -205,43 +194,21 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
override fun onOpenUnlockWalletsBottomSheetClick() {
analyticsEventHandler.send(MainScreen.WalletUnlockTapped())
if (hotWalletFeatureToggles.isHotWalletEnabled) {
modelScope.launch {
userWalletsListRepository.unlockAllWallets()
.onLeft {
val selectedUserWalletId = stateHolder.getSelectedWalletId()
nonBiometricUnlockWalletUseCase(selectedUserWalletId)
.onLeft { error ->
error.handle(
onAlreadyUnlocked = {},
onUserCancelled = {},
analyticsEventHandler = analyticsEventHandler,
isFromUnlockAll = true,
showMessage = uiMessageSender::send,
)
}
}
}
return
}
// Will be removed after hot wallet release
stateHolder.showBottomSheet(
WalletBottomSheetConfig.UnlockWallets(
onUnlockClick = this::onUnlockWalletClick,
onScanClick = this::onScanToUnlockWalletClick,
),
)
}
@Deprecated("Will be removed with hot wallet release")
override fun onUnlockWalletClick() {
analyticsEventHandler.send(MainScreen.UnlockAllWithBiometrics())
modelScope.launch(dispatchers.main) {
unlockWalletsUseCase(type = UnlockType.ALL_WITHOUT_SELECT)
.onRight { stateHolder.update(CloseBottomSheetTransformer(stateHolder.getSelectedWalletId())) }
.onLeft(::handleUnlockWalletsError)
modelScope.launch {
userWalletsListRepository.unlockAllWallets()
.onLeft {
val selectedUserWalletId = stateHolder.getSelectedWalletId()
nonBiometricUnlockWalletUseCase(selectedUserWalletId)
.onLeft { error ->
error.handle(
onAlreadyUnlocked = {},
onUserCancelled = {},
analyticsEventHandler = analyticsEventHandler,
isFromUnlockAll = true,
showMessage = uiMessageSender::send,
)
}
}
}
}
@ -249,25 +216,6 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
openScanCardDialog()
}
private fun handleUnlockWalletsError(error: UnlockWalletsError) {
val event = when (error) {
is UnlockWalletsError.DataError,
is UnlockWalletsError.UnableToUnlockWallets,
-> WalletEvent.ShowError(resourceReference(R.string.user_wallet_list_error_unable_to_unlock))
is UnlockWalletsError.NoUserWalletSelected,
is UnlockWalletsError.NotAllUserWalletsUnlocked,
-> WalletEvent.ShowAlert(WalletAlertState.RescanWallets)
}
walletEventSender.send(event)
}
@Deprecated("Will be removed with hot wallet release")
override fun onScanToUnlockWalletClick() {
analyticsEventHandler.send(MainScreen.UnlockWithCardScan())
openScanCardDialog()
}
private fun openScanCardDialog() {
modelScope.launch(dispatchers.main) {
scanCardToUnlockWalletClickHandler(walletId = stateHolder.getSelectedWalletId())

View file

@ -2,14 +2,11 @@ package com.tangem.feature.wallet.presentation.wallet.domain
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.wallet.copy
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository
import timber.log.Timber
class WalletNameMigrationUseCase(
private val userWalletsListManager: UserWalletsListManager,
private val userWalletsListRepository: UserWalletsListRepository,
private val useNewListRepository: Boolean,
private val walletNamesMigrationRepository: WalletNamesMigrationRepository,
) {
@ -18,28 +15,15 @@ class WalletNameMigrationUseCase(
return
}
if (useNewListRepository) {
val wallets = userWalletsListRepository.userWalletsSync()
val existingNames: MutableSet<String> = mutableSetOf()
wallets.forEach {
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)
}
} else {
val wallets = userWalletsListManager.userWalletsSync
val existingNames: MutableSet<String> = mutableSetOf()
wallets.indices.forEach { i ->
val defaultName = wallets[i].name
val suggestedWalletName = suggestedWalletName(defaultName, existingNames)
if (defaultName != suggestedWalletName) {
userWalletsListManager.update(wallets[i].walletId) { it.copy(name = suggestedWalletName) }
}
Timber.tag("Migrated names").e(i.toString() + " " + suggestedWalletName)
val wallets = userWalletsListRepository.userWalletsSync()
val existingNames: MutableSet<String> = mutableSetOf()
wallets.forEach {
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)
}
walletNamesMigrationRepository.setMigrationDone()

View file

@ -9,7 +9,6 @@ import com.tangem.domain.staking.usecase.StakingAvailabilityListUseCase
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
@ -21,7 +20,6 @@ import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenList
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.subscribers.*
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.features.tangempay.TangemPayFeatureToggles
@Suppress("LongParameterList")
@ -40,14 +38,12 @@ internal class MultiWalletContentLoader(
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase,
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val walletsRepository: WalletsRepository,
private val currenciesRepository: CurrenciesRepository,
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
private val stakingAvailabilityListUseCase: StakingAvailabilityListUseCase,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
private val tangemPayMainSubscriberFactory: TangemPayMainSubscriber.Factory,
) : WalletContentLoader(id = userWallet.walletId) {
@ -92,13 +88,6 @@ internal class MultiWalletContentLoader(
getStoryContentUseCase = getStoryContentUseCase,
).let(::add)
WalletDropDownItemsSubscriber(
stateHolder = stateHolder,
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,
clickIntents = clickIntents,
hotWalletFeatureToggles = hotWalletFeatureToggles,
).let(::add)
if (tangemPayFeatureToggles.isTangemPayEnabled) {
add(tangemPayMainSubscriberFactory.create(userWallet))
}

View file

@ -9,7 +9,6 @@ import com.tangem.domain.staking.usecase.StakingAvailabilityListUseCase
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
@ -21,7 +20,6 @@ import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenList
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.subscribers.TangemPayMainSubscriber
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.features.tangempay.TangemPayFeatureToggles
import javax.inject.Inject
@ -38,7 +36,6 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase,
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val walletWarningsSingleEventSender: WalletWarningsSingleEventSender,
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val walletsRepository: WalletsRepository,
private val getNFTCollectionsUseCase: GetNFTCollectionsUseCase,
@ -46,7 +43,6 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
private val stakingAvailabilityListUseCase: StakingAvailabilityListUseCase,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
private val tangemPayMainSubscriberFactory: TangemPayMainSubscriber.Factory,
) {
@ -65,13 +61,11 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
applyTokenListSortingUseCase = applyTokenListSortingUseCase,
getStoryContentUseCase = getStoryContentUseCase,
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,
walletsRepository = walletsRepository,
getNFTCollectionsUseCase = getNFTCollectionsUseCase,
currenciesRepository = currenciesRepository,
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
stakingAvailabilityListUseCase = stakingAvailabilityListUseCase,
hotWalletFeatureToggles = hotWalletFeatureToggles,
tangemPayFeatureToggles = tangemPayFeatureToggles,
tangemPayMainSubscriberFactory = tangemPayMainSubscriberFactory,
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,

View file

@ -2,14 +2,12 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsSingleEventSender
import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.subscribers.*
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.features.tangempay.TangemPayFeatureToggles
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@ -25,10 +23,8 @@ internal class MultiWalletContentLoaderV2 @AssistedInject constructor(
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val walletWarningsSingleEventSender: WalletWarningsSingleEventSender,
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val checkWalletWithFundsSubscriberFactory: CheckWalletWithFundsSubscriber.Factory,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
private val tangemPayMainSubscriberFactory: TangemPayMainSubscriber.Factory,
) : WalletContentLoader(id = userWallet.walletId) {
@ -50,12 +46,6 @@ internal class MultiWalletContentLoaderV2 @AssistedInject constructor(
stateHolder = stateController,
getStoryContentUseCase = getStoryContentUseCase,
),
WalletDropDownItemsSubscriber(
stateHolder = stateController,
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,
clickIntents = clickIntents,
hotWalletFeatureToggles = hotWalletFeatureToggles,
),
if (tangemPayFeatureToggles.isTangemPayEnabled) {
tangemPayMainSubscriberFactory.create(userWallet)

View file

@ -10,13 +10,11 @@ import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.domain.GetSingleWalletWarningsFactory
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.subscribers.*
import com.tangem.features.hotwallet.HotWalletFeatureToggles
@Suppress("LongParameterList")
internal class SingleWalletContentLoader(
@ -33,10 +31,8 @@ internal class SingleWalletContentLoader(
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getOnrampTransactionsUseCase: GetOnrampTransactionsUseCase,
private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase,
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> {
@ -63,12 +59,6 @@ internal class SingleWalletContentLoader(
getSingleWalletWarningsFactory = getSingleWalletWarningsFactory,
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
),
WalletDropDownItemsSubscriber(
stateHolder = stateHolder,
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,
clickIntents = clickIntents,
hotWalletFeatureToggles = hotWalletFeatureToggles,
),
SingleWalletExpressStatusesSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,

View file

@ -11,12 +11,10 @@ import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.domain.GetSingleWalletWarningsFactory
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import javax.inject.Inject
@ModelScoped
@ -33,10 +31,8 @@ internal class SingleWalletContentLoaderFactory @Inject constructor(
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getOnrampTransactionsUseCase: GetOnrampTransactionsUseCase,
private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase,
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
) {
fun create(userWallet: UserWallet.Cold, clickIntents: WalletClickIntents, isRefresh: Boolean): WalletContentLoader {
@ -56,8 +52,6 @@ internal class SingleWalletContentLoaderFactory @Inject constructor(
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
getOnrampTransactionsUseCase = getOnrampTransactionsUseCase,
onrampRemoveTransactionUseCase = onrampRemoveTransactionUseCase,
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,
hotWalletFeatureToggles = hotWalletFeatureToggles,
)
}
}

View file

@ -8,7 +8,6 @@ import com.tangem.domain.onramp.GetOnrampTransactionsUseCase
import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.account.AccountDependencies
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
@ -16,7 +15,6 @@ import com.tangem.feature.wallet.presentation.wallet.domain.GetSingleWalletWarni
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.subscribers.*
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@ -35,13 +33,11 @@ internal class SingleWalletContentLoaderV2 @AssistedInject constructor(
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getOnrampTransactionsUseCase: GetOnrampTransactionsUseCase,
private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase,
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val accountDependencies: AccountDependencies,
private val walletWithFundsChecker: WalletWithFundsChecker,
private val dispatchers: CoroutineDispatcherProvider,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> = listOf(
@ -66,12 +62,6 @@ internal class SingleWalletContentLoaderV2 @AssistedInject constructor(
getSingleWalletWarningsFactory = getSingleWalletWarningsFactory,
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
),
WalletDropDownItemsSubscriber(
stateHolder = stateHolder,
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,
clickIntents = clickIntents,
hotWalletFeatureToggles = hotWalletFeatureToggles,
),
SingleWalletExpressStatusesSubscriberV2(
userWallet = userWallet,
singleAccountStatusListSupplier = accountDependencies.singleAccountStatusListSupplier,

View file

@ -4,7 +4,6 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.staking.usecase.StakingAvailabilityListUseCase
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
@ -15,8 +14,10 @@ import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarnin
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.subscribers.*
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletActionButtonsSubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletWarningsSubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.SingleWalletWithTokenListSubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber
@Deprecated("Use SingleWalletWithTokenContentLoaderV2 instead")
@Suppress("LongParameterList")
@ -31,11 +32,9 @@ internal class SingleWalletWithTokenContentLoader(
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
private val tokenListStore: MultiWalletTokenListStore,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
private val stakingAvailabilityListUseCase: StakingAvailabilityListUseCase,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
) : WalletContentLoader(id = userWallet.walletId) {
@ -66,12 +65,6 @@ internal class SingleWalletWithTokenContentLoader(
stateHolder = stateHolder,
getStoryContentUseCase = getStoryContentUseCase,
).let(::add)
WalletDropDownItemsSubscriber(
stateHolder = stateHolder,
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,
clickIntents = clickIntents,
hotWalletFeatureToggles = hotWalletFeatureToggles,
).let(::add)
}
}
}

View file

@ -5,7 +5,6 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.staking.usecase.StakingAvailabilityListUseCase
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
@ -16,7 +15,6 @@ import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarnin
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import javax.inject.Inject
// TODO: Refactor
@ -32,11 +30,9 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val walletWarningsSingleEventSender: WalletWarningsSingleEventSender,
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
private val stakingAvailabilityListUseCase: StakingAvailabilityListUseCase,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
) {
@ -53,10 +49,8 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
getStoryContentUseCase = getStoryContentUseCase,
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
stakingAvailabilityListUseCase = stakingAvailabilityListUseCase,
hotWalletFeatureToggles = hotWalletFeatureToggles,
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
)
}

View file

@ -1,14 +1,15 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsSingleEventSender
import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.subscribers.*
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.feature.wallet.presentation.wallet.subscribers.CheckWalletWithFundsSubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletWarningsSubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.SingleWalletWithTokenSubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -23,8 +24,6 @@ internal class SingleWalletWithTokenContentLoaderV2 @AssistedInject constructor(
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val walletWarningsSingleEventSender: WalletWarningsSingleEventSender,
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> = listOf(
@ -37,12 +36,6 @@ internal class SingleWalletWithTokenContentLoaderV2 @AssistedInject constructor(
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
),
WalletDropDownItemsSubscriber(
stateHolder = stateController,
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,
clickIntents = clickIntents,
hotWalletFeatureToggles = hotWalletFeatureToggles,
),
checkWalletWithFundsSubscriberFactory.create(userWallet),
)

View file

@ -38,13 +38,6 @@ internal sealed interface WalletAlertState {
override val onConfirmClick: (() -> Unit)?,
) : Basic()
data class RemoveWalletAlert(override val onConfirmClick: (() -> Unit)?) : Basic() {
override val title: TextReference? = null
override val message: TextReference = resourceReference(id = R.string.user_wallet_list_delete_prompt)
override val confirmButtonText: TextReference = resourceReference(id = R.string.common_delete)
override val isWarningConfirmButton: Boolean = true
}
data class VisaLimitsInfo(
val totalLimit: String,
val otherLimit: String,

View file

@ -1,73 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.child.wallet.model.intents.WalletCardClickIntents
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDropDownItems
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
internal class SetWalletCardDropDownItemsTransformer(
private val dropdownEnabled: Boolean,
private val clickIntents: WalletCardClickIntents,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
) : WalletScreenStateTransformer {
override fun transform(prevState: WalletScreenState): WalletScreenState {
return prevState.copy(wallets = prevState.wallets.map(::transformWalletState).toImmutableList())
}
private fun transformWalletState(prevState: WalletState): WalletState {
return when (prevState) {
is WalletState.MultiCurrency.Content -> prevState.copy(
walletCardState = prevState.walletCardState.copySealed(
dropDownItems = constructDropDownItems(prevState.walletCardState.id),
),
)
is WalletState.SingleCurrency.Content -> prevState.copy(
walletCardState = prevState.walletCardState.copySealed(
dropDownItems = constructDropDownItems(prevState.walletCardState.id),
),
)
is WalletState.MultiCurrency.Locked -> prevState.copy(
walletCardState = prevState.walletCardState.copySealed(
dropDownItems = constructDropDownItems(prevState.walletCardState.id),
),
)
is WalletState.SingleCurrency.Locked -> prevState.copy(
walletCardState = prevState.walletCardState.copySealed(
dropDownItems = constructDropDownItems(prevState.walletCardState.id),
),
)
}
}
private fun constructDropDownItems(userWalletId: UserWalletId): ImmutableList<WalletDropDownItems> {
return if (dropdownEnabled) {
buildList {
add(
WalletDropDownItems(
text = resourceReference(id = R.string.common_rename),
icon = R.drawable.ic_edit_24,
onClick = { clickIntents.onRenameBeforeConfirmationClick(userWalletId) },
),
)
if (!hotWalletFeatureToggles.isHotWalletEnabled) {
add(
WalletDropDownItems(
text = resourceReference(id = R.string.common_delete),
icon = R.drawable.ic_trash_24,
onClick = { clickIntents.onDeleteBeforeConfirmationClick(userWalletId) },
),
)
}
}.toImmutableList()
} else {
persistentListOf()
}
}
}

View file

@ -4,12 +4,17 @@ import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent.Companion
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.MutableStateFlow
@ -41,13 +46,7 @@ internal class WalletLoadingStateFactory(
private fun createLoadingHotWalletContent(userWallet: UserWallet.Hot): WalletState.MultiCurrency.Content {
return WalletState.MultiCurrency.Content(
pullToRefreshConfig = createPullToRefreshConfig(),
walletCardState = WalletCardState.Loading(
id = userWallet.walletId,
title = userWallet.name,
additionalInfo = WalletAdditionalInfoFactory.resolve(wallet = userWallet),
imageResId = null,
dropDownItems = persistentListOf(),
),
walletCardState = createLoadingWalletCardState(userWallet),
buttons = createMultiWalletActions(userWallet),
warnings = persistentListOf(),
bottomSheetConfig = null,
@ -61,7 +60,7 @@ internal class WalletLoadingStateFactory(
private fun createLoadingMultiCurrencyContent(userWallet: UserWallet.Cold): WalletState.MultiCurrency.Content {
return WalletState.MultiCurrency.Content(
pullToRefreshConfig = createPullToRefreshConfig(),
walletCardState = userWallet.toLoadingWalletCardState(),
walletCardState = createLoadingWalletCardState(userWallet),
buttons = createMultiWalletActions(userWallet),
warnings = persistentListOf(),
bottomSheetConfig = null,
@ -76,7 +75,7 @@ internal class WalletLoadingStateFactory(
val currencySymbol = userWallet.scanResponse.cardTypesResolver.getBlockchain().currency
return WalletState.SingleCurrency.Content(
pullToRefreshConfig = createPullToRefreshConfig(),
walletCardState = userWallet.toLoadingWalletCardState(),
walletCardState = createLoadingWalletCardState(userWallet),
warnings = persistentListOf(),
bottomSheetConfig = null,
buttons = createDimmedButtons(),
@ -98,13 +97,21 @@ internal class WalletLoadingStateFactory(
)
}
private fun UserWallet.Cold.toLoadingWalletCardState(): WalletCardState {
private fun createLoadingWalletCardState(userWallet: UserWallet): WalletCardState {
return WalletCardState.Loading(
id = walletId,
title = name,
additionalInfo = if (isMultiCurrency) WalletAdditionalInfoFactory.resolve(wallet = this) else null,
imageResId = walletImageResolver.resolve(userWallet = this),
dropDownItems = persistentListOf(),
id = userWallet.walletId,
title = userWallet.name,
additionalInfo = if (!userWallet.isMultiCurrency) {
null
} else {
WalletAdditionalInfoFactory.resolve(wallet = userWallet)
},
imageResId = if (userWallet is UserWallet.Cold) {
walletImageResolver.resolve(userWallet)
} else {
null
},
dropDownItems = createDropDownItems(userWalletId = userWallet.walletId),
)
}
@ -150,4 +157,14 @@ internal class WalletLoadingStateFactory(
WalletManageButton.Sell(enabled = true, dimContent = true, onClick = {}),
)
}
private fun createDropDownItems(userWalletId: UserWalletId): ImmutableList<WalletDropDownItems> {
return persistentListOf(
WalletDropDownItems(
text = resourceReference(id = R.string.common_rename),
icon = R.drawable.ic_edit_24,
onClick = { clickIntents.onRenameBeforeConfirmationClick(userWalletId) },
),
)
}
}

View file

@ -1,45 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetWalletCardDropDownItemsTransformer
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onEach
internal class WalletDropDownItemsSubscriber(
private val stateHolder: WalletStateController,
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val clickIntents: WalletClickIntents,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
) : WalletSubscriber() {
override fun create(coroutineScope: CoroutineScope): Flow<*> {
if (!hotWalletFeatureToggles.isHotWalletEnabled) {
return shouldSaveUserWalletsUseCase.invoke()
.distinctUntilChanged()
.onEach { shouldSaveUserWallets ->
stateHolder.update(
SetWalletCardDropDownItemsTransformer(
dropdownEnabled = shouldSaveUserWallets,
clickIntents = clickIntents,
hotWalletFeatureToggles = hotWalletFeatureToggles,
),
)
}
} else {
return flow<Unit> {
stateHolder.update(
SetWalletCardDropDownItemsTransformer(
dropdownEnabled = true,
clickIntents = clickIntents,
hotWalletFeatureToggles = hotWalletFeatureToggles,
),
)
}
}
}
}

View file

@ -257,7 +257,7 @@ internal class WelcomeModel @Inject constructor(
return canUseBiometryUseCase() && walletsRepository.useBiometricAuthentication()
}
suspend fun nonBiometricUnlockWallet(userWalletId: UserWalletId) {
private suspend fun nonBiometricUnlockWallet(userWalletId: UserWalletId) {
nonBiometricUnlockWalletUseCase(userWalletId)
.onRight {
routedOut = true