Updated on 2026-08-14
This commit is contained in:
commit
808cd4a301
503 changed files with 12306 additions and 3036 deletions
|
|
@ -18,6 +18,7 @@ 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.TangemPayMainScreenCustomerInfoUseCase
|
||||
import com.tangem.domain.settings.*
|
||||
import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase
|
||||
import com.tangem.domain.wallets.usecase.*
|
||||
|
|
@ -33,12 +34,14 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent
|
|||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent.DemonstrateWalletsScrollPreview.Direction
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.*
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TangemPayStateConverter
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider
|
||||
import com.tangem.features.biometry.AskBiometryComponent
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks
|
||||
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
||||
import com.tangem.features.tangempay.TangemPayFeatureToggles
|
||||
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionListener
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.*
|
||||
|
|
@ -84,6 +87,9 @@ internal class WalletModel @Inject constructor(
|
|||
private val getIsHuaweiDeviceWithoutGoogleServicesUseCase: GetIsHuaweiDeviceWithoutGoogleServicesUseCase,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase,
|
||||
private val tangemPayStateConverter: TangemPayStateConverter,
|
||||
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
|
||||
val screenLifecycleProvider: ScreenLifecycleProvider,
|
||||
val innerWalletRouter: InnerWalletRouter,
|
||||
) : Model() {
|
||||
|
|
@ -112,6 +118,7 @@ internal class WalletModel @Inject constructor(
|
|||
subscribeOnSelectedWalletFlow()
|
||||
subscribeToScreenBackgroundState()
|
||||
subscribeOnPushNotificationsPermission()
|
||||
subscribeToTangemPayInfo()
|
||||
enableNotificationsIfNeeded()
|
||||
|
||||
clickIntents.initialize(innerWalletRouter, modelScope)
|
||||
|
|
@ -313,6 +320,27 @@ internal class WalletModel @Inject constructor(
|
|||
.saveIn(clearNFTCacheJobHolder)
|
||||
}
|
||||
|
||||
private fun subscribeToTangemPayInfo() {
|
||||
/**
|
||||
* Update state each time a user opens/returns to wallet screen
|
||||
*/
|
||||
screenLifecycleProvider.isBackgroundState.onEach { isBackground ->
|
||||
if (!isBackground) { updateTangemPayInfo() }
|
||||
}.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun updateTangemPayInfo() {
|
||||
if (tangemPayFeatureToggles.isTangemPayEnabled) {
|
||||
modelScope.launch {
|
||||
tangemPayMainScreenCustomerInfoUseCase()?.let { info ->
|
||||
stateHolder.update {
|
||||
it.copy(tangemPayState = tangemPayStateConverter.convert(info))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun needToRefreshTimer() {
|
||||
modelScope.launch {
|
||||
delay(REFRESH_WALLET_BACKGROUND_TIMER_MILLIS)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ 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.isLocked
|
||||
|
|
@ -16,6 +17,7 @@ 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
|
||||
|
|
@ -46,6 +48,10 @@ internal interface WalletContentClickIntents {
|
|||
|
||||
fun onTokenItemLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onAccountExpandClick(account: Account)
|
||||
|
||||
fun onAccountCollapseClick(account: Account)
|
||||
|
||||
fun onTransactionClick(txHash: String)
|
||||
|
||||
fun onDissmissBottomSheet()
|
||||
|
|
@ -78,6 +84,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
private val walletEventSender: WalletEventSender,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
) : BaseWalletClickIntents(), WalletContentClickIntents {
|
||||
|
||||
override fun onDetailsClick() {
|
||||
|
|
@ -157,6 +164,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(
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
package com.tangem.feature.wallet.child.wallet.model.intents
|
||||
|
||||
import android.os.Build
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.TangemBlogUrlBuilder
|
||||
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.navigation.url.UrlOpener
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.wallets.usecase.DerivePublicKeysUseCase
|
||||
import com.tangem.domain.card.SetCardWasScannedUseCase
|
||||
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
|
|
@ -20,17 +21,21 @@ 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
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
||||
import com.tangem.domain.settings.NeverToSuggestRateAppUseCase
|
||||
import com.tangem.domain.settings.RemindToRateAppLaterUseCase
|
||||
import com.tangem.domain.settings.repositories.PermissionRepository
|
||||
import com.tangem.domain.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
||||
import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent
|
||||
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
|
||||
|
|
@ -54,6 +59,7 @@ import kotlinx.coroutines.launch
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
internal interface WalletWarningsClickIntents {
|
||||
|
||||
fun onAddBackupCardClick()
|
||||
|
|
@ -93,9 +99,13 @@ internal interface WalletWarningsClickIntents {
|
|||
fun onSeedPhraseSecondNotificationReject()
|
||||
|
||||
fun onFinishWalletActivationClick()
|
||||
|
||||
fun onAllowPermissions()
|
||||
|
||||
fun onDenyPermissions()
|
||||
}
|
||||
|
||||
@Suppress("LargeClass", "LongParameterList")
|
||||
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
|
||||
@ModelScoped
|
||||
internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
||||
private val stateHolder: WalletStateController,
|
||||
|
|
@ -121,6 +131,9 @@ 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 permissionRepository: PermissionRepository,
|
||||
) : BaseWalletClickIntents(), WalletWarningsClickIntents {
|
||||
|
||||
override fun onAddBackupCardClick() {
|
||||
|
|
@ -422,6 +435,39 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
|||
appRouter.push(AppRoute.WalletActivation(userWallet.walletId))
|
||||
}
|
||||
|
||||
override fun onAllowPermissions() {
|
||||
if (isNotificationsPermissionGranted()) {
|
||||
updateSubscribeOnPushPermissions(true)
|
||||
} else {
|
||||
walletEventSender.send(
|
||||
event = WalletEvent.RequestPushPermissions(
|
||||
onAllow = { updateSubscribeOnPushPermissions(true) },
|
||||
onDeny = { updateSubscribeOnPushPermissions(false) },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDenyPermissions() = updateSubscribeOnPushPermissions(false)
|
||||
|
||||
private fun updateSubscribeOnPushPermissions(shouldAllow: Boolean) {
|
||||
modelScope.launch {
|
||||
notificationsRepository.setUserAllowToSubscribeOnPushNotifications(shouldAllow)
|
||||
setShouldShowNotificationUseCase(
|
||||
key = NotificationId.EnablePushesReminderNotification.key,
|
||||
value = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isNotificationsPermissionGranted(): Boolean {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
permissionRepository.hasRuntimePermission(android.Manifest.permission.POST_NOTIFICATIONS)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchCryptoCurrencies(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
coroutineScope {
|
||||
listOf(
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
|
|||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent
|
||||
import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent.*
|
||||
import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent.Program
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
|
||||
|
|
@ -49,11 +49,11 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
|
|||
is WalletNotification.RateApp -> MainScreen.HowDoYouLikeTangem
|
||||
is WalletNotification.Critical.BackupError -> MainScreen.BackupError
|
||||
is WalletNotification.NoteMigration -> MainScreen.NotePromo
|
||||
is WalletNotification.SwapPromo -> TokenSwapPromoAnalyticsEvent.NoticePromotionBanner(
|
||||
is WalletNotification.SwapPromo -> NoticePromotionBanner(
|
||||
source = AnalyticsParam.ScreensSources.Main,
|
||||
program = Program.Empty, // Use it on new promo action
|
||||
)
|
||||
is WalletNotification.Sepa -> TokenSwapPromoAnalyticsEvent.NoticePromotionBanner(
|
||||
is WalletNotification.Sepa -> NoticePromotionBanner(
|
||||
source = AnalyticsParam.ScreensSources.Main,
|
||||
program = Program.Sepa,
|
||||
)
|
||||
|
|
@ -66,9 +66,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 -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
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
|
||||
|
|
@ -18,6 +19,7 @@ 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
|
||||
|
|
@ -31,16 +33,18 @@ import com.tangem.domain.wallets.usecase.SeedPhraseNotificationUseCase
|
|||
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.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 timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
@ModelScoped
|
||||
internal class GetMultiWalletWarningsFactory @Inject constructor(
|
||||
private val tokenListStore: MultiWalletTokenListStore,
|
||||
|
|
@ -53,19 +57,31 @@ 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,
|
||||
) {
|
||||
|
||||
@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.Referral),
|
||||
flow6 = shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.Sepa),
|
||||
) { maybeTokenList, isReadyToShowRating, isNeedToBackup, seedPhraseIssueStatus, shouldShowReferralPromo, shouldShowSepaBanner ->
|
||||
return combine(
|
||||
tokenListStore.getOrThrow(userWallet.walletId),
|
||||
isReadyToShowRateAppUseCase(),
|
||||
isNeedToBackupUseCase(userWallet.walletId),
|
||||
seedPhraseNotificationUseCase(userWalletId = userWallet.walletId),
|
||||
shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.Referral),
|
||||
shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.Sepa),
|
||||
notificationsRepository.getShouldShowNotification(NotificationId.EnablePushesReminderNotification.key),
|
||||
) { array ->
|
||||
val maybeTokenList = array[0] as Lce<TokenListError, TokenList>
|
||||
val isReadyToShowRating = array[1] as Boolean
|
||||
val isNeedToBackup = array[2] as Boolean
|
||||
val seedPhraseIssueStatus = array[3] as SeedPhraseNotificationsStatus
|
||||
val shouldShowReferralPromo = array[4] as Boolean
|
||||
val shouldShowSepaBanner = array[5] as Boolean
|
||||
val shouldShowEnablePushesReminderNotification = array[6] as Boolean
|
||||
|
||||
buildList {
|
||||
addUsedOutdatedDataNotification(maybeTokenList)
|
||||
|
||||
|
|
@ -81,6 +97,14 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
|
||||
addWarningNotifications(cardTypesResolver, maybeTokenList, isNeedToBackup, clickIntents)
|
||||
|
||||
addPushReminderNotification(
|
||||
clickIntents = clickIntents,
|
||||
shouldShowPushReminderBanner = shouldShowEnablePushesReminderNotification &&
|
||||
!notificationsRepository.isUserAllowToSubscribeOnPushNotifications(),
|
||||
)
|
||||
|
||||
addYieldSupplyNotifications(maybeTokenList)
|
||||
|
||||
val hasCriticalOrWarning = any { notification ->
|
||||
notification is WalletNotification.Critical || notification is WalletNotification.Warning
|
||||
}
|
||||
|
|
@ -266,6 +290,15 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addYieldSupplyNotifications(
|
||||
tokenList: Lce<TokenListError, TokenList>,
|
||||
) {
|
||||
addIf(
|
||||
element = WalletNotification.Warning.YeildSupplyApprove,
|
||||
condition = tokenList.hasTokensWithActivatedSupplyWithoutApprove(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addWarningNotifications(
|
||||
cardTypesResolver: CardTypesResolver?,
|
||||
tokenList: Lce<TokenListError, TokenList>,
|
||||
|
|
@ -290,12 +323,33 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addPushReminderNotification(
|
||||
clickIntents: WalletClickIntents,
|
||||
shouldShowPushReminderBanner: Boolean,
|
||||
) {
|
||||
addIf(
|
||||
element = WalletNotification.PushNotifications(
|
||||
onCloseClick = clickIntents::onDenyPermissions,
|
||||
onEnabledClick = clickIntents::onAllowPermissions,
|
||||
),
|
||||
condition = shouldShowPushReminderBanner,
|
||||
)
|
||||
}
|
||||
|
||||
private fun Lce<TokenListError, TokenList>.hasUnreachableNetworks(): Boolean {
|
||||
val tokenList = getOrNull(isPartialContentAccepted = false) ?: return false
|
||||
|
||||
return tokenList.flattenCurrencies().any { it.value is CryptoCurrencyStatus.Unreachable }
|
||||
}
|
||||
|
||||
private fun Lce<TokenListError, TokenList>.hasTokensWithActivatedSupplyWithoutApprove(): Boolean {
|
||||
val tokenList = getOrNull(isPartialContentAccepted = false) ?: return false
|
||||
val yieldSupplyEnabled = yieldSupplyFeatureToggles.isYieldSupplyFeatureEnabled
|
||||
return yieldSupplyEnabled && tokenList.flattenCurrencies().any {
|
||||
it.value.yieldSupplyStatus?.isAllowedToSpend == false
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addRateTheAppNotification(
|
||||
isReadyToShowRating: Boolean,
|
||||
clickIntents: WalletClickIntents,
|
||||
|
|
@ -355,10 +409,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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,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 +41,7 @@ internal class MultiWalletContentLoader(
|
|||
private val getStoryContentUseCase: GetStoryContentUseCase,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
) : WalletContentLoader(id = userWallet.walletId) {
|
||||
|
||||
override fun create(): List<WalletSubscriber> {
|
||||
|
|
@ -54,6 +56,7 @@ internal class MultiWalletContentLoader(
|
|||
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
|
||||
applyTokenListSortingUseCase = applyTokenListSortingUseCase,
|
||||
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
|
||||
accountDependencies = accountDependencies,
|
||||
).let(::add)
|
||||
|
||||
WalletNFTListSubscriber(
|
||||
|
|
|
|||
|
|
@ -18,6 +18,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 +39,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
|
|||
private val walletsRepository: WalletsRepository,
|
||||
private val getNFTCollectionsUseCase: GetNFTCollectionsUseCase,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
) {
|
||||
|
||||
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): WalletContentLoader {
|
||||
|
|
@ -59,6 +61,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
|
|||
walletsRepository = walletsRepository,
|
||||
getNFTCollectionsUseCase = getNFTCollectionsUseCase,
|
||||
currenciesRepository = currenciesRepository,
|
||||
accountDependencies = accountDependencies,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,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 +31,7 @@ internal class SingleWalletWithTokenContentLoader(
|
|||
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
|
||||
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
|
||||
private val getStoryContentUseCase: GetStoryContentUseCase,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
) : WalletContentLoader(id = userWallet.walletId) {
|
||||
|
||||
override fun create(): List<WalletSubscriber> {
|
||||
|
|
@ -43,6 +45,7 @@ internal class SingleWalletWithTokenContentLoader(
|
|||
tokenListStore = tokenListStore,
|
||||
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
|
||||
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
|
||||
accountDependencies = accountDependencies,
|
||||
).let(::add)
|
||||
MultiWalletWarningsSubscriber(
|
||||
userWallet = userWallet,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,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 +32,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
|
|||
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
|
||||
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
|
||||
private val getStoryContentUseCase: GetStoryContentUseCase,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
) {
|
||||
|
||||
fun create(userWallet: UserWallet.Cold, clickIntents: WalletClickIntents): SingleWalletWithTokenContentLoader {
|
||||
|
|
@ -48,6 +50,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
|
|||
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
|
||||
getStoryContentUseCase = getStoryContentUseCase,
|
||||
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,
|
||||
accountDependencies = accountDependencies,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
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,
|
||||
) : TangemPayState()
|
||||
|
||||
data class Card(
|
||||
val lastFourDigits: TextReference,
|
||||
val balanceText: TextReference,
|
||||
) : TangemPayState()
|
||||
}
|
||||
|
|
@ -30,4 +30,9 @@ internal sealed class WalletEvent {
|
|||
RIGHT,
|
||||
}
|
||||
}
|
||||
|
||||
data class RequestPushPermissions(
|
||||
val onAllow: () -> Unit,
|
||||
val onDeny: () -> Unit,
|
||||
) : WalletEvent()
|
||||
}
|
||||
|
|
@ -138,6 +138,12 @@ sealed class WalletNotification(val config: NotificationConfig) {
|
|||
formatArgs = wrappedList(count),
|
||||
),
|
||||
)
|
||||
|
||||
data object YeildSupplyApprove : Warning(
|
||||
title = resourceReference(R.string.yield_module_main_view_approve_notification_title),
|
||||
// todo yield supply lokalise
|
||||
subtitle = resourceReference(R.string.yield_module_main_view_approve_notification_title),
|
||||
)
|
||||
}
|
||||
|
||||
sealed class Informational(
|
||||
|
|
@ -309,4 +315,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,
|
||||
)
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
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.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
|
||||
|
|
@ -13,7 +12,7 @@ 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,
|
||||
|
|
@ -42,8 +41,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,7 +54,7 @@ internal class SetTokenListTransformer(
|
|||
|
||||
private fun WalletTokensListState.toLoadedState(): WalletTokensListState {
|
||||
return TokenListStateConverter(
|
||||
tokenList = tokenList,
|
||||
params = params,
|
||||
selectedWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
clickIntents = clickIntents,
|
||||
|
|
|
|||
|
|
@ -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())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
||||
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
|
||||
sealed interface TokenConverterParams {
|
||||
data class Wallet(
|
||||
val tokenList: TokenList,
|
||||
) : TokenConverterParams
|
||||
|
||||
data class Account(
|
||||
val accountList: AccountStatusList,
|
||||
val expandedAccounts: Set<AccountId>,
|
||||
) : TokenConverterParams
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
|
||||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
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.model.CustomerInfo.CardInfo
|
||||
import com.tangem.domain.pay.model.CustomerInfo
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
|
||||
import com.tangem.utils.converter.Converter
|
||||
import java.util.Currency
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class TangemPayStateConverter @Inject constructor(
|
||||
private val router: Router,
|
||||
) : Converter<CustomerInfo, TangemPayState> {
|
||||
|
||||
override fun convert(value: CustomerInfo): TangemPayState {
|
||||
val route = AppRoute.TangemPayOnboarding(AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding)
|
||||
val cardInfo = value.cardInfo
|
||||
return when {
|
||||
!value.isKycApproved() -> 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 = { router.push(route) },
|
||||
)
|
||||
!value.isProductInstanceActive() -> TangemPayState.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 = { router.push(route) },
|
||||
)
|
||||
cardInfo != null -> {
|
||||
TangemPayState.Card(
|
||||
lastFourDigits = TextReference.Str("*${cardInfo.lastFourDigits}"),
|
||||
balanceText = TextReference.Str(getBalanceText(cardInfo)),
|
||||
)
|
||||
}
|
||||
else -> TangemPayState.Empty
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBalanceText(cardInfo: CardInfo): String {
|
||||
val currency = Currency.getInstance(cardInfo.currencyCode)
|
||||
return cardInfo.balance.format {
|
||||
fiat(fiatCurrencyCode = currency.currencyCode, fiatCurrencySymbol = currency.symbol)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,16 @@
|
|||
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.TotalFiatBalance
|
||||
import com.tangem.domain.models.account.Account
|
||||
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
|
||||
|
|
@ -14,39 +18,98 @@ import com.tangem.domain.models.wallet.UserWallet
|
|||
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,
|
||||
) : Converter<WalletTokensListState, WalletTokensListState> {
|
||||
|
||||
private val tokenStatusConverter = TokenItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
onItemClick = { _, status -> clickIntents.onTokenItemClick(status) },
|
||||
onItemLongClick = { _, status -> clickIntents.onTokenItemLongClick(status) },
|
||||
)
|
||||
private val tokenStatusConverter = when (params) {
|
||||
is TokenConverterParams.Account, // todo account Click with accountId param
|
||||
is TokenConverterParams.Wallet,
|
||||
-> TokenItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
onItemClick = { _, status -> clickIntents.onTokenItemClick(status) },
|
||||
onItemLongClick = { _, status -> clickIntents.onTokenItemLongClick(status) },
|
||||
)
|
||||
}
|
||||
|
||||
override fun convert(value: WalletTokensListState): WalletTokensListState {
|
||||
return 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,
|
||||
),
|
||||
return when (params) {
|
||||
is TokenConverterParams.Account -> convertAccountList(params)
|
||||
is TokenConverterParams.Wallet -> convertTokenList(params.tokenList)
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertTokenList(tokenList: TokenList): WalletTokensListState = when (tokenList) {
|
||||
is TokenList.Empty -> WalletTokensListState.Empty
|
||||
is TokenList.GroupedByNetwork -> WalletTokensListState.ContentState.Content(
|
||||
items = tokenList.toGroupedItems(),
|
||||
organizeTokensButtonConfig = getOrganizeTokensButtonState(tokenList = tokenList),
|
||||
)
|
||||
is TokenList.Ungrouped -> WalletTokensListState.ContentState.Content(
|
||||
items = tokenList.toUngroupedItems(),
|
||||
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,
|
||||
)
|
||||
is TokenList.Ungrouped -> WalletTokensListState.ContentState.Content(
|
||||
items = tokenList.toUngroupedItems(),
|
||||
organizeTokensButtonConfig = getOrganizeTokensButtonState(currenciesSize = tokenList.currencies.size),
|
||||
val accountItem = converter.convert(tokenList.totalFiatBalance)
|
||||
val tokensListState = convertTokenList(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(
|
||||
state = accountItem,
|
||||
isExpanded = isExtend,
|
||||
tokens = items.filterIsInstance<PortfolioTokensListItemUM>(),
|
||||
)
|
||||
}
|
||||
|
||||
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> {
|
||||
|
|
@ -83,7 +146,12 @@ internal class TokenListStateConverter(
|
|||
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,
|
||||
|
|
|
|||
|
|
@ -1,50 +1,61 @@
|
|||
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.account.AccountStatus
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
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.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
|
||||
|
||||
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 ->
|
||||
|
|
@ -60,40 +71,40 @@ 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
|
||||
}
|
||||
flow2 = appCurrencyFlow(),
|
||||
transform = { maybeTokenList, appCurrency -> singleAccountTransform(maybeTokenList, appCurrency) },
|
||||
)
|
||||
}
|
||||
|
||||
val tokenList = maybeTokenList.getOrElse(
|
||||
ifLoading = { maybeContent ->
|
||||
val isRefreshing = stateHolder.getWalletState(userWallet.walletId)
|
||||
?.pullToRefreshConfig
|
||||
?.isRefreshing == true
|
||||
private suspend fun singleAccountTransform(
|
||||
maybeTokenList: Lce<TokenListError, TokenList>,
|
||||
appCurrency: AppCurrency,
|
||||
) {
|
||||
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
|
||||
},
|
||||
maybeContent
|
||||
?.takeIf { !isRefreshing }
|
||||
?: return
|
||||
},
|
||||
ifError = { e ->
|
||||
Timber.e("Failed to load token list: $e")
|
||||
stateHolder.update(
|
||||
SetTokenListErrorTransformer(
|
||||
selectedWallet = userWallet,
|
||||
error = e,
|
||||
appCurrency = appCurrency,
|
||||
),
|
||||
)
|
||||
|
||||
updateContent(tokenList, appCurrency)
|
||||
walletWithFundsChecker.check(tokenList)
|
||||
return
|
||||
},
|
||||
)
|
||||
|
||||
updateContent(TokenConverterParams.Wallet(tokenList), appCurrency)
|
||||
walletWithFundsChecker.check(tokenList)
|
||||
}
|
||||
|
||||
private suspend fun startCheck(maybeTokenList: Lce<TokenListError, TokenList>) {
|
||||
|
|
@ -110,6 +121,69 @@ internal abstract class BasicTokenListSubscriber(
|
|||
}
|
||||
}
|
||||
|
||||
private fun createAccountListFlow(coroutineScope: CoroutineScope): Flow<*> = combine(
|
||||
flow = accountListFlow(coroutineScope)
|
||||
// todo account analytics for account total balance
|
||||
/*.onEach { maybeTokenList ->
|
||||
coroutineScope.launch {
|
||||
sendTokenListAnalytics(maybeTokenList)
|
||||
}.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(),
|
||||
transform = { accountList, appCurrency, expandedAccounts, isAccountMode ->
|
||||
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)
|
||||
|
||||
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)
|
||||
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(maybeTokenList: Lce<TokenListError, TokenList>) {
|
||||
val displayedState = stateHolder.getWalletStateIfSelected(userWallet.walletId)
|
||||
|
||||
|
|
@ -120,14 +194,23 @@ internal abstract class BasicTokenListSubscriber(
|
|||
)
|
||||
}
|
||||
|
||||
private fun updateContent(tokenList: TokenList, appCurrency: AppCurrency) {
|
||||
private fun updateContent(params: TokenConverterParams, appCurrency: AppCurrency) {
|
||||
stateHolder.update(
|
||||
SetTokenListTransformer(
|
||||
tokenList = tokenList,
|
||||
params = params,
|
||||
userWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
clickIntents = clickIntents,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun appCurrencyFlow(): Flow<AppCurrency> = getSelectedAppCurrencyUseCase()
|
||||
.map {
|
||||
it.getOrElse { e ->
|
||||
Timber.e("Failed to load app currency: $e")
|
||||
AppCurrency.Default
|
||||
}
|
||||
}
|
||||
.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
|
||||
|
|
@ -12,32 +14,27 @@ import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
|
|||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
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,
|
||||
) : BasicTokenListSubscriber() {
|
||||
|
||||
override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList> {
|
||||
tokenListStore.addIfNot(userWallet.walletId, coroutineScope)
|
||||
|
|
@ -45,6 +42,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,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,31 +10,26 @@ import com.tangem.domain.models.wallet.UserWallet
|
|||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
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,
|
||||
) : BasicTokenListSubscriber() {
|
||||
|
||||
override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList> {
|
||||
tokenListStore.addIfNot(userWallet.walletId, coroutineScope)
|
||||
|
|
@ -40,5 +37,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
|
||||
}
|
||||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
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,
|
||||
) {
|
||||
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,79 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
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.ui.components.singlecurrency.TangemPayCardMainBlock
|
||||
|
||||
@Composable
|
||||
internal fun TangemPayMainScreenBlock(state: TangemPayState, modifier: Modifier = Modifier) {
|
||||
AnimatedVisibility(state !is TangemPayState.Empty) {
|
||||
when (state) {
|
||||
is TangemPayState.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,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
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(
|
||||
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 = {},
|
||||
),
|
||||
)
|
||||
|
||||
TangemPayMainScreenBlock(
|
||||
TangemPayState.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(
|
||||
TangemPayState.Card(
|
||||
lastFourDigits = TextReference.Str("*1234"),
|
||||
balanceText = TextReference.Str("$ 0.00"),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue