Updated on 2026-08-14
This commit is contained in:
commit
3fc5bf8fb3
388 changed files with 5078 additions and 2871 deletions
|
|
@ -7,6 +7,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||
import com.arkivanov.decompose.router.slot.childSlot
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.arkivanov.essenty.lifecycle.doOnResume
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.child
|
||||
|
|
@ -48,6 +49,7 @@ internal class WalletComponent @AssistedInject constructor(
|
|||
|
||||
init {
|
||||
lifecycle.subscribe(model.screenLifecycleProvider)
|
||||
doOnResume { model.onResume() }
|
||||
componentScope.launch { model.innerWalletRouter.navigateToFlow.collect { navigate(it) } }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@ import androidx.compose.runtime.Stable
|
|||
import arrow.core.getOrElse
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.squareup.sqldelight.internal.AtomicBoolean
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
|
||||
import com.tangem.core.analytics.utils.TrackingContextProxy
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
|
|
@ -96,6 +98,7 @@ internal class WalletModel @Inject constructor(
|
|||
private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase,
|
||||
private val trackingContextProxy: TrackingContextProxy,
|
||||
val screenLifecycleProvider: ScreenLifecycleProvider,
|
||||
val innerWalletRouter: InnerWalletRouter,
|
||||
) : Model() {
|
||||
|
|
@ -111,16 +114,12 @@ internal class WalletModel @Inject constructor(
|
|||
private val updateTangemPayJobHolder = JobHolder()
|
||||
|
||||
private var expressTxStatusTaskScheduler = SingleTaskScheduler<Unit>()
|
||||
private val hasMainScreenOpenedEventSent = AtomicBoolean(false)
|
||||
|
||||
init {
|
||||
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.MainScreen.ScreenOpened)
|
||||
|
||||
screenLifecycleProvider.isBackgroundState
|
||||
.onEach { isBackground ->
|
||||
if (isBackground.not()) {
|
||||
suggestToEnableBiometrics()
|
||||
}
|
||||
}.launchIn(modelScope)
|
||||
if (!hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.MainScreen.ScreenOpenedLegacy)
|
||||
}
|
||||
|
||||
suggestToOpenMarkets()
|
||||
|
||||
|
|
@ -138,6 +137,12 @@ internal class WalletModel @Inject constructor(
|
|||
clickIntents.initialize(innerWalletRouter, modelScope)
|
||||
}
|
||||
|
||||
fun onResume() {
|
||||
modelScope.launch(dispatchers.main) {
|
||||
suggestToEnableBiometrics()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateYieldSupplyApy() {
|
||||
if (yieldSupplyFeatureToggles.isYieldSupplyFeatureEnabled) {
|
||||
modelScope.launch(dispatchers.default) {
|
||||
|
|
@ -193,7 +198,6 @@ internal class WalletModel @Inject constructor(
|
|||
private suspend fun shouldShowAskBiometryBottomSheet(): Boolean {
|
||||
return if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
userWalletsListRepository.userWalletsSync().any { it is UserWallet.Cold } &&
|
||||
innerWalletRouter.isWalletLastScreen() &&
|
||||
shouldShowAskBiometryUseCase() &&
|
||||
canUseBiometryUseCase()
|
||||
} else {
|
||||
|
|
@ -271,11 +275,25 @@ internal class WalletModel @Inject constructor(
|
|||
// It's okay here because we need to be able to observe the selected wallet changes
|
||||
@Suppress("DEPRECATION")
|
||||
private fun subscribeOnSelectedWalletFlow() {
|
||||
getSelectedWalletUseCase().onRight {
|
||||
it
|
||||
getSelectedWalletUseCase().onRight { walletFlow ->
|
||||
walletFlow
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.onEach { selectedWallet ->
|
||||
trackingContextProxy.setContext(selectedWallet)
|
||||
|
||||
if (hotWalletFeatureToggles.isHotWalletEnabled && !hasMainScreenOpenedEventSent.get()) {
|
||||
// send it here because we need context to be set
|
||||
modelScope.launch {
|
||||
val hasMobileWallet = userWalletsListRepository.userWalletsSync()
|
||||
.any { it is UserWallet.Hot }
|
||||
analyticsEventsHandler.send(
|
||||
WalletScreenAnalyticsEvent.MainScreen.ScreenOpened(hasMobileWallet),
|
||||
)
|
||||
hasMainScreenOpenedEventSent.set(true)
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedWallet.isMultiCurrency) {
|
||||
selectedWalletAnalyticsSender.send(selectedWallet)
|
||||
}
|
||||
|
|
@ -416,6 +434,7 @@ internal class WalletModel @Inject constructor(
|
|||
when (action) {
|
||||
is WalletsUpdateActionResolver.Action.InitializeWallets -> initializeWallets(action)
|
||||
is WalletsUpdateActionResolver.Action.ReinitializeWallet -> reinitializeWallet(action)
|
||||
is WalletsUpdateActionResolver.Action.ReinitializeWallets -> reinitializeWallets(action)
|
||||
is WalletsUpdateActionResolver.Action.AddWallet -> addWallet(action)
|
||||
is WalletsUpdateActionResolver.Action.DeleteWallet -> deleteWallet(action)
|
||||
is WalletsUpdateActionResolver.Action.UnlockWallet -> unlockWallet(action)
|
||||
|
|
@ -520,6 +539,32 @@ internal class WalletModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun reinitializeWallets(action: WalletsUpdateActionResolver.Action.ReinitializeWallets) {
|
||||
action.wallets.forEach { userWallet ->
|
||||
walletScreenContentLoader.cancel(userWallet.walletId)
|
||||
tokenListStore.remove(userWallet.walletId)
|
||||
|
||||
walletScreenContentLoader.load(
|
||||
userWallet = userWallet,
|
||||
clickIntents = clickIntents,
|
||||
coroutineScope = modelScope,
|
||||
)
|
||||
|
||||
modelScope.launch(dispatchers.main) {
|
||||
fetchWalletContent(userWallet = userWallet)
|
||||
}
|
||||
|
||||
stateHolder.update(
|
||||
ReinitializeWalletTransformer(
|
||||
prevWalletId = userWallet.walletId,
|
||||
newUserWallet = userWallet,
|
||||
clickIntents = clickIntents,
|
||||
walletImageResolver = walletImageResolver,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun addWallet(action: WalletsUpdateActionResolver.Action.AddWallet) {
|
||||
if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
fetchWalletContent(userWallet = action.selectedWallet)
|
||||
|
|
|
|||
|
|
@ -64,8 +64,11 @@ internal class WalletsUpdateActionResolver @Inject constructor(
|
|||
selectedWallet: UserWallet,
|
||||
): Action {
|
||||
return when {
|
||||
isHotWalletUpgraded(state, wallets) -> {
|
||||
getHotWalletsUpgradedAction(state, wallets)
|
||||
isAnyHotWalletUpgraded(state, wallets) -> {
|
||||
getHotWalletsUpgradedAction(state, wallets, selectedWallet)
|
||||
}
|
||||
isAnyHotWalletBackedUpChange(state, wallets) -> {
|
||||
getHotWalletsBackedUpAction(state, wallets)
|
||||
}
|
||||
isWalletsCountChanged(state, wallets) -> {
|
||||
getChangeWalletsListAction(state, wallets, selectedWallet)
|
||||
|
|
@ -79,31 +82,35 @@ internal class WalletsUpdateActionResolver @Inject constructor(
|
|||
isAnyWalletNameChanged(state, wallets) -> {
|
||||
getRenameWalletsAction(state, wallets)
|
||||
}
|
||||
isAnyHotWalletBackedUpChange(state, wallets) -> {
|
||||
getHotWalletsBackedUpAction(state, wallets)
|
||||
isAnyWalletUnlocked(state, wallets) -> {
|
||||
Action.UnlockWallet(
|
||||
selectedWallet = selectedWallet,
|
||||
unlockedWallets = wallets.filterNot(UserWallet::isLocked),
|
||||
)
|
||||
}
|
||||
else -> getUpdateSelectedWalletAction(state, wallets, selectedWallet)
|
||||
isSelectedWalletCardsCountChanged(state, selectedWallet) -> {
|
||||
Action.UpdateWalletCardCount(selectedWallet)
|
||||
}
|
||||
else -> Action.Unknown
|
||||
}
|
||||
}
|
||||
|
||||
private fun isAnyHotWalletBackedUpChange(state: WalletScreenState, wallets: List<UserWallet>): Boolean {
|
||||
val incompleteActivationWalletIds = state.incompleteActivationWalletIds()
|
||||
val walletsToUpdate = wallets.filter {
|
||||
it is UserWallet.Hot && it.backedUp == incompleteActivationWalletIds.contains(it.walletId)
|
||||
return wallets.any {
|
||||
it is UserWallet.Hot && it.backedUp && incompleteActivationWalletIds.contains(it.walletId)
|
||||
}
|
||||
return walletsToUpdate.isNotEmpty()
|
||||
}
|
||||
|
||||
private fun isHotWalletUpgraded(state: WalletScreenState, wallets: List<UserWallet>): Boolean {
|
||||
val previousWallet = state
|
||||
.wallets
|
||||
.getOrNull(state.selectedWalletIndex)
|
||||
return when (previousWallet) {
|
||||
is WalletState.MultiCurrency -> {
|
||||
val wallet = wallets.firstOrNull { it.walletId == previousWallet.walletCardState.id }
|
||||
previousWallet.type == WalletState.MultiCurrency.WalletType.Hot && wallet is UserWallet.Cold
|
||||
private fun isAnyHotWalletUpgraded(state: WalletScreenState, wallets: List<UserWallet>): Boolean {
|
||||
return state.wallets.any { walletState ->
|
||||
when (walletState) {
|
||||
is WalletState.MultiCurrency -> {
|
||||
val wallet = wallets.firstOrNull { it.walletId == walletState.walletCardState.id }
|
||||
walletState.type == WalletState.MultiCurrency.WalletType.Hot && wallet is UserWallet.Cold
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -189,9 +196,15 @@ internal class WalletsUpdateActionResolver @Inject constructor(
|
|||
private fun getHotWalletsUpgradedAction(
|
||||
state: WalletScreenState,
|
||||
wallets: List<UserWallet>,
|
||||
): Action.ReloadWallets {
|
||||
val walletsToUpdate = wallets.filter { it.walletId == state.getPrevSelectedWallet().id }
|
||||
return Action.ReloadWallets(walletsToUpdate)
|
||||
selectedWallet: UserWallet,
|
||||
): Action.ReinitializeWallets {
|
||||
val walletsToUpdate = wallets.filter { wallet ->
|
||||
val previousState = state.wallets.firstOrNull { it.walletCardState.id == wallet.walletId }
|
||||
?: return@filter false
|
||||
wallet is UserWallet.Cold && previousState is WalletState.MultiCurrency &&
|
||||
previousState.type == WalletState.MultiCurrency.WalletType.Hot
|
||||
}
|
||||
return Action.ReinitializeWallets(selectedWallet, walletsToUpdate)
|
||||
}
|
||||
|
||||
private fun getRenameWalletsAction(state: WalletScreenState, wallets: List<UserWallet>): Action.RenameWallets {
|
||||
|
|
@ -205,29 +218,14 @@ internal class WalletsUpdateActionResolver @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun getUpdateSelectedWalletAction(
|
||||
state: WalletScreenState,
|
||||
wallets: List<UserWallet>,
|
||||
selectedWallet: UserWallet,
|
||||
): Action {
|
||||
return when {
|
||||
isSelectedWalletUnlocked(state, selectedWallet) -> {
|
||||
Action.UnlockWallet(
|
||||
selectedWallet = selectedWallet,
|
||||
unlockedWallets = wallets.filterNot(UserWallet::isLocked),
|
||||
)
|
||||
}
|
||||
isSelectedWalletCardsCountChanged(state, selectedWallet) -> {
|
||||
Action.UpdateWalletCardCount(selectedWallet)
|
||||
}
|
||||
else -> Action.Unknown
|
||||
private fun isAnyWalletUnlocked(state: WalletScreenState, wallets: List<UserWallet>): Boolean {
|
||||
return state.wallets.any { walletState ->
|
||||
val wallet = wallets.firstOrNull { it.walletId == walletState.walletCardState.id } ?: return@any false
|
||||
!wallet.isLocked &&
|
||||
(walletState is WalletState.MultiCurrency.Locked || walletState is WalletState.SingleCurrency.Locked)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSelectedWalletUnlocked(state: WalletScreenState, selectedWallet: UserWallet): Boolean {
|
||||
return state.isSelectedWalletLocked() && !selectedWallet.isLocked
|
||||
}
|
||||
|
||||
private fun isSelectedWalletCardsCountChanged(state: WalletScreenState, selectedWallet: UserWallet): Boolean {
|
||||
if (selectedWallet !is UserWallet.Cold) return false
|
||||
val prevSelectedWallet = state.getPrevSelectedWallet()
|
||||
|
|
@ -235,12 +233,6 @@ internal class WalletsUpdateActionResolver @Inject constructor(
|
|||
prevSelectedWallet.cardCount != selectedWallet.getCardsCount()
|
||||
}
|
||||
|
||||
private fun WalletScreenState.isSelectedWalletLocked(): Boolean {
|
||||
val selectedWalletState = wallets.getOrNull(selectedWalletIndex) ?: error("Selected wallet is not found")
|
||||
return selectedWalletState is WalletState.MultiCurrency.Locked ||
|
||||
selectedWalletState is WalletState.SingleCurrency.Locked
|
||||
}
|
||||
|
||||
private fun WalletScreenState.getPrevSelectedWallet(): WalletCardState {
|
||||
return wallets
|
||||
.map(WalletState::walletCardState)
|
||||
|
|
@ -249,9 +241,12 @@ internal class WalletsUpdateActionResolver @Inject constructor(
|
|||
}
|
||||
|
||||
private fun WalletScreenState.incompleteActivationWalletIds(): List<UserWalletId> {
|
||||
return wallets.mapNotNull {
|
||||
if (it.warnings.any { it is WalletNotification.FinishWalletActivation }) {
|
||||
it.walletCardState.id
|
||||
return wallets.mapNotNull { wallet ->
|
||||
if (wallet.warnings.any { it is WalletNotification.FinishWalletActivation } ||
|
||||
wallet.walletCardState is WalletState.MultiCurrency &&
|
||||
wallet.walletCardState.additionalInfo?.isHotBackedUp == false
|
||||
) {
|
||||
wallet.walletCardState.id
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
|
@ -306,6 +301,19 @@ internal class WalletsUpdateActionResolver @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reinitialize wallets
|
||||
*/
|
||||
data class ReinitializeWallets(
|
||||
val selectedWallet: UserWallet,
|
||||
val wallets: List<UserWallet>,
|
||||
) : Action() {
|
||||
|
||||
override fun toString(): String {
|
||||
return "ReinitializeWallets(wallets = ${wallets.joinToString { it.walletId.toString() }}"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename wallets
|
||||
*
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
|
|||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.staking.YieldBalance
|
||||
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
|
||||
|
|
@ -250,7 +250,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
token = currencyStatus.currency.symbol,
|
||||
blockchain = currencyStatus.currency.network.name,
|
||||
action = "Staking",
|
||||
state = if (currencyStatus.value.yieldBalance is YieldBalance.Data) {
|
||||
state = if (currencyStatus.value.stakingBalance is StakingBalance.Data) {
|
||||
"Enabled"
|
||||
} else {
|
||||
"Disabled"
|
||||
|
|
|
|||
|
|
@ -11,9 +11,7 @@ import com.tangem.core.analytics.models.AnalyticsParam
|
|||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.core.ui.components.bottomsheets.message.*
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.bottomSheetMessage
|
||||
import com.tangem.domain.card.SetCardWasScannedUseCase
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
||||
|
|
@ -33,14 +31,13 @@ import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
|||
import com.tangem.domain.settings.NeverToSuggestRateAppUseCase
|
||||
import com.tangem.domain.settings.RemindToRateAppLaterUseCase
|
||||
import com.tangem.domain.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
||||
import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
|
||||
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.wallets.legacy.UserWalletsListManager.Lockable.UnlockType
|
||||
import com.tangem.domain.wallets.models.UnlockWalletsError
|
||||
import com.tangem.domain.wallets.usecase.*
|
||||
import com.tangem.feature.wallet.child.wallet.model.WalletActivationBannerType
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.Basic
|
||||
|
|
@ -106,7 +103,7 @@ internal interface WalletWarningsClickIntents {
|
|||
|
||||
fun onDenyPermissions()
|
||||
|
||||
fun onFinishWalletActivationClick(bannerType: WalletActivationBannerType, isBackupExists: Boolean)
|
||||
fun onFinishWalletActivationClick(isBackupExists: Boolean)
|
||||
}
|
||||
|
||||
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
|
||||
|
|
@ -131,7 +128,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
|||
private val urlOpener: UrlOpener,
|
||||
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||
private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
private val multiStakingBalanceFetcher: MultiStakingBalanceFetcher,
|
||||
private val stakingIdFactory: StakingIdFactory,
|
||||
private val appRouter: AppRouter,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
|
|
@ -460,40 +457,10 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onFinishWalletActivationClick(bannerType: WalletActivationBannerType, isBackupExists: Boolean) {
|
||||
when (bannerType) {
|
||||
WalletActivationBannerType.Attention -> {
|
||||
val userWallet = getSelectedUserWallet() ?: return
|
||||
appRouter.push(WalletActivation(userWallet.walletId, isBackupExists))
|
||||
}
|
||||
WalletActivationBannerType.Warning -> {
|
||||
val message = bottomSheetMessage {
|
||||
infoBlock {
|
||||
icon(R.drawable.img_knight_shield_32) {
|
||||
type = MessageBottomSheetUMV2.Icon.Type.Warning
|
||||
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint
|
||||
}
|
||||
title = resourceReference(R.string.hw_activation_need_title)
|
||||
body = resourceReference(R.string.hw_activation_need_description)
|
||||
}
|
||||
secondaryButton {
|
||||
text = resourceReference(R.string.common_later)
|
||||
onClick {
|
||||
closeBs()
|
||||
}
|
||||
}
|
||||
primaryButton {
|
||||
text = resourceReference(R.string.hw_activation_need_backup)
|
||||
onClick {
|
||||
val userWallet = getSelectedUserWallet() ?: return@onClick
|
||||
appRouter.push(WalletActivation(userWallet.walletId, isBackupExists))
|
||||
closeBs()
|
||||
}
|
||||
}
|
||||
}
|
||||
uiMessageSender.send(message)
|
||||
}
|
||||
}
|
||||
override fun onFinishWalletActivationClick(isBackupExists: Boolean) {
|
||||
analyticsEventHandler.send(MainScreen.ButtonFinalizeActivation)
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
appRouter.push(WalletActivation(userWalletId, isBackupExists))
|
||||
}
|
||||
|
||||
override fun onAllowPermissions() {
|
||||
|
|
@ -562,8 +529,11 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
|||
stakingIdFactory.create(userWalletId = userWalletId, cryptoCurrency = it).getOrNull()
|
||||
}
|
||||
|
||||
multiYieldBalanceFetcher(
|
||||
params = MultiYieldBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = stakingIds),
|
||||
multiStakingBalanceFetcher(
|
||||
params = MultiStakingBalanceFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
stakingIds = stakingIds,
|
||||
),
|
||||
)
|
||||
.onLeft { Timber.e("Unable to fetch yield balances: $it") }
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.feature.wallet.presentation.common.preview
|
|||
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState
|
||||
import com.tangem.core.ui.components.token.AccountItemPreviewData
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
|
|
@ -14,6 +13,7 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.feature.wallet.child.wallet.model.WalletActivationBannerType
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.common.WalletPreviewData.topBarConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.*
|
||||
|
|
@ -167,11 +167,12 @@ internal object WalletScreenPreviewData {
|
|||
warnings = persistentListOf(
|
||||
WalletNotification.Warning.SomeNetworksUnreachable,
|
||||
WalletNotification.FinishWalletActivation(
|
||||
iconTint = NotificationConfig.IconTint.Attention,
|
||||
type = WalletActivationBannerType.Attention,
|
||||
buttonsState = ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.hw_activation_need_finish),
|
||||
onClick = { },
|
||||
),
|
||||
isBackupExists = false,
|
||||
),
|
||||
),
|
||||
bottomSheetConfig = null,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import com.tangem.core.ui.format.bigdecimal.fiat
|
|||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.staking.YieldBalance
|
||||
import com.tangem.domain.models.staking.StakingBalance
|
||||
import com.tangem.domain.staking.utils.getTotalWithRewardsStakingBalance
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.getGroupHeaderId
|
||||
|
|
@ -63,12 +63,12 @@ internal class CryptoCurrencyToDraggableItemConverter(
|
|||
}
|
||||
|
||||
private fun getFormattedFiatAmount(currency: CryptoCurrencyStatus, appCurrency: AppCurrency): String {
|
||||
val yieldBalance = currency.value.yieldBalance as? YieldBalance.Data
|
||||
val stakingBalance = currency.value.stakingBalance as? StakingBalance.Data
|
||||
val fiatRate = currency.value.fiatRate ?: BigDecimal.ZERO
|
||||
val fiatYieldBalance = yieldBalance?.getTotalWithRewardsStakingBalance(currency.currency.network.rawId)
|
||||
val fiatStakingBalance = stakingBalance?.getTotalWithRewardsStakingBalance(currency.currency.network.rawId)
|
||||
?.multiply(fiatRate).orZero()
|
||||
|
||||
val fiatAmount = currency.value.fiatAmount ?: return BigDecimalFormatConstants.EMPTY_BALANCE_SIGN
|
||||
return (fiatAmount + fiatYieldBalance).format { fiat(appCurrency.code, appCurrency.symbol) }
|
||||
return (fiatAmount + fiatStakingBalance).format { fiat(appCurrency.code, appCurrency.symbol) }
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import com.tangem.core.ui.format.bigdecimal.format
|
|||
import com.tangem.domain.account.status.model.AccountCryptoCurrencyStatus
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.staking.YieldBalance
|
||||
import com.tangem.domain.models.staking.StakingBalance
|
||||
import com.tangem.domain.staking.utils.getTotalWithRewardsStakingBalance
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.getGroupHeaderId
|
||||
|
|
@ -56,12 +56,12 @@ internal class CryptoCurrencyToDraggableItemConverterV2(
|
|||
}
|
||||
|
||||
private fun getFormattedFiatAmount(currency: CryptoCurrencyStatus, appCurrency: AppCurrency): String {
|
||||
val yieldBalance = currency.value.yieldBalance as? YieldBalance.Data
|
||||
val stakingBalance = currency.value.stakingBalance as? StakingBalance.Data
|
||||
val fiatRate = currency.value.fiatRate ?: BigDecimal.ZERO
|
||||
val fiatYieldBalance = yieldBalance?.getTotalWithRewardsStakingBalance(currency.currency.network.rawId)
|
||||
val fiatStakingBalance = stakingBalance?.getTotalWithRewardsStakingBalance(currency.currency.network.rawId)
|
||||
?.multiply(fiatRate).orZero()
|
||||
|
||||
val fiatAmount = currency.value.fiatAmount ?: return BigDecimalFormatConstants.EMPTY_BALANCE_SIGN
|
||||
return (fiatAmount + fiatYieldBalance).format { fiat(appCurrency.code, appCurrency.symbol) }
|
||||
return (fiatAmount + fiatStakingBalance).format { fiat(appCurrency.code, appCurrency.symbol) }
|
||||
}
|
||||
}
|
||||
|
|
@ -51,7 +51,30 @@ sealed class WalletScreenAnalyticsEvent {
|
|||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent(category = "Main Screen", event = event, params = params) {
|
||||
|
||||
data object ScreenOpened : MainScreen(event = "Screen opened")
|
||||
data object ScreenOpenedLegacy : MainScreen(
|
||||
event = "Screen opened",
|
||||
)
|
||||
|
||||
data class ScreenOpened(
|
||||
private val hasMobileWallet: Boolean,
|
||||
) : MainScreen(
|
||||
event = "Screen opened",
|
||||
params = mapOf("Mobile Wallet" to if (hasMobileWallet) "Yes" else "No"),
|
||||
)
|
||||
|
||||
data class NoticeFinishActivation(private val activationState: ActivationState) : MainScreen(
|
||||
event = "Notice - Finish Activation",
|
||||
params = mapOf("Activation State" to activationState.value),
|
||||
) {
|
||||
enum class ActivationState(val value: String) {
|
||||
NotStarted("Not Started"),
|
||||
Unfinished("Unfinished"),
|
||||
}
|
||||
}
|
||||
|
||||
data object ButtonFinalizeActivation : MainScreen(
|
||||
event = "Button - Finalize Activation",
|
||||
)
|
||||
|
||||
class WalletSelected(val isImported: Boolean) : MainScreen(
|
||||
event = "Wallet Selected",
|
||||
|
|
|
|||
|
|
@ -70,9 +70,16 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
|
|||
is WalletNotification.Warning.NetworksUnreachable,
|
||||
is WalletNotification.UsedOutdatedData,
|
||||
is WalletNotification.UnlockVisaAccess,
|
||||
is WalletNotification.FinishWalletActivation,
|
||||
is WalletNotification.Warning.YeildSupplyApprove, // TODO apply correct event
|
||||
-> null
|
||||
is WalletNotification.FinishWalletActivation -> {
|
||||
val activationState = if (warning.isBackupExists) {
|
||||
MainScreen.NoticeFinishActivation.ActivationState.Unfinished
|
||||
} else {
|
||||
MainScreen.NoticeFinishActivation.ActivationState.NotStarted
|
||||
}
|
||||
MainScreen.NoticeFinishActivation(activationState)
|
||||
}
|
||||
is WalletNotification.Critical.SeedPhraseNotification -> MainScreen.NoticeSeedPhraseSupport
|
||||
is WalletNotification.Critical.SeedPhraseSecondNotification -> MainScreen.NoticeSeedPhraseSupportSecond
|
||||
is WalletNotification.PushNotifications -> WalletScreenAnalyticsEvent.PushBannerPromo.PushBanner
|
||||
|
|
|
|||
|
|
@ -1,8 +1,24 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.analytics.utils
|
||||
|
||||
import arrow.atomic.AtomicBoolean
|
||||
import com.tangem.common.routing.AppRoute.WalletActivation
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2
|
||||
import com.tangem.core.ui.components.bottomsheets.message.icon
|
||||
import com.tangem.core.ui.components.bottomsheets.message.infoBlock
|
||||
import com.tangem.core.ui.components.bottomsheets.message.onClick
|
||||
import com.tangem.core.ui.components.bottomsheets.message.primaryButton
|
||||
import com.tangem.core.ui.components.bottomsheets.message.secondaryButton
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.bottomSheetMessage
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.SeedPhraseNotificationUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.WalletActivationBannerType
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider
|
||||
|
|
@ -12,8 +28,13 @@ import javax.inject.Inject
|
|||
internal class WalletWarningsSingleEventSender @Inject constructor(
|
||||
private val seedPhraseNotificationUseCase: SeedPhraseNotificationUseCase,
|
||||
private val screenLifecycleProvider: ScreenLifecycleProvider,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val router: Router,
|
||||
) {
|
||||
|
||||
private val isActivationBottomSheetShown: AtomicBoolean = AtomicBoolean(false)
|
||||
|
||||
suspend fun send(
|
||||
userWalletId: UserWalletId,
|
||||
displayedUiState: WalletState?,
|
||||
|
|
@ -26,9 +47,49 @@ internal class WalletWarningsSingleEventSender @Inject constructor(
|
|||
val events = newWarnings.filter { it !in displayedUiState.warnings }
|
||||
|
||||
events.forEach { event ->
|
||||
if (event is WalletNotification.Critical.SeedPhraseNotification) {
|
||||
seedPhraseNotificationUseCase.notified(userWalletId = userWalletId)
|
||||
when (event) {
|
||||
is WalletNotification.Critical.SeedPhraseNotification -> {
|
||||
seedPhraseNotificationUseCase.notified(userWalletId = userWalletId)
|
||||
}
|
||||
is WalletNotification.FinishWalletActivation -> {
|
||||
if (event.type == WalletActivationBannerType.Warning && !isActivationBottomSheetShown.get()) {
|
||||
showFinishActivationBottomSheet(userWalletId)
|
||||
isActivationBottomSheetShown.set(true)
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showFinishActivationBottomSheet(userWalletId: UserWalletId) {
|
||||
val userWallet = getUserWalletUseCase(userWalletId).getOrNull() ?: return
|
||||
if (userWallet !is UserWallet.Hot) return
|
||||
|
||||
val message = bottomSheetMessage {
|
||||
infoBlock {
|
||||
icon(R.drawable.img_knight_shield_32) {
|
||||
type = MessageBottomSheetUMV2.Icon.Type.Warning
|
||||
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint
|
||||
}
|
||||
title = resourceReference(R.string.hw_activation_need_title)
|
||||
body = resourceReference(R.string.hw_activation_need_description)
|
||||
}
|
||||
secondaryButton {
|
||||
text = resourceReference(R.string.common_later)
|
||||
onClick {
|
||||
closeBs()
|
||||
}
|
||||
}
|
||||
primaryButton {
|
||||
text = resourceReference(R.string.hw_activation_need_backup)
|
||||
onClick {
|
||||
router.push(WalletActivation(userWallet.walletId, userWallet.backedUp))
|
||||
closeBs()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uiMessageSender.send(message)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ import com.tangem.common.ui.notifications.NotificationId
|
|||
import com.tangem.common.ui.userwallet.ext.walletInterationIcon
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig.IconTint
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||
import com.tangem.domain.card.CardTypesResolver
|
||||
|
|
@ -465,24 +464,20 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
ifError = { WalletActivationBannerType.Attention },
|
||||
)
|
||||
|
||||
val tint = when (type) {
|
||||
WalletActivationBannerType.Attention -> IconTint.Attention
|
||||
WalletActivationBannerType.Warning -> IconTint.Warning
|
||||
}
|
||||
|
||||
addIf(
|
||||
element = WalletNotification.FinishWalletActivation(
|
||||
iconTint = tint,
|
||||
type = type,
|
||||
buttonsState = when (type) {
|
||||
WalletActivationBannerType.Warning -> ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.hw_activation_need_finish),
|
||||
onClick = { clickIntents.onFinishWalletActivationClick(type, isBackupExists) },
|
||||
onClick = { clickIntents.onFinishWalletActivationClick(isBackupExists) },
|
||||
)
|
||||
else -> ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.hw_activation_need_finish),
|
||||
onClick = { clickIntents.onFinishWalletActivationClick(type, isBackupExists) },
|
||||
onClick = { clickIntents.onFinishWalletActivationClick(isBackupExists) },
|
||||
)
|
||||
},
|
||||
isBackupExists = isBackupExists,
|
||||
),
|
||||
condition = shouldShowFinishActivation,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ internal object WalletAdditionalInfoFactory {
|
|||
backedUp.not() -> DIVIDER + TextReference.Res(R.string.hw_backup_no_backup)
|
||||
else -> TextReference.Str("")
|
||||
},
|
||||
isHotBackedUp = backedUp,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,4 +7,5 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
data class WalletAdditionalInfo(
|
||||
val hideable: Boolean,
|
||||
val content: TextReference,
|
||||
val isHotBackedUp: Boolean = false,
|
||||
)
|
||||
|
|
@ -10,6 +10,7 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.core.ui.extensions.pluralReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.feature.wallet.child.wallet.model.WalletActivationBannerType
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import org.joda.time.DateTime
|
||||
|
||||
|
|
@ -287,14 +288,18 @@ sealed class WalletNotification(val config: NotificationConfig) {
|
|||
)
|
||||
|
||||
data class FinishWalletActivation(
|
||||
val iconTint: IconTint,
|
||||
val type: WalletActivationBannerType,
|
||||
val buttonsState: ButtonsState,
|
||||
val isBackupExists: Boolean,
|
||||
) : WalletNotification(
|
||||
config = NotificationConfig(
|
||||
title = resourceReference(R.string.hw_activation_need_title),
|
||||
subtitle = resourceReference(R.string.hw_activation_need_description),
|
||||
iconResId = R.drawable.img_knight_shield_32,
|
||||
iconTint = iconTint,
|
||||
iconTint = when (type) {
|
||||
WalletActivationBannerType.Attention -> IconTint.Attention
|
||||
WalletActivationBannerType.Warning -> IconTint.Warning
|
||||
},
|
||||
buttonsState = buttonsState,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ internal class SetVisaInfoTransformer(
|
|||
fiatAmount = visaCurrency.balances.available.multiply(visaCurrency.fiatRate),
|
||||
fiatRate = visaCurrency.fiatRate,
|
||||
priceChange = visaCurrency.priceChange,
|
||||
yieldBalance = null,
|
||||
stakingBalance = null,
|
||||
yieldSupplyStatus = null,
|
||||
hasCurrentNetworkTransactions = false,
|
||||
pendingTransactions = emptySet(),
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
|
|||
yieldSupplyApyMap: Map<String, String> = emptyMap(),
|
||||
stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
|
||||
) {
|
||||
val accountFlattenCurrencies = accountList.flattenCurrencies()
|
||||
val mainAccount = accountList.mainAccount
|
||||
|
||||
when {
|
||||
|
|
@ -70,20 +69,8 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
|
|||
)
|
||||
}
|
||||
isAccountMode -> {
|
||||
val isAllAccountsEmpty = accountFlattenCurrencies.isEmpty()
|
||||
if (isAllAccountsEmpty) {
|
||||
stateController.update(
|
||||
SetTokenListErrorTransformer(
|
||||
selectedWallet = userWallet,
|
||||
error = TokenListError.EmptyTokens,
|
||||
appCurrency = appCurrency,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
val convertParams = TokenConverterParams.Account(accountList, expandedAccounts)
|
||||
|
||||
updateContent(convertParams, appCurrency, yieldSupplyApyMap, stakingApyMap)
|
||||
}
|
||||
val convertParams = TokenConverterParams.Account(accountList, expandedAccounts)
|
||||
updateContent(convertParams, appCurrency, yieldSupplyApyMap, stakingApyMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue