Updated on 2026-08-14
This commit is contained in:
commit
7e48e67354
942 changed files with 40912 additions and 7544 deletions
|
|
@ -23,6 +23,7 @@ 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
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.KycRejectedComponent
|
||||
import com.tangem.feature.walletsettings.component.RenameWalletComponent
|
||||
import com.tangem.features.biometry.AskBiometryComponent
|
||||
import com.tangem.features.feed.entry.components.FeedEntryComponent
|
||||
|
|
@ -54,7 +55,10 @@ internal class WalletComponent @AssistedInject constructor(
|
|||
private val model: WalletModel = getOrCreateModel()
|
||||
|
||||
private val feedEntryComponent by lazy {
|
||||
feedEntryComponentFactory.create(child("feedEntryComponent"))
|
||||
feedEntryComponentFactory.create(
|
||||
context = child("feedEntryComponent"),
|
||||
entryRoute = null,
|
||||
)
|
||||
}
|
||||
private val marketsEntryComponent by lazy {
|
||||
marketsEntryComponentFactory.create(child("marketsEntryComponent"))
|
||||
|
|
@ -123,6 +127,17 @@ internal class WalletComponent @AssistedInject constructor(
|
|||
),
|
||||
)
|
||||
}
|
||||
is WalletDialogConfig.KycRejected -> {
|
||||
KycRejectedComponent(
|
||||
appComponentContext = childByContext(componentContext),
|
||||
params = KycRejectedComponent.Params(
|
||||
walletId = dialogConfig.walletId,
|
||||
customerId = dialogConfig.customerId,
|
||||
callbacks = model.tangemPayKycRejectedCallbacks,
|
||||
onDismiss = model.innerWalletRouter.dialogNavigation::dismiss,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import com.tangem.domain.apptheme.model.AppThemeMode
|
|||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.*
|
||||
import com.tangem.domain.nft.ObserveAndClearNFTCacheIfNeedUseCase
|
||||
import com.tangem.domain.notifications.GetIsHuaweiDeviceWithoutGoogleServicesUseCase
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
|
|
@ -40,6 +39,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent.Dem
|
|||
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.utils.WalletEventSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.KycRejectedCallbacks
|
||||
import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider
|
||||
import com.tangem.features.biometry.AskBiometryComponent
|
||||
import com.tangem.features.feed.entry.featuretoggle.FeedFeatureToggle
|
||||
|
|
@ -84,7 +84,6 @@ internal class WalletModel @Inject constructor(
|
|||
private val onrampStatusFactory: OnrampStatusFactory,
|
||||
private val analyticsEventsHandler: AnalyticsEventHandler,
|
||||
private val walletContentFetcher: WalletContentFetcher,
|
||||
private val observeAndClearNFTCacheIfNeedUseCase: ObserveAndClearNFTCacheIfNeedUseCase,
|
||||
private val walletDeepLinkActionListener: WalletDeepLinkActionListener,
|
||||
private val notificationsRepository: NotificationsRepository,
|
||||
private val getWalletsListForEnablingUseCase: GetWalletsForAutomaticallyPushEnablingUseCase,
|
||||
|
|
@ -109,12 +108,12 @@ internal class WalletModel @Inject constructor(
|
|||
) : Model() {
|
||||
|
||||
val askBiometryModelCallbacks = AskBiometryModelCallbacks()
|
||||
val tangemPayKycRejectedCallbacks = TangemPayKycRejectedCallbacks()
|
||||
val askForPushNotificationsModelCallbacks = AskForPushNotificationsCallbacks()
|
||||
val uiState: StateFlow<WalletScreenState> = stateHolder.uiState
|
||||
|
||||
private val walletsUpdateJobHolder = JobHolder()
|
||||
private val refreshWalletJobHolder = JobHolder()
|
||||
private val clearNFTCacheJobHolder = JobHolder()
|
||||
private val updateTangemPayJobHolder = JobHolder()
|
||||
|
||||
private var needToRefreshWallet = false
|
||||
|
|
@ -334,7 +333,6 @@ internal class WalletModel @Inject constructor(
|
|||
}
|
||||
|
||||
subscribeOnExpressTransactionsUpdates(selectedWallet)
|
||||
observeAndClearNFTCacheIfNeedUseCase(selectedWallet)
|
||||
}
|
||||
.flowOn(dispatchers.main)
|
||||
.launchIn(modelScope)
|
||||
|
|
@ -390,13 +388,6 @@ internal class WalletModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun observeAndClearNFTCacheIfNeedUseCase(selectedWallet: UserWallet) {
|
||||
observeAndClearNFTCacheIfNeedUseCase
|
||||
.invoke(selectedWallet.walletId)
|
||||
.launchIn(modelScope)
|
||||
.saveIn(clearNFTCacheJobHolder)
|
||||
}
|
||||
|
||||
private fun subscribeTangemPayOnWalletState() {
|
||||
/**
|
||||
* Update state each time a user opens/returns to wallet screen
|
||||
|
|
@ -780,6 +771,20 @@ internal class WalletModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
inner class TangemPayKycRejectedCallbacks : KycRejectedCallbacks {
|
||||
override fun onClickYourProfile(userWalletId: UserWalletId) {
|
||||
clickIntents.onKycRejectedOpenProfileClicked(userWalletId)
|
||||
}
|
||||
|
||||
override fun onClickGoToSupport(customerId: String) {
|
||||
clickIntents.onKycRejectedGoToSupportClicked(customerId)
|
||||
}
|
||||
|
||||
override fun onClickHideKyc(userWalletId: UserWalletId) {
|
||||
clickIntents.onKycRejectedHideKycClicked(userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
inner class AskForPushNotificationsCallbacks : PushNotificationsModelCallbacks {
|
||||
|
||||
override fun onAllowSystemPermission() {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.wallet.child.wallet.model.intents
|
||||
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
|
|
@ -13,6 +14,7 @@ import com.tangem.core.ui.message.bottomSheetMessage
|
|||
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.feedback.models.WalletMetaInfo
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.TangemPayDetailsConfig
|
||||
|
|
@ -21,6 +23,7 @@ import com.tangem.domain.pay.repository.OnboardingRepository
|
|||
import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase
|
||||
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDialogConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayHideOnboardingStateTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayRefreshNeededStateTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayRefreshShowProgressTransformer
|
||||
|
|
@ -38,6 +41,14 @@ internal interface TangemPayIntents {
|
|||
|
||||
fun onKycProgressClicked(userWalletId: UserWalletId)
|
||||
|
||||
fun onKycRejectedClicked(userWalletId: UserWalletId, customerId: String)
|
||||
|
||||
fun onKycRejectedOpenProfileClicked(userWalletId: UserWalletId)
|
||||
|
||||
fun onKycRejectedGoToSupportClicked(customerId: String)
|
||||
|
||||
fun onKycRejectedHideKycClicked(userWalletId: UserWalletId)
|
||||
|
||||
fun onIssuingCardClicked()
|
||||
|
||||
fun onIssuingFailedClicked()
|
||||
|
|
@ -137,6 +148,42 @@ internal class TangemPayClickIntentsImplementor @Inject constructor(
|
|||
uiMessageSender.send(kycInfoBottomSheet)
|
||||
}
|
||||
|
||||
override fun onKycRejectedOpenProfileClicked(userWalletId: UserWalletId) {
|
||||
router.openTangemPayOnboarding(
|
||||
mode = AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding(userWalletId),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onKycRejectedGoToSupportClicked(customerId: String) {
|
||||
goToSupportForRejectKyc(customerId)
|
||||
}
|
||||
|
||||
override fun onKycRejectedHideKycClicked(userWalletId: UserWalletId) {
|
||||
disableTangemPay(userWalletId)
|
||||
}
|
||||
|
||||
override fun onKycRejectedClicked(userWalletId: UserWalletId, customerId: String) {
|
||||
router.dialogNavigation.activate(
|
||||
configuration = WalletDialogConfig.KycRejected(
|
||||
walletId = userWalletId,
|
||||
customerId = customerId,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun goToSupportForRejectKyc(customerId: String) {
|
||||
modelScope.launch {
|
||||
sendFeedbackEmailUseCase(
|
||||
type = FeedbackEmailType.Visa.KycRejected(
|
||||
walletMetaInfo = WalletMetaInfo(
|
||||
userWalletId = stateHolder.getSelectedWalletId(),
|
||||
),
|
||||
customerId = customerId,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onIssuingCardClicked() {
|
||||
val issuingBottomSheet = bottomSheetMessage {
|
||||
infoBlock {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import com.tangem.domain.tokens.model.TokenActionsState
|
|||
import com.tangem.domain.tokens.model.details.NavigationAction
|
||||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyEnterStatusUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplySetShouldShowMainPromoUseCase
|
||||
import com.tangem.feature.wallet.presentation.account.AccountDependencies
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
|
|
@ -103,7 +102,6 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
private val yieldSupplyEnterStatusUseCase: YieldSupplyEnterStatusUseCase,
|
||||
private val yieldSupplySetShouldShowMainPromoUseCase: YieldSupplySetShouldShowMainPromoUseCase,
|
||||
private val tokenListAnalyticsSender: TokenListAnalyticsSender,
|
||||
) : BaseWalletClickIntents(), WalletContentClickIntents {
|
||||
|
|
@ -199,7 +197,6 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
is NavigationAction.YieldSupply -> openYieldSupply(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyStatus = currencyStatus,
|
||||
navigationAction = navigationAction,
|
||||
apy = apy,
|
||||
)
|
||||
}
|
||||
|
|
@ -233,43 +230,21 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onAccountExpandClick(account: Account) {
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
analyticsEventHandler.send(MainScreenAnalyticsEvent.AccountShowTokens())
|
||||
accountDependencies.expandedAccountsHolder.expandAccount(userWalletId, account.accountId)
|
||||
accountDependencies.expandedAccountsHolder.expandAccount(account.accountId)
|
||||
}
|
||||
|
||||
override fun onAccountCollapseClick(account: Account) {
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
analyticsEventHandler.send(MainScreenAnalyticsEvent.AccountHideTokens())
|
||||
accountDependencies.expandedAccountsHolder.collapseAccount(userWalletId, account.accountId)
|
||||
accountDependencies.expandedAccountsHolder.collapseAccount(account.accountId)
|
||||
}
|
||||
|
||||
private fun openYieldSupply(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
navigationAction: NavigationAction.YieldSupply,
|
||||
apy: String,
|
||||
) {
|
||||
modelScope.launch {
|
||||
val tokenEnterStatus = yieldSupplyEnterStatusUseCase(userWalletId, cryptoCurrencyStatus).getOrNull()
|
||||
when {
|
||||
tokenEnterStatus != null -> router.openTokenDetails(
|
||||
userWalletId = userWalletId,
|
||||
currencyStatus = cryptoCurrencyStatus,
|
||||
navigationAction = navigationAction,
|
||||
)
|
||||
navigationAction.isActive -> router.openYieldSupplyActiveScreen(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
apy = apy,
|
||||
)
|
||||
else -> router.openYieldSupplyPromoScreen(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
apy = apy,
|
||||
)
|
||||
}
|
||||
}
|
||||
private fun openYieldSupply(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus, apy: String) {
|
||||
router.openYieldSupplyEntryScreen(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
apy = apy,
|
||||
)
|
||||
}
|
||||
|
||||
private fun sendApyLabelClickAnalytics(navigationAction: NavigationAction, currencyStatus: CryptoCurrencyStatus) {
|
||||
|
|
|
|||
|
|
@ -505,6 +505,8 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
) {
|
||||
stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWalletId))
|
||||
|
||||
val integrationId = option?.integrationId ?: return
|
||||
|
||||
modelScope.launch {
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
|
||||
|
|
@ -512,7 +514,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
AppRoute.Staking(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
yieldId = option?.integrationId ?: return@launch,
|
||||
integrationId = integrationId,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.core.decompose.model.Model
|
|||
import com.tangem.feature.wallet.DefaultWalletEntryComponent
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.OrganizeTokensModel
|
||||
import com.tangem.feature.wallet.child.wallet.model.WalletModel
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.KycRejectedModel
|
||||
import com.tangem.feature.wallet.utils.DefaultUserWalletImageFetcher
|
||||
import com.tangem.feature.wallet.utils.DefaultUserWalletsFetcher
|
||||
import com.tangem.features.wallet.WalletEntryComponent
|
||||
|
|
@ -36,6 +37,11 @@ internal interface WalletFeatureModule {
|
|||
@ClassKey(WalletModel::class)
|
||||
fun bindWalletModel(model: WalletModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(KycRejectedModel::class)
|
||||
fun bindKycRejectedModel(model: KycRejectedModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(OrganizeTokensModel::class)
|
||||
|
|
|
|||
|
|
@ -1,60 +1,100 @@
|
|||
package com.tangem.feature.wallet.presentation.account
|
||||
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.domain.account.models.AccountExpandedState
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.producer.SingleAccountListProducer
|
||||
import com.tangem.domain.account.repository.AccountsExpandedRepository
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class ExpandedAccountsHolder @Inject constructor(
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier,
|
||||
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
|
||||
private val accountsExpandedRepository: AccountsExpandedRepository,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
private val expandedAccounts = MutableStateFlow<Map<UserWalletId, Set<AccountId>>>(mapOf())
|
||||
private val actionChannel = MutableSharedFlow<Pair<AccountId, Boolean>>(
|
||||
extraBufferCapacity = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST,
|
||||
)
|
||||
|
||||
fun expandedAccounts(userWallet: UserWallet): Flow<Set<AccountId>> = channelFlow {
|
||||
combine(
|
||||
flow = walletAccounts(userWallet),
|
||||
flow2 = isAccountsModeEnabledUseCase.invoke(),
|
||||
transform = { accountList, isAccountsMode ->
|
||||
val isSingleAccount = accountList.accounts.size == 1
|
||||
val defaultExpanded = when {
|
||||
!isAccountsMode -> setOf()
|
||||
isSingleAccount -> setOf(accountList.mainAccount.accountId)
|
||||
else -> setOf()
|
||||
val walletId = userWallet.walletId
|
||||
|
||||
val storedState = accountsExpandedRepository.expandedAccounts
|
||||
.map { it[walletId].orEmpty() }
|
||||
.stateIn(this)
|
||||
|
||||
val isAccountsMode = isAccountsModeEnabledUseCase.invoke()
|
||||
.stateIn(this)
|
||||
|
||||
val initExpandedState = storedState.value
|
||||
.mapNotNull { it.takeIf { state -> state.isExpanded }?.accountId }
|
||||
.toSet()
|
||||
// main state holder
|
||||
val expandedAccounts = MutableStateFlow(initExpandedState)
|
||||
|
||||
actionChannel
|
||||
.filter { (accountId, _) -> accountId.userWalletId == walletId }
|
||||
.onEach { (accountId, isExpand) ->
|
||||
val newState = AccountExpandedState(accountId, isExpand)
|
||||
launch { accountsExpandedRepository.update(newState) }
|
||||
if (isExpand) {
|
||||
expandedAccounts.update { it.plus(accountId) }
|
||||
} else {
|
||||
expandedAccounts.update { it.minus(accountId) }
|
||||
}
|
||||
expandedAccounts.update { map ->
|
||||
var expandedSet = map[userWallet.walletId] ?: defaultExpanded
|
||||
// force expand for single account
|
||||
if (isSingleAccount || !isAccountsMode) expandedSet = defaultExpanded
|
||||
map.plus(userWallet.walletId to expandedSet)
|
||||
}
|
||||
.launchIn(this)
|
||||
|
||||
walletAccounts(walletId).onEach { accountList ->
|
||||
val idsSet = accountList.accounts.mapTo(mutableSetOf()) { it.accountId }
|
||||
accountsExpandedRepository.syncStore(walletId, idsSet)
|
||||
|
||||
if (!isAccountsMode.value) return@onEach
|
||||
|
||||
val isSingleAccount = accountList.accounts.size == 1
|
||||
val storedMainAccountState = storedState.value
|
||||
.find { it.accountId == accountList.mainAccount.accountId }
|
||||
|
||||
if (isSingleAccount && storedMainAccountState == null) {
|
||||
// force expand for single and not stored account
|
||||
expandedAccounts.update { setOf(accountList.mainAccount.accountId) }
|
||||
}
|
||||
}.launchIn(this)
|
||||
|
||||
combine(
|
||||
flow = expandedAccounts,
|
||||
flow2 = isAccountsMode,
|
||||
transform = { expanded, isAccountMode ->
|
||||
if (isAccountMode) {
|
||||
channel.send(expanded)
|
||||
} else {
|
||||
channel.send(setOf())
|
||||
}
|
||||
},
|
||||
).launchIn(this)
|
||||
).collect()
|
||||
}
|
||||
.flowOn(dispatchers.default)
|
||||
.distinctUntilChanged()
|
||||
|
||||
expandedAccounts
|
||||
.mapNotNull { map -> map[userWallet.walletId] }
|
||||
.onEach { expanded -> channel.send(expanded) }
|
||||
.collect()
|
||||
fun expandAccount(accountId: AccountId) {
|
||||
actionChannel.tryEmit(accountId to true)
|
||||
}
|
||||
|
||||
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(accountId: AccountId) {
|
||||
actionChannel.tryEmit(accountId to false)
|
||||
}
|
||||
|
||||
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))
|
||||
private fun walletAccounts(walletId: UserWalletId): Flow<AccountList> = singleAccountListSupplier(walletId)
|
||||
}
|
||||
|
|
@ -133,19 +133,9 @@ internal class DefaultWalletRouter @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
override fun openYieldSupplyActiveScreen(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, apy: String) {
|
||||
override fun openYieldSupplyEntryScreen(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, apy: String) {
|
||||
router.push(
|
||||
AppRoute.YieldSupplyActive(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
apy = apy,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun openYieldSupplyPromoScreen(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, apy: String) {
|
||||
router.push(
|
||||
AppRoute.YieldSupplyPromo(
|
||||
AppRoute.YieldSupplyEntry(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
apy = apy,
|
||||
|
|
|
|||
|
|
@ -75,9 +75,6 @@ internal interface InnerWalletRouter {
|
|||
onWarningAcknowledged: (TokenAction) -> Unit,
|
||||
)
|
||||
|
||||
/** Open yield supply active screen */
|
||||
fun openYieldSupplyActiveScreen(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, apy: String)
|
||||
|
||||
/** Open yield supply promo screen */
|
||||
fun openYieldSupplyPromoScreen(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, apy: String)
|
||||
/** Open yield supply entry screen */
|
||||
fun openYieldSupplyEntryScreen(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, apy: String)
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.analytics.utils
|
|||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.domain.pay.model.CustomerInfo
|
||||
import com.tangem.domain.pay.model.MainScreenCustomerInfo
|
||||
import com.tangem.domain.pay.model.OrderStatus
|
||||
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
|
||||
|
|
@ -25,8 +26,10 @@ internal class WalletTangemPayAnalyticsEventSender @Inject constructor(
|
|||
// TODO: TangemPay refactor analytics
|
||||
// when statement copied from TangemPayUpdateInfoStateTransformer. Be careful when editing
|
||||
val event = when {
|
||||
customerInfo.orderStatus == OrderStatus.CANCELED -> return // ignore cancelled state on analytics
|
||||
!customerInfo.info.isKycApproved -> return // ignore kyc not approved state on analytics
|
||||
// ignore cancelled state on analytics
|
||||
customerInfo.orderStatus == OrderStatus.CANCELED -> return
|
||||
// ignore kyc not approved state on analytics
|
||||
customerInfo.info.kycStatus != CustomerInfo.KycStatus.APPROVED -> return
|
||||
cardInfo != null && productInstance != null -> return
|
||||
else -> TangemPayAnalyticsEvents.IssuingBannerDisplayed()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,4 +32,7 @@ internal sealed interface WalletDialogConfig {
|
|||
val tokenAction: TokenAction,
|
||||
val onWarningAcknowledged: (TokenAction) -> Unit,
|
||||
) : WalletDialogConfig
|
||||
|
||||
@Serializable
|
||||
data class KycRejected(val walletId: UserWalletId, val customerId: String) : WalletDialogConfig
|
||||
}
|
||||
|
|
@ -16,6 +16,8 @@ import com.tangem.feature.wallet.child.wallet.model.intents.TangemPayIntents
|
|||
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.state.model.WalletState
|
||||
import com.tangem.domain.pay.model.CustomerInfo.KycStatus.APPROVED
|
||||
import com.tangem.domain.pay.model.CustomerInfo
|
||||
import java.util.Currency
|
||||
|
||||
/**
|
||||
|
|
@ -43,11 +45,13 @@ internal class TangemPayUpdateInfoStateTransformer(
|
|||
private fun createInitialState(): TangemPayState {
|
||||
val cardInfo = value.info.cardInfo
|
||||
val productInstance = value.info.productInstance
|
||||
val customerId = value.info.customerId
|
||||
|
||||
// when statement copied to WalletTangemPayAnalyticsEventSender. Be careful when editing.
|
||||
return when {
|
||||
value.orderStatus == OrderStatus.CANCELED -> createCancelledState()
|
||||
!value.info.isKycApproved -> createKycInProgressState()
|
||||
value.info.kycStatus != APPROVED && !customerId.isNullOrEmpty() ->
|
||||
createKycInProgressState(kycStatus = value.info.kycStatus, customerId = customerId)
|
||||
cardInfo != null && productInstance != null -> getCardInfoState(cardInfo, productInstance)
|
||||
else -> createIssueProgressState()
|
||||
}
|
||||
|
|
@ -80,13 +84,25 @@ internal class TangemPayUpdateInfoStateTransformer(
|
|||
}
|
||||
}
|
||||
|
||||
private fun createKycInProgressState(): TangemPayState = Progress(
|
||||
title = TextReference.Res(R.string.tangempay_payment_account),
|
||||
description = TextReference.Res(R.string.tangempay_kyc_in_progress),
|
||||
buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button),
|
||||
iconRes = R.drawable.ic_promo_kyc_36,
|
||||
onButtonClick = { tangemPayClickIntents.onKycProgressClicked(userWalletId) },
|
||||
)
|
||||
private fun createKycInProgressState(kycStatus: CustomerInfo.KycStatus, customerId: String): TangemPayState =
|
||||
Progress(
|
||||
title = TextReference.Res(R.string.tangempay_payment_account),
|
||||
description = when (kycStatus) {
|
||||
CustomerInfo.KycStatus.REJECTED -> TextReference.Res(R.string.tangempay_kyc_has_failed)
|
||||
else -> TextReference.Res(R.string.tangempay_kyc_in_progress)
|
||||
},
|
||||
buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button),
|
||||
iconRes = R.drawable.ic_promo_kyc_36,
|
||||
onButtonClick = {
|
||||
when (kycStatus) {
|
||||
CustomerInfo.KycStatus.REJECTED -> tangemPayClickIntents.onKycRejectedClicked(
|
||||
userWalletId = userWalletId,
|
||||
customerId = customerId,
|
||||
)
|
||||
else -> tangemPayClickIntents.onKycProgressClicked(userWalletId)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
private fun createIssueProgressState(): TangemPayState = Progress(
|
||||
title = TextReference.Res(R.string.tangempay_payment_account),
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ internal class TokenListStateConverter(
|
|||
private val shouldShowMainPromo: Boolean,
|
||||
) : Converter<WalletTokensListState, WalletTokensListState> {
|
||||
|
||||
private val yieldSupplyPromoBannerKeyConverter = YieldSupplyPromoBannerKeyConverter(
|
||||
private val yieldSupplyPromoBannerConverter = YieldSupplyPromoBannerConverter(
|
||||
yieldModuleApyMap,
|
||||
shouldShowMainPromo,
|
||||
)
|
||||
|
|
@ -72,7 +72,7 @@ internal class TokenListStateConverter(
|
|||
private fun tokenStatusConverter(accountId: AccountId? = null) = TokenItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
yieldModuleApyMap = yieldModuleApyMap,
|
||||
yieldSupplyPromoBannerKey = yieldSupplyPromoBannerKeyConverter.convert(params),
|
||||
promoCryptoCurrencyStatus = yieldSupplyPromoBannerConverter.convert(params),
|
||||
stakingApyMap = stakingAvailabilityMap,
|
||||
onItemClick = { _, status -> onTokenClick(accountId, status) },
|
||||
onItemLongClick = { _, status -> onTokenLongClick(accountId, status) },
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ internal class TxHistoryItemStateConverter(
|
|||
is TransactionType.UnknownOperation,
|
||||
is TransactionType.YieldSupply.Send,
|
||||
TransactionType.YieldSupply.Topup,
|
||||
TransactionType.GaslessFee,
|
||||
-> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24
|
||||
}
|
||||
}
|
||||
|
|
@ -114,6 +115,7 @@ internal class TxHistoryItemStateConverter(
|
|||
)
|
||||
}
|
||||
is TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation)
|
||||
TransactionType.GaslessFee -> resourceReference(R.string.gasless_transaction_fee)
|
||||
}
|
||||
|
||||
private fun TxInfo.extractSubtitle(): TextReference {
|
||||
|
|
|
|||
|
|
@ -2,18 +2,17 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers.convert
|
|||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.currency.yieldSupplyKey
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenConverterParams
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.utils.converter.Converter
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class YieldSupplyPromoBannerKeyConverter(
|
||||
internal class YieldSupplyPromoBannerConverter(
|
||||
private val yieldModuleApyMap: Map<String, BigDecimal>,
|
||||
private val shouldShowMainPromo: Boolean,
|
||||
) : Converter<TokenConverterParams, String?> {
|
||||
) : Converter<TokenConverterParams, CryptoCurrencyStatus?> {
|
||||
|
||||
override fun convert(value: TokenConverterParams): String? {
|
||||
override fun convert(value: TokenConverterParams): CryptoCurrencyStatus? {
|
||||
if (!shouldShowMainPromo) return null
|
||||
|
||||
val currencies = when (value) {
|
||||
|
|
@ -31,8 +30,8 @@ internal class YieldSupplyPromoBannerKeyConverter(
|
|||
val max = cryptoCurrencyStatuses.asSequence()
|
||||
.mapNotNull { status ->
|
||||
val token = status.currency as? CryptoCurrency.Token ?: return@mapNotNull null
|
||||
val tokenKey = token.yieldSupplyKey()
|
||||
val shouldIgnoreCase = BlockchainUtils.isCaseInsensitiveContractAddress(token.network.rawId)
|
||||
val tokenKey = "${token.network.rawId}_${token.contractAddress}"
|
||||
|
||||
val matchedKey = yieldModuleApyMap.keys.firstOrNull { mapKey ->
|
||||
mapKey.equals(tokenKey, shouldIgnoreCase)
|
||||
|
|
@ -42,6 +41,6 @@ internal class YieldSupplyPromoBannerKeyConverter(
|
|||
}
|
||||
.maxByOrNull { (status, _) -> status.value.amount ?: BigDecimal.ZERO }
|
||||
|
||||
return max?.second
|
||||
return max?.first
|
||||
}
|
||||
}
|
||||
|
|
@ -409,7 +409,7 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
}
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalMainBottomSheetColor provides remember { mutableStateOf(background) },
|
||||
LocalMainBottomSheetColor provides remember(background) { mutableStateOf(background) },
|
||||
) {
|
||||
val backgroundColor = LocalMainBottomSheetColor.current
|
||||
var isSearchFieldFocused by remember { mutableStateOf(false) }
|
||||
|
|
@ -449,20 +449,21 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.sizeIn(maxHeight = maxHeight - statusBarHeight),
|
||||
modifier = Modifier
|
||||
// expand bottom sheet when clicked on the header
|
||||
.clickable(
|
||||
enabled = bottomSheetState.currentValue == TangemSheetValue.PartiallyExpanded,
|
||||
indication = null,
|
||||
interactionSource = null,
|
||||
) {
|
||||
coroutineScope.launch { bottomSheetState.expand() }
|
||||
}
|
||||
.sizeIn(maxHeight = maxHeight - statusBarHeight),
|
||||
) {
|
||||
Hand(Modifier.drawBehind { drawRect(backgroundColor.value) })
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
// expand bottom sheet when clicked on the header
|
||||
.clickable(
|
||||
enabled = bottomSheetState.currentValue == TangemSheetValue.PartiallyExpanded,
|
||||
indication = null,
|
||||
interactionSource = null,
|
||||
) {
|
||||
coroutineScope.launch { bottomSheetState.expand() }
|
||||
}
|
||||
.onFocusChanged {
|
||||
isSearchFieldFocused = it.isFocused
|
||||
},
|
||||
|
|
@ -492,7 +493,7 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
color = if (state.showMarketsOnboarding) {
|
||||
Color.Black.copy(alpha = .65f)
|
||||
} else {
|
||||
BottomSheetDefaults.ScrimColor
|
||||
Color.Black.copy(alpha = .40f)
|
||||
},
|
||||
visible = bottomSheetState.targetValue == TangemSheetValue.Expanded ||
|
||||
state.showMarketsOnboarding,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import androidx.compose.animation.core.calculateTargetValue
|
|||
import androidx.compose.animation.splineBasedDecay
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.snapping.SnapFlingBehavior
|
||||
import androidx.compose.foundation.lazy.LazyListLayoutInfo
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.ui.unit.Density
|
||||
|
|
@ -23,7 +22,7 @@ import kotlin.math.sign
|
|||
* This position should be considered with regard to the start edge of the item and the placement
|
||||
* within the viewport.
|
||||
*
|
||||
* @return A [SnapLayoutInfoProvider] that can be used with [SnapFlingBehavior]
|
||||
* @return A [SnapLayoutInfoProvider] that can be used with snap fling behavior
|
||||
*/
|
||||
@Suppress("FunctionNaming")
|
||||
@ExperimentalFoundationApi
|
||||
|
|
|
|||
|
|
@ -18,12 +18,11 @@ import androidx.compose.runtime.derivedStateOf
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.res.LocalWindowSize
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.MainScreenTestTags
|
||||
|
|
@ -50,7 +49,7 @@ internal fun WalletsList(
|
|||
isBalanceHidden: Boolean,
|
||||
) {
|
||||
val horizontalCardPadding = TangemTheme.dimens.spacing16
|
||||
val screenWidth = LocalConfiguration.current.screenWidthDp.dp
|
||||
val screenWidth = LocalWindowSize.current.width
|
||||
val itemWidth by remember(screenWidth) { derivedStateOf { screenWidth - horizontalCardPadding * 2 } }
|
||||
|
||||
LazyRow(
|
||||
|
|
|
|||
|
|
@ -2,12 +2,10 @@ package com.tangem.feature.wallet.presentation.wallet.ui.components.common
|
|||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.core.ui.components.FontSizeRange
|
||||
import com.tangem.core.ui.components.buttons.HorizontalActionChips
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletManageButton
|
||||
|
|
@ -66,16 +64,10 @@ internal fun LazyListScope.actions(
|
|||
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
val fontSizeRange = FontSizeRange(min = 10.sp, max = 14.sp)
|
||||
var fontSizeValue by remember { mutableFloatStateOf(fontSizeRange.max.value) }
|
||||
|
||||
actions.fastForEach { action ->
|
||||
key(action::class.java) {
|
||||
MultiCurrencyAction(
|
||||
config = action.config,
|
||||
fontSizeValue = fontSizeValue.sp,
|
||||
fontSizeRange = fontSizeRange,
|
||||
onFontSizeChange = { fontSizeValue = it },
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ import androidx.compose.foundation.indication
|
|||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.PressInteraction
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.TextAutoSize
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
|
|
@ -32,18 +34,11 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import androidx.constraintlayout.compose.ConstraintLayoutScope
|
||||
import androidx.constraintlayout.compose.Dimension
|
||||
import com.tangem.core.ui.components.FontSizeRange
|
||||
import androidx.compose.ui.unit.*
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.ResizableText
|
||||
import com.tangem.core.ui.components.text.applyBladeBrush
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.conditional
|
||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemDimens
|
||||
|
|
@ -51,6 +46,7 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.MainScreenTestTags
|
||||
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAdditionalInfo
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDropDownItems
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.fastForEach
|
||||
|
|
@ -69,89 +65,6 @@ private const val HALF_OF_ITEM_WIDTH = 0.5
|
|||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
internal fun WalletCard(state: WalletCardState, isBalanceHidden: Boolean, modifier: Modifier = Modifier) {
|
||||
@Suppress("DestructuringDeclarationWithTooManyEntries")
|
||||
CardContainer(
|
||||
dropDownItems = state.dropDownItems,
|
||||
isLockedState = state is WalletCardState.LockedContent,
|
||||
modifier = modifier,
|
||||
) { itemSize ->
|
||||
val (titleRef, balanceRef, additionalTextRef, imageRef) = createRefs()
|
||||
|
||||
val contentVerticalMargin = TangemTheme.dimens.spacing12
|
||||
TitleText(
|
||||
text = state.title,
|
||||
modifier = Modifier.constrainAs(titleRef) {
|
||||
start.linkTo(parent.start)
|
||||
top.linkTo(anchor = parent.top, margin = contentVerticalMargin)
|
||||
end.linkTo(imageRef.start)
|
||||
width = Dimension.fillToConstraints
|
||||
},
|
||||
)
|
||||
|
||||
var balanceWidth by remember { mutableIntStateOf(value = Int.MIN_VALUE) }
|
||||
Balance(
|
||||
state = state,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = Modifier
|
||||
.onSizeChanged { balanceWidth = it.width }
|
||||
.padding(vertical = TangemTheme.dimens.spacing8)
|
||||
.constrainAs(balanceRef) {
|
||||
start.linkTo(parent.start)
|
||||
top.linkTo(anchor = titleRef.bottom)
|
||||
bottom.linkTo(anchor = additionalTextRef.top)
|
||||
},
|
||||
)
|
||||
|
||||
val additionalText by remember(state.additionalInfo, isBalanceHidden) {
|
||||
mutableStateOf(
|
||||
state.additionalInfo?.content?.orMaskWithStars(
|
||||
maskWithStars = state.additionalInfo?.hideable == true && isBalanceHidden,
|
||||
),
|
||||
)
|
||||
}
|
||||
AdditionalInfo(
|
||||
text = additionalText,
|
||||
modifier = Modifier.constrainAs(additionalTextRef) {
|
||||
start.linkTo(parent.start)
|
||||
top.linkTo(balanceRef.bottom)
|
||||
bottom.linkTo(anchor = parent.bottom, margin = contentVerticalMargin)
|
||||
|
||||
if (additionalText != null) {
|
||||
width = if (state.imageResId != null) {
|
||||
end.linkTo(imageRef.start)
|
||||
Dimension.fillToConstraints
|
||||
} else {
|
||||
Dimension.wrapContent
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// If balance has a large width then image must be hidden
|
||||
val hasSpaceForImage by remember(key1 = balanceWidth, key2 = itemSize.width) {
|
||||
mutableStateOf(value = balanceWidth < itemSize.width * HALF_OF_ITEM_WIDTH)
|
||||
}
|
||||
|
||||
if (hasSpaceForImage) {
|
||||
Image(
|
||||
id = state.imageResId,
|
||||
modifier = Modifier.constrainAs(imageRef) {
|
||||
end.linkTo(parent.end)
|
||||
bottom.linkTo(parent.bottom)
|
||||
height = Dimension.fillToConstraints
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CardContainer(
|
||||
dropDownItems: ImmutableList<WalletDropDownItems>,
|
||||
isLockedState: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable (ConstraintLayoutScope.(IntSize) -> Unit),
|
||||
) {
|
||||
var isMenuVisible by rememberSaveable { mutableStateOf(value = false) }
|
||||
var pressOffset by remember { mutableStateOf(value = DpOffset.Zero) }
|
||||
var itemSize by remember { mutableStateOf(value = IntSize.Zero) }
|
||||
|
|
@ -166,7 +79,7 @@ private fun CardContainer(
|
|||
.onSizeChanged { itemSize = it }
|
||||
.testTag(MainScreenTestTags.TOTAL_BALANCE_CONTAINER)
|
||||
.then(
|
||||
if (isLockedState || dropDownItems.isEmpty()) {
|
||||
if (state is WalletCardState.LockedContent || state.dropDownItems.isEmpty()) {
|
||||
Modifier
|
||||
} else {
|
||||
Modifier
|
||||
|
|
@ -189,15 +102,19 @@ private fun CardContainer(
|
|||
}
|
||||
},
|
||||
),
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
color = TangemTheme.colors.background.primary,
|
||||
) {
|
||||
ConstraintLayout(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
content(itemSize)
|
||||
CardContainer(
|
||||
state = state,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
itemSize = itemSize,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -209,10 +126,65 @@ private fun CardContainer(
|
|||
pressOffset = pressOffset,
|
||||
itemHeight = itemHeight,
|
||||
onDismissRequest = { isMenuVisible = false },
|
||||
dropDownItems = dropDownItems,
|
||||
dropDownItems = state.dropDownItems,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("DestructuringDeclarationWithTooManyEntries")
|
||||
@Composable
|
||||
private fun CardContainer(state: WalletCardState, isBalanceHidden: Boolean, itemSize: IntSize) {
|
||||
var balanceWidth by remember { mutableIntStateOf(value = Int.MIN_VALUE) }
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.padding(vertical = 12.dp),
|
||||
) {
|
||||
TitleText(
|
||||
text = state.title,
|
||||
modifier = Modifier,
|
||||
)
|
||||
Balance(
|
||||
state = state,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = Modifier
|
||||
.onSizeChanged { balanceWidth = it.width }
|
||||
.padding(vertical = TangemTheme.dimens.spacing8),
|
||||
)
|
||||
|
||||
val additionalText by remember(state.additionalInfo, isBalanceHidden) {
|
||||
mutableStateOf(
|
||||
state.additionalInfo?.content?.orMaskWithStars(
|
||||
maskWithStars = state.additionalInfo?.hideable == true && isBalanceHidden,
|
||||
),
|
||||
)
|
||||
}
|
||||
AdditionalInfo(
|
||||
text = additionalText,
|
||||
modifier = Modifier.conditional(
|
||||
state.imageResId == null,
|
||||
) { fillMaxWidth() },
|
||||
)
|
||||
}
|
||||
|
||||
// If balance has a large width then image must be hidden
|
||||
val hasSpaceForImage by remember(key1 = balanceWidth, key2 = itemSize.width) {
|
||||
mutableStateOf(value = balanceWidth < itemSize.width * HALF_OF_ITEM_WIDTH)
|
||||
}
|
||||
|
||||
if (hasSpaceForImage) {
|
||||
Image(
|
||||
id = state.imageResId,
|
||||
modifier = Modifier.wrapContentWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
private fun ManageWalletContextMenu(
|
||||
|
|
@ -282,10 +254,13 @@ private fun Balance(state: WalletCardState, isBalanceHidden: Boolean, modifier:
|
|||
) { balance ->
|
||||
when (state) {
|
||||
is WalletCardState.Content -> {
|
||||
ResizableText(
|
||||
modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size32),
|
||||
Text(
|
||||
text = balance,
|
||||
fontSizeRange = FontSizeRange(min = 16.sp, max = TangemTheme.typography.h2.fontSize),
|
||||
modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size32),
|
||||
autoSize = TextAutoSize.StepBased(
|
||||
minFontSize = 16.sp,
|
||||
maxFontSize = TangemTheme.typography.h2.fontSize,
|
||||
),
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.h2
|
||||
|
|
@ -401,6 +376,16 @@ private fun Preview_WalletCard(
|
|||
private class WalletCardStateProvider : CollectionPreviewParameterProvider<WalletCardState>(
|
||||
collection = listOf(
|
||||
WalletPreviewData.walletCardContentState,
|
||||
WalletPreviewData.walletCardContentState.copy(
|
||||
balance = "0.00",
|
||||
),
|
||||
WalletPreviewData.walletCardContentState.copy(
|
||||
title = "Title",
|
||||
additionalInfo = WalletAdditionalInfo(
|
||||
hideable = false,
|
||||
content = TextReference.Str("3 cards"),
|
||||
),
|
||||
),
|
||||
WalletPreviewData.walletCardContentState.copy(
|
||||
isBalanceFlickering = true,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ package com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrenc
|
|||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.TextAutoSize
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.FontSizeRange
|
||||
import com.tangem.core.ui.components.ResizableText
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.core.ui.components.buttons.actions.ActionBaseButton
|
||||
import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
|
||||
import com.tangem.core.ui.components.buttons.actions.ActionButtonContent
|
||||
|
|
@ -19,13 +19,7 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
internal fun MultiCurrencyAction(
|
||||
config: ActionButtonConfig,
|
||||
fontSizeValue: TextUnit,
|
||||
fontSizeRange: FontSizeRange,
|
||||
onFontSizeChange: (Float) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
internal fun MultiCurrencyAction(config: ActionButtonConfig, modifier: Modifier = Modifier) {
|
||||
ActionBaseButton(
|
||||
config = config,
|
||||
shape = RoundedCornerShape(size = TangemTheme.dimens.radius12),
|
||||
|
|
@ -33,11 +27,12 @@ internal fun MultiCurrencyAction(
|
|||
ActionButtonContent(
|
||||
config = config,
|
||||
text = { color ->
|
||||
ResizableText(
|
||||
Text(
|
||||
text = config.text.resolveReference(),
|
||||
fontSizeValue = fontSizeValue,
|
||||
fontSizeRange = fontSizeRange,
|
||||
onFontSizeChange = onFontSizeChange,
|
||||
autoSize = TextAutoSize.StepBased(
|
||||
minFontSize = 10.sp,
|
||||
maxFontSize = TangemTheme.typography.button.fontSize,
|
||||
),
|
||||
color = color,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetV2
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class KycRejectedComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted params: Params,
|
||||
) : ComposableBottomSheetComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: KycRejectedModel = getOrCreateModel(params = params)
|
||||
|
||||
override fun dismiss() {
|
||||
model.onDismiss()
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun BottomSheet() {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
MessageBottomSheetV2(state = state, onDismissRequest = ::dismiss)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val callbacks: KycRejectedCallbacks,
|
||||
val walletId: UserWalletId,
|
||||
val customerId: String,
|
||||
val onDismiss: () -> Unit,
|
||||
)
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(appComponentContext: AppComponentContext, params: Params): KycRejectedComponent
|
||||
}
|
||||
}
|
||||
|
||||
internal interface KycRejectedCallbacks {
|
||||
fun onClickYourProfile(userWalletId: UserWalletId)
|
||||
fun onClickGoToSupport(customerId: String)
|
||||
fun onClickHideKyc(userWalletId: UserWalletId)
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.res.R
|
||||
import com.tangem.core.ui.components.bottomsheets.message.*
|
||||
import com.tangem.core.ui.extensions.combinedReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.styledResourceReference
|
||||
import com.tangem.core.ui.message.bottomSheetMessage
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class KycRejectedModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<KycRejectedComponent.Params>()
|
||||
|
||||
val uiState: StateFlow<MessageBottomSheetUMV2>
|
||||
field = MutableStateFlow(getInitialState())
|
||||
|
||||
private fun getInitialState(): MessageBottomSheetUMV2 {
|
||||
return bottomSheetMessage {
|
||||
infoBlock {
|
||||
icon(com.tangem.core.ui.R.drawable.ic_heart_broken_32) {
|
||||
type = MessageBottomSheetUMV2.Icon.Type.Warning
|
||||
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.Warning
|
||||
}
|
||||
title = resourceReference(R.string.tangempay_kyc_rejected)
|
||||
body = combinedReference(
|
||||
resourceReference(R.string.tangempay_kyc_rejected_description),
|
||||
stringReference(" "),
|
||||
styledResourceReference(
|
||||
id = R.string.tangempay_kyc_rejected_description_span,
|
||||
spanStyleReference = {
|
||||
TangemTheme.typography.body2.copy(TangemTheme.colors.text.accent).toSpanStyle()
|
||||
.copy(textDecoration = TextDecoration.None)
|
||||
},
|
||||
onClick = {
|
||||
params.callbacks.onClickYourProfile(userWalletId = params.walletId)
|
||||
onDismiss()
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
primaryButton {
|
||||
text = resourceReference(R.string.tangempay_go_to_support)
|
||||
onClick = {
|
||||
params.callbacks.onClickGoToSupport(customerId = params.customerId)
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
secondaryButton {
|
||||
text = resourceReference(R.string.tangempay_kyc_rejected_button_text)
|
||||
onClick = {
|
||||
params.callbacks.onClickHideKyc(userWalletId = params.walletId)
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
}.messageBottomSheetUMV2
|
||||
}
|
||||
|
||||
fun onDismiss() {
|
||||
params.onDismiss()
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ import com.tangem.domain.models.PortfolioId
|
|||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.currency.yieldSupplyKey
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.network.NetworkAddress
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
|
|
@ -15,7 +14,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenCon
|
|||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
class YieldSupplyPromoBannerKeyConverterTest {
|
||||
class YieldSupplyPromoBannerConverterTest {
|
||||
|
||||
@Test
|
||||
fun `GIVEN promo disabled WHEN convert THEN return null`() {
|
||||
|
|
@ -26,8 +25,8 @@ class YieldSupplyPromoBannerKeyConverterTest {
|
|||
portfolioId = PortfolioId.Wallet(UserWalletId("00")),
|
||||
tokenList = tokenList,
|
||||
)
|
||||
val converter = YieldSupplyPromoBannerKeyConverter(
|
||||
yieldModuleApyMap = mapOf(token.yieldSupplyKey() to BigDecimal("0.10")),
|
||||
val converter = YieldSupplyPromoBannerConverter(
|
||||
yieldModuleApyMap = mapOf("${token.network.rawId}_${token.contractAddress}" to BigDecimal("0.10")),
|
||||
shouldShowMainPromo = false,
|
||||
)
|
||||
|
||||
|
|
@ -44,7 +43,7 @@ class YieldSupplyPromoBannerKeyConverterTest {
|
|||
portfolioId = PortfolioId.Wallet(UserWalletId("00")),
|
||||
tokenList = ungroupedTokenList(status),
|
||||
)
|
||||
val converter = YieldSupplyPromoBannerKeyConverter(
|
||||
val converter = YieldSupplyPromoBannerConverter(
|
||||
yieldModuleApyMap = emptyMap(),
|
||||
shouldShowMainPromo = true,
|
||||
)
|
||||
|
|
@ -62,8 +61,8 @@ class YieldSupplyPromoBannerKeyConverterTest {
|
|||
portfolioId = PortfolioId.Wallet(UserWalletId("00")),
|
||||
tokenList = ungroupedTokenList(statusActive),
|
||||
)
|
||||
val converter = YieldSupplyPromoBannerKeyConverter(
|
||||
yieldModuleApyMap = mapOf(token.yieldSupplyKey() to BigDecimal("0.12")),
|
||||
val converter = YieldSupplyPromoBannerConverter(
|
||||
yieldModuleApyMap = mapOf("${token.network.rawId}_${token.contractAddress}" to BigDecimal("0.12")),
|
||||
shouldShowMainPromo = true,
|
||||
)
|
||||
|
||||
|
|
@ -73,7 +72,7 @@ class YieldSupplyPromoBannerKeyConverterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN multiple candidates EVM case insensitive WHEN convert THEN return key of max amount`() {
|
||||
fun `GIVEN multiple candidates EVM case insensitive WHEN convert THEN return status of max amount`() {
|
||||
val evmNetworkId = "ETH"
|
||||
val tokenSmall = createToken(networkId = evmNetworkId, backendId = evmNetworkId, contract = "0xAbCd")
|
||||
val tokenBig = createToken(networkId = evmNetworkId, backendId = evmNetworkId, contract = "0xBEEF")
|
||||
|
|
@ -82,23 +81,22 @@ class YieldSupplyPromoBannerKeyConverterTest {
|
|||
val statusBig = createLoadedStatus(token = tokenBig, amount = BigDecimal("10.00"), isYieldActive = false)
|
||||
|
||||
val apyMap = mapOf(
|
||||
"${tokenSmall.network.backendId}_${tokenSmall.contractAddress.lowercase()}" to BigDecimal("0.05"),
|
||||
"${tokenBig.network.backendId}_${tokenBig.contractAddress.uppercase()}" to BigDecimal("0.15"),
|
||||
"${tokenSmall.network.rawId}_${tokenSmall.contractAddress.lowercase()}" to BigDecimal("0.05"),
|
||||
"${tokenBig.network.rawId}_${tokenBig.contractAddress.uppercase()}" to BigDecimal("0.15"),
|
||||
)
|
||||
|
||||
val params = TokenConverterParams.Wallet(
|
||||
portfolioId = PortfolioId.Wallet(UserWalletId("00")),
|
||||
tokenList = ungroupedTokenList(statusSmall, statusBig),
|
||||
)
|
||||
val converter = YieldSupplyPromoBannerKeyConverter(
|
||||
val converter = YieldSupplyPromoBannerConverter(
|
||||
yieldModuleApyMap = apyMap,
|
||||
shouldShowMainPromo = true,
|
||||
)
|
||||
|
||||
val result = converter.convert(params)
|
||||
|
||||
val expectedKey = "${tokenBig.network.backendId}_${tokenBig.contractAddress.uppercase()}"
|
||||
assertThat(result).isEqualTo(expectedKey)
|
||||
assertThat(result).isEqualTo(statusBig)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -107,14 +105,14 @@ class YieldSupplyPromoBannerKeyConverterTest {
|
|||
val token = createToken(networkId = nonEvmId, backendId = nonEvmId, contract = "rAbC123")
|
||||
val status = createLoadedStatus(token = token, amount = BigDecimal("3"), isYieldActive = false)
|
||||
|
||||
val mismatchedKey = "${token.network.backendId}_${token.contractAddress.lowercase()}"
|
||||
val mismatchedKey = "${token.network.rawId}_${token.contractAddress.lowercase()}"
|
||||
val apyMap = mapOf(mismatchedKey to BigDecimal("0.07"))
|
||||
|
||||
val params = TokenConverterParams.Wallet(
|
||||
portfolioId = PortfolioId.Wallet(UserWalletId("00")),
|
||||
tokenList = ungroupedTokenList(status),
|
||||
)
|
||||
val converter = YieldSupplyPromoBannerKeyConverter(
|
||||
val converter = YieldSupplyPromoBannerConverter(
|
||||
yieldModuleApyMap = apyMap,
|
||||
shouldShowMainPromo = true,
|
||||
)
|
||||
|
|
@ -132,8 +130,8 @@ class YieldSupplyPromoBannerKeyConverterTest {
|
|||
portfolioId = PortfolioId.Wallet(UserWalletId("00")),
|
||||
tokenList = ungroupedTokenList(status),
|
||||
)
|
||||
val converter = YieldSupplyPromoBannerKeyConverter(
|
||||
yieldModuleApyMap = mapOf(token.yieldSupplyKey() to BigDecimal("0.10")),
|
||||
val converter = YieldSupplyPromoBannerConverter(
|
||||
yieldModuleApyMap = mapOf("${token.network.rawId}_${token.contractAddress}" to BigDecimal("0.10")),
|
||||
shouldShowMainPromo = true,
|
||||
)
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue