Updated on 2026-08-14
This commit is contained in:
commit
a0c9ace219
1607 changed files with 40580 additions and 16776 deletions
|
|
@ -15,6 +15,7 @@ import com.tangem.core.decompose.model.getOrCreateModel
|
|||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.decompose.ComposableDialogComponent
|
||||
import com.tangem.domain.tokens.model.details.TokenAction
|
||||
import com.tangem.feature.wallet.child.wallet.model.WalletModel
|
||||
import com.tangem.feature.wallet.navigation.WalletRoute
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDialogConfig
|
||||
|
|
@ -25,6 +26,7 @@ import com.tangem.features.markets.entry.MarketsEntryComponent
|
|||
import com.tangem.features.pushnotifications.api.PushNotificationsBottomSheetComponent
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsParams
|
||||
import com.tangem.features.tokenreceive.TokenReceiveComponent
|
||||
import com.tangem.features.yield.supply.api.YieldSupplyDepositedWarningComponent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
|
@ -39,6 +41,7 @@ internal class WalletComponent @AssistedInject constructor(
|
|||
private val askBiometryComponentFactory: AskBiometryComponent.Factory,
|
||||
private val pushNotificationsBottomSheetComponent: PushNotificationsBottomSheetComponent.Factory,
|
||||
private val tokenReceiveComponentFactory: TokenReceiveComponent.Factory,
|
||||
private val yieldSupplyDepositedWarningComponent: YieldSupplyDepositedWarningComponent.Factory,
|
||||
) : ComposableContentComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: WalletModel = getOrCreateModel()
|
||||
|
|
@ -68,7 +71,7 @@ internal class WalletComponent @AssistedInject constructor(
|
|||
askBiometryComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = AskBiometryComponent.Params(
|
||||
bottomSheetVariant = true,
|
||||
isBottomSheetVariant = true,
|
||||
modelCallbacks = model.askBiometryModelCallbacks,
|
||||
),
|
||||
)
|
||||
|
|
@ -90,6 +93,21 @@ internal class WalletComponent @AssistedInject constructor(
|
|||
),
|
||||
)
|
||||
}
|
||||
is WalletDialogConfig.YieldSupplyWarning -> {
|
||||
yieldSupplyDepositedWarningComponent.create(
|
||||
context = childByContext(componentContext),
|
||||
params = YieldSupplyDepositedWarningComponent.Params(
|
||||
cryptoCurrency = dialogConfig.cryptoCurrency,
|
||||
onDismiss = model.innerWalletRouter.dialogNavigation::dismiss,
|
||||
tokenAction = dialogConfig.tokenAction,
|
||||
modelCallback = object : YieldSupplyDepositedWarningComponent.ModelCallback {
|
||||
override fun onYieldSupplyWarningAcknowledged(tokenAction: TokenAction) {
|
||||
dialogConfig.onWarningAcknowledged(tokenAction)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.feature.wallet.child.wallet.model;
|
||||
|
||||
public enum WalletActivationBannerType {
|
||||
Attention,
|
||||
Warning,
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
|
|||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
|
|
@ -18,9 +18,12 @@ import com.tangem.domain.models.wallet.isMultiCurrency
|
|||
import com.tangem.domain.nft.ObserveAndClearNFTCacheIfNeedUseCase
|
||||
import com.tangem.domain.notifications.GetIsHuaweiDeviceWithoutGoogleServicesUseCase
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import com.tangem.domain.pay.usecase.TangemPayIssueOrderUseCase
|
||||
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
|
||||
import com.tangem.domain.settings.*
|
||||
import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase
|
||||
import com.tangem.domain.wallets.usecase.*
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyUpdateUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
||||
|
|
@ -38,8 +41,9 @@ import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvid
|
|||
import com.tangem.features.biometry.AskBiometryComponent
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks
|
||||
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
||||
import com.tangem.features.tangempay.TangemPayFeatureToggles
|
||||
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionListener
|
||||
import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.*
|
||||
import kotlinx.coroutines.*
|
||||
|
|
@ -48,6 +52,8 @@ import kotlinx.coroutines.flow.*
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val TANGEM_PAY_UPDATE_INTERVAL = 60_000L
|
||||
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
@Stable
|
||||
@ModelScoped
|
||||
|
|
@ -69,7 +75,6 @@ internal class WalletModel @Inject constructor(
|
|||
private val selectedWalletAnalyticsSender: SelectedWalletAnalyticsSender,
|
||||
private val walletNameMigrationUseCase: WalletNameMigrationUseCase,
|
||||
private val refreshMultiCurrencyWalletQuotesUseCase: RefreshMultiCurrencyWalletQuotesUseCase,
|
||||
private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase,
|
||||
private val walletImageResolver: WalletImageResolver,
|
||||
private val tokenListStore: MultiWalletTokenListStore,
|
||||
private val onrampStatusFactory: OnrampStatusFactory,
|
||||
|
|
@ -84,6 +89,11 @@ internal class WalletModel @Inject constructor(
|
|||
private val getIsHuaweiDeviceWithoutGoogleServicesUseCase: GetIsHuaweiDeviceWithoutGoogleServicesUseCase,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase,
|
||||
private val tangemPayIssueOrderUseCase: TangemPayIssueOrderUseCase,
|
||||
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
|
||||
private val yieldSupplyApyUpdateUseCase: YieldSupplyApyUpdateUseCase,
|
||||
private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles,
|
||||
val screenLifecycleProvider: ScreenLifecycleProvider,
|
||||
val innerWalletRouter: InnerWalletRouter,
|
||||
) : Model() {
|
||||
|
|
@ -96,6 +106,7 @@ internal class WalletModel @Inject constructor(
|
|||
private val refreshWalletJobHolder = JobHolder()
|
||||
private var needToRefreshWallet = false
|
||||
private val clearNFTCacheJobHolder = JobHolder()
|
||||
private val updateTangemPayJobHolder = JobHolder()
|
||||
|
||||
private var expressTxStatusTaskScheduler = SingleTaskScheduler<Unit>()
|
||||
|
||||
|
|
@ -107,16 +118,26 @@ internal class WalletModel @Inject constructor(
|
|||
|
||||
maybeMigrateNames()
|
||||
maybeSetWalletFirstTimeUsage()
|
||||
updateYieldSupplyApy()
|
||||
subscribeToUserWalletsUpdates()
|
||||
subscribeOnBalanceHiding()
|
||||
subscribeOnSelectedWalletFlow()
|
||||
subscribeToScreenBackgroundState()
|
||||
subscribeOnPushNotificationsPermission()
|
||||
subscribeToTangemPayInfo()
|
||||
enableNotificationsIfNeeded()
|
||||
|
||||
clickIntents.initialize(innerWalletRouter, modelScope)
|
||||
}
|
||||
|
||||
private fun updateYieldSupplyApy() {
|
||||
if (yieldSupplyFeatureToggles.isYieldSupplyFeatureEnabled) {
|
||||
modelScope.launch(dispatchers.default) {
|
||||
yieldSupplyApyUpdateUseCase()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeMigrateNames() {
|
||||
modelScope.launch {
|
||||
walletNameMigrationUseCase()
|
||||
|
|
@ -215,19 +236,23 @@ internal class WalletModel @Inject constructor(
|
|||
|
||||
private fun subscribeOnPushNotificationsPermission() {
|
||||
modelScope.launch {
|
||||
val shouldAskPermission = shouldAskPermissionUseCase(PUSH_PERMISSION)
|
||||
val afterUpdate = notificationsRepository.shouldShowSubscribeOnNotificationsAfterUpdate()
|
||||
val shouldAskNotificationPermissionsViaBs = notificationsRepository.shouldAskNotificationPermissionsViaBs()
|
||||
val shouldShow = notificationsRepository.shouldShowSubscribeOnNotificationsAfterUpdate()
|
||||
val isBiometricsEnabled = shouldSaveUserWalletsSyncUseCase()
|
||||
val isHuaweiDevice = getIsHuaweiDeviceWithoutGoogleServicesUseCase()
|
||||
val shouldShowBottomSheet = shouldAskPermission || afterUpdate
|
||||
Timber.d(
|
||||
"push BS afterUpdate: $afterUpdate," +
|
||||
"shouldAskPermission $shouldAskPermission," +
|
||||
"push BS afterUpdate: $shouldShow," +
|
||||
"isBiometricsEnabled $isBiometricsEnabled," +
|
||||
"isHuaweiDevice $isHuaweiDevice",
|
||||
)
|
||||
if (!shouldAskNotificationPermissionsViaBs) {
|
||||
notificationsRepository.setShouldAskNotificationPermissionsViaBs(true)
|
||||
return@launch
|
||||
}
|
||||
if (!isBiometricsEnabled) return@launch
|
||||
if (!shouldShowBottomSheet) return@launch
|
||||
if (!shouldShow) {
|
||||
return@launch
|
||||
}
|
||||
|
||||
delay(timeMillis = 1_800)
|
||||
|
||||
|
|
@ -313,6 +338,48 @@ internal class WalletModel @Inject constructor(
|
|||
.saveIn(clearNFTCacheJobHolder)
|
||||
}
|
||||
|
||||
private fun subscribeToTangemPayInfo() {
|
||||
/**
|
||||
* Update state each time a user opens/returns to wallet screen
|
||||
* and every minute while user stays on the main screen
|
||||
*/
|
||||
screenLifecycleProvider.isBackgroundState.onEach { inBackground ->
|
||||
updateTangemPayJobHolder.cancel()
|
||||
if (!inBackground && tangemPayFeatureToggles.isTangemPayEnabled) {
|
||||
modelScope.launch {
|
||||
refreshTangemPayInfo()
|
||||
while (isActive) {
|
||||
delay(TANGEM_PAY_UPDATE_INTERVAL)
|
||||
refreshTangemPayInfo()
|
||||
}
|
||||
}.saveIn(updateTangemPayJobHolder)
|
||||
}
|
||||
}.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private suspend fun refreshTangemPayInfo() {
|
||||
val info = tangemPayMainScreenCustomerInfoUseCase()
|
||||
if (info != null) {
|
||||
stateHolder.update(
|
||||
transformer = TangemPayInitialStateTransformer(
|
||||
value = info,
|
||||
onClickIssue = ::issueOrder,
|
||||
onClickKyc = innerWalletRouter::openTangemPayOnboarding,
|
||||
openDetails = innerWalletRouter::openTangemPayDetails,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun issueOrder() {
|
||||
modelScope.launch {
|
||||
stateHolder.update(TangemPayIssueProgressStateTransformer())
|
||||
tangemPayIssueOrderUseCase().onLeft {
|
||||
stateHolder.update(TangemPayIssueAvailableStateTransformer(onClickIssue = ::issueOrder))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun needToRefreshTimer() {
|
||||
modelScope.launch {
|
||||
delay(REFRESH_WALLET_BACKGROUND_TIMER_MILLIS)
|
||||
|
|
@ -366,7 +433,7 @@ internal class WalletModel @Inject constructor(
|
|||
is WalletsUpdateActionResolver.Action.RenameWallets -> {
|
||||
stateHolder.update(transformer = RenameWalletsTransformer(renamedWallets = action.renamedWallets))
|
||||
}
|
||||
is WalletsUpdateActionResolver.Action.ReloadWarningsForWallets -> {
|
||||
is WalletsUpdateActionResolver.Action.ReloadWallets -> {
|
||||
reloadWarnings(action)
|
||||
}
|
||||
WalletsUpdateActionResolver.Action.EmptyWallets -> {
|
||||
|
|
@ -378,7 +445,7 @@ internal class WalletModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun reloadWarnings(action: WalletsUpdateActionResolver.Action.ReloadWarningsForWallets) {
|
||||
private fun reloadWarnings(action: WalletsUpdateActionResolver.Action.ReloadWallets) {
|
||||
action.wallets.forEach {
|
||||
walletScreenContentLoader.load(
|
||||
userWallet = it,
|
||||
|
|
|
|||
|
|
@ -64,6 +64,9 @@ internal class WalletsUpdateActionResolver @Inject constructor(
|
|||
selectedWallet: UserWallet,
|
||||
): Action {
|
||||
return when {
|
||||
isHotWalletUpgraded(state, wallets) -> {
|
||||
getHotWalletsUpgradedAction(state, wallets)
|
||||
}
|
||||
isWalletsCountChanged(state, wallets) -> {
|
||||
getChangeWalletsListAction(state, wallets, selectedWallet)
|
||||
}
|
||||
|
|
@ -91,6 +94,19 @@ internal class WalletsUpdateActionResolver @Inject constructor(
|
|||
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
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun isWalletsCountChanged(state: WalletScreenState, wallets: List<UserWallet>): Boolean {
|
||||
val prevWalletsSize = state.wallets.size
|
||||
val walletsSize = wallets.size
|
||||
|
|
@ -160,14 +176,22 @@ internal class WalletsUpdateActionResolver @Inject constructor(
|
|||
private fun getHotWalletsBackedUpAction(
|
||||
state: WalletScreenState,
|
||||
wallets: List<UserWallet>,
|
||||
): Action.ReloadWarningsForWallets {
|
||||
): Action.ReloadWallets {
|
||||
val incompleteActivationWalletIds = state.incompleteActivationWalletIds()
|
||||
|
||||
val walletsToUpdate = wallets.filter {
|
||||
it is UserWallet.Hot && it.backedUp == incompleteActivationWalletIds.contains(it.walletId)
|
||||
}
|
||||
|
||||
return Action.ReloadWarningsForWallets(walletsToUpdate)
|
||||
return Action.ReloadWallets(walletsToUpdate)
|
||||
}
|
||||
|
||||
private fun getHotWalletsUpgradedAction(
|
||||
state: WalletScreenState,
|
||||
wallets: List<UserWallet>,
|
||||
): Action.ReloadWallets {
|
||||
val walletsToUpdate = wallets.filter { it.walletId == state.getPrevSelectedWallet().id }
|
||||
return Action.ReloadWallets(walletsToUpdate)
|
||||
}
|
||||
|
||||
private fun getRenameWalletsAction(state: WalletScreenState, wallets: List<UserWallet>): Action.RenameWallets {
|
||||
|
|
@ -358,7 +382,7 @@ internal class WalletsUpdateActionResolver @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
data class ReloadWarningsForWallets(
|
||||
data class ReloadWallets(
|
||||
val wallets: List<UserWallet>,
|
||||
) : Action()
|
||||
|
||||
|
|
|
|||
|
|
@ -4,18 +4,19 @@ import arrow.core.getOrElse
|
|||
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
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.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.redux.ReduxStateHolder
|
||||
import com.tangem.domain.settings.ShouldShowMarketsTooltipUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
|
||||
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
|
||||
import com.tangem.domain.tokens.TokensAction
|
||||
import com.tangem.domain.tokens.model.TokenActionsState
|
||||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.feature.wallet.presentation.account.AccountDependencies
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.unwrap
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
|
|
@ -36,15 +37,17 @@ internal interface WalletContentClickIntents {
|
|||
|
||||
fun onDetailsClick()
|
||||
|
||||
fun onManageTokensClick()
|
||||
|
||||
fun onOrganizeTokensClick()
|
||||
|
||||
fun onDismissMarketsOnboarding()
|
||||
|
||||
fun onTokenItemClick(currencyStatus: CryptoCurrencyStatus)
|
||||
fun onTokenItemClick(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onTokenItemLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
fun onTokenItemLongClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onAccountExpandClick(account: Account)
|
||||
|
||||
fun onAccountCollapseClick(account: Account)
|
||||
|
||||
fun onTransactionClick(txHash: String)
|
||||
|
||||
|
|
@ -74,10 +77,10 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
|
||||
private val shouldShowMarketsTooltipUseCase: ShouldShowMarketsTooltipUseCase,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
private val walletEventSender: WalletEventSender,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
) : BaseWalletClickIntents(), WalletContentClickIntents {
|
||||
|
||||
override fun onDetailsClick() {
|
||||
|
|
@ -114,11 +117,6 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onManageTokensClick() {
|
||||
reduxStateHolder.dispatch(action = TokensAction.SetArgs.ManageAccess)
|
||||
router.openManageTokensScreen(userWalletId = stateHolder.getSelectedWalletId())
|
||||
}
|
||||
|
||||
override fun onOrganizeTokensClick() {
|
||||
router.openOrganizeTokensScreen(userWalletId = stateHolder.getSelectedWalletId())
|
||||
}
|
||||
|
|
@ -130,13 +128,12 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onTokenItemClick(currencyStatus: CryptoCurrencyStatus) {
|
||||
router.openTokenDetails(stateHolder.getSelectedWalletId(), currencyStatus)
|
||||
override fun onTokenItemClick(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus) {
|
||||
router.openTokenDetails(userWalletId, currencyStatus)
|
||||
}
|
||||
|
||||
override fun onTokenItemLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
override fun onTokenItemLongClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
modelScope.launch(dispatchers.main) {
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
val userWallet = getUserWalletUseCase(userWalletId).getOrElse {
|
||||
Timber.e(
|
||||
"""
|
||||
|
|
@ -157,6 +154,16 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onAccountExpandClick(account: Account) {
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
accountDependencies.expandedAccountsHolder.expandAccount(userWalletId, account.accountId)
|
||||
}
|
||||
|
||||
override fun onAccountCollapseClick(account: Account) {
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
accountDependencies.expandedAccountsHolder.collapseAccount(userWalletId, account.accountId)
|
||||
}
|
||||
|
||||
private fun showActionsBottomSheet(tokenActionsState: TokenActionsState, userWallet: UserWallet) {
|
||||
stateHolder.showBottomSheet(
|
||||
ActionsBottomSheetConfig(
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ import com.tangem.core.ui.extensions.WrappedList
|
|||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.haptic.TangemHapticEffect
|
||||
import com.tangem.core.ui.haptic.VibratorHapticManager
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
|
||||
import com.tangem.domain.account.status.usecase.SaveCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.extenstions.unwrap
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
|
|
@ -29,7 +32,6 @@ import com.tangem.domain.core.utils.lceError
|
|||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.markets.TokenMarketParams
|
||||
import com.tangem.domain.models.ReceiveAddressModel
|
||||
import com.tangem.domain.models.TokenReceiveConfig
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
|
|
@ -42,10 +44,7 @@ import com.tangem.domain.promo.GetStoryContentUseCase
|
|||
import com.tangem.domain.promo.models.StoryContentIds
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
|
||||
import com.tangem.domain.tokens.GetViewedTokenReceiveWarningUseCase
|
||||
import com.tangem.domain.tokens.IsCryptoCurrencyCoinCouldHideUseCase
|
||||
import com.tangem.domain.tokens.RemoveCurrencyUseCase
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||
import com.tangem.domain.tokens.model.analytics.TokenReceiveAnalyticsEvent
|
||||
|
|
@ -54,7 +53,8 @@ import com.tangem.domain.tokens.model.analytics.TokenReceiveNewAnalyticsEvent
|
|||
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent
|
||||
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.Companion.AVAILABLE
|
||||
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.Companion.toReasonAnalyticsText
|
||||
import com.tangem.domain.transaction.usecase.GetEnsNameUseCase
|
||||
import com.tangem.domain.tokens.model.details.TokenAction
|
||||
import com.tangem.domain.transaction.usecase.ReceiveAddressesFactory
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
|
|
@ -68,21 +68,31 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensLis
|
|||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
|
||||
import com.tangem.features.tokenreceive.TokenReceiveFeatureToggle
|
||||
import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import javax.inject.Inject
|
||||
|
||||
interface WalletCurrencyActionsClickIntents {
|
||||
|
||||
fun onSendClick(cryptoCurrencyStatus: CryptoCurrencyStatus, unavailabilityReason: ScenarioUnavailabilityReason)
|
||||
fun onSendClick(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
unavailabilityReason: ScenarioUnavailabilityReason,
|
||||
)
|
||||
|
||||
fun onSellClick(cryptoCurrencyStatus: CryptoCurrencyStatus, unavailabilityReason: ScenarioUnavailabilityReason)
|
||||
|
||||
fun onBuyClick(cryptoCurrencyStatus: CryptoCurrencyStatus, unavailabilityReason: ScenarioUnavailabilityReason)
|
||||
fun onBuyClick(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
unavailabilityReason: ScenarioUnavailabilityReason,
|
||||
)
|
||||
|
||||
fun onSwapClick(
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
|
|
@ -90,17 +100,21 @@ interface WalletCurrencyActionsClickIntents {
|
|||
unavailabilityReason: ScenarioUnavailabilityReason,
|
||||
)
|
||||
|
||||
fun onReceiveClick(cryptoCurrencyStatus: CryptoCurrencyStatus, event: AnalyticsEvent? = null)
|
||||
fun onReceiveClick(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
event: AnalyticsEvent? = null,
|
||||
)
|
||||
|
||||
fun onStakeClick(cryptoCurrencyStatus: CryptoCurrencyStatus, yield: Yield?)
|
||||
fun onStakeClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus, yield: Yield?)
|
||||
|
||||
fun onCopyAddressLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus): TextReference?
|
||||
|
||||
fun onCopyAddressClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
fun onCopyAddressClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onHideTokensClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
fun onHideTokensClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onPerformHideToken(cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
fun onPerformHideToken(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onExploreClick()
|
||||
|
||||
|
|
@ -122,8 +136,6 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val isDemoCardUseCase: IsDemoCardUseCase,
|
||||
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
|
||||
private val isCryptoCurrencyCoinCouldHide: IsCryptoCurrencyCoinCouldHideUseCase,
|
||||
private val removeCurrencyUseCase: RemoveCurrencyUseCase,
|
||||
private val getExploreUrlUseCase: GetExploreUrlUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getStoryContentUseCase: GetStoryContentUseCase,
|
||||
|
|
@ -136,16 +148,23 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
private val appRouter: AppRouter,
|
||||
private val rampStateManager: RampStateManager,
|
||||
private val tokenReceiveFeatureToggle: TokenReceiveFeatureToggle,
|
||||
private val getViewedTokenReceiveWarningUseCase: GetViewedTokenReceiveWarningUseCase,
|
||||
private val getEnsNameUseCase: GetEnsNameUseCase,
|
||||
private val saveViewedTokenReceiveWarningUseCase: SaveViewedTokenReceiveWarningUseCase,
|
||||
private val receiveAddressesFactory: ReceiveAddressesFactory,
|
||||
private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles,
|
||||
private val needShowYieldSupplyDepositedWarningUseCase: NeedShowYieldSupplyDepositedWarningUseCase,
|
||||
private val saveViewedYieldSupplyWarningUseCase: SaveViewedYieldSupplyWarningUseCase,
|
||||
private val isCryptoCurrencyCoinCouldHide: IsCryptoCurrencyCoinCouldHideUseCase,
|
||||
private val removeCurrencyUseCase: RemoveCurrencyUseCase,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
|
||||
private val saveCryptoCurrenciesUseCase: SaveCryptoCurrenciesUseCase,
|
||||
) : BaseWalletClickIntents(), WalletCurrencyActionsClickIntents {
|
||||
|
||||
override fun onSendClick(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
unavailabilityReason: ScenarioUnavailabilityReason,
|
||||
) {
|
||||
val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return
|
||||
|
||||
analyticsEventHandler.send(
|
||||
event = TokenScreenAnalyticsEvent.ButtonWithParams.ButtonSend(
|
||||
token = cryptoCurrencyStatus.currency.symbol,
|
||||
|
|
@ -156,18 +175,31 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
|
||||
if (handleUnavailabilityReason(unavailabilityReason)) return
|
||||
|
||||
stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWallet.walletId))
|
||||
val route = AppRoute.Send(
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
userWalletId = userWallet.walletId,
|
||||
)
|
||||
|
||||
appRouter.push(route)
|
||||
modelScope.launch(dispatchers.main) {
|
||||
if (needShowYieldSupplyWarning(cryptoCurrencyStatus)) {
|
||||
stateHolder.hideBottomSheet()
|
||||
router.openYieldSupplyBottomSheet(
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
tokenAction = TokenAction.Send,
|
||||
onWarningAcknowledged = { tokenAction ->
|
||||
modelScope.launch {
|
||||
saveViewedYieldSupplyWarningUseCase(cryptoCurrencyStatus.currency.name)
|
||||
stateHolder.hideBottomSheet()
|
||||
navigateToSend(cryptoCurrencyStatus, userWalletId)
|
||||
}
|
||||
},
|
||||
)
|
||||
} else {
|
||||
navigateToSend(cryptoCurrencyStatus, userWalletId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReceiveClick(cryptoCurrencyStatus: CryptoCurrencyStatus, event: AnalyticsEvent?) {
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
|
||||
override fun onReceiveClick(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
event: AnalyticsEvent?,
|
||||
) {
|
||||
analyticsEventHandler.send(
|
||||
event = TokenScreenAnalyticsEvent.ButtonWithParams.ButtonReceive(
|
||||
token = cryptoCurrencyStatus.currency.symbol,
|
||||
|
|
@ -178,24 +210,24 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
|
||||
event?.let { analyticsEventHandler.send(it) }
|
||||
|
||||
if (tokenReceiveFeatureToggle.isNewTokenReceiveEnabled) {
|
||||
stateHolder.hideBottomSheet()
|
||||
modelScope.launch {
|
||||
configureReceiveAddresses(cryptoCurrencyStatus = cryptoCurrencyStatus)?.let {
|
||||
router.openTokenReceiveBottomSheet(it)
|
||||
}
|
||||
modelScope.launch(dispatchers.main) {
|
||||
if (needShowYieldSupplyWarning(cryptoCurrencyStatus)) {
|
||||
stateHolder.hideBottomSheet()
|
||||
router.openYieldSupplyBottomSheet(
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
tokenAction = TokenAction.Receive,
|
||||
onWarningAcknowledged = { tokenAction ->
|
||||
modelScope.launch {
|
||||
saveViewedTokenReceiveWarningUseCase(cryptoCurrencyStatus.currency.name)
|
||||
saveViewedYieldSupplyWarningUseCase(cryptoCurrencyStatus.currency.name)
|
||||
stateHolder.hideBottomSheet()
|
||||
navigateToReceive(cryptoCurrencyStatus)
|
||||
}
|
||||
},
|
||||
)
|
||||
} else {
|
||||
navigateToReceive(cryptoCurrencyStatus)
|
||||
}
|
||||
} else {
|
||||
analyticsEventHandler.send(
|
||||
event = TokenReceiveAnalyticsEvent.ReceiveScreenOpened(cryptoCurrencyStatus.currency.symbol),
|
||||
)
|
||||
stateHolder.showBottomSheet(
|
||||
createReceiveBottomSheetContent(
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
addresses = cryptoCurrencyStatus.value.networkAddress ?: return,
|
||||
),
|
||||
userWalletId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -240,7 +272,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
override fun onCopyAddressClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
override fun onCopyAddressClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
analyticsEventHandler.send(
|
||||
event = TokenReceiveNewAnalyticsEvent.ButtonCopyAddress(
|
||||
token = cryptoCurrencyStatus.currency.symbol,
|
||||
|
|
@ -251,10 +283,10 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
|
||||
modelScope.launch(dispatchers.main) {
|
||||
walletManagersFacade.getDefaultAddress(
|
||||
userWalletId = stateHolder.getSelectedWalletId(),
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
)?.let { address ->
|
||||
stateHolder.update(CloseBottomSheetTransformer(userWalletId = stateHolder.getSelectedWalletId()))
|
||||
stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWalletId))
|
||||
|
||||
clipboardManager.setText(text = address, isSensitive = true)
|
||||
walletEventSender.send(event = WalletEvent.CopyAddress)
|
||||
|
|
@ -262,7 +294,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onHideTokensClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
override fun onHideTokensClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
analyticsEventHandler.send(
|
||||
event = TokenScreenAnalyticsEvent.ButtonRemoveToken(cryptoCurrencyStatus.currency.symbol),
|
||||
)
|
||||
|
|
@ -270,7 +302,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
modelScope.launch(dispatchers.main) {
|
||||
walletEventSender.send(
|
||||
event = WalletEvent.ShowAlert(
|
||||
state = getHideTokeAlertConfig(stateHolder.getSelectedWalletId(), cryptoCurrencyStatus),
|
||||
state = getHideTokeAlertConfig(userWalletId, cryptoCurrencyStatus),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -281,7 +313,9 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
): WalletAlertState.DefaultAlert {
|
||||
val currency = cryptoCurrencyStatus.currency
|
||||
return if (currency is CryptoCurrency.Coin && !isCryptoCurrencyCoinCouldHide(userWalletId, currency)) {
|
||||
val isCryptoCurrencyCoinCouldHide = currency is CryptoCurrency.Coin &&
|
||||
!isCryptoCurrencyCoinCouldHide(userWalletId = userWalletId, cryptoCurrencyCoin = currency)
|
||||
return if (isCryptoCurrencyCoinCouldHide) {
|
||||
WalletAlertState.DefaultAlert(
|
||||
title = resourceReference(
|
||||
id = R.string.token_details_unable_hide_alert_title,
|
||||
|
|
@ -306,16 +340,30 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
formatArgs = WrappedList(listOf(cryptoCurrencyStatus.currency.name)),
|
||||
),
|
||||
message = resourceReference(R.string.token_details_hide_alert_message),
|
||||
onConfirmClick = { onPerformHideToken(cryptoCurrencyStatus) },
|
||||
onConfirmClick = { onPerformHideToken(userWalletId, cryptoCurrencyStatus) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPerformHideToken(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
|
||||
override fun onPerformHideToken(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
modelScope.launch(dispatchers.io) {
|
||||
removeCurrencyUseCase(userWalletId, cryptoCurrencyStatus.currency)
|
||||
if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
val accountId = getAccountCurrencyStatusUseCase.invokeSync(
|
||||
userWalletId = userWalletId,
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
)
|
||||
.map { it.account.accountId }
|
||||
.getOrNull()
|
||||
|
||||
if (accountId == null) {
|
||||
Timber.e("Account ID is null, cannot hide currency ${cryptoCurrencyStatus.currency.id}")
|
||||
return@launch
|
||||
}
|
||||
|
||||
saveCryptoCurrenciesUseCase(accountId = accountId, remove = cryptoCurrencyStatus.currency)
|
||||
} else {
|
||||
removeCurrencyUseCase(userWalletId, cryptoCurrencyStatus.currency)
|
||||
}
|
||||
.fold(
|
||||
ifLeft = {
|
||||
walletEventSender.send(
|
||||
|
|
@ -356,11 +404,10 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onBuyClick(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
unavailabilityReason: ScenarioUnavailabilityReason,
|
||||
) {
|
||||
val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return
|
||||
|
||||
analyticsEventHandler.send(
|
||||
event = TokenScreenAnalyticsEvent.ButtonWithParams.ButtonBuy(
|
||||
token = cryptoCurrencyStatus.currency.symbol,
|
||||
|
|
@ -373,7 +420,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
|
||||
appRouter.push(
|
||||
AppRoute.Onramp(
|
||||
userWalletId = userWallet.walletId,
|
||||
userWalletId = userWalletId,
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
source = OnrampSource.TOKEN_LONG_TAP,
|
||||
),
|
||||
|
|
@ -395,13 +442,24 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
|
||||
if (handleUnavailabilityReason(unavailabilityReason)) return
|
||||
|
||||
appRouter.push(
|
||||
AppRoute.Swap(
|
||||
currencyFrom = cryptoCurrencyStatus.currency,
|
||||
userWalletId = userWalletId,
|
||||
screenSource = AnalyticsParam.ScreensSources.LongTap.value,
|
||||
),
|
||||
)
|
||||
modelScope.launch(dispatchers.main) {
|
||||
if (needShowYieldSupplyWarning(cryptoCurrencyStatus)) {
|
||||
stateHolder.hideBottomSheet()
|
||||
router.openYieldSupplyBottomSheet(
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
tokenAction = TokenAction.Swap,
|
||||
onWarningAcknowledged = { tokenAction ->
|
||||
modelScope.launch {
|
||||
saveViewedYieldSupplyWarningUseCase(cryptoCurrencyStatus.currency.name)
|
||||
stateHolder.hideBottomSheet()
|
||||
navigateToSwap(cryptoCurrencyStatus, userWalletId)
|
||||
}
|
||||
},
|
||||
)
|
||||
} else {
|
||||
navigateToSwap(cryptoCurrencyStatus, userWalletId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onExploreClick() {
|
||||
|
|
@ -428,7 +486,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
AppRoute.MarketsTokenDetails(
|
||||
token = tokenMarketParams,
|
||||
appCurrency = getSelectedAppCurrencyUseCase.unwrap(),
|
||||
showPortfolio = false,
|
||||
shouldShowPortfolio = false,
|
||||
analyticsParams = AppRoute.MarketsTokenDetails.AnalyticsParams(
|
||||
blockchain = cryptoCurrencyStatus.currency.network.name,
|
||||
source = "Main",
|
||||
|
|
@ -440,12 +498,10 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onStakeClick(cryptoCurrencyStatus: CryptoCurrencyStatus, yield: Yield?) {
|
||||
val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return
|
||||
stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWallet.walletId))
|
||||
override fun onStakeClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus, yield: Yield?) {
|
||||
stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWalletId))
|
||||
|
||||
modelScope.launch {
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
|
||||
appRouter.push(
|
||||
|
|
@ -468,15 +524,19 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
|
||||
override fun onMultiWalletSwapClick(userWalletId: UserWalletId) {
|
||||
val selectedWallet = stateHolder.getSelectedWallet() as? WalletState.MultiCurrency.Content ?: return
|
||||
val tokenListState = selectedWallet.tokensListState as? WalletTokensListState.ContentState.Content ?: return
|
||||
val tokenListState = selectedWallet.tokensListState
|
||||
|
||||
if (tokenListState.items.count { it is TokensListItemUM.Token } < 2) {
|
||||
handleError(
|
||||
alertState = WalletAlertState.InsufficientTokensCountForSwapping,
|
||||
eventCreator = MainScreenAnalyticsEvent::ButtonSwap,
|
||||
when (tokenListState) {
|
||||
is WalletTokensListState.ContentState.Content -> checkSwapCryptoAvailability(
|
||||
tokenCount = tokenListState.items.count { it is TokensListItemUM.Token },
|
||||
)
|
||||
|
||||
return
|
||||
is WalletTokensListState.ContentState.PortfolioContent -> checkSwapCryptoAvailability(
|
||||
tokenCount = tokenListState.items.sumOf { it.tokens.count { it is TokensListItemUM.Token } },
|
||||
)
|
||||
WalletTokensListState.ContentState.Loading,
|
||||
WalletTokensListState.ContentState.Locked,
|
||||
WalletTokensListState.Empty,
|
||||
-> return
|
||||
}
|
||||
|
||||
modelScope.launch {
|
||||
|
|
@ -669,44 +729,69 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun configureReceiveAddresses(cryptoCurrencyStatus: CryptoCurrencyStatus): TokenReceiveConfig? {
|
||||
val networkAddress = cryptoCurrencyStatus.value.networkAddress ?: return null
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
|
||||
val ensName = getEnsNameUseCase.invoke(
|
||||
return receiveAddressesFactory.create(
|
||||
status = cryptoCurrencyStatus,
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
address = networkAddress.defaultAddress.value,
|
||||
)
|
||||
|
||||
val receiveAddresses = buildList {
|
||||
ensName?.let { ens ->
|
||||
add(
|
||||
ReceiveAddressModel(
|
||||
nameService = ReceiveAddressModel.NameService.Ens,
|
||||
value = ens,
|
||||
),
|
||||
)
|
||||
}
|
||||
networkAddress.availableAddresses.map { address ->
|
||||
add(
|
||||
ReceiveAddressModel(
|
||||
nameService = when (address.type) {
|
||||
NetworkAddress.Address.Type.Primary -> ReceiveAddressModel.NameService.Default
|
||||
NetworkAddress.Address.Type.Secondary -> ReceiveAddressModel.NameService.Legacy
|
||||
},
|
||||
value = address.value,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return TokenReceiveConfig(
|
||||
shouldShowWarning = cryptoCurrencyStatus.currency.name !in getViewedTokenReceiveWarningUseCase(),
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
userWalletId = userWalletId,
|
||||
showMemoDisclaimer = cryptoCurrencyStatus.currency.network.transactionExtrasType != Network
|
||||
.TransactionExtrasType.NONE,
|
||||
receiveAddress = receiveAddresses,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun needShowYieldSupplyWarning(cryptoCurrencyStatus: CryptoCurrencyStatus): Boolean {
|
||||
return yieldSupplyFeatureToggles.isYieldSupplyFeatureEnabled &&
|
||||
needShowYieldSupplyDepositedWarningUseCase(cryptoCurrencyStatus)
|
||||
}
|
||||
|
||||
private fun navigateToSend(cryptoCurrencyStatus: CryptoCurrencyStatus, userWalletId: UserWalletId) {
|
||||
stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWalletId))
|
||||
val route = AppRoute.Send(
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
userWalletId = userWalletId,
|
||||
)
|
||||
|
||||
appRouter.push(route)
|
||||
}
|
||||
|
||||
private fun navigateToSwap(cryptoCurrencyStatus: CryptoCurrencyStatus, userWalletId: UserWalletId) {
|
||||
appRouter.push(
|
||||
AppRoute.Swap(
|
||||
currencyFrom = cryptoCurrencyStatus.currency,
|
||||
userWalletId = userWalletId,
|
||||
screenSource = AnalyticsParam.ScreensSources.LongTap.value,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun navigateToReceive(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
if (tokenReceiveFeatureToggle.isNewTokenReceiveEnabled) {
|
||||
stateHolder.hideBottomSheet()
|
||||
modelScope.launch {
|
||||
configureReceiveAddresses(cryptoCurrencyStatus = cryptoCurrencyStatus)?.let {
|
||||
router.openTokenReceiveBottomSheet(it)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
analyticsEventHandler.send(
|
||||
event = TokenReceiveAnalyticsEvent.ReceiveScreenOpened(cryptoCurrencyStatus.currency.symbol),
|
||||
)
|
||||
stateHolder.showBottomSheet(
|
||||
createReceiveBottomSheetContent(
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
addresses = cryptoCurrencyStatus.value.networkAddress ?: return,
|
||||
),
|
||||
userWalletId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkSwapCryptoAvailability(tokenCount: Int) {
|
||||
if (tokenCount < 2) {
|
||||
handleError(
|
||||
alertState = WalletAlertState.InsufficientTokensCountForSwapping,
|
||||
eventCreator = MainScreenAnalyticsEvent::ButtonSwap,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,17 +2,19 @@ package com.tangem.feature.wallet.child.wallet.model.intents
|
|||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.TangemBlogUrlBuilder
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRoute.Onramp
|
||||
import com.tangem.common.routing.AppRoute.ReferralProgram
|
||||
import com.tangem.common.routing.AppRoute.*
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.ui.notifications.NotificationId
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
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.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
|
|
@ -21,6 +23,8 @@ import com.tangem.domain.models.wallet.UserWallet
|
|||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.requireColdWallet
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||
import com.tangem.domain.notifications.SetShouldShowNotificationUseCase
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import com.tangem.domain.onramp.model.OnrampSource
|
||||
import com.tangem.domain.promo.ShouldShowPromoWalletUseCase
|
||||
import com.tangem.domain.promo.models.PromoId
|
||||
|
|
@ -34,11 +38,10 @@ 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.DerivePublicKeysUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.SeedPhraseNotificationUseCase
|
||||
import com.tangem.domain.wallets.usecase.UnlockWalletsUseCase
|
||||
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
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.ScanCardToUnlockWalletClickHandler
|
||||
|
|
@ -50,6 +53,7 @@ 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
|
||||
import kotlinx.coroutines.awaitAll
|
||||
|
|
@ -58,6 +62,7 @@ import kotlinx.coroutines.launch
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
internal interface WalletWarningsClickIntents {
|
||||
|
||||
fun onAddBackupCardClick()
|
||||
|
|
@ -96,10 +101,14 @@ internal interface WalletWarningsClickIntents {
|
|||
|
||||
fun onSeedPhraseSecondNotificationReject()
|
||||
|
||||
fun onFinishWalletActivationClick()
|
||||
fun onAllowPermissions()
|
||||
|
||||
fun onDenyPermissions()
|
||||
|
||||
fun onFinishWalletActivationClick(type: WalletActivationBannerType)
|
||||
}
|
||||
|
||||
@Suppress("LargeClass", "LongParameterList")
|
||||
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
|
||||
@ModelScoped
|
||||
internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
||||
private val stateHolder: WalletStateController,
|
||||
|
|
@ -125,8 +134,39 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
|||
private val appRouter: AppRouter,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val setShouldShowNotificationUseCase: SetShouldShowNotificationUseCase,
|
||||
private val notificationsRepository: NotificationsRepository,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val setNotificationsEnabledUseCase: SetNotificationsEnabledUseCase,
|
||||
private val getWalletsListForEnablingUseCase: GetWalletsForAutomaticallyPushEnablingUseCase,
|
||||
) : BaseWalletClickIntents(), WalletWarningsClickIntents {
|
||||
|
||||
private val finalizeWalletSetupAlertBS
|
||||
get() = 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))
|
||||
closeBs()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAddBackupCardClick() {
|
||||
analyticsEventHandler.send(MainScreen.NoticeBackupYourWalletTapped)
|
||||
|
||||
|
|
@ -324,7 +364,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
|||
userWalletId = userWallet.walletId,
|
||||
currency = cryptoCurrency,
|
||||
source = OnrampSource.SEPA_BANNER,
|
||||
launchSepa = true,
|
||||
shouldLaunchSepa = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -426,9 +466,56 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onFinishWalletActivationClick() {
|
||||
val userWallet = getSelectedUserWallet() ?: return
|
||||
appRouter.push(AppRoute.WalletActivation(userWallet.walletId))
|
||||
override fun onFinishWalletActivationClick(type: WalletActivationBannerType) {
|
||||
when (type) {
|
||||
WalletActivationBannerType.Attention -> {
|
||||
val userWallet = getSelectedUserWallet() ?: return
|
||||
appRouter.push(WalletActivation(userWallet.walletId))
|
||||
}
|
||||
WalletActivationBannerType.Warning -> {
|
||||
messageSender.send(finalizeWalletSetupAlertBS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAllowPermissions() {
|
||||
analyticsEventHandler.send(WalletScreenAnalyticsEvent.PushBannerPromo.ButtonAllowPush)
|
||||
walletEventSender.send(
|
||||
event = WalletEvent.RequestPushPermissions(
|
||||
onAllow = {
|
||||
modelScope.launch {
|
||||
updateSubscribeOnPushPermissions(true)
|
||||
analyticsEventHandler.send(
|
||||
PushNotificationAnalyticEvents.PermissionStatus(isAllowed = true),
|
||||
)
|
||||
enableNotificationsIfNeeded()
|
||||
}
|
||||
},
|
||||
onDeny = {
|
||||
modelScope.launch {
|
||||
updateSubscribeOnPushPermissions(false)
|
||||
analyticsEventHandler.send(
|
||||
PushNotificationAnalyticEvents.PermissionStatus(isAllowed = false),
|
||||
)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDenyPermissions() {
|
||||
modelScope.launch {
|
||||
analyticsEventHandler.send(WalletScreenAnalyticsEvent.PushBannerPromo.ButtonLaterPush)
|
||||
updateSubscribeOnPushPermissions(false)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateSubscribeOnPushPermissions(shouldAllow: Boolean) {
|
||||
notificationsRepository.setUserAllowToSubscribeOnPushNotifications(shouldAllow)
|
||||
setShouldShowNotificationUseCase(
|
||||
key = NotificationId.EnablePushesReminderNotification.key,
|
||||
value = false,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun fetchCryptoCurrencies(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
|
|
@ -482,6 +569,20 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun enableNotificationsIfNeeded() {
|
||||
val alreadyEnabledWallets = notificationsRepository.getWalletAutomaticallyEnabledList().map {
|
||||
UserWalletId(it)
|
||||
}
|
||||
val walletsListWhichShouldBeEnabled = getWalletsListForEnablingUseCase(alreadyEnabledWallets)
|
||||
walletsListWhichShouldBeEnabled.forEach { userWalletId ->
|
||||
setNotificationsEnabledUseCase(userWalletId, true).onRight {
|
||||
notificationsRepository.setNotificationsWasEnabledAutomatically(userWalletId.stringValue)
|
||||
}.onLeft {
|
||||
Timber.e(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val VISA_PROMO_LINK = "https://tangem.com/en/cardwaitlist/?utm_source=tangem-app-banner" +
|
||||
"&utm_medium=banner" +
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.feature.wallet.presentation.account
|
||||
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class AccountDependencies @Inject constructor(
|
||||
val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
|
||||
val expandedAccountsHolder: ExpandedAccountsHolder,
|
||||
val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
)
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package com.tangem.feature.wallet.presentation.account
|
||||
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.producer.SingleAccountListProducer
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.coroutines.flow.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class ExpandedAccountsHolder @Inject constructor(
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier,
|
||||
) {
|
||||
|
||||
private val expandedAccounts = MutableStateFlow<Map<UserWalletId, Set<AccountId>>>(mapOf())
|
||||
|
||||
fun expandedAccounts(userWallet: UserWallet): Flow<Set<AccountId>> = channelFlow {
|
||||
walletAccounts(userWallet)
|
||||
.onEach { accountList ->
|
||||
val isSingleAccount = accountList.totalAccounts == 1
|
||||
val defaultExpanded = when {
|
||||
isSingleAccount -> setOf(accountList.mainAccount.accountId)
|
||||
else -> setOf()
|
||||
}
|
||||
expandedAccounts.update { map ->
|
||||
var expandedSet = map[userWallet.walletId] ?: defaultExpanded
|
||||
// force expand for single account
|
||||
if (isSingleAccount) expandedSet = defaultExpanded
|
||||
map.plus(userWallet.walletId to expandedSet)
|
||||
}
|
||||
}.launchIn(this)
|
||||
|
||||
expandedAccounts
|
||||
.mapNotNull { map -> map[userWallet.walletId] }
|
||||
.onEach { expanded -> channel.send(expanded) }
|
||||
.collect()
|
||||
}
|
||||
|
||||
fun expandAccount(userWalletId: UserWalletId, accountId: AccountId) = expandedAccounts.update { map ->
|
||||
val expandedSet = map[userWalletId]?.plus(accountId) ?: return@update map
|
||||
map.plus(userWalletId to expandedSet)
|
||||
}
|
||||
|
||||
fun collapseAccount(userWalletId: UserWalletId, accountId: AccountId) = expandedAccounts.update { map ->
|
||||
val expandedSet = map[userWalletId]?.minus(accountId) ?: return@update map
|
||||
map.plus(userWalletId to expandedSet)
|
||||
}
|
||||
|
||||
private fun walletAccounts(userWallet: UserWallet): Flow<AccountList> =
|
||||
singleAccountListSupplier(SingleAccountListProducer.Params(userWallet.walletId))
|
||||
}
|
||||
|
|
@ -79,7 +79,7 @@ internal object WalletPreviewData {
|
|||
fallbackTint = TangemColorPalette.Black,
|
||||
fallbackBackground = TangemColorPalette.Meadow,
|
||||
isGrayscale = false,
|
||||
showCustomBadge = false,
|
||||
shouldShowCustomBadge = false,
|
||||
),
|
||||
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
|
||||
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "3 172,14 $"),
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import com.tangem.feature.wallet.presentation.common.WalletPreviewData.topBarCon
|
|||
import com.tangem.feature.wallet.presentation.wallet.state.model.*
|
||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal object WalletScreenPreviewData {
|
||||
private val tokenItemState = TokenItemState.Content(
|
||||
|
|
@ -86,15 +87,17 @@ internal object WalletScreenPreviewData {
|
|||
private val portfolioContentState = WalletTokensListState.ContentState.PortfolioContent(
|
||||
items = persistentListOf(
|
||||
TokensListItemUM.Portfolio(
|
||||
tokens = textContentTokensState.items.filterIsInstance<PortfolioTokensListItemUM>(),
|
||||
tokens = textContentTokensState.items.filterIsInstance<PortfolioTokensListItemUM>().toPersistentList(),
|
||||
isExpanded = false,
|
||||
state = AccountItemPreviewData.accountItem
|
||||
isCollapsable = true,
|
||||
tokenItemUM = AccountItemPreviewData.accountItem
|
||||
.copy(iconState = AccountItemPreviewData.accountLetterIcon),
|
||||
),
|
||||
TokensListItemUM.Portfolio(
|
||||
tokens = textContentTokensState.items.filterIsInstance<PortfolioTokensListItemUM>(),
|
||||
tokens = textContentTokensState.items.filterIsInstance<PortfolioTokensListItemUM>().toPersistentList(),
|
||||
isExpanded = true,
|
||||
state = AccountItemPreviewData.accountItem,
|
||||
isCollapsable = true,
|
||||
tokenItemUM = AccountItemPreviewData.accountItem,
|
||||
),
|
||||
),
|
||||
organizeTokensButtonConfig = WalletTokensListState.OrganizeTokensButtonConfig(
|
||||
|
|
@ -159,6 +162,7 @@ internal object WalletScreenPreviewData {
|
|||
isFlickering = false,
|
||||
onItemClick = { },
|
||||
),
|
||||
type = WalletState.MultiCurrency.WalletType.Cold,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -123,6 +123,14 @@ private fun TokenList(
|
|||
end = TangemTheme.dimens.spacing16,
|
||||
)
|
||||
|
||||
// workaround to force recomposition of list items when the list is updated
|
||||
// because sometimes items disappear after reordering in 1.7.4+ compose-runtime version
|
||||
// check removing after update to compose-runtime 1.8.0+
|
||||
val forceRecompose = remember { mutableIntStateOf(0) }
|
||||
LaunchedEffect(state.items) {
|
||||
forceRecompose.intValue++
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopCenter)
|
||||
|
|
@ -143,13 +151,15 @@ private fun TokenList(
|
|||
}
|
||||
}
|
||||
|
||||
DraggableItem(
|
||||
index = index,
|
||||
item = item,
|
||||
reorderableState = reorderableListState,
|
||||
onDragStart = onDragStart,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
key(forceRecompose.intValue) {
|
||||
DraggableItem(
|
||||
index = index,
|
||||
item = item,
|
||||
reorderableState = reorderableListState,
|
||||
onDragStart = onDragStart,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -260,9 +270,9 @@ private fun TopBar(
|
|||
config = ActionButtonConfig(
|
||||
text = TextReference.Res(id = R.string.organize_tokens_sort_by_balance),
|
||||
iconResId = R.drawable.ic_sort_24,
|
||||
enabled = config.isEnabled,
|
||||
isEnabled = config.isEnabled,
|
||||
onClick = config.onSortClick,
|
||||
dimContent = config.isSortedByBalance,
|
||||
shouldDimContent = config.isSortedByBalance,
|
||||
),
|
||||
color = TangemTheme.colors.background.primary,
|
||||
)
|
||||
|
|
@ -279,7 +289,7 @@ private fun TopBar(
|
|||
},
|
||||
),
|
||||
iconResId = R.drawable.ic_group_24,
|
||||
enabled = config.isEnabled,
|
||||
isEnabled = config.isEnabled,
|
||||
onClick = config.onGroupClick,
|
||||
),
|
||||
color = TangemTheme.colors.background.primary,
|
||||
|
|
|
|||
|
|
@ -3,17 +3,19 @@ package com.tangem.feature.wallet.presentation.router
|
|||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRoute.ManageTokens.Source
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.domain.models.TokenReceiveConfig
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.TangemPayDetailsConfig
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
import com.tangem.domain.tokens.model.details.TokenAction
|
||||
import com.tangem.feature.wallet.navigation.WalletRoute
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDialogConfig
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
|
|
@ -84,10 +86,6 @@ internal class DefaultWalletRouter @Inject constructor(
|
|||
return router.stack.lastOrNull() is AppRoute.Wallet
|
||||
}
|
||||
|
||||
override fun openManageTokensScreen(userWalletId: UserWalletId) {
|
||||
router.push(AppRoute.ManageTokens(Source.SETTINGS, userWalletId))
|
||||
}
|
||||
|
||||
override fun openScanFailedDialog(onTryAgain: () -> Unit) {
|
||||
reduxStateHolder.dispatchDialogShow(StateDialog.ScanFailsDialog(StateDialog.ScanFailsSource.MAIN, onTryAgain))
|
||||
}
|
||||
|
|
@ -106,4 +104,26 @@ internal class DefaultWalletRouter @Inject constructor(
|
|||
configuration = WalletDialogConfig.TokenReceive(tokenReceiveConfig),
|
||||
)
|
||||
}
|
||||
|
||||
override fun openTangemPayOnboarding() {
|
||||
router.push(AppRoute.TangemPayOnboarding(AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding))
|
||||
}
|
||||
|
||||
override fun openTangemPayDetails(config: TangemPayDetailsConfig) {
|
||||
router.push(AppRoute.TangemPayDetails(config))
|
||||
}
|
||||
|
||||
override fun openYieldSupplyBottomSheet(
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
tokenAction: TokenAction,
|
||||
onWarningAcknowledged: (TokenAction) -> Unit,
|
||||
) {
|
||||
dialogNavigation.activate(
|
||||
configuration = WalletDialogConfig.YieldSupplyWarning(
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
tokenAction = tokenAction,
|
||||
onWarningAcknowledged = onWarningAcknowledged,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,10 +3,13 @@ package com.tangem.feature.wallet.presentation.router
|
|||
import androidx.compose.runtime.Stable
|
||||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.tangem.domain.models.TokenReceiveConfig
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.TangemPayDetailsConfig
|
||||
import com.tangem.domain.tokens.model.details.TokenAction
|
||||
import com.tangem.feature.wallet.navigation.WalletRoute
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDialogConfig
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
|
|
@ -47,9 +50,6 @@ internal interface InnerWalletRouter {
|
|||
/** Is wallet last screen */
|
||||
fun isWalletLastScreen(): Boolean
|
||||
|
||||
/** Open manage tokens screen */
|
||||
fun openManageTokensScreen(userWalletId: UserWalletId)
|
||||
|
||||
/** Open scan failed dialog */
|
||||
fun openScanFailedDialog(onTryAgain: () -> Unit)
|
||||
|
||||
|
|
@ -57,4 +57,15 @@ internal interface InnerWalletRouter {
|
|||
fun openNFT(userWallet: UserWallet)
|
||||
|
||||
fun openTokenReceiveBottomSheet(tokenReceiveConfig: TokenReceiveConfig)
|
||||
|
||||
fun openTangemPayOnboarding()
|
||||
|
||||
fun openTangemPayDetails(config: TangemPayDetailsConfig)
|
||||
|
||||
/** Open BS abput yield supply active and all money deposited in AAVE */
|
||||
fun openYieldSupplyBottomSheet(
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
tokenAction: TokenAction,
|
||||
onWarningAcknowledged: (TokenAction) -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -73,6 +73,13 @@ sealed class WalletScreenAnalyticsEvent {
|
|||
|
||||
data object ScreenOpened : MainScreen(event = "Screen opened")
|
||||
|
||||
class WalletSelected(val isImported: Boolean) : MainScreen(
|
||||
event = "Wallet Selected",
|
||||
params = mapOf(
|
||||
"Wallet Type" to if (isImported) "Seed Phrase" else "Seedless",
|
||||
),
|
||||
)
|
||||
|
||||
class EnableBiometrics(state: AnalyticsParam.OnOffState) : MainScreen(
|
||||
event = "Enable Biometric",
|
||||
params = mapOf("State" to state.value),
|
||||
|
|
@ -144,4 +151,14 @@ sealed class WalletScreenAnalyticsEvent {
|
|||
data object ReferralPromoButtonDismiss : MainScreen(event = "Button - Referral Dismiss")
|
||||
//endregion
|
||||
}
|
||||
|
||||
sealed class PushBannerPromo(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent(category = "Promo", event = event, params = params) {
|
||||
|
||||
data object PushBanner : PushBannerPromo(event = "Push Banner")
|
||||
data object ButtonAllowPush : PushBannerPromo(event = "Button - Allow Push")
|
||||
data object ButtonLaterPush : PushBannerPromo(event = "Button - Later Push")
|
||||
}
|
||||
}
|
||||
|
|
@ -29,6 +29,14 @@ internal class SelectedWalletAnalyticsSender @Inject constructor(
|
|||
* */
|
||||
private fun getEvent(userWallet: UserWallet): AnalyticsEvent? = when {
|
||||
userWallet.isLocked -> WalletScreenAnalyticsEvent.MainScreen.WalletUnlock
|
||||
else -> null
|
||||
|
||||
else -> WalletScreenAnalyticsEvent.MainScreen.WalletSelected(userWallet.isImported())
|
||||
}
|
||||
|
||||
private fun UserWallet.isImported(): Boolean {
|
||||
return when (this) {
|
||||
is UserWallet.Cold -> isImported
|
||||
is UserWallet.Hot -> true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,6 @@ import com.tangem.domain.analytics.model.WalletBalanceState
|
|||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
|
|
@ -38,33 +37,41 @@ internal class TokenListAnalyticsSender @Inject constructor(
|
|||
private val mutex = Mutex()
|
||||
private val loadingTraces = mutableMapOf<UserWalletId, Trace>()
|
||||
|
||||
suspend fun send(displayedUiState: WalletState?, userWallet: UserWallet, tokenList: TokenList) {
|
||||
suspend fun send(
|
||||
displayedUiState: WalletState?,
|
||||
userWallet: UserWallet,
|
||||
totalFiatBalance: TotalFiatBalance,
|
||||
flattenCurrencies: List<CryptoCurrencyStatus>,
|
||||
) {
|
||||
if (screenLifecycleProvider.isBackgroundState.value) return
|
||||
if (displayedUiState == null || displayedUiState.pullToRefreshConfig.isRefreshing) return
|
||||
|
||||
if (tokenList.totalFiatBalance is TotalFiatBalance.Loading) {
|
||||
startLoadingTraceIfNeeded(userWallet.walletId, tokenList)
|
||||
if (totalFiatBalance is TotalFiatBalance.Loading) {
|
||||
startLoadingTraceIfNeeded(userWallet.walletId, flattenCurrencies)
|
||||
return
|
||||
}
|
||||
|
||||
if (isTerminalState(tokenList.totalFiatBalance)) {
|
||||
stopLoadingTraceIfNeeded(userWallet.walletId, tokenList.totalFiatBalance)
|
||||
if (isTerminalState(totalFiatBalance)) {
|
||||
stopLoadingTraceIfNeeded(userWallet.walletId, totalFiatBalance)
|
||||
}
|
||||
|
||||
val currenciesStatuses = tokenList.flattenCurrencies()
|
||||
val currenciesStatuses = flattenCurrencies
|
||||
|
||||
sendBalanceLoadedEventIfNeeded(tokenList.totalFiatBalance, currenciesStatuses)
|
||||
sendToppedUpEventIfNeeded(userWallet, tokenList.totalFiatBalance, currenciesStatuses)
|
||||
sendBalanceLoadedEventIfNeeded(totalFiatBalance, currenciesStatuses)
|
||||
sendToppedUpEventIfNeeded(userWallet, totalFiatBalance, currenciesStatuses)
|
||||
sendUnreachableNetworksEventIfNeeded(currenciesStatuses)
|
||||
sendTokenBalancesIfNeeded(currenciesStatuses)
|
||||
}
|
||||
|
||||
private suspend fun startLoadingTraceIfNeeded(userWalletId: UserWalletId, tokenList: TokenList) {
|
||||
private suspend fun startLoadingTraceIfNeeded(
|
||||
userWalletId: UserWalletId,
|
||||
flattenCurrencies: List<CryptoCurrencyStatus>,
|
||||
) {
|
||||
mutex.withLock {
|
||||
if (!loadingTraces.containsKey(userWalletId)) {
|
||||
val trace = FirebasePerformance.getInstance().newTrace(BALANCE_LOADED_TRACE_NAME)
|
||||
trace.start()
|
||||
trace.putAttribute(TOKENS_COUNT, tokenList.flattenCurrencies().size.toString())
|
||||
trace.putAttribute(TOKENS_COUNT, flattenCurrencies.size.toString())
|
||||
loadingTraces[userWalletId] = trace
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.core.analytics.models.AnalyticsParam
|
|||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent
|
||||
import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent.Program
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||
|
|
@ -67,9 +68,11 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
|
|||
is WalletNotification.UsedOutdatedData,
|
||||
is WalletNotification.UnlockVisaAccess,
|
||||
is WalletNotification.FinishWalletActivation,
|
||||
is WalletNotification.Warning.YeildSupplyApprove, // TODO apply correct event
|
||||
-> null
|
||||
is WalletNotification.Critical.SeedPhraseNotification -> MainScreen.NoticeSeedPhraseSupport
|
||||
is WalletNotification.Critical.SeedPhraseSecondNotification -> MainScreen.NoticeSeedPhraseSupportSecond
|
||||
is WalletNotification.PushNotifications -> WalletScreenAnalyticsEvent.PushBannerPromo.PushBanner
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,21 +3,24 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.domain
|
||||
|
||||
import arrow.core.getOrElse
|
||||
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
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import com.tangem.domain.onramp.GetOnrampCountryUseCase
|
||||
import com.tangem.domain.onramp.OnrampSepaAvailableUseCase
|
||||
import com.tangem.domain.promo.ShouldShowPromoWalletUseCase
|
||||
|
|
@ -28,19 +31,24 @@ import com.tangem.domain.tokens.error.TokenListError
|
|||
import com.tangem.domain.wallets.models.SeedPhraseNotificationsStatus
|
||||
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
|
||||
import com.tangem.domain.wallets.usecase.SeedPhraseNotificationUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.WalletActivationBannerType
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.account.AccountDependencies
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
|
||||
import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isBitcoin
|
||||
import com.tangem.utils.coroutines.combine6
|
||||
import com.tangem.utils.extensions.addIf
|
||||
import com.tangem.utils.extensions.isPositive
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
@ModelScoped
|
||||
internal class GetMultiWalletWarningsFactory @Inject constructor(
|
||||
private val tokenListStore: MultiWalletTokenListStore,
|
||||
|
|
@ -53,56 +61,103 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
private val getCryptoCurrenciesUseCase: GetCryptoCurrenciesUseCase,
|
||||
private val onrampSepaAvailableUseCase: OnrampSepaAvailableUseCase,
|
||||
private val getOnrampCountryUseCase: GetOnrampCountryUseCase,
|
||||
private val notificationsRepository: NotificationsRepository,
|
||||
private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
) {
|
||||
|
||||
@Suppress("UNCHECKED_CAST", "MagicNumber")
|
||||
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow<ImmutableList<WalletNotification>> {
|
||||
val cardTypesResolver = (userWallet as? UserWallet.Cold)?.scanResponse?.cardTypesResolver
|
||||
|
||||
return combine6(
|
||||
flow1 = tokenListStore.getOrThrow(userWallet.walletId),
|
||||
flow2 = isReadyToShowRateAppUseCase(),
|
||||
flow3 = isNeedToBackupUseCase(userWallet.walletId),
|
||||
flow4 = seedPhraseNotificationUseCase(userWalletId = userWallet.walletId),
|
||||
flow5 = shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.VisaPresale),
|
||||
flow6 = shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.Sepa),
|
||||
) { maybeTokenList, isReadyToShowRating, isNeedToBackup, seedPhraseIssueStatus, shouldShowVisaPresalePromo, shouldShowSepaBanner ->
|
||||
buildList {
|
||||
addUsedOutdatedDataNotification(maybeTokenList)
|
||||
|
||||
addCriticalNotifications(userWallet, seedPhraseIssueStatus, clickIntents)
|
||||
|
||||
addFinishWalletActivationNotification(userWallet, maybeTokenList, clickIntents)
|
||||
|
||||
addVisaPresalePromoNotification(clickIntents, shouldShowVisaPresalePromo)
|
||||
|
||||
addSepaPromoNotification(userWallet, clickIntents, shouldShowSepaBanner)
|
||||
|
||||
addInformationalNotifications(userWallet, cardTypesResolver, maybeTokenList, clickIntents)
|
||||
|
||||
addWarningNotifications(cardTypesResolver, maybeTokenList, isNeedToBackup, clickIntents)
|
||||
|
||||
val hasCriticalOrWarning = any { notification ->
|
||||
notification is WalletNotification.Critical || notification is WalletNotification.Warning
|
||||
}
|
||||
|
||||
if (!hasCriticalOrWarning) {
|
||||
addRateTheAppNotification(isReadyToShowRating, clickIntents)
|
||||
}
|
||||
}.toImmutableList()
|
||||
val accountStatusList by lazy {
|
||||
val params = SingleAccountStatusListProducer.Params(userWallet.walletId)
|
||||
accountDependencies.singleAccountStatusListSupplier(params)
|
||||
.map { it.totalFiatBalance to it.flattenCurrencies() }
|
||||
.map { Lce.Content(it) }
|
||||
}
|
||||
|
||||
fun tokenListFlow(): LceFlow<TokenListError, Pair<TotalFiatBalance, List<CryptoCurrencyStatus>>> {
|
||||
return if (accountDependencies.accountsFeatureToggles.isFeatureEnabled) {
|
||||
accountStatusList
|
||||
} else {
|
||||
runCatching { tokenListStore.getOrThrow(userWallet.walletId) }
|
||||
.map { result -> result.map { lce -> lce.map { it.totalFiatBalance to it.flattenCurrencies() } } }
|
||||
.getOrNull()
|
||||
// in case of runtime change ft in tester menu
|
||||
?: accountStatusList
|
||||
}
|
||||
}
|
||||
|
||||
// val params = SingleAccountStatusListProducer.Params(userWallet.walletId)
|
||||
// val accountStatusListFlow = accountDependencies.singleAccountStatusListSupplier(params)
|
||||
return combine(
|
||||
// todo account just use it, after delete accountsFeatureToggles
|
||||
// accountStatusListFlow,
|
||||
isReadyToShowRateAppUseCase(),
|
||||
isNeedToBackupUseCase(userWallet.walletId),
|
||||
seedPhraseNotificationUseCase(userWalletId = userWallet.walletId),
|
||||
shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.VisaPresale),
|
||||
shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.Sepa),
|
||||
notificationsRepository.getShouldShowNotification(NotificationId.EnablePushesReminderNotification.key),
|
||||
) { array -> array }
|
||||
.combine(tokenListFlow()) { array, any: Any -> arrayOf(any).plus(elements = array) }
|
||||
.map { array ->
|
||||
val lceTokens = array[0] as Lce<TokenListError, Pair<TotalFiatBalance, List<CryptoCurrencyStatus>>>
|
||||
val totalFiatBalance = lceTokens.map { it.first }
|
||||
val flattenCurrencies = lceTokens.map { it.second }
|
||||
val isReadyToShowRating = array[1] as Boolean
|
||||
val isNeedToBackup = array[2] as Boolean
|
||||
val seedPhraseIssueStatus = array[3] as SeedPhraseNotificationsStatus
|
||||
val shouldShowVisaPromo = array[4] as Boolean
|
||||
val shouldShowSepaBanner = array[5] as Boolean
|
||||
val shouldShowEnablePushesReminderNotification = array[6] as Boolean
|
||||
|
||||
buildList {
|
||||
addUsedOutdatedDataNotification(totalFiatBalance)
|
||||
|
||||
addCriticalNotifications(userWallet, seedPhraseIssueStatus, clickIntents)
|
||||
|
||||
addFinishWalletActivationNotification(userWallet, totalFiatBalance, clickIntents)
|
||||
|
||||
addVisaPresalePromoNotification(clickIntents, shouldShowVisaPromo)
|
||||
|
||||
addSepaPromoNotification(userWallet, clickIntents, shouldShowSepaBanner)
|
||||
|
||||
addInformationalNotifications(userWallet, cardTypesResolver, flattenCurrencies, clickIntents)
|
||||
|
||||
addWarningNotifications(cardTypesResolver, flattenCurrencies, isNeedToBackup, clickIntents)
|
||||
|
||||
addPushReminderNotification(
|
||||
clickIntents = clickIntents,
|
||||
shouldShowPushReminderBanner = shouldShowEnablePushesReminderNotification &&
|
||||
!notificationsRepository.isUserAllowToSubscribeOnPushNotifications(),
|
||||
)
|
||||
|
||||
addYieldSupplyNotifications(flattenCurrencies)
|
||||
|
||||
val hasCriticalOrWarning = any { notification ->
|
||||
notification is WalletNotification.Critical || notification is WalletNotification.Warning
|
||||
}
|
||||
|
||||
if (!hasCriticalOrWarning) {
|
||||
addRateTheAppNotification(isReadyToShowRating, clickIntents)
|
||||
}
|
||||
}.toImmutableList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addUsedOutdatedDataNotification(
|
||||
maybeTokenList: Lce<TokenListError, TokenList>,
|
||||
totalFiatBalance: Lce<TokenListError, TotalFiatBalance>,
|
||||
) {
|
||||
addIf(
|
||||
element = WalletNotification.UsedOutdatedData,
|
||||
condition = maybeTokenList.fold(
|
||||
condition = totalFiatBalance.fold(
|
||||
ifLoading = {
|
||||
(it?.totalFiatBalance as? TotalFiatBalance.Loaded)?.source == StatusSource.ONLY_CACHE
|
||||
(it as? TotalFiatBalance.Loaded)?.source == StatusSource.ONLY_CACHE
|
||||
},
|
||||
ifContent = {
|
||||
(it.totalFiatBalance as? TotalFiatBalance.Loaded)?.source == StatusSource.ONLY_CACHE
|
||||
(it as? TotalFiatBalance.Loaded)?.source == StatusSource.ONLY_CACHE
|
||||
},
|
||||
ifError = { false },
|
||||
),
|
||||
|
|
@ -180,7 +235,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
private fun MutableList<WalletNotification>.addInformationalNotifications(
|
||||
userWallet: UserWallet,
|
||||
cardTypesResolver: CardTypesResolver?,
|
||||
maybeTokenList: Lce<TokenListError, TokenList>,
|
||||
flattenCurrencies: Lce<TokenListError, List<CryptoCurrencyStatus>>,
|
||||
clickIntents: WalletClickIntents,
|
||||
) {
|
||||
addIf(
|
||||
|
|
@ -188,15 +243,15 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
condition = cardTypesResolver != null && isDemoCardUseCase(cardId = cardTypesResolver.getCardId()),
|
||||
)
|
||||
|
||||
addMissingAddressesNotification(userWallet, maybeTokenList, clickIntents)
|
||||
addMissingAddressesNotification(userWallet, flattenCurrencies, clickIntents)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addMissingAddressesNotification(
|
||||
userWallet: UserWallet,
|
||||
maybeTokenList: Lce<TokenListError, TokenList>,
|
||||
flattenCurrencies: Lce<TokenListError, List<CryptoCurrencyStatus>>,
|
||||
clickIntents: WalletClickIntents,
|
||||
) {
|
||||
val currencies = maybeTokenList.getMissingAddressCurrencies()
|
||||
val currencies = flattenCurrencies.getMissingAddressCurrencies()
|
||||
.ifEmpty { return }
|
||||
|
||||
addIf(
|
||||
|
|
@ -211,11 +266,10 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun Lce<TokenListError, TokenList>.getMissingAddressCurrencies(): List<CryptoCurrency> {
|
||||
val tokenList = getOrNull(isPartialContentAccepted = true) ?: return emptyList()
|
||||
private fun Lce<TokenListError, List<CryptoCurrencyStatus>>.getMissingAddressCurrencies(): List<CryptoCurrency> {
|
||||
val flattenCurrencies = getOrNull(isPartialContentAccepted = true) ?: return emptyList()
|
||||
|
||||
return tokenList
|
||||
.flattenCurrencies()
|
||||
return flattenCurrencies
|
||||
.filter { it.value is CryptoCurrencyStatus.MissedDerivation }
|
||||
.map(CryptoCurrencyStatus::currency)
|
||||
}
|
||||
|
|
@ -265,9 +319,18 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addYieldSupplyNotifications(
|
||||
flattenCurrencies: Lce<TokenListError, List<CryptoCurrencyStatus>>,
|
||||
) {
|
||||
addIf(
|
||||
element = WalletNotification.Warning.YeildSupplyApprove,
|
||||
condition = flattenCurrencies.hasTokensWithActivatedSupplyWithoutApprove(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addWarningNotifications(
|
||||
cardTypesResolver: CardTypesResolver?,
|
||||
tokenList: Lce<TokenListError, TokenList>,
|
||||
flattenCurrencies: Lce<TokenListError, List<CryptoCurrencyStatus>>,
|
||||
isNeedToBackup: Boolean,
|
||||
clickIntents: WalletClickIntents,
|
||||
) {
|
||||
|
|
@ -285,14 +348,35 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
|
||||
addIf(
|
||||
element = WalletNotification.Warning.SomeNetworksUnreachable,
|
||||
condition = tokenList.hasUnreachableNetworks(),
|
||||
condition = flattenCurrencies.hasUnreachableNetworks(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun Lce<TokenListError, TokenList>.hasUnreachableNetworks(): Boolean {
|
||||
val tokenList = getOrNull(isPartialContentAccepted = false) ?: return false
|
||||
private fun MutableList<WalletNotification>.addPushReminderNotification(
|
||||
clickIntents: WalletClickIntents,
|
||||
shouldShowPushReminderBanner: Boolean,
|
||||
) {
|
||||
addIf(
|
||||
element = WalletNotification.PushNotifications(
|
||||
onCloseClick = clickIntents::onDenyPermissions,
|
||||
onEnabledClick = clickIntents::onAllowPermissions,
|
||||
),
|
||||
condition = shouldShowPushReminderBanner,
|
||||
)
|
||||
}
|
||||
|
||||
return tokenList.flattenCurrencies().any { it.value is CryptoCurrencyStatus.Unreachable }
|
||||
private fun Lce<TokenListError, List<CryptoCurrencyStatus>>.hasUnreachableNetworks(): Boolean {
|
||||
val flattenCurrencies = getOrNull(isPartialContentAccepted = false) ?: return false
|
||||
|
||||
return flattenCurrencies.any { it.value is CryptoCurrencyStatus.Unreachable }
|
||||
}
|
||||
|
||||
private fun Lce<TokenListError, List<CryptoCurrencyStatus>>.hasTokensWithActivatedSupplyWithoutApprove(): Boolean {
|
||||
val flattenCurrencies = getOrNull(isPartialContentAccepted = false) ?: return false
|
||||
val yieldSupplyEnabled = yieldSupplyFeatureToggles.isYieldSupplyFeatureEnabled
|
||||
return yieldSupplyEnabled && flattenCurrencies.any {
|
||||
it.value.yieldSupplyStatus?.isAllowedToSpend == false
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addRateTheAppNotification(
|
||||
|
|
@ -309,44 +393,45 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun TotalFiatBalance?.getFinishWalletActivationType(): WalletActivationBannerType {
|
||||
return if ((this as? TotalFiatBalance.Loaded)?.amount?.isPositive() == true) {
|
||||
WalletActivationBannerType.Warning
|
||||
} else {
|
||||
WalletActivationBannerType.Attention
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addFinishWalletActivationNotification(
|
||||
userWallet: UserWallet,
|
||||
maybeTokenList: Lce<TokenListError, TokenList>,
|
||||
totalFiatBalance: Lce<TokenListError, TotalFiatBalance>,
|
||||
clickIntents: WalletClickIntents,
|
||||
) {
|
||||
if (userWallet !is UserWallet.Hot) return
|
||||
|
||||
val shouldShowFinishActivation = !userWallet.backedUp
|
||||
|
||||
val iconTint = maybeTokenList.fold(
|
||||
ifLoading = {
|
||||
if ((it?.totalFiatBalance as? TotalFiatBalance.Loaded)?.amount?.isPositive() == true) {
|
||||
IconTint.Warning
|
||||
} else {
|
||||
IconTint.Attention
|
||||
}
|
||||
},
|
||||
ifContent = {
|
||||
if ((it.totalFiatBalance as? TotalFiatBalance.Loaded)?.amount?.isPositive() == true) {
|
||||
IconTint.Warning
|
||||
} else {
|
||||
IconTint.Attention
|
||||
}
|
||||
},
|
||||
ifError = { IconTint.Attention },
|
||||
val type = totalFiatBalance.fold(
|
||||
ifLoading = { it.getFinishWalletActivationType() },
|
||||
ifContent = { it.getFinishWalletActivationType() },
|
||||
ifError = { WalletActivationBannerType.Attention },
|
||||
)
|
||||
|
||||
val tint = when (type) {
|
||||
WalletActivationBannerType.Attention -> IconTint.Attention
|
||||
WalletActivationBannerType.Warning -> IconTint.Warning
|
||||
}
|
||||
|
||||
addIf(
|
||||
element = WalletNotification.FinishWalletActivation(
|
||||
iconTint = iconTint,
|
||||
buttonsState = when (iconTint) {
|
||||
IconTint.Warning -> ButtonsState.PrimaryButtonConfig(
|
||||
iconTint = tint,
|
||||
buttonsState = when (type) {
|
||||
WalletActivationBannerType.Warning -> ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.hw_activation_need_finish),
|
||||
onClick = clickIntents::onFinishWalletActivationClick,
|
||||
onClick = { clickIntents.onFinishWalletActivationClick(type) },
|
||||
)
|
||||
else -> ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.hw_activation_need_finish),
|
||||
onClick = clickIntents::onFinishWalletActivationClick,
|
||||
onClick = { clickIntents.onFinishWalletActivationClick(type) },
|
||||
)
|
||||
},
|
||||
),
|
||||
|
|
@ -354,10 +439,6 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addIf(element: WalletNotification, condition: Boolean) {
|
||||
if (condition) add(element = element)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val MAX_REMAINING_SIGNATURES_COUNT = 10
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
|||
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
|
||||
import com.tangem.utils.extensions.addIf
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
|
@ -204,14 +205,15 @@ internal class GetSingleWalletWarningsFactory @Inject constructor(
|
|||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addIf(element: WalletNotification, condition: Boolean) {
|
||||
if (condition) {
|
||||
add(element = element)
|
||||
addIf(condition) {
|
||||
if (element is WalletNotification.Critical ||
|
||||
element is WalletNotification.Warning ||
|
||||
element is WalletNotification.NoteMigration
|
||||
) {
|
||||
readyForRateAppNotification = false
|
||||
}
|
||||
|
||||
element
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -351,6 +351,12 @@ internal enum class Wallet2CobrandImage(
|
|||
batchIds = setOf("AF85", "AF86", "AF87", "AF990013", "AF990012", "AF990011"),
|
||||
),
|
||||
|
||||
WinterSakura(
|
||||
cards2ResId = R.drawable.ill_winter_sakura_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_winter_sakura_card3_120_106,
|
||||
batchIds = setOf("AF990053", "AF990054", "AF990055"),
|
||||
),
|
||||
|
||||
LockedMoney(
|
||||
cards2ResId = R.drawable.ill_locked_money_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_locked_money_card3_120_106,
|
||||
|
|
|
|||
|
|
@ -99,7 +99,10 @@ internal object WalletAdditionalInfoFactory {
|
|||
TextReference.EMPTY
|
||||
}
|
||||
|
||||
return WalletAdditionalInfo(hideable = false, content = content)
|
||||
return WalletAdditionalInfo(
|
||||
hideable = false,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getBackupInfoTextReference(count: Int): TextReference {
|
||||
|
|
@ -112,12 +115,18 @@ internal object WalletAdditionalInfoFactory {
|
|||
|
||||
private fun UserWallet.Cold.resolveSingleCurrencyInfo(currencyAmount: BigDecimal?): WalletAdditionalInfo {
|
||||
return if (isLocked) {
|
||||
WalletAdditionalInfo(hideable = false, content = TextReference.Res(R.string.common_locked))
|
||||
WalletAdditionalInfo(
|
||||
hideable = false,
|
||||
content = TextReference.Res(R.string.common_locked),
|
||||
)
|
||||
} else {
|
||||
val blockchain = scanResponse.cardTypesResolver.getBlockchain()
|
||||
val amount = currencyAmount?.format { crypto(blockchain.currency, blockchain.decimals()) }
|
||||
|
||||
WalletAdditionalInfo(hideable = true, content = TextReference.Str(value = amount.orEmpty()))
|
||||
WalletAdditionalInfo(
|
||||
hideable = true,
|
||||
content = TextReference.Str(value = amount.orEmpty()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.domain
|
||||
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.copy
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository
|
||||
import timber.log.Timber
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
|
|||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
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.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
|
||||
|
|
@ -19,6 +21,7 @@ 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.feature.wallet.presentation.account.AccountDependencies
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ModelScoped
|
||||
|
|
@ -40,6 +43,9 @@ internal class MultiWalletContentLoader(
|
|||
private val getStoryContentUseCase: GetStoryContentUseCase,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
) : WalletContentLoader(id = userWallet.walletId) {
|
||||
|
||||
override fun create(): List<WalletSubscriber> {
|
||||
|
|
@ -54,6 +60,9 @@ internal class MultiWalletContentLoader(
|
|||
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
|
||||
applyTokenListSortingUseCase = applyTokenListSortingUseCase,
|
||||
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
|
||||
accountDependencies = accountDependencies,
|
||||
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
|
||||
stakingApyFlowUseCase = stakingApyFlowUseCase,
|
||||
).let(::add)
|
||||
|
||||
WalletNFTListSubscriber(
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
|
|||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
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.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
|
||||
|
|
@ -18,6 +20,7 @@ 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.account.AccountDependencies
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -38,6 +41,9 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
|
|||
private val walletsRepository: WalletsRepository,
|
||||
private val getNFTCollectionsUseCase: GetNFTCollectionsUseCase,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
) {
|
||||
|
||||
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): WalletContentLoader {
|
||||
|
|
@ -59,6 +65,9 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
|
|||
walletsRepository = walletsRepository,
|
||||
getNFTCollectionsUseCase = getNFTCollectionsUseCase,
|
||||
currenciesRepository = currenciesRepository,
|
||||
accountDependencies = accountDependencies,
|
||||
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
|
||||
stakingApyFlowUseCase = stakingApyFlowUseCase,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
|
|||
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.account.AccountDependencies
|
||||
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
|
||||
|
|
@ -35,6 +36,7 @@ internal class SingleWalletContentLoader(
|
|||
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
) : WalletContentLoader(id = userWallet.walletId) {
|
||||
|
||||
override fun create(): List<WalletSubscriber> {
|
||||
|
|
@ -53,6 +55,7 @@ internal class SingleWalletContentLoader(
|
|||
clickIntents = clickIntents,
|
||||
getSingleCryptoCurrencyStatusUseCase = getSingleCryptoCurrencyStatusUseCase,
|
||||
getCryptoCurrencyActionsUseCase = getCryptoCurrencyActionsUseCase,
|
||||
accountDependencies = accountDependencies,
|
||||
),
|
||||
SingleWalletNotificationsSubscriber(
|
||||
userWallet = userWallet,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
|
|||
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.account.AccountDependencies
|
||||
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
|
||||
|
|
@ -34,12 +35,14 @@ internal class SingleWalletContentLoaderFactory @Inject constructor(
|
|||
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
) {
|
||||
|
||||
fun create(userWallet: UserWallet.Cold, clickIntents: WalletClickIntents, isRefresh: Boolean): WalletContentLoader {
|
||||
return SingleWalletContentLoader(
|
||||
userWallet = userWallet,
|
||||
clickIntents = clickIntents,
|
||||
accountDependencies = accountDependencies,
|
||||
isRefresh = isRefresh,
|
||||
stateHolder = stateHolder,
|
||||
getSingleCryptoCurrencyStatusUseCase = getSingleCryptoCurrencyStatusUseCase,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
|||
import com.tangem.domain.promo.GetStoryContentUseCase
|
||||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
|
||||
|
|
@ -14,6 +16,7 @@ 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.feature.wallet.presentation.account.AccountDependencies
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class SingleWalletWithTokenContentLoader(
|
||||
|
|
@ -30,6 +33,9 @@ internal class SingleWalletWithTokenContentLoader(
|
|||
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
|
||||
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
|
||||
private val getStoryContentUseCase: GetStoryContentUseCase,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
) : WalletContentLoader(id = userWallet.walletId) {
|
||||
|
||||
override fun create(): List<WalletSubscriber> {
|
||||
|
|
@ -43,6 +49,9 @@ internal class SingleWalletWithTokenContentLoader(
|
|||
tokenListStore = tokenListStore,
|
||||
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
|
||||
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
|
||||
accountDependencies = accountDependencies,
|
||||
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
|
||||
stakingApyFlowUseCase = stakingApyFlowUseCase,
|
||||
).let(::add)
|
||||
MultiWalletWarningsSubscriber(
|
||||
userWallet = userWallet,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
|||
import com.tangem.domain.promo.GetStoryContentUseCase
|
||||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
|
||||
|
|
@ -14,6 +16,7 @@ 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.account.AccountDependencies
|
||||
import javax.inject.Inject
|
||||
|
||||
// TODO: Refactor
|
||||
|
|
@ -31,6 +34,9 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
|
|||
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
|
||||
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
|
||||
private val getStoryContentUseCase: GetStoryContentUseCase,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
) {
|
||||
|
||||
fun create(userWallet: UserWallet.Cold, clickIntents: WalletClickIntents): SingleWalletWithTokenContentLoader {
|
||||
|
|
@ -48,6 +54,9 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
|
|||
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
|
||||
getStoryContentUseCase = getStoryContentUseCase,
|
||||
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,
|
||||
accountDependencies = accountDependencies,
|
||||
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
|
||||
stakingApyFlowUseCase = stakingApyFlowUseCase,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.model
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
@Immutable
|
||||
internal sealed class TangemPayState {
|
||||
|
||||
object Empty : TangemPayState()
|
||||
|
||||
data class Progress(
|
||||
val title: TextReference,
|
||||
val buttonText: TextReference,
|
||||
@DrawableRes val iconRes: Int,
|
||||
val onButtonClick: () -> Unit,
|
||||
val showProgress: Boolean = false,
|
||||
) : TangemPayState()
|
||||
|
||||
data class Card(
|
||||
val lastFourDigits: TextReference,
|
||||
val balanceText: TextReference,
|
||||
val onClick: () -> Unit,
|
||||
) : TangemPayState()
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.model
|
||||
|
||||
import com.tangem.domain.models.TokenReceiveConfig
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.tokens.model.details.TokenAction
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
|
|
@ -23,4 +25,11 @@ internal sealed interface WalletDialogConfig {
|
|||
|
||||
@Serializable
|
||||
data class TokenReceive(val tokenReceiveConfig: TokenReceiveConfig) : WalletDialogConfig
|
||||
|
||||
@Serializable
|
||||
data class YieldSupplyWarning(
|
||||
val cryptoCurrency: CryptoCurrency,
|
||||
val tokenAction: TokenAction,
|
||||
val onWarningAcknowledged: (TokenAction) -> Unit,
|
||||
) : WalletDialogConfig
|
||||
}
|
||||
|
|
@ -30,4 +30,9 @@ internal sealed class WalletEvent {
|
|||
RIGHT,
|
||||
}
|
||||
}
|
||||
|
||||
data class RequestPushPermissions(
|
||||
val onAllow: () -> Unit,
|
||||
val onDeny: () -> Unit,
|
||||
) : WalletEvent()
|
||||
}
|
||||
|
|
@ -45,8 +45,8 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) {
|
|||
text = TextReference.Res(id = R.string.common_buy),
|
||||
iconResId = R.drawable.ic_plus_24,
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
dimContent = dimContent,
|
||||
isEnabled = enabled,
|
||||
shouldDimContent = dimContent,
|
||||
isInProgress = isInProgress,
|
||||
),
|
||||
)
|
||||
|
|
@ -67,8 +67,8 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) {
|
|||
text = TextReference.Res(id = R.string.common_send),
|
||||
iconResId = R.drawable.ic_arrow_up_24,
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
dimContent = dimContent,
|
||||
isEnabled = enabled,
|
||||
shouldDimContent = dimContent,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -87,8 +87,8 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) {
|
|||
config = ActionButtonConfig(
|
||||
text = TextReference.Res(id = R.string.common_receive),
|
||||
iconResId = R.drawable.ic_arrow_down_24,
|
||||
enabled = enabled,
|
||||
dimContent = dimContent,
|
||||
isEnabled = enabled,
|
||||
shouldDimContent = dimContent,
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick,
|
||||
),
|
||||
|
|
@ -103,8 +103,8 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) {
|
|||
text = TextReference.Res(id = R.string.common_stake),
|
||||
iconResId = R.drawable.ic_staking_24,
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
dimContent = dimContent,
|
||||
isEnabled = enabled,
|
||||
shouldDimContent = dimContent,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -125,8 +125,8 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) {
|
|||
text = TextReference.Res(id = R.string.common_sell),
|
||||
iconResId = R.drawable.ic_currency_24,
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
dimContent = dimContent,
|
||||
isEnabled = enabled,
|
||||
shouldDimContent = dimContent,
|
||||
isInProgress = isInProgress,
|
||||
),
|
||||
)
|
||||
|
|
@ -149,10 +149,10 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) {
|
|||
text = TextReference.Res(id = R.string.swapping_swap_action),
|
||||
iconResId = R.drawable.ic_exchange_vertical_24,
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
dimContent = dimContent,
|
||||
isEnabled = enabled,
|
||||
shouldDimContent = dimContent,
|
||||
isInProgress = isInProgress,
|
||||
showBadge = showBadge,
|
||||
shouldShowBadge = showBadge,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -138,6 +138,11 @@ sealed class WalletNotification(val config: NotificationConfig) {
|
|||
formatArgs = wrappedList(count),
|
||||
),
|
||||
)
|
||||
|
||||
data object YeildSupplyApprove : Warning(
|
||||
title = resourceReference(R.string.yield_module_main_view_approve_notification_title),
|
||||
subtitle = resourceReference(R.string.yield_module_main_view_approve_notification_description),
|
||||
)
|
||||
}
|
||||
|
||||
sealed class Informational(
|
||||
|
|
@ -326,4 +331,23 @@ sealed class WalletNotification(val config: NotificationConfig) {
|
|||
iconSize = 54.dp,
|
||||
),
|
||||
)
|
||||
|
||||
data class PushNotifications(
|
||||
val onCloseClick: () -> Unit,
|
||||
val onEnabledClick: () -> Unit,
|
||||
) : WalletNotification(
|
||||
config = NotificationConfig(
|
||||
title = resourceReference(R.string.user_push_notification_banner_title),
|
||||
subtitle = resourceReference(R.string.user_push_notification_banner_subtitle),
|
||||
iconResId = R.drawable.img_push_reminder,
|
||||
onCloseClick = onCloseClick,
|
||||
buttonsState = NotificationConfig.ButtonsState.PairButtonsConfig(
|
||||
primaryText = resourceReference(id = R.string.common_enable),
|
||||
onPrimaryClick = onEnabledClick,
|
||||
secondaryText = resourceReference(id = R.string.common_later),
|
||||
onSecondaryClick = onCloseClick,
|
||||
),
|
||||
iconSize = 54.dp,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -14,4 +14,5 @@ internal data class WalletScreenState(
|
|||
val isHidingMode: Boolean,
|
||||
val showMarketsOnboarding: Boolean,
|
||||
val onDismissMarketsOnboarding: () -> Unit,
|
||||
val tangemPayState: TangemPayState = TangemPayState.Empty,
|
||||
)
|
||||
|
|
@ -23,6 +23,7 @@ internal sealed interface WalletState : WalletStateHolder {
|
|||
|
||||
abstract val tokensListState: WalletTokensListState
|
||||
abstract val nftState: WalletNFTItemUM
|
||||
abstract val type: WalletType
|
||||
|
||||
data class Content(
|
||||
override val pullToRefreshConfig: PullToRefreshConfig,
|
||||
|
|
@ -32,12 +33,14 @@ internal sealed interface WalletState : WalletStateHolder {
|
|||
override val bottomSheetConfig: TangemBottomSheetConfig?,
|
||||
override val tokensListState: WalletTokensListState,
|
||||
override val nftState: WalletNFTItemUM,
|
||||
override val type: WalletType,
|
||||
) : MultiCurrency()
|
||||
|
||||
data class Locked(
|
||||
override val walletCardState: WalletCardState,
|
||||
override val buttons: PersistentList<WalletManageButton>,
|
||||
override val bottomSheetConfig: TangemBottomSheetConfig?,
|
||||
override val type: WalletType,
|
||||
val onUnlockNotificationClick: () -> Unit,
|
||||
) : MultiCurrency(),
|
||||
WalletStateHolder by LockedWalletStateHolder(
|
||||
|
|
@ -50,6 +53,11 @@ internal sealed interface WalletState : WalletStateHolder {
|
|||
override val tokensListState = WalletTokensListState.ContentState.Locked
|
||||
override val nftState: WalletNFTItemUM = WalletNFTItemUM.Hidden
|
||||
}
|
||||
|
||||
enum class WalletType {
|
||||
Hot,
|
||||
Cold,
|
||||
}
|
||||
}
|
||||
|
||||
sealed class SingleCurrency : WalletState, TxHistoryStateHolder {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.*
|
|||
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.utils.createStateByWalletType
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState.MultiCurrency.WalletType
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
|
@ -61,6 +62,10 @@ internal class InitializeWalletsTransformer(
|
|||
buttons = createMultiWalletEnabledButtons(userWallet),
|
||||
bottomSheetConfig = null,
|
||||
onUnlockNotificationClick = clickIntents::onOpenUnlockWalletsBottomSheetClick,
|
||||
type = when (userWallet) {
|
||||
is UserWallet.Cold -> WalletType.Cold
|
||||
is UserWallet.Hot -> WalletType.Hot
|
||||
},
|
||||
)
|
||||
},
|
||||
singleCurrencyCreator = {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
||||
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||
import com.tangem.domain.tokens.model.TokenActionsState
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
|
@ -14,6 +15,7 @@ import timber.log.Timber
|
|||
internal class SetCryptoCurrencyActionsTransformer(
|
||||
private val tokenActionsState: TokenActionsState,
|
||||
private val userWallet: UserWallet,
|
||||
private val portfolioId: PortfolioId,
|
||||
private val clickIntents: WalletClickIntents,
|
||||
) : WalletStateTransformer(userWallet.walletId) {
|
||||
|
||||
|
|
@ -48,6 +50,7 @@ internal class SetCryptoCurrencyActionsTransformer(
|
|||
dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None,
|
||||
onClick = {
|
||||
clickIntents.onBuyClick(
|
||||
userWalletId = portfolioId.userWalletId,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
unavailabilityReason = action.unavailabilityReason,
|
||||
)
|
||||
|
|
@ -59,7 +62,10 @@ internal class SetCryptoCurrencyActionsTransformer(
|
|||
enabled = true,
|
||||
dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None,
|
||||
onClick = {
|
||||
clickIntents.onReceiveClick(cryptoCurrencyStatus = cryptoCurrencyStatus)
|
||||
clickIntents.onReceiveClick(
|
||||
portfolioId.userWalletId,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
)
|
||||
},
|
||||
onLongClick = {
|
||||
clickIntents.onCopyAddressLongClick(cryptoCurrencyStatus = cryptoCurrencyStatus)
|
||||
|
|
@ -84,6 +90,7 @@ internal class SetCryptoCurrencyActionsTransformer(
|
|||
dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None,
|
||||
onClick = {
|
||||
clickIntents.onSendClick(
|
||||
userWalletId = portfolioId.userWalletId,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
unavailabilityReason = action.unavailabilityReason,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
||||
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||
|
|
@ -13,10 +13,12 @@ import com.tangem.feature.wallet.presentation.wallet.state.utils.enableButtons
|
|||
import timber.log.Timber
|
||||
|
||||
internal class SetTokenListTransformer(
|
||||
private val tokenList: TokenList,
|
||||
private val params: TokenConverterParams,
|
||||
private val userWallet: UserWallet,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val clickIntents: WalletClickIntents,
|
||||
private val yieldSupplyApyMap: Map<String, String> = emptyMap(),
|
||||
private val stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
|
||||
) : WalletStateTransformer(userWallet.walletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
|
|
@ -42,8 +44,12 @@ internal class SetTokenListTransformer(
|
|||
}
|
||||
|
||||
private fun WalletCardState.toLoadedState(): WalletCardState {
|
||||
val fiatBalance = when (params) {
|
||||
is TokenConverterParams.Account -> params.accountList.totalFiatBalance
|
||||
is TokenConverterParams.Wallet -> params.tokenList.totalFiatBalance
|
||||
}
|
||||
return MultiWalletCardStateConverter(
|
||||
fiatBalance = tokenList.totalFiatBalance,
|
||||
fiatBalance = fiatBalance,
|
||||
selectedWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
).convert(value = this)
|
||||
|
|
@ -51,10 +57,12 @@ internal class SetTokenListTransformer(
|
|||
|
||||
private fun WalletTokensListState.toLoadedState(): WalletTokensListState {
|
||||
return TokenListStateConverter(
|
||||
tokenList = tokenList,
|
||||
params = params,
|
||||
selectedWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
clickIntents = clickIntents,
|
||||
yieldModuleApyMap = yieldSupplyApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
).convert(value = this)
|
||||
}
|
||||
}
|
||||
|
|
@ -59,8 +59,8 @@ internal class SetTxHistoryCountTransformer(
|
|||
private fun createLoadingItems(): List<TxHistoryState.TxHistoryItemState> {
|
||||
return buildList {
|
||||
add(TxHistoryState.TxHistoryItemState.Title(onExploreClick = clickIntents::onExploreClick))
|
||||
(1..transactionsCount).forEach {
|
||||
add(TxHistoryState.TxHistoryItemState.Transaction(state = TransactionState.Loading(it.toString())))
|
||||
for (i in 1..transactionsCount) {
|
||||
add(TxHistoryState.TxHistoryItemState.Transaction(state = TransactionState.Loading(i.toString())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,7 +105,10 @@ internal class SetVisaInfoTransformer(
|
|||
},
|
||||
)
|
||||
|
||||
return WalletAdditionalInfo(hideable = true, infoContent)
|
||||
return WalletAdditionalInfo(
|
||||
hideable = true,
|
||||
content = infoContent,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getRefreshTokenExpiredState(prevState: WalletState.Visa.Content): WalletState {
|
||||
|
|
@ -148,7 +151,11 @@ internal class SetVisaInfoTransformer(
|
|||
enabled = true,
|
||||
dimContent = false,
|
||||
onClick = {
|
||||
clickIntents.onReceiveClick(cryptoCurrencyStatus, MainScreenAnalyticsEvent.ButtonReceive)
|
||||
clickIntents.onReceiveClick(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
event = MainScreenAnalyticsEvent.ButtonReceive,
|
||||
)
|
||||
},
|
||||
onLongClick = {
|
||||
clickIntents.onCopyAddressLongClick(cryptoCurrencyStatus)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.pay.TangemPayDetailsConfig
|
||||
import com.tangem.domain.pay.model.CustomerInfo.CardInfo
|
||||
import com.tangem.domain.pay.model.MainScreenCustomerInfo
|
||||
import com.tangem.domain.pay.model.OrderStatus.CANCELED
|
||||
import com.tangem.domain.pay.model.OrderStatus.UNKNOWN
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createIssueAvailableState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createIssueProgressState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createKycInProgressState
|
||||
import java.util.Currency
|
||||
|
||||
/**
|
||||
* Hardcode Polygon chain id only for F&F.
|
||||
* Later chain id will be fetched from BFF.
|
||||
*/
|
||||
private const val POLYGON_CHAIN_ID = 137
|
||||
|
||||
internal class TangemPayInitialStateTransformer(
|
||||
private val value: MainScreenCustomerInfo? = null,
|
||||
private val onClickIssue: () -> Unit = {},
|
||||
private val onClickKyc: () -> Unit = {},
|
||||
private val openDetails: (config: TangemPayDetailsConfig) -> Unit = {},
|
||||
) : WalletScreenStateTransformer {
|
||||
|
||||
override fun transform(prevState: WalletScreenState): WalletScreenState {
|
||||
val tangemPayState = createInitialState()
|
||||
return prevState.copy(tangemPayState = tangemPayState)
|
||||
}
|
||||
|
||||
private fun createInitialState(): TangemPayState {
|
||||
val cardInfo = value?.info?.cardInfo
|
||||
return when {
|
||||
value == null -> TangemPayState.Empty
|
||||
!value.info.isKycApproved -> createKycInProgressState(onClickKyc)
|
||||
cardInfo != null -> getCardInfoState(cardInfo)
|
||||
value.orderStatus == UNKNOWN || value.orderStatus == CANCELED -> createIssueAvailableState(onClickIssue)
|
||||
else -> createIssueProgressState()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCardInfoState(cardInfo: CardInfo): TangemPayState = TangemPayState.Card(
|
||||
lastFourDigits = TextReference.Str("*${cardInfo.lastFourDigits}"),
|
||||
balanceText = TextReference.Str(getBalanceText(cardInfo)),
|
||||
onClick = {
|
||||
openDetails(
|
||||
TangemPayDetailsConfig(
|
||||
customerWalletAddress = cardInfo.customerWalletAddress,
|
||||
cardNumberEnd = cardInfo.lastFourDigits,
|
||||
chainId = POLYGON_CHAIN_ID,
|
||||
depositAddress = cardInfo.depositAddress,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
private fun getBalanceText(cardInfo: CardInfo): String {
|
||||
val currency = Currency.getInstance(cardInfo.currencyCode)
|
||||
return cardInfo.balance.format {
|
||||
fiat(fiatCurrencyCode = currency.currencyCode, fiatCurrencySymbol = currency.symbol)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
||||
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createIssueAvailableState
|
||||
|
||||
internal class TangemPayIssueAvailableStateTransformer(
|
||||
private val onClickIssue: () -> Unit = {},
|
||||
) : WalletScreenStateTransformer {
|
||||
|
||||
override fun transform(prevState: WalletScreenState): WalletScreenState =
|
||||
prevState.copy(tangemPayState = createIssueAvailableState(onClickIssue))
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
||||
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createIssueProgressState
|
||||
|
||||
internal class TangemPayIssueProgressStateTransformer : WalletScreenStateTransformer {
|
||||
|
||||
override fun transform(prevState: WalletScreenState): WalletScreenState =
|
||||
prevState.copy(tangemPayState = createIssueProgressState())
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
||||
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
|
||||
sealed interface TokenConverterParams {
|
||||
data class Wallet(
|
||||
val portfolioId: PortfolioId,
|
||||
val tokenList: TokenList,
|
||||
) : TokenConverterParams
|
||||
|
||||
data class Account(
|
||||
val accountList: AccountStatusList,
|
||||
val expandedAccounts: Set<AccountId>,
|
||||
) : TokenConverterParams
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||
import com.tangem.domain.tokens.model.TokenActionsState
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletCurrencyActionsClickIntents
|
||||
|
|
@ -20,6 +21,8 @@ internal class MultiWalletCurrencyActionsConverter(
|
|||
private val clickIntents: WalletCurrencyActionsClickIntents,
|
||||
) : Converter<TokenActionsState, ImmutableList<TokenActionButtonConfig>> {
|
||||
|
||||
private val userWalletId: UserWalletId = userWallet.walletId
|
||||
|
||||
override fun convert(value: TokenActionsState): ImmutableList<TokenActionButtonConfig> {
|
||||
return value.states
|
||||
.filterIfSingleWithToken()
|
||||
|
|
@ -46,7 +49,7 @@ internal class MultiWalletCurrencyActionsConverter(
|
|||
if (actionsState is TokenActionsState.ActionState.Send && cryptoCurrencyStatus.value.amount.isNullOrZero()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val noneReason = ScenarioUnavailabilityReason.None
|
||||
val title: TextReference
|
||||
val icon: Int
|
||||
val action: () -> Unit
|
||||
|
|
@ -54,27 +57,27 @@ internal class MultiWalletCurrencyActionsConverter(
|
|||
is TokenActionsState.ActionState.Buy -> {
|
||||
title = resourceReference(R.string.common_buy)
|
||||
icon = R.drawable.ic_plus_24
|
||||
action = { clickIntents.onBuyClick(cryptoCurrencyStatus, ScenarioUnavailabilityReason.None) }
|
||||
action = { clickIntents.onBuyClick(userWalletId, cryptoCurrencyStatus, noneReason) }
|
||||
}
|
||||
is TokenActionsState.ActionState.Receive -> {
|
||||
title = resourceReference(R.string.common_receive)
|
||||
icon = R.drawable.ic_arrow_down_24
|
||||
action = { clickIntents.onReceiveClick(cryptoCurrencyStatus) }
|
||||
action = { clickIntents.onReceiveClick(userWalletId, cryptoCurrencyStatus) }
|
||||
}
|
||||
is TokenActionsState.ActionState.Stake -> {
|
||||
title = resourceReference(R.string.common_stake)
|
||||
icon = R.drawable.ic_staking_24
|
||||
action = { clickIntents.onStakeClick(cryptoCurrencyStatus, actionsState.yield) }
|
||||
action = { clickIntents.onStakeClick(userWalletId, cryptoCurrencyStatus, actionsState.yield) }
|
||||
}
|
||||
is TokenActionsState.ActionState.Sell -> {
|
||||
title = resourceReference(R.string.common_sell)
|
||||
icon = R.drawable.ic_currency_24
|
||||
action = { clickIntents.onSellClick(cryptoCurrencyStatus, ScenarioUnavailabilityReason.None) }
|
||||
action = { clickIntents.onSellClick(cryptoCurrencyStatus, noneReason) }
|
||||
}
|
||||
is TokenActionsState.ActionState.Send -> {
|
||||
title = resourceReference(R.string.common_send)
|
||||
icon = R.drawable.ic_arrow_up_24
|
||||
action = { clickIntents.onSendClick(cryptoCurrencyStatus, ScenarioUnavailabilityReason.None) }
|
||||
action = { clickIntents.onSendClick(userWalletId, cryptoCurrencyStatus, noneReason) }
|
||||
}
|
||||
is TokenActionsState.ActionState.Swap -> {
|
||||
title = resourceReference(R.string.swapping_swap_action)
|
||||
|
|
@ -82,20 +85,20 @@ internal class MultiWalletCurrencyActionsConverter(
|
|||
action = {
|
||||
clickIntents.onSwapClick(
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
userWalletId = userWallet.walletId,
|
||||
unavailabilityReason = ScenarioUnavailabilityReason.None,
|
||||
userWalletId = userWalletId,
|
||||
unavailabilityReason = noneReason,
|
||||
)
|
||||
}
|
||||
}
|
||||
is TokenActionsState.ActionState.CopyAddress -> {
|
||||
title = resourceReference(R.string.common_copy_address)
|
||||
icon = R.drawable.ic_copy_24
|
||||
action = { clickIntents.onCopyAddressClick(cryptoCurrencyStatus) }
|
||||
action = { clickIntents.onCopyAddressClick(userWalletId, cryptoCurrencyStatus) }
|
||||
}
|
||||
is TokenActionsState.ActionState.HideToken -> {
|
||||
title = resourceReference(R.string.token_details_hide_token)
|
||||
icon = R.drawable.ic_hide_24
|
||||
action = { clickIntents.onHideTokensClick(cryptoCurrencyStatus) }
|
||||
action = { clickIntents.onHideTokensClick(userWalletId, cryptoCurrencyStatus) }
|
||||
}
|
||||
is TokenActionsState.ActionState.Analytics -> {
|
||||
title = resourceReference(R.string.common_analytics)
|
||||
|
|
@ -109,7 +112,7 @@ internal class MultiWalletCurrencyActionsConverter(
|
|||
iconResId = icon,
|
||||
onClick = action,
|
||||
isWarning = actionsState is TokenActionsState.ActionState.HideToken,
|
||||
enabled = actionsState.unavailabilityReason == ScenarioUnavailabilityReason.None,
|
||||
enabled = actionsState.unavailabilityReason == noneReason,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,67 +1,156 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
|
||||
|
||||
import com.tangem.common.ui.account.AccountCryptoPortfolioItemStateConverter
|
||||
import com.tangem.common.ui.tokens.TokenItemStateConverter
|
||||
import com.tangem.core.ui.components.tokenlist.state.PortfolioTokensListItemUM
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.models.tokenlist.TokenList.GroupedByNetwork.NetworkGroup
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.OrganizeTokensButtonConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenConverterParams
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.mutate
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.OrganizeTokensButtonConfig as WalletOrganizeTokensButtonConfig
|
||||
|
||||
internal class TokenListStateConverter(
|
||||
appCurrency: AppCurrency,
|
||||
private val tokenList: TokenList,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val params: TokenConverterParams,
|
||||
private val selectedWallet: UserWallet,
|
||||
private val clickIntents: WalletClickIntents,
|
||||
private val yieldModuleApyMap: Map<String, String>,
|
||||
private val stakingApyMap: Map<String, List<Yield.Validator>>,
|
||||
) : Converter<WalletTokensListState, WalletTokensListState> {
|
||||
|
||||
private val tokenStatusConverter = TokenItemStateConverter(
|
||||
private val onTokenClick: (accountId: AccountId?, currencyStatus: CryptoCurrencyStatus) -> Unit =
|
||||
{ accountId, currencyStatus ->
|
||||
clickIntents.onTokenItemClick(selectedWallet.walletId, currencyStatus)
|
||||
}
|
||||
|
||||
private val onTokenLongClick: (accountId: AccountId?, currencyStatus: CryptoCurrencyStatus) -> Unit =
|
||||
{ accountId, currencyStatus ->
|
||||
clickIntents.onTokenItemLongClick(selectedWallet.walletId, currencyStatus)
|
||||
}
|
||||
|
||||
private fun tokenStatusConverter(accountId: AccountId? = null) = TokenItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
onItemClick = { _, status -> clickIntents.onTokenItemClick(status) },
|
||||
onItemLongClick = { _, status -> clickIntents.onTokenItemLongClick(status) },
|
||||
yieldModuleApyMap = yieldModuleApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
onItemClick = { _, status -> onTokenClick(accountId, status) },
|
||||
onItemLongClick = { _, status -> onTokenLongClick(accountId, status) },
|
||||
)
|
||||
|
||||
override fun convert(value: WalletTokensListState): WalletTokensListState {
|
||||
return when (tokenList) {
|
||||
return when (params) {
|
||||
is TokenConverterParams.Account -> convertAccountList(params)
|
||||
is TokenConverterParams.Wallet -> convertTokenList(
|
||||
tokenConverter = tokenStatusConverter((params.portfolioId as? PortfolioId.Account)?.accountId),
|
||||
tokenList = params.tokenList,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertTokenList(tokenConverter: TokenItemStateConverter, tokenList: TokenList): WalletTokensListState =
|
||||
when (tokenList) {
|
||||
is TokenList.Empty -> WalletTokensListState.Empty
|
||||
is TokenList.GroupedByNetwork -> WalletTokensListState.ContentState.Content(
|
||||
items = tokenList.toGroupedItems(),
|
||||
organizeTokensButtonConfig = getOrganizeTokensButtonState(
|
||||
currenciesSize = tokenList.groups.flatMap(NetworkGroup::currencies).size,
|
||||
),
|
||||
items = tokenList.toGroupedItems(tokenConverter),
|
||||
organizeTokensButtonConfig = getOrganizeTokensButtonState(tokenList = tokenList),
|
||||
)
|
||||
is TokenList.Ungrouped -> WalletTokensListState.ContentState.Content(
|
||||
items = tokenList.toUngroupedItems(),
|
||||
organizeTokensButtonConfig = getOrganizeTokensButtonState(currenciesSize = tokenList.currencies.size),
|
||||
items = tokenList.toUngroupedItems(tokenConverter),
|
||||
organizeTokensButtonConfig = getOrganizeTokensButtonState(tokenList = tokenList),
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertAccountList(params: TokenConverterParams.Account): WalletTokensListState {
|
||||
val accountList = params.accountList
|
||||
// todo account null for firs iteration?
|
||||
val organizeTokensButtonConfig: OrganizeTokensButtonConfig? = null
|
||||
|
||||
fun AccountStatus.CryptoPortfolio.map(): TokensListItemUM.Portfolio {
|
||||
val tokenList: TokenList = this.tokenList
|
||||
val account: Account.CryptoPortfolio = this.account
|
||||
val isExtend = params.expandedAccounts.contains(account.accountId)
|
||||
val onItemClick: (Account.CryptoPortfolio) -> Unit = {
|
||||
if (isExtend) {
|
||||
clickIntents.onAccountCollapseClick(it)
|
||||
} else {
|
||||
clickIntents.onAccountExpandClick(it)
|
||||
}
|
||||
}
|
||||
val converter = AccountCryptoPortfolioItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
account = account,
|
||||
onItemClick = onItemClick,
|
||||
)
|
||||
val accountItem = converter.convert(tokenList.totalFiatBalance)
|
||||
val tokenConverter = tokenStatusConverter(account.accountId)
|
||||
val tokensListState = convertTokenList(tokenConverter, tokenList)
|
||||
val items = when (tokensListState) {
|
||||
is WalletTokensListState.ContentState.PortfolioContent -> tokensListState.items
|
||||
is WalletTokensListState.ContentState.Content -> tokensListState.items
|
||||
is WalletTokensListState.ContentState.Loading -> tokensListState.items
|
||||
is WalletTokensListState.ContentState.Locked -> tokensListState.items
|
||||
is WalletTokensListState.Empty -> listOf()
|
||||
}
|
||||
return TokensListItemUM.Portfolio(
|
||||
tokenItemUM = accountItem,
|
||||
isExpanded = isExtend,
|
||||
isCollapsable = true,
|
||||
tokens = items.filterIsInstance<PortfolioTokensListItemUM>().toPersistentList(),
|
||||
)
|
||||
}
|
||||
|
||||
val accountItems = accountList.accountStatuses
|
||||
.map { accountStatus ->
|
||||
when (accountStatus) {
|
||||
is AccountStatus.CryptoPortfolio -> accountStatus.map()
|
||||
}
|
||||
}
|
||||
return WalletTokensListState.ContentState.PortfolioContent(
|
||||
items = accountItems.toPersistentList(),
|
||||
organizeTokensButtonConfig = organizeTokensButtonConfig,
|
||||
)
|
||||
}
|
||||
|
||||
private fun TokenList.GroupedByNetwork.toGroupedItems(): PersistentList<TokensListItemUM> {
|
||||
private fun TokenList.GroupedByNetwork.toGroupedItems(
|
||||
tokenConverter: TokenItemStateConverter,
|
||||
): PersistentList<TokensListItemUM> {
|
||||
return groups.fold(initial = persistentListOf()) { acc, group ->
|
||||
acc.mutate { it.addGroup(group) }
|
||||
acc.mutate { it.addGroup(tokenConverter, group) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun TokenList.Ungrouped.toUngroupedItems(): PersistentList<TokensListItemUM> {
|
||||
private fun TokenList.Ungrouped.toUngroupedItems(
|
||||
tokenConverter: TokenItemStateConverter,
|
||||
): PersistentList<TokensListItemUM> {
|
||||
return currencies.fold(initial = persistentListOf()) { acc, token ->
|
||||
acc.mutate { it.addToken(token) }
|
||||
acc.mutate { it.addToken(tokenConverter, token) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<TokensListItemUM>.addGroup(group: NetworkGroup): List<TokensListItemUM> {
|
||||
private fun MutableList<TokensListItemUM>.addGroup(
|
||||
tokenConverter: TokenItemStateConverter,
|
||||
group: NetworkGroup,
|
||||
): List<TokensListItemUM> {
|
||||
val groupTitle = TokensListItemUM.GroupTitle(
|
||||
id = group.network.hashCode(),
|
||||
text = resourceReference(
|
||||
|
|
@ -71,19 +160,28 @@ internal class TokenListStateConverter(
|
|||
)
|
||||
|
||||
add(groupTitle)
|
||||
group.currencies.forEach { token -> addToken(token) }
|
||||
group.currencies.forEach { token -> addToken(tokenConverter, token) }
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
private fun MutableList<TokensListItemUM>.addToken(token: CryptoCurrencyStatus): List<TokensListItemUM> {
|
||||
val tokenItemState = tokenStatusConverter.convert(token)
|
||||
private fun MutableList<TokensListItemUM>.addToken(
|
||||
tokenConverter: TokenItemStateConverter,
|
||||
token: CryptoCurrencyStatus,
|
||||
): List<TokensListItemUM> {
|
||||
val tokenItemState = tokenConverter.convert(token)
|
||||
|
||||
add(TokensListItemUM.Token(tokenItemState))
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
private fun getOrganizeTokensButtonState(currenciesSize: Int): WalletOrganizeTokensButtonConfig? {
|
||||
private fun getOrganizeTokensButtonState(tokenList: TokenList): WalletOrganizeTokensButtonConfig? {
|
||||
val currenciesSize = when (tokenList) {
|
||||
TokenList.Empty -> return null
|
||||
is TokenList.GroupedByNetwork -> tokenList.groups.flatMap(NetworkGroup::currencies).size
|
||||
is TokenList.Ungrouped -> tokenList.currencies.size
|
||||
}
|
||||
return if (currenciesSize > 1 && !isSingleCurrencyWalletWithToken()) {
|
||||
WalletOrganizeTokensButtonConfig(
|
||||
isEnabled = tokenList.totalFiatBalance !is TotalFiatBalance.Loading,
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ internal class TxHistoryItemStateConverter(
|
|||
is TransactionType.Operation,
|
||||
is TransactionType.Swap,
|
||||
is TransactionType.Transfer,
|
||||
is TransactionType.YieldSupply,
|
||||
is TransactionType.UnknownOperation,
|
||||
-> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24
|
||||
}
|
||||
|
|
@ -71,6 +72,7 @@ internal class TxHistoryItemStateConverter(
|
|||
is TransactionType.Operation -> stringReference(type.name)
|
||||
is TransactionType.Swap -> resourceReference(R.string.common_swap)
|
||||
is TransactionType.Transfer -> resourceReference(R.string.common_transfer)
|
||||
is TransactionType.YieldSupply -> resourceReference(R.string.yield_module_supply)
|
||||
is TransactionType.Staking.Stake -> resourceReference(R.string.common_stake)
|
||||
is TransactionType.Staking.Unstake -> resourceReference(R.string.common_unstake)
|
||||
is TransactionType.Staking.Vote -> resourceReference(R.string.staking_vote)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.util
|
||||
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState.Progress
|
||||
|
||||
internal object TangemPayStateCreator {
|
||||
|
||||
fun createKycInProgressState(onClickKyc: () -> Unit): TangemPayState = Progress(
|
||||
title = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_title),
|
||||
buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button),
|
||||
iconRes = R.drawable.ic_promo_kyc_36,
|
||||
onButtonClick = onClickKyc,
|
||||
)
|
||||
|
||||
fun createIssueAvailableState(onClickIssue: () -> Unit) = Progress(
|
||||
title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
|
||||
buttonText = TextReference.Res(R.string.common_continue),
|
||||
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
|
||||
onButtonClick = onClickIssue,
|
||||
)
|
||||
|
||||
fun createIssueProgressState(): TangemPayState = Progress(
|
||||
title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
|
||||
buttonText = TextReference.EMPTY,
|
||||
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
|
||||
onButtonClick = {},
|
||||
showProgress = true,
|
||||
)
|
||||
}
|
||||
|
|
@ -54,6 +54,7 @@ internal class WalletLoadingStateFactory(
|
|||
bottomSheetConfig = null,
|
||||
tokensListState = WalletTokensListState.ContentState.Loading,
|
||||
nftState = WalletNFTItemUM.Hidden,
|
||||
type = WalletState.MultiCurrency.WalletType.Hot,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -66,6 +67,7 @@ internal class WalletLoadingStateFactory(
|
|||
bottomSheetConfig = null,
|
||||
tokensListState = WalletTokensListState.ContentState.Loading,
|
||||
nftState = WalletNFTItemUM.Hidden,
|
||||
type = WalletState.MultiCurrency.WalletType.Cold,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,55 +1,78 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.subscribers
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.core.utils.getOrElse
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
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.TokenListAnalyticsSender
|
||||
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.state.transformers.SetTokenListErrorTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTokenListTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenConverterParams
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.combine6
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.joinAll
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal abstract class BasicTokenListSubscriber(
|
||||
private val userWallet: UserWallet,
|
||||
private val stateHolder: WalletStateController,
|
||||
private val clickIntents: WalletClickIntents,
|
||||
private val tokenListAnalyticsSender: TokenListAnalyticsSender,
|
||||
private val walletWithFundsChecker: WalletWithFundsChecker,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
|
||||
) : WalletSubscriber() {
|
||||
internal abstract class BasicTokenListSubscriber : WalletSubscriber() {
|
||||
protected abstract val userWallet: UserWallet
|
||||
protected abstract val stateHolder: WalletStateController
|
||||
protected abstract val clickIntents: WalletClickIntents
|
||||
protected abstract val tokenListAnalyticsSender: TokenListAnalyticsSender
|
||||
protected abstract val walletWithFundsChecker: WalletWithFundsChecker
|
||||
protected abstract val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase
|
||||
protected abstract val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase
|
||||
protected abstract val accountDependencies: AccountDependencies
|
||||
protected abstract val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase
|
||||
protected abstract val stakingApyFlowUseCase: StakingApyFlowUseCase
|
||||
|
||||
private val sendAnalyticsJobHolder = JobHolder()
|
||||
private val onTokenListReceivedJobHolder = JobHolder()
|
||||
protected val isAccountsEnabled get() = accountDependencies.accountsFeatureToggles.isFeatureEnabled
|
||||
|
||||
protected abstract fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList>
|
||||
|
||||
protected abstract suspend fun onTokenListReceived(maybeTokenList: Lce<TokenListError, TokenList>)
|
||||
protected abstract fun accountListFlow(coroutineScope: CoroutineScope): Flow<AccountStatusList>
|
||||
|
||||
override fun create(coroutineScope: CoroutineScope): Flow<*> {
|
||||
protected abstract suspend fun onTokenListReceived(maybeTokenList: Lce<TokenListError, TokenList>)
|
||||
protected open suspend fun onAccountListReceived() = {
|
||||
// todo account updateSortingIfNeeded? like [onTokenListReceived]
|
||||
}
|
||||
|
||||
override fun create(coroutineScope: CoroutineScope): Flow<*> =
|
||||
if (isAccountsEnabled) createAccountListFlow(coroutineScope) else createTokenListFlow(coroutineScope)
|
||||
|
||||
private fun createTokenListFlow(coroutineScope: CoroutineScope): Flow<*> {
|
||||
return combine(
|
||||
flow = tokenListFlow(coroutineScope)
|
||||
.onEach { maybeTokenList ->
|
||||
coroutineScope.launch {
|
||||
sendTokenListAnalytics(maybeTokenList)
|
||||
sendTokenListAnalytics(
|
||||
flattenCurrencies = maybeTokenList.getOrNull()?.flattenCurrencies(),
|
||||
totalFiatBalance = maybeTokenList.getOrNull()?.totalFiatBalance,
|
||||
)
|
||||
}.saveIn(sendAnalyticsJobHolder)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
|
|
@ -60,42 +83,60 @@ internal abstract class BasicTokenListSubscriber(
|
|||
|
||||
coroutineScope.launch { startCheck(maybeTokenList) }
|
||||
},
|
||||
flow2 = getSelectedAppCurrencyUseCase().distinctUntilChanged(),
|
||||
transform = { maybeTokenList, maybeAppCurrency ->
|
||||
val appCurrency = maybeAppCurrency.getOrElse { e ->
|
||||
Timber.e("Failed to load app currency: $e")
|
||||
AppCurrency.Default
|
||||
}
|
||||
|
||||
val tokenList = maybeTokenList.getOrElse(
|
||||
ifLoading = { maybeContent ->
|
||||
val isRefreshing = stateHolder.getWalletState(userWallet.walletId)
|
||||
?.pullToRefreshConfig
|
||||
?.isRefreshing == true
|
||||
|
||||
maybeContent
|
||||
?.takeIf { !isRefreshing }
|
||||
?: return@combine
|
||||
},
|
||||
ifError = { e ->
|
||||
Timber.e("Failed to load token list: $e")
|
||||
stateHolder.update(
|
||||
SetTokenListErrorTransformer(
|
||||
selectedWallet = userWallet,
|
||||
error = e,
|
||||
appCurrency = appCurrency,
|
||||
),
|
||||
)
|
||||
return@combine
|
||||
},
|
||||
flow2 = appCurrencyFlow(),
|
||||
flow3 = yieldSupplyApyFlow(),
|
||||
flow4 = stakingApyFlow(),
|
||||
transform = { maybeTokenList, appCurrency, yieldSupplyApyMap, stakingApyMap ->
|
||||
singleAccountTransform(
|
||||
maybeTokenList = maybeTokenList,
|
||||
appCurrency = appCurrency,
|
||||
portfolioId = PortfolioId(userWallet.walletId),
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
)
|
||||
|
||||
updateContent(tokenList, appCurrency)
|
||||
walletWithFundsChecker.check(tokenList)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun singleAccountTransform(
|
||||
maybeTokenList: Lce<TokenListError, TokenList>,
|
||||
appCurrency: AppCurrency,
|
||||
portfolioId: PortfolioId,
|
||||
yieldSupplyApyMap: Map<String, String>,
|
||||
stakingApyMap: Map<String, List<Yield.Validator>>,
|
||||
) {
|
||||
val tokenList = maybeTokenList.getOrElse(
|
||||
ifLoading = { maybeContent ->
|
||||
val isRefreshing = stateHolder.getWalletState(userWallet.walletId)
|
||||
?.pullToRefreshConfig
|
||||
?.isRefreshing == true
|
||||
|
||||
maybeContent
|
||||
?.takeIf { !isRefreshing }
|
||||
?: return
|
||||
},
|
||||
ifError = { e ->
|
||||
Timber.e("Failed to load token list: $e")
|
||||
stateHolder.update(
|
||||
SetTokenListErrorTransformer(
|
||||
selectedWallet = userWallet,
|
||||
error = e,
|
||||
appCurrency = appCurrency,
|
||||
),
|
||||
)
|
||||
return
|
||||
},
|
||||
)
|
||||
updateContent(
|
||||
params = TokenConverterParams.Wallet(portfolioId, tokenList),
|
||||
appCurrency = appCurrency,
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
)
|
||||
|
||||
walletWithFundsChecker.check(tokenList)
|
||||
}
|
||||
|
||||
private suspend fun startCheck(maybeTokenList: Lce<TokenListError, TokenList>) {
|
||||
// Run Polkadot account health check
|
||||
maybeTokenList.getOrNull()?.let { tokenList ->
|
||||
|
|
@ -110,24 +151,123 @@ internal abstract class BasicTokenListSubscriber(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun sendTokenListAnalytics(maybeTokenList: Lce<TokenListError, TokenList>) {
|
||||
private fun createAccountListFlow(coroutineScope: CoroutineScope): Flow<*> = combine6(
|
||||
flow1 = accountListFlow(coroutineScope)
|
||||
.onEach { accountStatusList ->
|
||||
coroutineScope.launch {
|
||||
sendTokenListAnalytics(
|
||||
flattenCurrencies = accountStatusList.flattenCurrencies(),
|
||||
totalFiatBalance = accountStatusList.totalFiatBalance,
|
||||
)
|
||||
}.saveIn(sendAnalyticsJobHolder)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.onEach { accountList ->
|
||||
// todo account see[onAccountListReceived]
|
||||
// coroutineScope.launch { onAccountListReceived() }.saveIn(onTokenListReceivedJobHolder)
|
||||
|
||||
accountList.flattenTokens()
|
||||
.forEach { tokenList -> startCheck(Lce.Content(tokenList)) }
|
||||
},
|
||||
flow2 = appCurrencyFlow(),
|
||||
flow3 = accountDependencies.expandedAccountsHolder.expandedAccounts(userWallet),
|
||||
flow4 = accountDependencies.isAccountsModeEnabledUseCase(),
|
||||
flow5 = yieldSupplyApyFlow(),
|
||||
flow6 = stakingApyFlow(),
|
||||
transform = { accountList, appCurrency, expandedAccounts, isAccountMode, yieldSupplyApyMap, stakingApyMap ->
|
||||
val accountFlattenTokensList = accountList.flattenTokens()
|
||||
val accountFlattenCurrencies = accountFlattenTokensList
|
||||
.map { it.flattenCurrencies() }
|
||||
.flatten()
|
||||
val mainAccount: AccountStatus.CryptoPortfolio = when (val mainAccount = accountList.mainAccount) {
|
||||
is AccountStatus.CryptoPortfolio -> mainAccount
|
||||
}
|
||||
|
||||
suspend fun singleAccountTransform(maybeTokenList: Lce<TokenListError, TokenList>) =
|
||||
this.singleAccountTransform(
|
||||
maybeTokenList,
|
||||
appCurrency,
|
||||
PortfolioId(mainAccount.account.accountId),
|
||||
yieldSupplyApyMap,
|
||||
stakingApyMap,
|
||||
)
|
||||
|
||||
when {
|
||||
!isAccountMode -> when (mainAccount.tokenList.flattenCurrencies().isEmpty()) {
|
||||
true -> singleAccountTransform(Lce.Error(TokenListError.EmptyTokens))
|
||||
false -> singleAccountTransform(Lce.Content(mainAccount.tokenList))
|
||||
}
|
||||
|
||||
isAccountMode -> when (accountFlattenCurrencies.isEmpty()) {
|
||||
true -> stateHolder.update(
|
||||
SetTokenListErrorTransformer(
|
||||
selectedWallet = userWallet,
|
||||
error = TokenListError.EmptyTokens,
|
||||
appCurrency = appCurrency,
|
||||
),
|
||||
)
|
||||
false -> {
|
||||
val convertParams = TokenConverterParams.Account(accountList, expandedAccounts)
|
||||
updateContent(convertParams, appCurrency, yieldSupplyApyMap, stakingApyMap)
|
||||
accountFlattenTokensList
|
||||
.map { tokenList -> coroutineScope.launch { walletWithFundsChecker.check(tokenList) } }
|
||||
.joinAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
private fun AccountStatusList.flattenTokens(): List<TokenList> = this.accountStatuses.map {
|
||||
when (it) {
|
||||
is AccountStatus.CryptoPortfolio -> it.tokenList
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun sendTokenListAnalytics(
|
||||
flattenCurrencies: List<CryptoCurrencyStatus>?,
|
||||
totalFiatBalance: TotalFiatBalance?,
|
||||
) {
|
||||
val displayedState = stateHolder.getWalletStateIfSelected(userWallet.walletId)
|
||||
|
||||
tokenListAnalyticsSender.send(
|
||||
displayedUiState = displayedState,
|
||||
userWallet = userWallet,
|
||||
tokenList = maybeTokenList.getOrNull() ?: return,
|
||||
flattenCurrencies = flattenCurrencies ?: return,
|
||||
totalFiatBalance = totalFiatBalance ?: return,
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateContent(tokenList: TokenList, appCurrency: AppCurrency) {
|
||||
private fun updateContent(
|
||||
params: TokenConverterParams,
|
||||
appCurrency: AppCurrency,
|
||||
yieldSupplyApyMap: Map<String, String>,
|
||||
stakingApyMap: Map<String, List<Yield.Validator>>,
|
||||
) {
|
||||
stateHolder.update(
|
||||
SetTokenListTransformer(
|
||||
tokenList = tokenList,
|
||||
params = params,
|
||||
userWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
clickIntents = clickIntents,
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun appCurrencyFlow(): Flow<AppCurrency> = getSelectedAppCurrencyUseCase()
|
||||
.map {
|
||||
it.getOrElse { e ->
|
||||
Timber.e("Failed to load app currency: $e")
|
||||
AppCurrency.Default
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
|
||||
private fun yieldSupplyApyFlow(): Flow<Map<String, String>> = yieldSupplyApyFlowUseCase()
|
||||
.distinctUntilChanged()
|
||||
|
||||
private fun stakingApyFlow(): Flow<Map<String, List<Yield.Validator>>> = stakingApyFlowUseCase()
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.subscribers
|
||||
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
|
|
@ -8,36 +10,35 @@ import com.tangem.domain.models.TotalFiatBalance
|
|||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
|
||||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
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.TokenListAnalyticsSender
|
||||
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 kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class MultiWalletTokenListSubscriber(
|
||||
private val userWallet: UserWallet,
|
||||
override val userWallet: UserWallet,
|
||||
private val tokenListStore: MultiWalletTokenListStore,
|
||||
private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase,
|
||||
stateHolder: WalletStateController,
|
||||
clickIntents: WalletClickIntents,
|
||||
tokenListAnalyticsSender: TokenListAnalyticsSender,
|
||||
walletWithFundsChecker: WalletWithFundsChecker,
|
||||
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
|
||||
) : BasicTokenListSubscriber(
|
||||
userWallet = userWallet,
|
||||
stateHolder = stateHolder,
|
||||
clickIntents = clickIntents,
|
||||
tokenListAnalyticsSender = tokenListAnalyticsSender,
|
||||
walletWithFundsChecker = walletWithFundsChecker,
|
||||
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
|
||||
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
|
||||
) {
|
||||
override val stateHolder: WalletStateController,
|
||||
override val clickIntents: WalletClickIntents,
|
||||
override val tokenListAnalyticsSender: TokenListAnalyticsSender,
|
||||
override val walletWithFundsChecker: WalletWithFundsChecker,
|
||||
override val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
override val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
|
||||
override val accountDependencies: AccountDependencies,
|
||||
override val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
override val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
) : BasicTokenListSubscriber() {
|
||||
|
||||
override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList> {
|
||||
tokenListStore.addIfNot(userWallet.walletId, coroutineScope)
|
||||
|
|
@ -45,6 +46,11 @@ internal class MultiWalletTokenListSubscriber(
|
|||
return tokenListStore.getOrThrow(userWallet.walletId)
|
||||
}
|
||||
|
||||
override fun accountListFlow(coroutineScope: CoroutineScope): Flow<AccountStatusList> {
|
||||
val params = SingleAccountStatusListProducer.Params(userWallet.walletId)
|
||||
return accountDependencies.singleAccountStatusListSupplier(params)
|
||||
}
|
||||
|
||||
override suspend fun onTokenListReceived(maybeTokenList: Lce<TokenListError, TokenList>) {
|
||||
updateSortingIfNeeded(maybeTokenList)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.subscribers
|
||||
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
|
||||
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
|
||||
import com.tangem.domain.tokens.model.TokenActionsState
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.account.AccountDependencies
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.collectLatest
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetCryptoCurrencyActionsTransformer
|
||||
|
|
@ -17,28 +20,39 @@ internal class SingleWalletButtonsSubscriber(
|
|||
private val clickIntents: WalletClickIntents,
|
||||
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
|
||||
private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
) : WalletSubscriber() {
|
||||
|
||||
override fun create(coroutineScope: CoroutineScope): Flow<TokenActionsState> {
|
||||
return channelFlow {
|
||||
getSingleCryptoCurrencyStatusUseCase.collectLatest(userWalletId = userWallet.walletId) { status ->
|
||||
getCryptoCurrencyActionsUseCase(userWallet = userWallet, cryptoCurrencyStatus = status)
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.firstOrNull()
|
||||
getCryptoCurrencyActionsUseCase(userWallet = userWallet, status = status)
|
||||
?.let { send(it) }
|
||||
}
|
||||
}
|
||||
.onEach(::updateContent)
|
||||
.onEach { actions ->
|
||||
val portfolioId = when (accountDependencies.accountsFeatureToggles.isFeatureEnabled) {
|
||||
true -> TODO("account") // get main account id for single wallet
|
||||
false -> PortfolioId(userWallet.walletId)
|
||||
}
|
||||
updateContent(actions, portfolioId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateContent(tokenActionsState: TokenActionsState) {
|
||||
private fun updateContent(tokenActionsState: TokenActionsState, portfolioId: PortfolioId) {
|
||||
stateHolder.update(
|
||||
SetCryptoCurrencyActionsTransformer(
|
||||
tokenActionsState = tokenActionsState,
|
||||
userWallet = userWallet,
|
||||
clickIntents = clickIntents,
|
||||
portfolioId = portfolioId,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getCryptoCurrencyActionsUseCase(userWallet: UserWallet, status: CryptoCurrencyStatus) =
|
||||
this.getCryptoCurrencyActionsUseCase(userWallet = userWallet, cryptoCurrencyStatus = status)
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.firstOrNull()
|
||||
}
|
||||
|
|
@ -1,38 +1,39 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.subscribers
|
||||
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
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.TokenListAnalyticsSender
|
||||
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 kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class SingleWalletWithTokenListSubscriber(
|
||||
private val userWallet: UserWallet.Cold,
|
||||
override val userWallet: UserWallet.Cold,
|
||||
private val tokenListStore: MultiWalletTokenListStore,
|
||||
stateHolder: WalletStateController,
|
||||
clickIntents: WalletClickIntents,
|
||||
tokenListAnalyticsSender: TokenListAnalyticsSender,
|
||||
walletWithFundsChecker: WalletWithFundsChecker,
|
||||
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
|
||||
) : BasicTokenListSubscriber(
|
||||
userWallet = userWallet,
|
||||
stateHolder = stateHolder,
|
||||
clickIntents = clickIntents,
|
||||
tokenListAnalyticsSender = tokenListAnalyticsSender,
|
||||
walletWithFundsChecker = walletWithFundsChecker,
|
||||
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
|
||||
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
|
||||
) {
|
||||
override val stateHolder: WalletStateController,
|
||||
override val clickIntents: WalletClickIntents,
|
||||
override val tokenListAnalyticsSender: TokenListAnalyticsSender,
|
||||
override val walletWithFundsChecker: WalletWithFundsChecker,
|
||||
override val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
override val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
|
||||
override val accountDependencies: AccountDependencies,
|
||||
override val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
override val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
) : BasicTokenListSubscriber() {
|
||||
|
||||
override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList> {
|
||||
tokenListStore.addIfNot(userWallet.walletId, coroutineScope)
|
||||
|
|
@ -40,5 +41,10 @@ internal class SingleWalletWithTokenListSubscriber(
|
|||
return tokenListStore.getOrThrow(userWallet.walletId)
|
||||
}
|
||||
|
||||
override fun accountListFlow(coroutineScope: CoroutineScope): Flow<AccountStatusList> {
|
||||
val params = SingleAccountStatusListProducer.Params(userWallet.walletId)
|
||||
return accountDependencies.singleAccountStatusListSupplier(params)
|
||||
}
|
||||
|
||||
override suspend fun onTokenListReceived(maybeTokenList: Lce<TokenListError, TokenList>) = Unit
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ private fun BasicAlert(state: WalletAlertState.Basic, onDismiss: () -> Unit) {
|
|||
if (onActionClick != null) {
|
||||
confirmButton = DialogButtonUM(
|
||||
title = state.confirmButtonText.resolveReference(),
|
||||
warning = state.isWarningConfirmButton,
|
||||
isWarning = state.isWarningConfirmButton,
|
||||
onClick = {
|
||||
onActionClick()
|
||||
onDismiss()
|
||||
|
|
@ -54,7 +54,7 @@ private fun BasicAlert(state: WalletAlertState.Basic, onDismiss: () -> Unit) {
|
|||
} else {
|
||||
confirmButton = DialogButtonUM(
|
||||
title = state.confirmButtonText.resolveReference(),
|
||||
warning = state.isWarningConfirmButton,
|
||||
isWarning = state.isWarningConfirmButton,
|
||||
onClick = onDismiss,
|
||||
)
|
||||
dismissButton = null
|
||||
|
|
@ -77,7 +77,7 @@ private fun TextInputAlert(state: WalletAlertState.TextInput, onDismiss: () -> U
|
|||
fieldValue = value,
|
||||
confirmButton = DialogButtonUM(
|
||||
title = state.confirmButtonText.resolveReference(),
|
||||
enabled = value.text.isNotEmpty() &&
|
||||
isEnabled = value.text.isNotEmpty() &&
|
||||
value.text != state.text &&
|
||||
state.errorTextProvider(value.text) == null,
|
||||
onClick = {
|
||||
|
|
|
|||
|
|
@ -3,19 +3,20 @@ package com.tangem.feature.wallet.presentation.wallet.ui
|
|||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.material3.SnackbarDuration
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.tangem.core.res.getStringSafe
|
||||
import com.tangem.core.ui.event.EventEffect
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.utils.requestPermission
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAlertState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.utils.ReviewManagerRequester
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.utils.animateScrollByIndex
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.utils.demonstrateScrolling
|
||||
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
||||
|
||||
@Composable
|
||||
internal fun WalletEventEffect(
|
||||
|
|
@ -28,6 +29,13 @@ internal fun WalletEventEffect(
|
|||
val coroutineScope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
val resources = LocalContext.current.resources
|
||||
|
||||
var showPermissionRequest by remember { mutableStateOf<Pair<() -> Unit, () -> Unit>?>(null) }
|
||||
HandlePermissionRequest(
|
||||
permissionRequestParams = showPermissionRequest,
|
||||
onPermissionRequestResult = { showPermissionRequest = null },
|
||||
)
|
||||
|
||||
EventEffect(
|
||||
event = event,
|
||||
onTrigger = { value ->
|
||||
|
|
@ -56,7 +64,34 @@ internal fun WalletEventEffect(
|
|||
is WalletEvent.DemonstrateWalletsScrollPreview -> {
|
||||
walletsListState.demonstrateScrolling(coroutineScope = coroutineScope, direction = value.direction)
|
||||
}
|
||||
is WalletEvent.RequestPushPermissions -> {
|
||||
showPermissionRequest = value.onAllow to value.onDeny
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HandlePermissionRequest(
|
||||
permissionRequestParams: Pair<() -> Unit, () -> Unit>?,
|
||||
onPermissionRequestResult: () -> Unit,
|
||||
) {
|
||||
val actualPermissionLauncher = permissionRequestParams?.let { (onAllowExternal, onDenyExternal) ->
|
||||
requestPermission(
|
||||
onAllow = {
|
||||
onAllowExternal()
|
||||
onPermissionRequestResult()
|
||||
},
|
||||
onDeny = {
|
||||
onDenyExternal()
|
||||
onPermissionRequestResult()
|
||||
},
|
||||
permission = PUSH_PERMISSION,
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(permissionRequestParams) {
|
||||
actualPermissionLauncher?.invoke()
|
||||
}
|
||||
}
|
||||
|
|
@ -79,6 +79,7 @@ import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency
|
|||
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.organizeTokensButton
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.marketPriceBlock
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.BalancesAndLimitsBottomSheet
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.TangemPayMainScreenBlock
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.VisaTxDetailsBottomSheet
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.balancesAndLimitsBlock
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.utils.changeWalletAnimator
|
||||
|
|
@ -212,6 +213,11 @@ private fun WalletContent(
|
|||
|
||||
notifications(configs = selectedWallet.warnings, modifier = itemModifier)
|
||||
|
||||
item(
|
||||
key = "TangemPayMainScreenBlock",
|
||||
contentType = state.tangemPayState::class.java,
|
||||
) { TangemPayMainScreenBlock(state.tangemPayState, itemModifier) }
|
||||
|
||||
(selectedWallet as? WalletState.SingleCurrency)?.let { walletState ->
|
||||
walletState.marketPriceBlockState?.let { marketPriceBlockState ->
|
||||
marketPriceBlock(state = marketPriceBlockState, modifier = itemModifier)
|
||||
|
|
@ -291,10 +297,10 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
listState: LazyListState,
|
||||
selectedWallet: WalletState,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
bottomSheetHeaderHeightProvider: () -> Dp,
|
||||
crossinline bottomSheetContent: @Composable () -> Unit,
|
||||
alertConfig: WalletAlertState?,
|
||||
bottomSheetHeaderHeightProvider: () -> Dp,
|
||||
noinline onBottomSheetStateChange: (BottomSheetState) -> Unit,
|
||||
crossinline bottomSheetContent: @Composable () -> Unit,
|
||||
crossinline content: @Composable (PaddingValues) -> Unit,
|
||||
) {
|
||||
val bottomSheetState = rememberTangemStandardBottomSheetState()
|
||||
|
|
|
|||
|
|
@ -350,6 +350,7 @@ private fun AdditionalInfoText(text: TextReference) {
|
|||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTheme.typography.caption2,
|
||||
modifier = Modifier.testTag(MainScreenTestTags.DEVICES_COUNT),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.tokenlist.PortfolioListItem
|
||||
import com.tangem.core.ui.components.tokenlist.PortfolioTokensListItem
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
|
|
@ -59,6 +60,7 @@ internal fun LazyListScope.portfolioTokensList(
|
|||
contentType = { _, item -> item::class.java },
|
||||
itemContent = { tokenIndex, token ->
|
||||
val indexWithHeader = tokenIndex.inc()
|
||||
val lastIndex = tokens.lastIndex.inc()
|
||||
val isPreview = LocalInspectionMode.current
|
||||
val appear = remember {
|
||||
MutableTransitionState(isPreview).apply { targetState = true }
|
||||
|
|
@ -69,11 +71,12 @@ internal fun LazyListScope.portfolioTokensList(
|
|||
.animateItem()
|
||||
.roundedShapeItemDecoration(
|
||||
currentIndex = indexWithHeader,
|
||||
lastIndex = tokens.lastIndex.inc(),
|
||||
lastIndex = lastIndex,
|
||||
backgroundColor = TangemTheme.colors.background.primary,
|
||||
),
|
||||
visibleState = appear,
|
||||
) {
|
||||
val modifier = if (indexWithHeader == lastIndex) Modifier.padding(bottom = 8.dp) else Modifier
|
||||
PortfolioTokensListItem(
|
||||
state = token,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
|
|
@ -114,10 +117,15 @@ private fun LazyListScope.portfolioItem(
|
|||
modifier = anchorModifier,
|
||||
visibleState = appear,
|
||||
) {
|
||||
val modifier = if (portfolio.tokens.isEmpty()) {
|
||||
Modifier.padding(vertical = 8.dp)
|
||||
} else {
|
||||
Modifier.padding(top = 8.dp)
|
||||
}
|
||||
PortfolioListItem(
|
||||
state = portfolio,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -15,7 +14,6 @@ import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
|
|||
import com.tangem.core.ui.components.buttons.actions.ActionButtonContent
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.test.MainScreenTestTags
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -50,7 +48,7 @@ internal fun MultiCurrencyAction(
|
|||
paddingBetweenIconAndText = 4.dp,
|
||||
)
|
||||
},
|
||||
modifier = modifier.testTag(MainScreenTestTags.MULTI_CURRENCY_ACTION_BUTTON),
|
||||
modifier = modifier,
|
||||
color = TangemTheme.colors.button.secondary,
|
||||
)
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ internal fun LazyListScope.organizeTokensButton(
|
|||
text = resourceReference(id = R.string.organize_tokens_title),
|
||||
iconResId = R.drawable.ic_filter_24,
|
||||
onClick = onClick,
|
||||
enabled = isEnabled,
|
||||
isEnabled = isEnabled,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
|
||||
|
||||
@Composable
|
||||
internal fun TangemPayCardMainBlock(state: TangemPayState.Card, modifier: Modifier = Modifier) {
|
||||
Surface(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
color = TangemTheme.colors.background.primary,
|
||||
onClick = state.onClick,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(IntrinsicSize.Min)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.img_tangem_pay_card_main_36),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(36.dp).clip(CircleShape),
|
||||
)
|
||||
|
||||
Spacer(Modifier.width(12.dp))
|
||||
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.tangempay_kyc_card_ready_notification_title),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(6.dp))
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.img_visa_label_26_16),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(26.dp, 16.dp),
|
||||
)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text(
|
||||
text = state.lastFourDigits.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
contentAlignment = Alignment.TopEnd,
|
||||
) {
|
||||
Text(
|
||||
text = state.balanceText.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.End,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -68,11 +68,11 @@ private fun BalancesAndLimitsBlock(state: BalancesAndLimitsBlockState, modifier:
|
|||
@Composable
|
||||
private inline fun ContentContainer(
|
||||
enabled: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
noinline onClick: () -> Unit,
|
||||
crossinline title: @Composable () -> Unit,
|
||||
crossinline content: @Composable () -> Unit,
|
||||
crossinline endIcon: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
|
|
|
|||
|
|
@ -133,10 +133,10 @@ private fun InfoButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
|
|||
|
||||
@Composable
|
||||
private inline fun ContentContainer(
|
||||
modifier: Modifier = Modifier,
|
||||
title: @Composable BoxScope.() -> Unit,
|
||||
firstBlock: @Composable ColumnScope.() -> Unit,
|
||||
secondBlock: @Composable ColumnScope.() -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.background(TangemTheme.colors.background.secondary),
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ private const val BLOCK_ITEM_VALUE_WEIGHT = .55f
|
|||
@Composable
|
||||
internal inline fun BlockContent(
|
||||
title: TextReference,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
description: @Composable RowScope.() -> Unit = {},
|
||||
) {
|
||||
Column(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState.Progress
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.TangemPayCardMainBlock
|
||||
|
||||
@Composable
|
||||
internal fun TangemPayMainScreenBlock(state: TangemPayState, modifier: Modifier = Modifier) {
|
||||
when (state) {
|
||||
is Progress -> {
|
||||
Notification(
|
||||
modifier = modifier
|
||||
.fillMaxWidth(),
|
||||
config = NotificationConfig(
|
||||
title = state.title,
|
||||
iconSize = 36.dp,
|
||||
subtitle = TextReference.EMPTY,
|
||||
iconResId = state.iconRes,
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = state.buttonText,
|
||||
onClick = state.onButtonClick,
|
||||
shouldShowProgress = state.showProgress,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
is TangemPayState.Card -> TangemPayCardMainBlock(state, modifier)
|
||||
is TangemPayState.Empty -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun ResetCardScreenPreview() {
|
||||
TangemThemePreview {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
|
||||
TangemPayMainScreenBlock(
|
||||
Progress(
|
||||
title = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_title),
|
||||
buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button),
|
||||
iconRes = R.drawable.ic_promo_kyc_36,
|
||||
onButtonClick = {},
|
||||
),
|
||||
)
|
||||
|
||||
TangemPayMainScreenBlock(
|
||||
Progress(
|
||||
title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
|
||||
buttonText = TextReference.Res(R.string.common_continue),
|
||||
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
|
||||
onButtonClick = {},
|
||||
),
|
||||
)
|
||||
|
||||
TangemPayMainScreenBlock(
|
||||
Progress(
|
||||
title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
|
||||
buttonText = TextReference.EMPTY,
|
||||
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
|
||||
onButtonClick = {},
|
||||
showProgress = true,
|
||||
),
|
||||
)
|
||||
|
||||
TangemPayMainScreenBlock(
|
||||
TangemPayState.Card(
|
||||
lastFourDigits = TextReference.Str("*1234"),
|
||||
balanceText = TextReference.Str("$ 0.00"),
|
||||
onClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import com.tangem.domain.models.wallet.UserWallet
|
|||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetCardImageUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.wallet.utils.UserWalletImageFetcher
|
||||
import com.tangem.operations.attestation.ArtworkSize
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
|
@ -17,12 +18,21 @@ import javax.inject.Inject
|
|||
class DefaultUserWalletImageFetcher @Inject constructor(
|
||||
private val getCardImageUseCase: GetCardImageUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getWalletsUseCase: GetWalletsUseCase,
|
||||
private val artworkUMConverter: ArtworkUMConverter,
|
||||
) : UserWalletImageFetcher {
|
||||
|
||||
private val smallCache = MutableStateFlow(mapOf<String, ArtworkUM>())
|
||||
private val largeCache = MutableStateFlow(mapOf<String, ArtworkUM>())
|
||||
|
||||
override fun allWallets(size: ArtworkSize): Flow<Map<UserWalletId, UserWalletItemUM.ImageState>> =
|
||||
getWalletsUseCase.invoke()
|
||||
.map { list -> list.map { it.walletId } }
|
||||
.distinctUntilChanged()
|
||||
.map { list -> list.map { walletId -> walletImage(walletId, size).map { image -> walletId to image } } }
|
||||
.flatMapLatest { flows -> combine(flows) { it.toMap() } }
|
||||
.distinctUntilChanged()
|
||||
|
||||
override fun walletImage(wallet: UserWallet, size: ArtworkSize): Flow<UserWalletItemUM.ImageState> = when (wallet) {
|
||||
is UserWallet.Cold -> walletImage(wallet.scanResponse.card, size)
|
||||
is UserWallet.Hot -> flowOf(UserWalletItemUM.ImageState.MobileWallet)
|
||||
|
|
@ -31,23 +41,20 @@ class DefaultUserWalletImageFetcher @Inject constructor(
|
|||
override fun walletsImage(
|
||||
wallets: Collection<UserWallet>,
|
||||
size: ArtworkSize,
|
||||
): Flow<Map<UserWalletId, UserWalletItemUM.ImageState>> = wallets
|
||||
.map { userWallet -> walletImage(userWallet, size).map { imageState -> userWallet.walletId to imageState } }
|
||||
.merge()
|
||||
.runningFold(mapOf<UserWalletId, UserWalletItemUM.ImageState>()) { map, newState -> map.plus(newState) }
|
||||
.filter { it.size >= wallets.size } // prevent spam, waiting full map
|
||||
): Flow<Map<UserWalletId, UserWalletItemUM.ImageState>> = allWallets(size)
|
||||
.map { allWallets -> allWallets.filter { (walletId, _) -> wallets.any { it.walletId == walletId } } }
|
||||
.distinctUntilChanged()
|
||||
|
||||
override fun walletImage(walletId: UserWalletId, size: ArtworkSize): Flow<UserWalletItemUM.ImageState> = flow {
|
||||
val imagesFlow = getUserWalletUseCase.invokeFlow(walletId)
|
||||
// emit Loading and wait wallet
|
||||
.onEach { if (it.isLeft()) emit(UserWalletItemUM.ImageState.Loading) }
|
||||
.filterIsInstance<Either.Right<UserWallet>>()
|
||||
.map { it.value }
|
||||
override fun walletImage(walletId: UserWalletId, size: ArtworkSize): Flow<UserWalletItemUM.ImageState> =
|
||||
getUserWalletUseCase.invokeFlow(walletId)
|
||||
.transform { either ->
|
||||
if (either.isLeft()) {
|
||||
emit(UserWalletItemUM.ImageState.Loading)
|
||||
} else if (either is Either.Right) {
|
||||
emitAll(walletImage(either.value, size))
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.flatMapLatest { wallet -> walletImage(wallet, size) }
|
||||
emitAll(imagesFlow)
|
||||
}.distinctUntilChanged()
|
||||
|
||||
override fun walletImage(cardDTO: CardDTO, size: ArtworkSize): Flow<UserWalletItemUM.ImageState> =
|
||||
internalGetCardImage(
|
||||
|
|
|
|||
|
|
@ -130,6 +130,11 @@ internal class DefaultUserWalletsFetcher @AssistedInject constructor(
|
|||
balance = balance,
|
||||
isBalanceHidden = balanceHidingSettings.isBalanceHidden,
|
||||
artwork = artworks[userWallet.walletId],
|
||||
endIcon = if (isAuthMode.not() && userWallet is UserWallet.Hot && !userWallet.backedUp) {
|
||||
UserWalletItemUM.EndIcon.Warning
|
||||
} else {
|
||||
UserWalletItemUM.EndIcon.None
|
||||
},
|
||||
isAuthMode = isAuthMode,
|
||||
)
|
||||
.convert(userWallet)
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Loading…
Add table
Add a link
Reference in a new issue