Updated on 2026-08-14
This commit is contained in:
parent
f3c2d98214
commit
f192c85914
59 changed files with 1679 additions and 432 deletions
|
|
@ -8,23 +8,20 @@ import com.tangem.utils.converter.Converter
|
||||||
|
|
||||||
class AccountIconItemStateConverter(
|
class AccountIconItemStateConverter(
|
||||||
val size: AccountIconSize = AccountIconSize.Default,
|
val size: AccountIconSize = AccountIconSize.Default,
|
||||||
) : Converter<Account, CurrencyIconState.CryptoPortfolio> {
|
) : Converter<Account.CryptoPortfolio, CurrencyIconState.CryptoPortfolio> {
|
||||||
|
|
||||||
override fun convert(value: Account): CurrencyIconState.CryptoPortfolio = when (value) {
|
override fun convert(value: Account.CryptoPortfolio): CurrencyIconState.CryptoPortfolio = when {
|
||||||
is Account.CryptoPortfolio -> when {
|
value.icon.value == CryptoPortfolioIcon.Icon.Letter -> CurrencyIconState.CryptoPortfolio.Letter(
|
||||||
value.icon.value == CryptoPortfolioIcon.Icon.Letter -> CurrencyIconState.CryptoPortfolio.Letter(
|
char = value.accountName.toUM().value,
|
||||||
char = value.accountName.toUM().value,
|
color = value.icon.color.getUiColor(),
|
||||||
color = value.icon.color.getUiColor(),
|
isGrayscale = false,
|
||||||
isGrayscale = false,
|
size = size,
|
||||||
size = size,
|
)
|
||||||
)
|
else -> CurrencyIconState.CryptoPortfolio.Icon(
|
||||||
else -> CurrencyIconState.CryptoPortfolio.Icon(
|
resId = value.icon.value.getResId(),
|
||||||
resId = value.icon.value.getResId(),
|
color = value.icon.color.getUiColor(),
|
||||||
color = value.icon.color.getUiColor(),
|
isGrayscale = false,
|
||||||
isGrayscale = false,
|
size = size,
|
||||||
size = size,
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
is Account.Payment -> TODO("[REDACTED_JIRA]")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
package com.tangem.common.ui.notifications
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
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.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.tangem.core.ui.R
|
||||||
|
import com.tangem.core.ui.components.notifications.CloseableIconButton
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
import com.tangem.core.ui.res.ForceDarkTheme
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
|
||||||
|
private const val GRADIENT_START_COLOR = 0xFF252934
|
||||||
|
private const val GRADIENT_END_COLOR = 0xFF12141E
|
||||||
|
private const val GRADIENT_OFFSET_X = 164f
|
||||||
|
private const val GRADIENT_OFFSET_Y = 39f
|
||||||
|
private const val GRADIENT_RADIUS = 82f
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CreatePaymentAccountNotification(
|
||||||
|
onClick: () -> Unit,
|
||||||
|
onCloseClick: () -> Unit,
|
||||||
|
@DrawableRes image: Int,
|
||||||
|
title: TextReference,
|
||||||
|
subtitle: TextReference,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.background(
|
||||||
|
brush = Brush.radialGradient(
|
||||||
|
colors = listOf(Color(GRADIENT_START_COLOR), Color(GRADIENT_END_COLOR)),
|
||||||
|
center = Offset(GRADIENT_OFFSET_X, GRADIENT_OFFSET_Y),
|
||||||
|
radius = GRADIENT_RADIUS,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.clickable(onClick = onClick),
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
modifier = Modifier.size(78.dp),
|
||||||
|
painter = painterResource(id = image),
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 78.dp, top = 12.dp, end = 12.dp, bottom = 12.dp)
|
||||||
|
.align(Alignment.CenterStart),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(end = TangemTheme.dimens.size32),
|
||||||
|
text = title.resolveReference(),
|
||||||
|
style = TangemTheme.typography.subtitle2,
|
||||||
|
color = TangemTheme.colors.text.constantWhite,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = subtitle.resolveReference(),
|
||||||
|
style = TangemTheme.typography.caption2,
|
||||||
|
color = TangemTheme.colors.text.tertiary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
CloseableIconButton(
|
||||||
|
onClick = onCloseClick,
|
||||||
|
modifier = Modifier.align(alignment = Alignment.TopEnd),
|
||||||
|
iconTint = TangemTheme.colors.icon.inactive,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(widthDp = 360)
|
||||||
|
@Composable
|
||||||
|
private fun CreatePaymentAccountNotification_Preview() {
|
||||||
|
ForceDarkTheme {
|
||||||
|
CreatePaymentAccountNotification(
|
||||||
|
onClick = {},
|
||||||
|
onCloseClick = {},
|
||||||
|
image = R.drawable.img_tangem_pay_visa_banner,
|
||||||
|
title = resourceReference(R.string.tangempay_onboarding_banner_title),
|
||||||
|
subtitle = resourceReference(R.string.tangempay_onboarding_banner_description),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ import com.tangem.common.json.MoshiJsonConverter
|
||||||
import com.tangem.datasource.api.common.adapter.*
|
import com.tangem.datasource.api.common.adapter.*
|
||||||
import com.tangem.datasource.local.config.providers.models.ProviderModel
|
import com.tangem.datasource.local.config.providers.models.ProviderModel
|
||||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||||
import com.tangem.datasource.local.visa.entity.PaymentAccountStatusDM
|
import com.tangem.datasource.local.visa.entity.PaymentAccountStatusValueDM
|
||||||
import com.tangem.datasource.utils.SerializeNullsFactory
|
import com.tangem.datasource.utils.SerializeNullsFactory
|
||||||
import com.tangem.domain.models.scan.serialization.*
|
import com.tangem.domain.models.scan.serialization.*
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
|
@ -47,13 +47,12 @@ class MoshiModule {
|
||||||
.withSubtype(NetworkStatusDM.NoAccount::class.java, "amount_to_create_account"),
|
.withSubtype(NetworkStatusDM.NoAccount::class.java, "amount_to_create_account"),
|
||||||
)
|
)
|
||||||
.add(
|
.add(
|
||||||
NamePolymorphicAdapterFactory.of(PaymentAccountStatusDM::class.java)
|
NamePolymorphicAdapterFactory.of(PaymentAccountStatusValueDM::class.java)
|
||||||
.withSubtype(PaymentAccountStatusDM.NotCreated::class.java, "not_created")
|
.withSubtype(PaymentAccountStatusValueDM.NotCreated::class.java, "not_created")
|
||||||
.withSubtype(PaymentAccountStatusDM.UnderReview::class.java, "kyc_status")
|
.withSubtype(PaymentAccountStatusValueDM.UnderReview::class.java, "kyc_status")
|
||||||
.withSubtype(PaymentAccountStatusDM.IssuingCard::class.java, "issuing_card")
|
.withSubtype(PaymentAccountStatusValueDM.IssuingCard::class.java, "issuing_card")
|
||||||
.withSubtype(PaymentAccountStatusDM.Locked::class.java, "locked")
|
.withSubtype(PaymentAccountStatusValueDM.ActiveCard::class.java, "active_card")
|
||||||
.withSubtype(PaymentAccountStatusDM.Loaded::class.java, "balance")
|
.withSubtype(PaymentAccountStatusValueDM.CardIssueFailed::class.java, "card_issue_failed"),
|
||||||
.withSubtype(PaymentAccountStatusDM.CardIssueFailed::class.java, "card_issue_failed"),
|
|
||||||
)
|
)
|
||||||
.add(
|
.add(
|
||||||
PolymorphicJsonAdapterFactory.of(NFTCollection.Identifier::class.java, "bc")
|
PolymorphicJsonAdapterFactory.of(NFTCollection.Identifier::class.java, "bc")
|
||||||
|
|
|
||||||
|
|
@ -11,43 +11,58 @@ import java.math.BigDecimal
|
||||||
/**
|
/**
|
||||||
* Payment account status for storage in the local cache.
|
* Payment account status for storage in the local cache.
|
||||||
*
|
*
|
||||||
* @see [com.tangem.domain.pay.PaymentAccountStatus]
|
* @see [com.tangem.domain.models.account.AccountStatus.Payment]
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true, generator = PolymorphicAdapterType.NAME_POLYMORPHIC_ADAPTER)
|
@JsonClass(generateAdapter = true, generator = PolymorphicAdapterType.NAME_POLYMORPHIC_ADAPTER)
|
||||||
sealed interface PaymentAccountStatusDM {
|
sealed interface PaymentAccountStatusValueDM {
|
||||||
|
|
||||||
@NameLabel("not_created")
|
@NameLabel("not_created")
|
||||||
data class NotCreated(
|
data class NotCreated(
|
||||||
@Json(name = "not_created") val marker: Boolean = true,
|
@Json(name = "not_created") val marker: Boolean = true,
|
||||||
) : PaymentAccountStatusDM
|
) : PaymentAccountStatusValueDM
|
||||||
|
|
||||||
@NameLabel("kyc_status")
|
@NameLabel("kyc_status")
|
||||||
data class UnderReview(
|
data class UnderReview(
|
||||||
@Json(name = "kyc_status") val kycStatus: KycStatus,
|
@Json(name = "kyc_status") val kycStatus: KycStatus,
|
||||||
) : PaymentAccountStatusDM
|
@Json(name = "customer_id") val customerId: String,
|
||||||
|
) : PaymentAccountStatusValueDM
|
||||||
|
|
||||||
@NameLabel("issuing_card")
|
@NameLabel("issuing_card")
|
||||||
data class IssuingCard(
|
data class IssuingCard(
|
||||||
@Json(name = "issuing_card") val marker: Boolean = true,
|
@Json(name = "issuing_card") val marker: Boolean = true,
|
||||||
) : PaymentAccountStatusDM
|
) : PaymentAccountStatusValueDM
|
||||||
|
|
||||||
@NameLabel("locked")
|
@NameLabel("active_card")
|
||||||
data class Locked(
|
data class ActiveCard(
|
||||||
@Json(name = "locked") val marker: Boolean = true,
|
@Json(name = "active_card") val isLocked: Boolean,
|
||||||
) : PaymentAccountStatusDM
|
@Json(name = "customer_id") val customerId: String,
|
||||||
|
|
||||||
@NameLabel("balance")
|
|
||||||
data class Loaded(
|
|
||||||
@Json(name = "card_id") val cardId: String,
|
@Json(name = "card_id") val cardId: String,
|
||||||
@Json(name = "last_four_digits") val lastFourDigits: String,
|
@Json(name = "last_four_digits") val lastFourDigits: String,
|
||||||
@Json(name = "balance") val balance: BigDecimal,
|
|
||||||
@Json(name = "currency_code") val currencyCode: String,
|
@Json(name = "currency_code") val currencyCode: String,
|
||||||
@Json(name = "deposit_address") val depositAddress: String?,
|
@Json(name = "deposit_address") val depositAddress: String?,
|
||||||
@Json(name = "is_pin_set") val isPinSet: Boolean,
|
@Json(name = "is_pin_set") val isPinSet: Boolean,
|
||||||
) : PaymentAccountStatusDM
|
@Json(name = "fiat_balance") val fiatBalance: FiatBalanceDM,
|
||||||
|
@Json(name = "crypto_balance") val cryptoBalance: CryptoBalanceDM,
|
||||||
|
) : PaymentAccountStatusValueDM
|
||||||
|
|
||||||
@NameLabel("card_issue_failed")
|
@NameLabel("card_issue_failed")
|
||||||
data class CardIssueFailed(
|
data class CardIssueFailed(
|
||||||
@Json(name = "card_issue_failed") val marker: Boolean = true,
|
@Json(name = "card_issue_failed") val marker: Boolean = true,
|
||||||
) : PaymentAccountStatusDM
|
@Json(name = "customer_id") val customerId: String,
|
||||||
|
) : PaymentAccountStatusValueDM
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class FiatBalanceDM(
|
||||||
|
@Json(name = "available_balance") val availableBalance: BigDecimal,
|
||||||
|
@Json(name = "currency") val currency: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class CryptoBalanceDM(
|
||||||
|
@Json(name = "id") val id: String,
|
||||||
|
@Json(name = "chain_id") val chainId: Long,
|
||||||
|
@Json(name = "deposit_address") val depositAddress: String,
|
||||||
|
@Json(name = "token_contract_address") val tokenContractAddress: String,
|
||||||
|
@Json(name = "balance") val balance: BigDecimal,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
BIN
core/ui/src/main/res/drawable/img_tangem_pay_visa_banner.webp
Normal file
BIN
core/ui/src/main/res/drawable/img_tangem_pay_visa_banner.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
|
|
@ -33,8 +33,11 @@ dependencies {
|
||||||
api(projects.domain.models)
|
api(projects.domain.models)
|
||||||
api(projects.domain.tokens)
|
api(projects.domain.tokens)
|
||||||
api(projects.domain.wallets)
|
api(projects.domain.wallets)
|
||||||
|
api(projects.domain.visa)
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
implementation(projects.features.tangempay.details.api) // Remove after TANGEM_PAY_ACCOUNTS_REFACTOR_ENABLED
|
||||||
|
|
||||||
// region Project - Data
|
// region Project - Data
|
||||||
implementation(projects.data.common)
|
implementation(projects.data.common)
|
||||||
// endregion
|
// endregion
|
||||||
|
|
@ -47,6 +50,7 @@ dependencies {
|
||||||
// region Tangem dependencies
|
// region Tangem dependencies
|
||||||
implementation(tangemDeps.card.core)
|
implementation(tangemDeps.card.core)
|
||||||
implementation(tangemDeps.blockchain)
|
implementation(tangemDeps.blockchain)
|
||||||
|
implementation(tangemDeps.hot.core)
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region DI
|
// region DI
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,18 @@
|
||||||
package com.tangem.data.account.producer
|
package com.tangem.data.account.producer
|
||||||
|
|
||||||
import arrow.core.Option
|
import arrow.core.Option
|
||||||
|
import arrow.core.getOrElse
|
||||||
import arrow.core.none
|
import arrow.core.none
|
||||||
|
import com.tangem.common.card.FirmwareVersion
|
||||||
import com.tangem.domain.account.models.AccountList
|
import com.tangem.domain.account.models.AccountList
|
||||||
import com.tangem.domain.account.producer.SingleAccountListProducer
|
import com.tangem.domain.account.producer.SingleAccountListProducer
|
||||||
|
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||||
|
import com.tangem.domain.common.wallets.getSyncStrict
|
||||||
import com.tangem.domain.core.flow.FlowProducerTools
|
import com.tangem.domain.core.flow.FlowProducerTools
|
||||||
|
import com.tangem.domain.models.account.Account
|
||||||
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
|
import com.tangem.features.tangempay.TangemPayFeatureToggles
|
||||||
|
import com.tangem.hot.sdk.model.HotWalletId
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
|
|
@ -12,6 +20,7 @@ import dagger.assisted.AssistedInject
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default implementation of [SingleAccountListProducer].
|
* Default implementation of [SingleAccountListProducer].
|
||||||
|
|
@ -27,6 +36,8 @@ internal class DefaultSingleAccountListProducer @AssistedInject constructor(
|
||||||
@Assisted val params: SingleAccountListProducer.Params,
|
@Assisted val params: SingleAccountListProducer.Params,
|
||||||
override val flowProducerTools: FlowProducerTools,
|
override val flowProducerTools: FlowProducerTools,
|
||||||
private val walletAccountListFlowFactory: WalletAccountListFlowFactory,
|
private val walletAccountListFlowFactory: WalletAccountListFlowFactory,
|
||||||
|
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
|
||||||
|
private val userWalletsListRepository: UserWalletsListRepository,
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
) : SingleAccountListProducer {
|
) : SingleAccountListProducer {
|
||||||
|
|
||||||
|
|
@ -34,8 +45,32 @@ internal class DefaultSingleAccountListProducer @AssistedInject constructor(
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
override fun produce(): Flow<AccountList> {
|
override fun produce(): Flow<AccountList> {
|
||||||
return walletAccountListFlowFactory.create(userWalletId = params.userWalletId)
|
val accountListFlow: Flow<AccountList> = if (tangemPayFeatureToggles.isTangemPayAccountsRefactorEnabled) {
|
||||||
.flowOn(dispatchers.default)
|
combineWithPaymentAccount()
|
||||||
|
} else {
|
||||||
|
walletAccountListFlowFactory.create(userWalletId = params.userWalletId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accountListFlow.flowOn(dispatchers.default)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun combineWithPaymentAccount(): Flow<AccountList> {
|
||||||
|
return walletAccountListFlowFactory.create(params.userWalletId)
|
||||||
|
.map { accountList ->
|
||||||
|
val userWallet = userWalletsListRepository.getSyncStrict(id = params.userWalletId)
|
||||||
|
if (userWallet.isPaymentAccountSupported()) {
|
||||||
|
accountList.plus(Account.Payment(params.userWalletId)).getOrElse { throwable ->
|
||||||
|
error("Can not combine account list and payment account status: $throwable")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
accountList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun UserWallet.isPaymentAccountSupported(): Boolean = when (this) {
|
||||||
|
is UserWallet.Cold -> scanResponse.card.firmwareVersion >= FirmwareVersion.HDWalletAvailable
|
||||||
|
is UserWallet.Hot -> hotWalletId.authType != HotWalletId.AuthType.NoPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,12 @@ package com.tangem.data.account.producer
|
||||||
import com.google.common.truth.Truth
|
import com.google.common.truth.Truth
|
||||||
import com.tangem.domain.account.models.AccountList
|
import com.tangem.domain.account.models.AccountList
|
||||||
import com.tangem.domain.account.producer.SingleAccountListProducer
|
import com.tangem.domain.account.producer.SingleAccountListProducer
|
||||||
|
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||||
import com.tangem.domain.core.flow.FlowProducerTools
|
import com.tangem.domain.core.flow.FlowProducerTools
|
||||||
import com.tangem.domain.models.TokensSortType
|
import com.tangem.domain.models.TokensSortType
|
||||||
import com.tangem.domain.models.wallet.UserWallet
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
import com.tangem.features.tangempay.TangemPayFeatureToggles
|
||||||
import com.tangem.test.core.getEmittedValues
|
import com.tangem.test.core.getEmittedValues
|
||||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||||
import io.mockk.*
|
import io.mockk.*
|
||||||
|
|
@ -29,6 +31,10 @@ class DefaultSingleAccountListProducerTest {
|
||||||
|
|
||||||
private val userWalletId = UserWalletId("011")
|
private val userWalletId = UserWalletId("011")
|
||||||
private val flowProducerTools: FlowProducerTools = mockk()
|
private val flowProducerTools: FlowProducerTools = mockk()
|
||||||
|
private val tangemPayFeatureToggles = mockk<TangemPayFeatureToggles> {
|
||||||
|
every { this@mockk.isTangemPayAccountsRefactorEnabled } returns false
|
||||||
|
}
|
||||||
|
private val userWalletsListRepository = mockk<UserWalletsListRepository>()
|
||||||
private val userWallet = mockk<UserWallet> {
|
private val userWallet = mockk<UserWallet> {
|
||||||
every { this@mockk.walletId } returns userWalletId
|
every { this@mockk.walletId } returns userWalletId
|
||||||
}
|
}
|
||||||
|
|
@ -38,6 +44,8 @@ class DefaultSingleAccountListProducerTest {
|
||||||
walletAccountListFlowFactory = walletAccountListFlowFactory,
|
walletAccountListFlowFactory = walletAccountListFlowFactory,
|
||||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||||
flowProducerTools = flowProducerTools,
|
flowProducerTools = flowProducerTools,
|
||||||
|
tangemPayFeatureToggles = tangemPayFeatureToggles,
|
||||||
|
userWalletsListRepository = userWalletsListRepository,
|
||||||
)
|
)
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
|
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
package com.tangem.data.pay.converter
|
|
||||||
|
|
||||||
import com.tangem.data.pay.converter.PaymentAccountStatusDMConverter.convert
|
|
||||||
import com.tangem.data.pay.converter.PaymentAccountStatusDMConverter.convertBack
|
|
||||||
import com.tangem.datasource.local.visa.entity.PaymentAccountStatusDM
|
|
||||||
import com.tangem.domain.models.StatusSource
|
|
||||||
import com.tangem.domain.pay.PaymentAccountStatus
|
|
||||||
import com.tangem.utils.converter.TwoWayConverter
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Two-way converter between [PaymentAccountStatus] and [PaymentAccountStatusDM].
|
|
||||||
*
|
|
||||||
* [convert] maps domain → data model. Returns null for transient statuses that should not be persisted
|
|
||||||
* (Loading, ExposedDevice, Unavailable, NotSynced).
|
|
||||||
*
|
|
||||||
* [convertBack] maps data model → domain. All restored statuses have [StatusSource.CACHE] as source.
|
|
||||||
*/
|
|
||||||
internal object PaymentAccountStatusDMConverter :
|
|
||||||
TwoWayConverter<PaymentAccountStatus, PaymentAccountStatusDM?> {
|
|
||||||
|
|
||||||
override fun convert(value: PaymentAccountStatus): PaymentAccountStatusDM? {
|
|
||||||
return when (value) {
|
|
||||||
is PaymentAccountStatus.NotCreated -> PaymentAccountStatusDM.NotCreated()
|
|
||||||
is PaymentAccountStatus.UnderReview -> PaymentAccountStatusDM.UnderReview(kycStatus = value.kycStatus)
|
|
||||||
is PaymentAccountStatus.IssuingCard -> PaymentAccountStatusDM.IssuingCard()
|
|
||||||
is PaymentAccountStatus.Locked -> PaymentAccountStatusDM.Locked()
|
|
||||||
is PaymentAccountStatus.Loaded -> PaymentAccountStatusDM.Loaded(
|
|
||||||
cardId = value.cardId,
|
|
||||||
lastFourDigits = value.lastFourDigits,
|
|
||||||
balance = value.balance,
|
|
||||||
currencyCode = value.currencyCode,
|
|
||||||
depositAddress = value.depositAddress,
|
|
||||||
isPinSet = value.isPinSet,
|
|
||||||
)
|
|
||||||
is PaymentAccountStatus.Error.CardIssueFailed -> PaymentAccountStatusDM.CardIssueFailed()
|
|
||||||
// Transient statuses are not persisted
|
|
||||||
is PaymentAccountStatus.Loading,
|
|
||||||
is PaymentAccountStatus.Error.ExposedDevice,
|
|
||||||
is PaymentAccountStatus.Error.Unavailable,
|
|
||||||
is PaymentAccountStatus.Error.NotSynced,
|
|
||||||
-> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun convertBack(value: PaymentAccountStatusDM?): PaymentAccountStatus {
|
|
||||||
return when (value) {
|
|
||||||
is PaymentAccountStatusDM.CardIssueFailed -> PaymentAccountStatus.Error.CardIssueFailed
|
|
||||||
is PaymentAccountStatusDM.NotCreated -> PaymentAccountStatus.NotCreated
|
|
||||||
is PaymentAccountStatusDM.IssuingCard -> PaymentAccountStatus.IssuingCard(source = StatusSource.CACHE)
|
|
||||||
is PaymentAccountStatusDM.Locked -> PaymentAccountStatus.Locked(source = StatusSource.CACHE)
|
|
||||||
is PaymentAccountStatusDM.UnderReview -> PaymentAccountStatus.UnderReview(
|
|
||||||
source = StatusSource.CACHE,
|
|
||||||
kycStatus = value.kycStatus,
|
|
||||||
)
|
|
||||||
is PaymentAccountStatusDM.Loaded -> PaymentAccountStatus.Loaded(
|
|
||||||
source = StatusSource.CACHE,
|
|
||||||
cardId = value.cardId,
|
|
||||||
lastFourDigits = value.lastFourDigits,
|
|
||||||
balance = value.balance,
|
|
||||||
currencyCode = value.currencyCode,
|
|
||||||
depositAddress = value.depositAddress,
|
|
||||||
isPinSet = value.isPinSet,
|
|
||||||
)
|
|
||||||
null -> PaymentAccountStatus.Error.Unavailable(source = StatusSource.CACHE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
package com.tangem.data.pay.converter
|
||||||
|
|
||||||
|
import com.tangem.data.pay.converter.PaymentAccountStatusValueDMConverter.convert
|
||||||
|
import com.tangem.data.pay.converter.PaymentAccountStatusValueDMConverter.convertBack
|
||||||
|
import com.tangem.datasource.local.visa.entity.PaymentAccountStatusValueDM
|
||||||
|
import com.tangem.domain.models.StatusSource
|
||||||
|
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||||
|
import com.tangem.utils.converter.TwoWayConverter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Two-way converter between [PaymentAccountStatusValue] and [PaymentAccountStatusValueDM].
|
||||||
|
*
|
||||||
|
* [convert] maps domain → data model. Returns null for transient statuses that should not be persisted
|
||||||
|
* (Loading, ExposedDevice, Unavailable, NotSynced).
|
||||||
|
*
|
||||||
|
* [convertBack] maps data model → domain. All restored statuses have [StatusSource.CACHE] as source.
|
||||||
|
*/
|
||||||
|
internal object PaymentAccountStatusValueDMConverter :
|
||||||
|
TwoWayConverter<PaymentAccountStatusValue, PaymentAccountStatusValueDM?> {
|
||||||
|
|
||||||
|
override fun convert(value: PaymentAccountStatusValue): PaymentAccountStatusValueDM? {
|
||||||
|
return when (value) {
|
||||||
|
is PaymentAccountStatusValue.NotCreated -> PaymentAccountStatusValueDM.NotCreated()
|
||||||
|
is PaymentAccountStatusValue.UnderReview -> PaymentAccountStatusValueDM.UnderReview(
|
||||||
|
kycStatus = value.kycStatus,
|
||||||
|
customerId = value.customerId,
|
||||||
|
)
|
||||||
|
is PaymentAccountStatusValue.IssuingCard -> PaymentAccountStatusValueDM.IssuingCard()
|
||||||
|
is PaymentAccountStatusValue.Locked -> PaymentAccountStatusValueDM.ActiveCard(
|
||||||
|
isLocked = true,
|
||||||
|
customerId = value.customerId,
|
||||||
|
cardId = value.cardId,
|
||||||
|
lastFourDigits = value.lastFourDigits,
|
||||||
|
currencyCode = value.currencyCode,
|
||||||
|
depositAddress = value.depositAddress,
|
||||||
|
isPinSet = value.isPinSet,
|
||||||
|
fiatBalance = value.fiatBalance.toDM(),
|
||||||
|
cryptoBalance = value.cryptoBalance.toDM(),
|
||||||
|
)
|
||||||
|
is PaymentAccountStatusValue.Loaded -> PaymentAccountStatusValueDM.ActiveCard(
|
||||||
|
isLocked = false,
|
||||||
|
customerId = value.customerId,
|
||||||
|
cardId = value.cardId,
|
||||||
|
lastFourDigits = value.lastFourDigits,
|
||||||
|
currencyCode = value.currencyCode,
|
||||||
|
depositAddress = value.depositAddress,
|
||||||
|
isPinSet = value.isPinSet,
|
||||||
|
fiatBalance = value.fiatBalance.toDM(),
|
||||||
|
cryptoBalance = value.cryptoBalance.toDM(),
|
||||||
|
)
|
||||||
|
is PaymentAccountStatusValue.Error.CardIssueFailed -> PaymentAccountStatusValueDM.CardIssueFailed(
|
||||||
|
customerId = value.customerId,
|
||||||
|
)
|
||||||
|
// Transient statuses are not persisted
|
||||||
|
is PaymentAccountStatusValue.Loading,
|
||||||
|
is PaymentAccountStatusValue.Error.ExposedDevice,
|
||||||
|
is PaymentAccountStatusValue.Error.Unavailable,
|
||||||
|
is PaymentAccountStatusValue.Error.NotSynced,
|
||||||
|
-> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun convertBack(value: PaymentAccountStatusValueDM?): PaymentAccountStatusValue {
|
||||||
|
return when (value) {
|
||||||
|
is PaymentAccountStatusValueDM.NotCreated -> PaymentAccountStatusValue.NotCreated
|
||||||
|
is PaymentAccountStatusValueDM.CardIssueFailed -> PaymentAccountStatusValue.Error.CardIssueFailed(
|
||||||
|
customerId = value.customerId,
|
||||||
|
)
|
||||||
|
is PaymentAccountStatusValueDM.IssuingCard -> PaymentAccountStatusValue.IssuingCard(
|
||||||
|
source = StatusSource.CACHE,
|
||||||
|
)
|
||||||
|
is PaymentAccountStatusValueDM.ActiveCard -> if (value.isLocked) {
|
||||||
|
PaymentAccountStatusValue.Locked(
|
||||||
|
source = StatusSource.CACHE,
|
||||||
|
customerId = value.customerId,
|
||||||
|
cardId = value.cardId,
|
||||||
|
lastFourDigits = value.lastFourDigits,
|
||||||
|
currencyCode = value.currencyCode,
|
||||||
|
depositAddress = value.depositAddress,
|
||||||
|
isPinSet = value.isPinSet,
|
||||||
|
fiatBalance = value.fiatBalance.toDomain(),
|
||||||
|
cryptoBalance = value.cryptoBalance.toDomain(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
PaymentAccountStatusValue.Loaded(
|
||||||
|
source = StatusSource.CACHE,
|
||||||
|
customerId = value.customerId,
|
||||||
|
cardId = value.cardId,
|
||||||
|
lastFourDigits = value.lastFourDigits,
|
||||||
|
currencyCode = value.currencyCode,
|
||||||
|
depositAddress = value.depositAddress,
|
||||||
|
isPinSet = value.isPinSet,
|
||||||
|
fiatBalance = value.fiatBalance.toDomain(),
|
||||||
|
cryptoBalance = value.cryptoBalance.toDomain(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is PaymentAccountStatusValueDM.UnderReview -> PaymentAccountStatusValue.UnderReview(
|
||||||
|
source = StatusSource.CACHE,
|
||||||
|
kycStatus = value.kycStatus,
|
||||||
|
customerId = value.customerId,
|
||||||
|
)
|
||||||
|
null -> PaymentAccountStatusValue.Error.Unavailable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PaymentAccountStatusValue.FiatBalance.toDM(): PaymentAccountStatusValueDM.FiatBalanceDM {
|
||||||
|
return PaymentAccountStatusValueDM.FiatBalanceDM(
|
||||||
|
availableBalance = availableBalance,
|
||||||
|
currency = currency,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PaymentAccountStatusValue.CryptoBalance.toDM(): PaymentAccountStatusValueDM.CryptoBalanceDM {
|
||||||
|
return PaymentAccountStatusValueDM.CryptoBalanceDM(
|
||||||
|
id = id,
|
||||||
|
chainId = chainId,
|
||||||
|
depositAddress = depositAddress,
|
||||||
|
tokenContractAddress = tokenContractAddress,
|
||||||
|
balance = balance,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PaymentAccountStatusValueDM.FiatBalanceDM.toDomain(): PaymentAccountStatusValue.FiatBalance {
|
||||||
|
return PaymentAccountStatusValue.FiatBalance(
|
||||||
|
availableBalance = availableBalance,
|
||||||
|
currency = currency,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PaymentAccountStatusValueDM.CryptoBalanceDM.toDomain(): PaymentAccountStatusValue.CryptoBalance {
|
||||||
|
return PaymentAccountStatusValue.CryptoBalance(
|
||||||
|
id = id,
|
||||||
|
chainId = chainId,
|
||||||
|
depositAddress = depositAddress,
|
||||||
|
tokenContractAddress = tokenContractAddress,
|
||||||
|
balance = balance,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,10 +15,9 @@ import com.tangem.data.pay.usecase.DefaultGetTangemPayCustomerIdUseCase
|
||||||
import com.tangem.data.pay.usecase.DefaultTangemPayWithdrawUseCase
|
import com.tangem.data.pay.usecase.DefaultTangemPayWithdrawUseCase
|
||||||
import com.tangem.datasource.di.NetworkMoshi
|
import com.tangem.datasource.di.NetworkMoshi
|
||||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||||
import com.tangem.datasource.local.visa.entity.PaymentAccountStatusDM
|
import com.tangem.datasource.local.visa.entity.PaymentAccountStatusValueDM
|
||||||
import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
||||||
import com.tangem.datasource.utils.mapWithStringKeyTypes
|
import com.tangem.datasource.utils.mapWithStringKeyTypes
|
||||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
|
||||||
import com.tangem.domain.pay.TangemPayCryptoCurrencyFactory
|
import com.tangem.domain.pay.TangemPayCryptoCurrencyFactory
|
||||||
import com.tangem.domain.pay.TangemPayEligibilityManager
|
import com.tangem.domain.pay.TangemPayEligibilityManager
|
||||||
import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher
|
import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher
|
||||||
|
|
@ -32,6 +31,7 @@ import com.tangem.domain.tangempay.GetTangemPayCustomerIdUseCase
|
||||||
import com.tangem.domain.tangempay.TangemPayWithdrawUseCase
|
import com.tangem.domain.tangempay.TangemPayWithdrawUseCase
|
||||||
import com.tangem.domain.tangempay.repository.TangemPayTxHistoryRepository
|
import com.tangem.domain.tangempay.repository.TangemPayTxHistoryRepository
|
||||||
import com.tangem.security.DeviceSecurityInfoProvider
|
import com.tangem.security.DeviceSecurityInfoProvider
|
||||||
|
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
|
@ -118,7 +118,7 @@ internal interface TangemPayDataModule {
|
||||||
persistenceDataStore = DataStoreFactory.create(
|
persistenceDataStore = DataStoreFactory.create(
|
||||||
serializer = MoshiDataStoreSerializer(
|
serializer = MoshiDataStoreSerializer(
|
||||||
moshi = moshi,
|
moshi = moshi,
|
||||||
types = mapWithStringKeyTypes<PaymentAccountStatusDM>(),
|
types = mapWithStringKeyTypes<PaymentAccountStatusValueDM>(),
|
||||||
defaultValue = emptyMap(),
|
defaultValue = emptyMap(),
|
||||||
),
|
),
|
||||||
produceFile = { context.dataStoreFile(fileName = "payment_account_statuses") },
|
produceFile = { context.dataStoreFile(fileName = "payment_account_statuses") },
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,19 @@ package com.tangem.data.pay.flow
|
||||||
|
|
||||||
import arrow.core.Either
|
import arrow.core.Either
|
||||||
import com.tangem.data.pay.store.PaymentAccountStatusesStore
|
import com.tangem.data.pay.store.PaymentAccountStatusesStore
|
||||||
import com.tangem.domain.core.utils.eitherOn
|
import com.tangem.domain.core.utils.catchOn
|
||||||
import com.tangem.domain.models.StatusSource
|
import com.tangem.domain.models.StatusSource
|
||||||
|
import com.tangem.domain.models.account.Account
|
||||||
|
import com.tangem.domain.models.account.AccountStatus
|
||||||
|
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||||
import com.tangem.domain.models.kyc.KycStatus
|
import com.tangem.domain.models.kyc.KycStatus
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
|
||||||
import com.tangem.domain.pay.PaymentAccountStatus
|
|
||||||
import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher
|
import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher
|
||||||
import com.tangem.domain.pay.model.CustomerInfo
|
import com.tangem.domain.pay.model.CustomerInfo
|
||||||
import com.tangem.domain.pay.model.OrderStatus
|
import com.tangem.domain.pay.model.OrderStatus
|
||||||
import com.tangem.domain.pay.repository.CustomerOrderRepository
|
import com.tangem.domain.pay.repository.CustomerOrderRepository
|
||||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||||
import com.tangem.domain.visa.error.VisaApiError
|
import com.tangem.domain.visa.error.VisaApiError
|
||||||
|
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||||
import com.tangem.security.DeviceSecurityInfoProvider
|
import com.tangem.security.DeviceSecurityInfoProvider
|
||||||
import com.tangem.security.isSecurityExposed
|
import com.tangem.security.isSecurityExposed
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
|
@ -29,87 +31,101 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
) : PaymentAccountStatusFetcher {
|
) : PaymentAccountStatusFetcher {
|
||||||
|
|
||||||
|
private val logger = TangemLogger.withTag(TAG)
|
||||||
|
|
||||||
override suspend fun invoke(params: PaymentAccountStatusFetcher.Params): Either<Throwable, Unit> =
|
override suspend fun invoke(params: PaymentAccountStatusFetcher.Params): Either<Throwable, Unit> =
|
||||||
eitherOn(dispatchers.default) {
|
Either.catchOn(dispatchers.default) {
|
||||||
TangemLogger.withTag(TAG).i("fetch: ${params.userWalletId.stringValue}")
|
val account = Account.Payment(userWalletId = params.userWalletId)
|
||||||
|
logger.i("fetch: ${params.userWalletId.stringValue}")
|
||||||
|
|
||||||
if (deviceSecurity.isSecurityExposed()) {
|
if (deviceSecurity.isSecurityExposed()) {
|
||||||
TangemLogger.withTag(TAG).i("fetch security info: rooted: ${deviceSecurity.isRooted}")
|
logger.i("fetch security info: rooted: ${deviceSecurity.isRooted}")
|
||||||
TangemLogger.withTag(TAG).i("fetch security info: xposed: ${deviceSecurity.isXposed}")
|
logger.i("fetch security info: xposed: ${deviceSecurity.isXposed}")
|
||||||
TangemLogger.withTag(
|
logger.i("fetch security info: bootloader unlocked: ${deviceSecurity.isBootloaderUnlocked}")
|
||||||
TAG,
|
|
||||||
).i("fetch security info: bootloader unlocked: ${deviceSecurity.isBootloaderUnlocked}")
|
|
||||||
|
|
||||||
return@eitherOn paymentAccountStatusesStore.store(
|
return@catchOn paymentAccountStatusesStore.store(
|
||||||
userWalletId = params.userWalletId,
|
userWalletId = params.userWalletId,
|
||||||
status = PaymentAccountStatus.Error.ExposedDevice,
|
status = AccountStatus.Payment(
|
||||||
|
account = account,
|
||||||
|
value = PaymentAccountStatusValue.Error.ExposedDevice,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val status = onboardingRepository.hasTangemPayInWallet(userWalletId = params.userWalletId)
|
val status = onboardingRepository.hasTangemPayInWallet(userWalletId = params.userWalletId)
|
||||||
.fold(
|
.fold(
|
||||||
ifLeft = { error ->
|
ifLeft = { error ->
|
||||||
TangemLogger.withTag(
|
logger.e("Failed check wallet ${params.userWalletId}: ${error.javaClass.simpleName}")
|
||||||
TAG,
|
|
||||||
).e("Failed check wallet ${params.userWalletId}: ${error.javaClass.simpleName}")
|
|
||||||
when (error) {
|
when (error) {
|
||||||
is VisaApiError.NotPaeraCustomer -> PaymentAccountStatus.NotCreated
|
is VisaApiError.NotPaeraCustomer -> PaymentAccountStatusValue.NotCreated
|
||||||
else -> PaymentAccountStatus.Error.Unavailable(source = StatusSource.ACTUAL)
|
else -> PaymentAccountStatusValue.Error.Unavailable
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ifRight = { hasTangemPay ->
|
ifRight = { hasTangemPay ->
|
||||||
proceedHasTangemPayResult(userWalletId = params.userWalletId, hasTangemPay = hasTangemPay)
|
proceedHasTangemPayResult(account = account, hasTangemPay = hasTangemPay)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
TangemLogger.withTag(TAG).i("invoke status ${params.userWalletId}: $status")
|
logger.i("invoke status ${params.userWalletId}: $status")
|
||||||
paymentAccountStatusesStore.store(userWalletId = params.userWalletId, status = status)
|
paymentAccountStatusesStore.store(
|
||||||
|
userWalletId = params.userWalletId,
|
||||||
|
status = AccountStatus.Payment(account = account, value = status),
|
||||||
|
)
|
||||||
|
}.onLeft {
|
||||||
|
paymentAccountStatusesStore.updateStatusSource(
|
||||||
|
userWalletId = params.userWalletId,
|
||||||
|
source = StatusSource.ONLY_CACHE,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun proceedHasTangemPayResult(
|
private suspend fun proceedHasTangemPayResult(
|
||||||
userWalletId: UserWalletId,
|
account: Account.Payment,
|
||||||
hasTangemPay: Boolean,
|
hasTangemPay: Boolean,
|
||||||
): PaymentAccountStatus {
|
): PaymentAccountStatusValue {
|
||||||
TangemLogger.withTag(TAG).i("proceedHasTangemPayResult for $userWalletId hasTangemPay: $hasTangemPay")
|
logger.i("proceedHasTangemPayResult for ${account.userWalletId} hasTangemPay: $hasTangemPay")
|
||||||
return if (hasTangemPay) {
|
return if (hasTangemPay) {
|
||||||
fetchTangemPayAccountStatus(userWalletId = userWalletId)
|
fetchTangemPayAccountStatus(account)
|
||||||
} else {
|
} else {
|
||||||
PaymentAccountStatus.NotCreated
|
PaymentAccountStatusValue.NotCreated
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchTangemPayAccountStatus(userWalletId: UserWalletId): PaymentAccountStatus {
|
private suspend fun fetchTangemPayAccountStatus(account: Account.Payment): PaymentAccountStatusValue {
|
||||||
val prevResult = paymentAccountStatusesStore.getSyncOrNull(userWalletId)
|
val prevResult = paymentAccountStatusesStore.getSyncOrNull(account.userWalletId)
|
||||||
if (prevResult == null || prevResult is PaymentAccountStatus.Error) {
|
if (prevResult == null || prevResult.value is PaymentAccountStatusValue.Error) {
|
||||||
paymentAccountStatusesStore.store(userWalletId = userWalletId, status = PaymentAccountStatus.Loading)
|
paymentAccountStatusesStore.store(
|
||||||
|
userWalletId = account.userWalletId,
|
||||||
|
status = AccountStatus.Payment(account = account, value = PaymentAccountStatusValue.Loading),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return proceedWithOrderId(userWalletId = userWalletId)
|
return proceedWithOrderId(account = account)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun proceedWithOrderId(userWalletId: UserWalletId): PaymentAccountStatus {
|
private suspend fun proceedWithOrderId(account: Account.Payment): PaymentAccountStatusValue {
|
||||||
return if (!onboardingRepository.isTangemPayInitialDataProduced(userWalletId)) {
|
return if (!onboardingRepository.isTangemPayInitialDataProduced(account.userWalletId)) {
|
||||||
PaymentAccountStatus.Error.NotSynced
|
PaymentAccountStatusValue.Error.NotSynced
|
||||||
} else {
|
} else {
|
||||||
val orderId = onboardingRepository.getOrderId(userWalletId)
|
val orderId = onboardingRepository.getOrderId(account.userWalletId)
|
||||||
if (orderId != null) {
|
if (orderId != null) {
|
||||||
proceedWithOrderId(userWalletId = userWalletId, orderId = orderId)
|
proceedWithOrderId(account = account, orderId = orderId)
|
||||||
} else {
|
} else {
|
||||||
proceedWithoutOrder(userWalletId = userWalletId)
|
proceedWithoutOrder(account = account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun proceedWithoutOrder(userWalletId: UserWalletId): PaymentAccountStatus {
|
private suspend fun proceedWithoutOrder(account: Account.Payment): PaymentAccountStatusValue {
|
||||||
return onboardingRepository.getCustomerInfo(userWalletId).fold(
|
return onboardingRepository.getCustomerInfo(account.userWalletId).fold(
|
||||||
ifLeft = { error ->
|
ifLeft = { error ->
|
||||||
TangemLogger.withTag(TAG).e("proceedWithoutOrder $userWalletId error: $error")
|
logger.e("proceedWithoutOrder ${account.userWalletId} error: $error")
|
||||||
error.mapToPaymentAccountStatus()
|
error.mapToPaymentAccountStatus()
|
||||||
},
|
},
|
||||||
ifRight = { customerInfo ->
|
ifRight = { customerInfo ->
|
||||||
TangemLogger.withTag(TAG).i("proceedWithoutOrder data customerInfo $userWalletId")
|
logger.i("proceedWithoutOrder data customerInfo ${account.userWalletId}")
|
||||||
val status = customerInfo.mapToPaymentAccountStatus()
|
val status = customerInfo.mapToPaymentAccountStatus()
|
||||||
if (customerInfo.productInstance == null) {
|
if (status is PaymentAccountStatusValue.IssuingCard && customerInfo.kycStatus == KycStatus.APPROVED) {
|
||||||
onboardingRepository.createOrder(userWalletId)
|
// If order id wasn't saved -> start order creation and get customer info
|
||||||
|
onboardingRepository.createOrder(account.userWalletId)
|
||||||
.onLeft { TangemLogger.withTag(TAG).e("createOrder failed: $it") }
|
.onLeft { TangemLogger.withTag(TAG).e("createOrder failed: $it") }
|
||||||
}
|
}
|
||||||
status
|
status
|
||||||
|
|
@ -117,63 +133,94 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun proceedWithOrderId(userWalletId: UserWalletId, orderId: String): PaymentAccountStatus {
|
private suspend fun proceedWithOrderId(account: Account.Payment, orderId: String): PaymentAccountStatusValue {
|
||||||
return customerOrderRepository.getOrderData(userWalletId, orderId = orderId).fold(
|
return customerOrderRepository.getOrderData(userWalletId = account.userWalletId, orderId = orderId).fold(
|
||||||
ifLeft = { error ->
|
ifLeft = { error ->
|
||||||
TangemLogger.withTag(TAG).e("proceedWithOrderId $userWalletId orderId: $orderId error: $error")
|
logger.e("proceedWithOrderId ${account.userWalletId} orderId: $orderId error: $error")
|
||||||
error.mapToPaymentAccountStatus()
|
error.mapToPaymentAccountStatus()
|
||||||
},
|
},
|
||||||
ifRight = { orderData ->
|
ifRight = { orderData ->
|
||||||
TangemLogger.withTag(TAG).i("proceedWithOrderId $userWalletId: $orderId status: ${orderData.status}")
|
logger.i("proceedWithOrderId $account.userWalletId: $orderId status: ${orderData.status}")
|
||||||
when (orderData.status) {
|
when (orderData.status) {
|
||||||
// Kyc is passed and user waits for order creation -> no need to get customer info
|
// Kyc is passed and user waits for order creation -> no need to get customer info
|
||||||
OrderStatus.NEW,
|
OrderStatus.NEW,
|
||||||
OrderStatus.PROCESSING,
|
OrderStatus.PROCESSING,
|
||||||
-> PaymentAccountStatus.IssuingCard(source = StatusSource.ACTUAL)
|
-> PaymentAccountStatusValue.IssuingCard(source = StatusSource.ACTUAL)
|
||||||
|
|
||||||
OrderStatus.CANCELED -> {
|
OrderStatus.CANCELED -> {
|
||||||
PaymentAccountStatus.Error.CardIssueFailed
|
PaymentAccountStatusValue.Error.CardIssueFailed(customerId = orderData.customerId)
|
||||||
}
|
}
|
||||||
OrderStatus.COMPLETED -> {
|
OrderStatus.COMPLETED -> {
|
||||||
// Order was completed -> clear order id and get customer info
|
// Order was completed -> clear order id and get customer info
|
||||||
onboardingRepository.clearOrderId(userWalletId)
|
onboardingRepository.clearOrderId(account.userWalletId)
|
||||||
onboardingRepository.getCustomerInfo(userWalletId = userWalletId)
|
onboardingRepository.getCustomerInfo(userWalletId = account.userWalletId)
|
||||||
.fold(
|
.fold(
|
||||||
ifLeft = { it.mapToPaymentAccountStatus() },
|
ifLeft = { it.mapToPaymentAccountStatus() },
|
||||||
ifRight = { customerInfo -> customerInfo.mapToPaymentAccountStatus() },
|
ifRight = { customerInfo -> customerInfo.mapToPaymentAccountStatus() },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
OrderStatus.UNKNOWN -> PaymentAccountStatus.Error.Unavailable(source = StatusSource.ACTUAL)
|
OrderStatus.UNKNOWN -> PaymentAccountStatusValue.Error.Unavailable
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CustomerInfo.mapToPaymentAccountStatus(): PaymentAccountStatus {
|
private fun CustomerInfo.mapToPaymentAccountStatus(): PaymentAccountStatusValue {
|
||||||
val cardInfo = this.cardInfo
|
val cardInfo = this.cardInfo
|
||||||
val productInstance = this.productInstance
|
val productInstance = this.productInstance
|
||||||
return if (kycStatus != KycStatus.APPROVED && !customerId.isNullOrEmpty()) {
|
return if (kycStatus != KycStatus.APPROVED && !customerId.isNullOrEmpty()) {
|
||||||
PaymentAccountStatus.UnderReview(source = StatusSource.ACTUAL, kycStatus = kycStatus)
|
PaymentAccountStatusValue.UnderReview(
|
||||||
} else if (cardInfo != null && productInstance != null) {
|
|
||||||
PaymentAccountStatus.Loaded(
|
|
||||||
source = StatusSource.ACTUAL,
|
source = StatusSource.ACTUAL,
|
||||||
cardId = productInstance.cardId,
|
kycStatus = kycStatus,
|
||||||
lastFourDigits = cardInfo.lastFourDigits,
|
customerId = requireNotNull(customerId) { "CustomerId must not be null" },
|
||||||
balance = cardInfo.balance,
|
)
|
||||||
currencyCode = cardInfo.currencyCode,
|
} else if (cardInfo != null && productInstance != null && !customerId.isNullOrEmpty()) {
|
||||||
depositAddress = cardInfo.depositAddress,
|
convertToContentState(
|
||||||
isPinSet = cardInfo.isPinSet,
|
productInstance = productInstance,
|
||||||
|
cardInfo = cardInfo,
|
||||||
|
customerId = requireNotNull(customerId) { "CustomerId must not be null" },
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
PaymentAccountStatus.IssuingCard(source = StatusSource.ACTUAL)
|
PaymentAccountStatusValue.IssuingCard(source = StatusSource.ACTUAL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun VisaApiError.mapToPaymentAccountStatus(): PaymentAccountStatus {
|
private fun convertToContentState(
|
||||||
|
productInstance: CustomerInfo.ProductInstance,
|
||||||
|
cardInfo: CustomerInfo.CardInfo,
|
||||||
|
customerId: String,
|
||||||
|
): PaymentAccountStatusValue {
|
||||||
|
return when (productInstance.frozenState) {
|
||||||
|
TangemPayCardFrozenState.Frozen -> PaymentAccountStatusValue.Locked(
|
||||||
|
source = StatusSource.ACTUAL,
|
||||||
|
customerId = customerId,
|
||||||
|
cardId = productInstance.cardId,
|
||||||
|
lastFourDigits = cardInfo.lastFourDigits,
|
||||||
|
currencyCode = cardInfo.currencyCode,
|
||||||
|
depositAddress = cardInfo.depositAddress,
|
||||||
|
isPinSet = cardInfo.isPinSet,
|
||||||
|
fiatBalance = cardInfo.fiatBalance,
|
||||||
|
cryptoBalance = cardInfo.cryptoBalance,
|
||||||
|
)
|
||||||
|
else -> PaymentAccountStatusValue.Loaded(
|
||||||
|
source = StatusSource.ACTUAL,
|
||||||
|
customerId = customerId,
|
||||||
|
cardId = productInstance.cardId,
|
||||||
|
lastFourDigits = cardInfo.lastFourDigits,
|
||||||
|
currencyCode = cardInfo.currencyCode,
|
||||||
|
depositAddress = cardInfo.depositAddress,
|
||||||
|
isPinSet = cardInfo.isPinSet,
|
||||||
|
fiatBalance = cardInfo.fiatBalance,
|
||||||
|
cryptoBalance = cardInfo.cryptoBalance,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun VisaApiError.mapToPaymentAccountStatus(): PaymentAccountStatusValue {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is VisaApiError.RefreshTokenExpired -> PaymentAccountStatus.Error.NotSynced
|
is VisaApiError.RefreshTokenExpired -> PaymentAccountStatusValue.Error.NotSynced
|
||||||
is VisaApiError.NotPaeraCustomer -> PaymentAccountStatus.NotCreated
|
is VisaApiError.NotPaeraCustomer -> PaymentAccountStatusValue.NotCreated
|
||||||
else -> PaymentAccountStatus.Error.Unavailable(source = StatusSource.ACTUAL)
|
else -> PaymentAccountStatusValue.Error.Unavailable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,8 +4,9 @@ import arrow.core.Option
|
||||||
import arrow.core.some
|
import arrow.core.some
|
||||||
import com.tangem.data.pay.store.PaymentAccountStatusesStore
|
import com.tangem.data.pay.store.PaymentAccountStatusesStore
|
||||||
import com.tangem.domain.core.flow.FlowProducerTools
|
import com.tangem.domain.core.flow.FlowProducerTools
|
||||||
import com.tangem.domain.models.StatusSource
|
import com.tangem.domain.models.account.Account
|
||||||
import com.tangem.domain.pay.PaymentAccountStatus
|
import com.tangem.domain.models.account.AccountStatus
|
||||||
|
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||||
import com.tangem.domain.pay.flow.PaymentAccountStatusProducer
|
import com.tangem.domain.pay.flow.PaymentAccountStatusProducer
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
|
|
@ -21,12 +22,15 @@ internal class DefaultPaymentAccountStatusProducer @AssistedInject constructor(
|
||||||
private val paymentAccountStatusesStore: PaymentAccountStatusesStore,
|
private val paymentAccountStatusesStore: PaymentAccountStatusesStore,
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
) : PaymentAccountStatusProducer {
|
) : PaymentAccountStatusProducer {
|
||||||
override val fallback: Option<PaymentAccountStatus>
|
|
||||||
get() = PaymentAccountStatus.Error.Unavailable(source = StatusSource.ACTUAL).some()
|
|
||||||
|
|
||||||
override fun produce(): Flow<PaymentAccountStatus> {
|
private val account = Account.Payment(userWalletId = params.userWalletId)
|
||||||
|
|
||||||
|
override val fallback: Option<AccountStatus.Payment>
|
||||||
|
get() = AccountStatus.Payment(account = account, value = PaymentAccountStatusValue.Error.Unavailable).some()
|
||||||
|
|
||||||
|
override fun produce(): Flow<AccountStatus.Payment> {
|
||||||
return paymentAccountStatusesStore.get(userWalletId = params.userWalletId)
|
return paymentAccountStatusesStore.get(userWalletId = params.userWalletId)
|
||||||
.onEmpty { emit(value = PaymentAccountStatus.NotCreated) }
|
.onEmpty { emit(value = AccountStatus.Payment(account, PaymentAccountStatusValue.NotCreated)) }
|
||||||
.flowOn(dispatchers.default)
|
.flowOn(dispatchers.default)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import com.tangem.datasource.local.visa.TangemPayCardFrozenStateStore
|
||||||
import com.tangem.datasource.local.visa.TangemPayStorage
|
import com.tangem.datasource.local.visa.TangemPayStorage
|
||||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||||
import com.tangem.domain.models.TangemPayEligibilityType
|
import com.tangem.domain.models.TangemPayEligibilityType
|
||||||
|
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||||
import com.tangem.domain.models.kyc.KycStatus
|
import com.tangem.domain.models.kyc.KycStatus
|
||||||
import com.tangem.domain.models.wallet.UserWallet
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
|
@ -139,6 +140,7 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
||||||
?: error("no userWallet found")
|
?: error("no userWallet found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("ComplexCondition")
|
||||||
private suspend fun getCustomerInfo(
|
private suspend fun getCustomerInfo(
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
response: CustomerMeResponse.Result?,
|
response: CustomerMeResponse.Result?,
|
||||||
|
|
@ -148,14 +150,26 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
||||||
|
|
||||||
val card = response?.card
|
val card = response?.card
|
||||||
val fiatBalance = response?.balance?.fiat
|
val fiatBalance = response?.balance?.fiat
|
||||||
|
val cryptoBalance = response?.balance?.crypto
|
||||||
val paymentAccount = response?.paymentAccount
|
val paymentAccount = response?.paymentAccount
|
||||||
val cardInfo = if (paymentAccount != null && card != null && fiatBalance != null) {
|
val cardInfo = if (paymentAccount != null && card != null && fiatBalance != null && cryptoBalance != null) {
|
||||||
CardInfo(
|
CardInfo(
|
||||||
lastFourDigits = card.cardNumberEnd,
|
lastFourDigits = card.cardNumberEnd,
|
||||||
balance = fiatBalance.availableBalance,
|
balance = fiatBalance.availableBalance,
|
||||||
currencyCode = fiatBalance.currency,
|
currencyCode = fiatBalance.currency,
|
||||||
depositAddress = response.depositAddress,
|
depositAddress = response.depositAddress,
|
||||||
isPinSet = response.card?.isPinSet == true,
|
isPinSet = response.card?.isPinSet == true,
|
||||||
|
fiatBalance = PaymentAccountStatusValue.FiatBalance(
|
||||||
|
availableBalance = fiatBalance.availableBalance,
|
||||||
|
currency = fiatBalance.currency,
|
||||||
|
),
|
||||||
|
cryptoBalance = PaymentAccountStatusValue.CryptoBalance(
|
||||||
|
id = cryptoBalance.id,
|
||||||
|
chainId = cryptoBalance.chainId.toLong(),
|
||||||
|
depositAddress = cryptoBalance.depositAddress.orEmpty(),
|
||||||
|
tokenContractAddress = cryptoBalance.tokenContractAddress,
|
||||||
|
balance = cryptoBalance.balance,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
|
@ -167,7 +181,7 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
cardFrozenStateStore.store(key = instance.cardId, value = cardFrozenState)
|
cardFrozenStateStore.store(key = instance.cardId, value = cardFrozenState)
|
||||||
|
|
||||||
ProductInstance(id = instance.id, cardId = instance.cardId)
|
ProductInstance(id = instance.id, cardId = instance.cardId, frozenState = cardFrozenState)
|
||||||
}
|
}
|
||||||
return CustomerInfo(
|
return CustomerInfo(
|
||||||
customerId = response?.id,
|
customerId = response?.id,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
package com.tangem.data.pay.store
|
package com.tangem.data.pay.store
|
||||||
|
|
||||||
import androidx.datastore.core.DataStore
|
import androidx.datastore.core.DataStore
|
||||||
import com.tangem.data.pay.converter.PaymentAccountStatusDMConverter
|
import com.tangem.data.pay.converter.PaymentAccountStatusValueDMConverter
|
||||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||||
import com.tangem.datasource.local.visa.entity.PaymentAccountStatusDM
|
import com.tangem.datasource.local.visa.entity.PaymentAccountStatusValueDM
|
||||||
|
import com.tangem.domain.models.StatusSource
|
||||||
|
import com.tangem.domain.models.account.Account
|
||||||
|
import com.tangem.domain.models.account.AccountStatus
|
||||||
|
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.pay.PaymentAccountStatus
|
|
||||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||||
import com.tangem.utils.logging.TangemLogger
|
import com.tangem.utils.logging.TangemLogger
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
|
@ -14,8 +17,8 @@ import kotlinx.coroutines.flow.firstOrNull
|
||||||
import kotlinx.coroutines.flow.mapNotNull
|
import kotlinx.coroutines.flow.mapNotNull
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
internal typealias WalletIdWithPaymentStatus = Map<String, PaymentAccountStatus>
|
internal typealias WalletIdWithPaymentStatus = Map<String, AccountStatus.Payment>
|
||||||
internal typealias WalletIdWithPaymentStatusDM = Map<String, PaymentAccountStatusDM>
|
internal typealias WalletIdWithPaymentStatusDM = Map<String, PaymentAccountStatusValueDM>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store for payment account statuses with dual storage (runtime + persistence).
|
* Store for payment account statuses with dual storage (runtime + persistence).
|
||||||
|
|
@ -26,7 +29,7 @@ internal typealias WalletIdWithPaymentStatusDM = Map<String, PaymentAccountStatu
|
||||||
internal class PaymentAccountStatusesStore(
|
internal class PaymentAccountStatusesStore(
|
||||||
private val runtimeStore: RuntimeSharedStore<WalletIdWithPaymentStatus>,
|
private val runtimeStore: RuntimeSharedStore<WalletIdWithPaymentStatus>,
|
||||||
private val persistenceDataStore: DataStore<WalletIdWithPaymentStatusDM>,
|
private val persistenceDataStore: DataStore<WalletIdWithPaymentStatusDM>,
|
||||||
private val scope: AppCoroutineScope,
|
scope: AppCoroutineScope,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
@ -34,8 +37,10 @@ internal class PaymentAccountStatusesStore(
|
||||||
try {
|
try {
|
||||||
val cachedStatuses = persistenceDataStore.data.firstOrNull() ?: return@launch
|
val cachedStatuses = persistenceDataStore.data.firstOrNull() ?: return@launch
|
||||||
runtimeStore.store(
|
runtimeStore.store(
|
||||||
value = cachedStatuses.mapValues { (_, statusDM) ->
|
value = cachedStatuses.mapValues { (rawUserWalletId, statusDM) ->
|
||||||
PaymentAccountStatusDMConverter.convertBack(statusDM)
|
val account = Account.Payment(userWalletId = UserWalletId(rawUserWalletId))
|
||||||
|
val statusValue = PaymentAccountStatusValueDMConverter.convertBack(value = statusDM)
|
||||||
|
AccountStatus.Payment(account = account, value = statusValue)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
@ -44,18 +49,28 @@ internal class PaymentAccountStatusesStore(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun get(userWalletId: UserWalletId): Flow<PaymentAccountStatus> {
|
fun get(userWalletId: UserWalletId): Flow<AccountStatus.Payment> {
|
||||||
return runtimeStore.get().mapNotNull { it[userWalletId.stringValue] }
|
return runtimeStore.get().mapNotNull { it[userWalletId.stringValue] }
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getSyncOrNull(userWalletId: UserWalletId): PaymentAccountStatus? {
|
suspend fun getSyncOrNull(userWalletId: UserWalletId): AccountStatus.Payment? {
|
||||||
return runtimeStore.getSyncOrNull()?.get(userWalletId.stringValue)
|
return runtimeStore.getSyncOrNull()?.get(userWalletId.stringValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun store(userWalletId: UserWalletId, status: PaymentAccountStatus) {
|
suspend fun updateStatusSource(userWalletId: UserWalletId, source: StatusSource) {
|
||||||
|
runtimeStore.update(emptyMap()) { stored ->
|
||||||
|
stored.toMutableMap().apply {
|
||||||
|
val paymentAccountStatus = this[userWalletId.stringValue] ?: return@update stored
|
||||||
|
val newValue = paymentAccountStatus.copy(value = paymentAccountStatus.value.copySealed(source = source))
|
||||||
|
put(key = userWalletId.stringValue, value = newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun store(userWalletId: UserWalletId, status: AccountStatus.Payment) {
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
launch { storeInRuntime(userWalletId = userWalletId, status = status) }
|
launch { storeInRuntime(userWalletId = userWalletId, status = status) }
|
||||||
launch { storeInPersistence(userWalletId = userWalletId, status = status) }
|
launch { storeInPersistence(userWalletId = userWalletId, status = status.value) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,7 +78,7 @@ internal class PaymentAccountStatusesStore(
|
||||||
return runtimeStore.getSyncOrDefault(emptyMap()).containsKey(userWalletId.stringValue)
|
return runtimeStore.getSyncOrDefault(emptyMap()).containsKey(userWalletId.stringValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun storeInRuntime(userWalletId: UserWalletId, status: PaymentAccountStatus) {
|
private suspend fun storeInRuntime(userWalletId: UserWalletId, status: AccountStatus.Payment) {
|
||||||
runtimeStore.update(default = emptyMap()) { stored ->
|
runtimeStore.update(default = emptyMap()) { stored ->
|
||||||
stored.toMutableMap().apply {
|
stored.toMutableMap().apply {
|
||||||
put(key = userWalletId.stringValue, value = status)
|
put(key = userWalletId.stringValue, value = status)
|
||||||
|
|
@ -71,8 +86,8 @@ internal class PaymentAccountStatusesStore(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun storeInPersistence(userWalletId: UserWalletId, status: PaymentAccountStatus) {
|
private suspend fun storeInPersistence(userWalletId: UserWalletId, status: PaymentAccountStatusValue) {
|
||||||
val statusDM = PaymentAccountStatusDMConverter.convert(value = status) ?: return
|
val statusDM = PaymentAccountStatusValueDMConverter.convert(value = status) ?: return
|
||||||
persistenceDataStore.updateData { storedStatuses ->
|
persistenceDataStore.updateData { storedStatuses ->
|
||||||
storedStatuses.toMutableMap().apply {
|
storedStatuses.toMutableMap().apply {
|
||||||
put(key = userWalletId.stringValue, value = statusDM)
|
put(key = userWalletId.stringValue, value = statusDM)
|
||||||
|
|
|
||||||
|
|
@ -108,16 +108,14 @@ data class AccountList private constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun flattenMapCurrencies(): Map<AccountCurrencyId, CryptoCurrency> = buildMap {
|
fun flattenMapCurrencies(): Map<AccountCurrencyId, CryptoCurrency> = buildMap {
|
||||||
accounts.forEach { acc ->
|
accounts
|
||||||
val account = when (acc) {
|
.filterIsInstance<Account.CryptoPortfolio>()
|
||||||
is Account.CryptoPortfolio -> acc
|
.forEach { account ->
|
||||||
is Account.Payment -> TODO("[REDACTED_JIRA]")
|
account.cryptoCurrencies.forEach { currency ->
|
||||||
|
val key = account.accountId to currency.id
|
||||||
|
put(key, currency)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
account.cryptoCurrencies.forEach { currency ->
|
|
||||||
val key = account.accountId to currency.id
|
|
||||||
put(key, currency)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ dependencies {
|
||||||
api(projects.domain.staking)
|
api(projects.domain.staking)
|
||||||
api(projects.domain.tokens)
|
api(projects.domain.tokens)
|
||||||
api(projects.domain.tokens.models)
|
api(projects.domain.tokens.models)
|
||||||
|
api(projects.domain.visa)
|
||||||
api(projects.domain.walletManager)
|
api(projects.domain.walletManager)
|
||||||
api(projects.domain.wallets)
|
api(projects.domain.wallets)
|
||||||
|
|
||||||
|
|
@ -39,6 +40,8 @@ dependencies {
|
||||||
implementation(deps.kotlin.serialization)
|
implementation(deps.kotlin.serialization)
|
||||||
|
|
||||||
implementation(tangemDeps.blockchain)
|
implementation(tangemDeps.blockchain)
|
||||||
|
implementation(tangemDeps.card.core)
|
||||||
|
implementation(tangemDeps.hot.core)
|
||||||
|
|
||||||
// region DI
|
// region DI
|
||||||
implementation(deps.hilt.android)
|
implementation(deps.hilt.android)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.tangem.domain.account.status.producer
|
||||||
import arrow.core.Option
|
import arrow.core.Option
|
||||||
import arrow.core.none
|
import arrow.core.none
|
||||||
import arrow.core.toOption
|
import arrow.core.toOption
|
||||||
|
import com.tangem.common.card.FirmwareVersion
|
||||||
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
||||||
import com.tangem.domain.account.models.AccountCurrencyId
|
import com.tangem.domain.account.models.AccountCurrencyId
|
||||||
import com.tangem.domain.account.models.AccountList
|
import com.tangem.domain.account.models.AccountList
|
||||||
|
|
@ -33,6 +34,7 @@ import com.tangem.domain.models.wallet.isMultiCurrency
|
||||||
import com.tangem.domain.networks.multi.MultiNetworkStatusProducer
|
import com.tangem.domain.networks.multi.MultiNetworkStatusProducer
|
||||||
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
|
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
|
||||||
import com.tangem.domain.networks.repository.NetworksRepository
|
import com.tangem.domain.networks.repository.NetworksRepository
|
||||||
|
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
|
||||||
import com.tangem.domain.quotes.multi.MultiQuoteStatusSupplier
|
import com.tangem.domain.quotes.multi.MultiQuoteStatusSupplier
|
||||||
import com.tangem.domain.staking.StakingIdFactory
|
import com.tangem.domain.staking.StakingIdFactory
|
||||||
import com.tangem.domain.staking.multi.MultiStakingBalanceProducer
|
import com.tangem.domain.staking.multi.MultiStakingBalanceProducer
|
||||||
|
|
@ -42,6 +44,7 @@ import com.tangem.domain.tokens.operations.CryptoCurrencyStatusFactory
|
||||||
import com.tangem.domain.tokens.operations.PriceChangeCalculator
|
import com.tangem.domain.tokens.operations.PriceChangeCalculator
|
||||||
import com.tangem.domain.tokens.operations.TokenListFactory
|
import com.tangem.domain.tokens.operations.TokenListFactory
|
||||||
import com.tangem.domain.tokens.operations.TotalFiatBalanceCalculator
|
import com.tangem.domain.tokens.operations.TotalFiatBalanceCalculator
|
||||||
|
import com.tangem.hot.sdk.model.HotWalletId
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
|
|
@ -69,6 +72,7 @@ import java.math.BigDecimal
|
||||||
*
|
*
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
*/
|
*/
|
||||||
|
// TODO: Move to :data:account:status [REDACTED_JIRA]
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
internal class DefaultSingleAccountStatusListProducer @AssistedInject constructor(
|
internal class DefaultSingleAccountStatusListProducer @AssistedInject constructor(
|
||||||
|
|
@ -76,6 +80,7 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo
|
||||||
override val flowProducerTools: FlowProducerTools,
|
override val flowProducerTools: FlowProducerTools,
|
||||||
private val userWalletsListRepository: UserWalletsListRepository,
|
private val userWalletsListRepository: UserWalletsListRepository,
|
||||||
private val singleAccountListSupplier: SingleAccountListSupplier,
|
private val singleAccountListSupplier: SingleAccountListSupplier,
|
||||||
|
private val paymentAccountStatusSupplier: PaymentAccountStatusSupplier,
|
||||||
private val networksRepository: NetworksRepository,
|
private val networksRepository: NetworksRepository,
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
private val networkStatusSupplier: MultiNetworkStatusSupplier,
|
private val networkStatusSupplier: MultiNetworkStatusSupplier,
|
||||||
|
|
@ -116,31 +121,52 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo
|
||||||
flattenCurrency = flattenCurrency,
|
flattenCurrency = flattenCurrency,
|
||||||
)
|
)
|
||||||
|
|
||||||
combine(
|
if (userWallet.isPaymentAccountSupported()) {
|
||||||
|
combineWithPaymentAccount(
|
||||||
|
accountListFlow = accountListFlow,
|
||||||
|
cryptoCurrencyStatusFlow = cryptoCurrencyStatusFlow,
|
||||||
|
paymentAccountStatusFlow = paymentAccountStatusSupplier.invoke(userWalletId = params.userWalletId),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
combineWithoutPaymentAccount(
|
||||||
|
accountListFlow = accountListFlow,
|
||||||
|
cryptoCurrencyStatusFlow = cryptoCurrencyStatusFlow,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.collect { accountStatusList -> channel.send(accountStatusList) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun combineWithPaymentAccount(
|
||||||
|
accountListFlow: StateFlow<AccountList>,
|
||||||
|
cryptoCurrencyStatusFlow: Flow<Map<AccountCurrencyId, CryptoCurrencyStatus>>,
|
||||||
|
paymentAccountStatusFlow: Flow<AccountStatus.Payment>,
|
||||||
|
): Flow<AccountStatusList> {
|
||||||
|
return combine(
|
||||||
flow = accountListFlow,
|
flow = accountListFlow,
|
||||||
flow2 = cryptoCurrencyStatusFlow,
|
flow2 = cryptoCurrencyStatusFlow,
|
||||||
transform = { accountList, currencyStatusMap ->
|
flow3 = paymentAccountStatusFlow,
|
||||||
val accountStatuses: List<AccountStatus.CryptoPortfolio> = accountList.accounts.map { acc ->
|
transform = { accountList, currencyStatusMap, paymentAccountStatus ->
|
||||||
val account: Account.CryptoPortfolio = when (acc) {
|
val accountStatuses = accountList.accounts.map { account ->
|
||||||
is Account.CryptoPortfolio -> acc
|
when (account) {
|
||||||
is Account.Payment -> TODO("[REDACTED_JIRA]")
|
is Account.Payment -> paymentAccountStatus
|
||||||
}
|
is Account.CryptoPortfolio -> if (account.cryptoCurrencies.isEmpty()) {
|
||||||
if (account.cryptoCurrencies.isEmpty()) {
|
account.toEmptyAccountStatus()
|
||||||
account.toEmptyAccountStatus()
|
} else {
|
||||||
} else {
|
val statuses: List<CryptoCurrencyStatus> =
|
||||||
val statuses: List<CryptoCurrencyStatus> = account.cryptoCurrencies.map { currency ->
|
account.cryptoCurrencies.map { currency ->
|
||||||
val acId = account.accountId to currency.id
|
val acId = account.accountId to currency.id
|
||||||
currencyStatusMap[acId] ?: currency.toLoadingCurrencyStatus()
|
currencyStatusMap[acId] ?: currency.toLoadingCurrencyStatus()
|
||||||
|
}
|
||||||
|
AccountStatus.CryptoPortfolio(
|
||||||
|
account = account,
|
||||||
|
tokenList = TokenListFactory.create(
|
||||||
|
statuses = statuses,
|
||||||
|
groupType = accountList.groupType,
|
||||||
|
sortType = accountList.sortType,
|
||||||
|
),
|
||||||
|
priceChangeLce = PriceChangeCalculator.calculate(statuses = statuses),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
AccountStatus.CryptoPortfolio(
|
|
||||||
account = account,
|
|
||||||
tokenList = TokenListFactory.create(
|
|
||||||
statuses = statuses,
|
|
||||||
groupType = accountList.groupType,
|
|
||||||
sortType = accountList.sortType,
|
|
||||||
),
|
|
||||||
priceChangeLce = PriceChangeCalculator.calculate(statuses = statuses),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val balances = accountStatuses.flattenTotalFiatBalance()
|
val balances = accountStatuses.flattenTotalFiatBalance()
|
||||||
|
|
@ -156,7 +182,58 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.collect { accountStatusList -> channel.send(accountStatusList) }
|
}
|
||||||
|
|
||||||
|
private fun combineWithoutPaymentAccount(
|
||||||
|
accountListFlow: StateFlow<AccountList>,
|
||||||
|
cryptoCurrencyStatusFlow: Flow<Map<AccountCurrencyId, CryptoCurrencyStatus>>,
|
||||||
|
): Flow<AccountStatusList> {
|
||||||
|
return combine(
|
||||||
|
flow = accountListFlow,
|
||||||
|
flow2 = cryptoCurrencyStatusFlow,
|
||||||
|
transform = { accountList, currencyStatusMap ->
|
||||||
|
val accountStatuses = accountList.accounts
|
||||||
|
.filterIsInstance<Account.CryptoPortfolio>()
|
||||||
|
.map { account ->
|
||||||
|
when (account) {
|
||||||
|
is Account.CryptoPortfolio -> if (account.cryptoCurrencies.isEmpty()) {
|
||||||
|
account.toEmptyAccountStatus()
|
||||||
|
} else {
|
||||||
|
val statuses: List<CryptoCurrencyStatus> =
|
||||||
|
account.cryptoCurrencies.map { currency ->
|
||||||
|
val acId = account.accountId to currency.id
|
||||||
|
currencyStatusMap[acId] ?: currency.toLoadingCurrencyStatus()
|
||||||
|
}
|
||||||
|
AccountStatus.CryptoPortfolio(
|
||||||
|
account = account,
|
||||||
|
tokenList = TokenListFactory.create(
|
||||||
|
statuses = statuses,
|
||||||
|
groupType = accountList.groupType,
|
||||||
|
sortType = accountList.sortType,
|
||||||
|
),
|
||||||
|
priceChangeLce = PriceChangeCalculator.calculate(statuses = statuses),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val balances = accountStatuses.flattenTotalFiatBalance()
|
||||||
|
|
||||||
|
AccountStatusList(
|
||||||
|
userWalletId = accountList.userWalletId,
|
||||||
|
accountStatuses = accountStatuses,
|
||||||
|
totalAccounts = accountList.totalAccounts,
|
||||||
|
totalFiatBalance = TotalFiatBalanceCalculator.calculate(balances),
|
||||||
|
totalArchivedAccounts = accountList.totalArchivedAccounts,
|
||||||
|
sortType = accountList.sortType,
|
||||||
|
groupType = accountList.groupType,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun UserWallet.isPaymentAccountSupported(): Boolean = when (this) {
|
||||||
|
is UserWallet.Cold -> scanResponse.card.firmwareVersion >= FirmwareVersion.HDWalletAvailable
|
||||||
|
is UserWallet.Hot -> hotWalletId.authType != HotWalletId.AuthType.NoPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ProducerScope<AccountStatusList>.flattenCurrencyStatusFlow(
|
private fun ProducerScope<AccountStatusList>.flattenCurrencyStatusFlow(
|
||||||
|
|
@ -278,7 +355,7 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo
|
||||||
return map { accountStatus ->
|
return map { accountStatus ->
|
||||||
when (accountStatus) {
|
when (accountStatus) {
|
||||||
is AccountStatus.CryptoPortfolio -> accountStatus.tokenList.totalFiatBalance
|
is AccountStatus.CryptoPortfolio -> accountStatus.tokenList.totalFiatBalance
|
||||||
is AccountStatus.Payment -> accountStatus.totalFiatBalance
|
is AccountStatus.Payment -> accountStatus.value.totalFiatBalance
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -178,12 +178,15 @@ sealed interface Account {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Payment(
|
data class Payment(
|
||||||
override val accountId: AccountId,
|
override val accountId: AccountId,
|
||||||
override val accountName: AccountName,
|
|
||||||
val cryptoCurrencies: List<CryptoCurrency>,
|
|
||||||
) : Account {
|
) : Account {
|
||||||
|
override val accountName: AccountName.Custom = AccountName.Custom("Payment").getOrElse {
|
||||||
|
error("Can not create account name for Payment account with userWalletId = ${accountId.userWalletId}")
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
companion object {
|
||||||
error("Not yet implemented")
|
operator fun invoke(userWalletId: UserWalletId): Payment {
|
||||||
|
return Payment(accountId = AccountId.forPaymentAccount(userWalletId = userWalletId))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package com.tangem.domain.models.account
|
package com.tangem.domain.models.account
|
||||||
|
|
||||||
import com.tangem.domain.core.lce.Lce
|
import com.tangem.domain.core.lce.Lce
|
||||||
import com.tangem.domain.models.TotalFiatBalance
|
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.models.quote.PriceChange
|
import com.tangem.domain.models.quote.PriceChange
|
||||||
import com.tangem.domain.models.tokenlist.TokenList
|
import com.tangem.domain.models.tokenlist.TokenList
|
||||||
|
|
@ -43,7 +42,7 @@ sealed interface AccountStatus {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Payment(
|
data class Payment(
|
||||||
override val account: Account.Payment,
|
override val account: Account.Payment,
|
||||||
val totalFiatBalance: TotalFiatBalance,
|
val value: PaymentAccountStatusValue,
|
||||||
) : AccountStatus
|
) : AccountStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,193 @@
|
||||||
|
package com.tangem.domain.models.account
|
||||||
|
|
||||||
|
import com.tangem.domain.models.StatusSource
|
||||||
|
import com.tangem.domain.models.TotalFiatBalance
|
||||||
|
import com.tangem.domain.models.kyc.KycStatus
|
||||||
|
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the various states a payment account can have, encapsulating different information based on the state.
|
||||||
|
*
|
||||||
|
* @property source The source of the status information.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
sealed class PaymentAccountStatusValue {
|
||||||
|
abstract val source: StatusSource
|
||||||
|
|
||||||
|
/** The total fiat balance associated with this status. */
|
||||||
|
val totalFiatBalance: TotalFiatBalance
|
||||||
|
get() = when (this) {
|
||||||
|
is Error,
|
||||||
|
is IssuingCard,
|
||||||
|
is NotCreated,
|
||||||
|
is UnderReview,
|
||||||
|
-> TotalFiatBalance.Loaded(amount = SerializedBigDecimal.ZERO, source = source)
|
||||||
|
is Loading -> TotalFiatBalance.Loading
|
||||||
|
is Locked -> TotalFiatBalance.Loaded(amount = fiatBalance.availableBalance, source = source)
|
||||||
|
is Loaded -> TotalFiatBalance.Loaded(amount = fiatBalance.availableBalance, source = source)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies the status with a new [source].
|
||||||
|
*
|
||||||
|
* @param source The new source of the status information.
|
||||||
|
*/
|
||||||
|
fun copySealed(source: StatusSource): PaymentAccountStatusValue {
|
||||||
|
return when (this) {
|
||||||
|
is IssuingCard -> copy(source = source)
|
||||||
|
is Loaded -> copy(source = source)
|
||||||
|
is Locked -> copy(source = source)
|
||||||
|
is UnderReview -> copy(source = source)
|
||||||
|
is Loading,
|
||||||
|
is NotCreated,
|
||||||
|
is Error,
|
||||||
|
-> this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Represents the Loading state of a payment account, typically while fetching its details. */
|
||||||
|
@Serializable
|
||||||
|
data object Loading : PaymentAccountStatusValue() {
|
||||||
|
override val source: StatusSource = StatusSource.ACTUAL
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Represents a state where the payment account has not been created yet. */
|
||||||
|
@Serializable
|
||||||
|
data object NotCreated : PaymentAccountStatusValue() {
|
||||||
|
override val source: StatusSource = StatusSource.ACTUAL
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a state where the payment account is under review (KYC).
|
||||||
|
*
|
||||||
|
* @property source The source of the status information.
|
||||||
|
* @property kycStatus The current KYC status.
|
||||||
|
* @property customerId The unique identifier of the customer.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class UnderReview(
|
||||||
|
override val source: StatusSource,
|
||||||
|
val kycStatus: KycStatus,
|
||||||
|
val customerId: String,
|
||||||
|
) : PaymentAccountStatusValue()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a state where the card for the payment account is being issued.
|
||||||
|
*
|
||||||
|
* @property source The source of the status information.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class IssuingCard(override val source: StatusSource) : PaymentAccountStatusValue()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a state where the payment account is locked.
|
||||||
|
*
|
||||||
|
* @property source The source of the status information.
|
||||||
|
* @property customerId The unique identifier of the customer.
|
||||||
|
* @property cardId The unique identifier of the card.
|
||||||
|
* @property lastFourDigits The last four digits of the card number.
|
||||||
|
* @property currencyCode The code of the currency.
|
||||||
|
* @property depositAddress The address for deposits, if available.
|
||||||
|
* @property isPinSet Indicates if the PIN is set for the card.
|
||||||
|
* @property fiatBalance The fiat balance details.
|
||||||
|
* @property cryptoBalance The crypto balance details.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class Locked(
|
||||||
|
override val source: StatusSource,
|
||||||
|
val customerId: String,
|
||||||
|
val cardId: String,
|
||||||
|
val lastFourDigits: String,
|
||||||
|
val currencyCode: String,
|
||||||
|
val depositAddress: String?,
|
||||||
|
val isPinSet: Boolean,
|
||||||
|
val fiatBalance: FiatBalance,
|
||||||
|
val cryptoBalance: CryptoBalance,
|
||||||
|
) : PaymentAccountStatusValue()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a state where the payment account is successfully loaded with complete information.
|
||||||
|
*
|
||||||
|
* @property source The source of the status information.
|
||||||
|
* @property customerId The unique identifier of the customer.
|
||||||
|
* @property cardId The unique identifier of the card.
|
||||||
|
* @property lastFourDigits The last four digits of the card number.
|
||||||
|
* @property currencyCode The code of the currency.
|
||||||
|
* @property depositAddress The address for deposits, if available.
|
||||||
|
* @property isPinSet Indicates if the PIN is set for the card.
|
||||||
|
* @property fiatBalance The fiat balance details.
|
||||||
|
* @property cryptoBalance The crypto balance details.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class Loaded(
|
||||||
|
override val source: StatusSource,
|
||||||
|
val customerId: String,
|
||||||
|
val cardId: String,
|
||||||
|
val lastFourDigits: String,
|
||||||
|
val currencyCode: String,
|
||||||
|
val depositAddress: String?,
|
||||||
|
val isPinSet: Boolean,
|
||||||
|
val fiatBalance: FiatBalance,
|
||||||
|
val cryptoBalance: CryptoBalance,
|
||||||
|
) : PaymentAccountStatusValue()
|
||||||
|
|
||||||
|
/** Represents an error state for the payment account status. */
|
||||||
|
@Serializable
|
||||||
|
sealed class Error : PaymentAccountStatusValue() {
|
||||||
|
/** Error state indicating the device is exposed. */
|
||||||
|
@Serializable
|
||||||
|
data object ExposedDevice : Error() {
|
||||||
|
override val source: StatusSource = StatusSource.ACTUAL
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Error state indicating the account is unavailable. */
|
||||||
|
@Serializable
|
||||||
|
data object Unavailable : Error() {
|
||||||
|
override val source: StatusSource = StatusSource.ACTUAL
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Error state indicating the account data is not synced. */
|
||||||
|
@Serializable
|
||||||
|
data object NotSynced : Error() {
|
||||||
|
override val source: StatusSource = StatusSource.ACTUAL
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error state indicating that card issuance failed.
|
||||||
|
*
|
||||||
|
* @property customerId The unique identifier of the customer.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class CardIssueFailed(val customerId: String) : Error() {
|
||||||
|
override val source: StatusSource = StatusSource.ACTUAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the fiat balance of the payment account.
|
||||||
|
*
|
||||||
|
* @property availableBalance The amount of available balance in fiat.
|
||||||
|
* @property currency The currency of the balance.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class FiatBalance(val availableBalance: SerializedBigDecimal, val currency: String)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the crypto balance of the payment account.
|
||||||
|
*
|
||||||
|
* @property id The unique identifier of the crypto asset.
|
||||||
|
* @property chainId The identifier of the blockchain network.
|
||||||
|
* @property depositAddress The address for deposits.
|
||||||
|
* @property tokenContractAddress The contract address of the token.
|
||||||
|
* @property balance The amount of the crypto balance.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class CryptoBalance(
|
||||||
|
val id: String,
|
||||||
|
val chainId: Long,
|
||||||
|
val depositAddress: String,
|
||||||
|
val tokenContractAddress: String,
|
||||||
|
val balance: SerializedBigDecimal,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
package com.tangem.domain.pay
|
|
||||||
|
|
||||||
import com.tangem.domain.models.StatusSource
|
|
||||||
import com.tangem.domain.models.kyc.KycStatus
|
|
||||||
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
sealed class PaymentAccountStatus {
|
|
||||||
|
|
||||||
abstract val source: StatusSource
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data object Loading : PaymentAccountStatus() {
|
|
||||||
override val source: StatusSource = StatusSource.ACTUAL
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data object NotCreated : PaymentAccountStatus() {
|
|
||||||
override val source: StatusSource = StatusSource.ACTUAL
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class UnderReview(
|
|
||||||
override val source: StatusSource,
|
|
||||||
val kycStatus: KycStatus,
|
|
||||||
) : PaymentAccountStatus()
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class IssuingCard(override val source: StatusSource) : PaymentAccountStatus()
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Locked(override val source: StatusSource) : PaymentAccountStatus()
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Loaded(
|
|
||||||
override val source: StatusSource,
|
|
||||||
val cardId: String,
|
|
||||||
val lastFourDigits: String,
|
|
||||||
val balance: SerializedBigDecimal,
|
|
||||||
val currencyCode: String,
|
|
||||||
val depositAddress: String?,
|
|
||||||
val isPinSet: Boolean,
|
|
||||||
) : PaymentAccountStatus()
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
sealed class Error : PaymentAccountStatus() {
|
|
||||||
@Serializable
|
|
||||||
data object ExposedDevice : Error() {
|
|
||||||
override val source: StatusSource = StatusSource.ACTUAL
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Unavailable(override val source: StatusSource) : Error()
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data object NotSynced : Error() {
|
|
||||||
override val source: StatusSource = StatusSource.ACTUAL
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data object CardIssueFailed : Error() {
|
|
||||||
override val source: StatusSource = StatusSource.ACTUAL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
package com.tangem.domain.pay.flow
|
package com.tangem.domain.pay.flow
|
||||||
|
|
||||||
import com.tangem.domain.core.flow.FlowProducer
|
import com.tangem.domain.core.flow.FlowProducer
|
||||||
|
import com.tangem.domain.models.account.AccountStatus
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.pay.PaymentAccountStatus
|
|
||||||
|
|
||||||
interface PaymentAccountStatusProducer : FlowProducer<PaymentAccountStatus> {
|
interface PaymentAccountStatusProducer : FlowProducer<AccountStatus.Payment> {
|
||||||
data class Params(val userWalletId: UserWalletId)
|
data class Params(val userWalletId: UserWalletId)
|
||||||
|
|
||||||
interface Factory : FlowProducer.Factory<Params, PaymentAccountStatusProducer>
|
interface Factory : FlowProducer.Factory<Params, PaymentAccountStatusProducer>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,18 @@
|
||||||
package com.tangem.domain.pay.flow
|
package com.tangem.domain.pay.flow
|
||||||
|
|
||||||
import com.tangem.domain.core.flow.FlowCachingSupplier
|
import com.tangem.domain.core.flow.FlowCachingSupplier
|
||||||
import com.tangem.domain.pay.PaymentAccountStatus
|
import com.tangem.domain.models.account.AccountStatus
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
@Suppress("UnnecessaryAbstractClass")
|
@Suppress("UnnecessaryAbstractClass")
|
||||||
abstract class PaymentAccountStatusSupplier(
|
abstract class PaymentAccountStatusSupplier(
|
||||||
override val factory: PaymentAccountStatusProducer.Factory,
|
override val factory: PaymentAccountStatusProducer.Factory,
|
||||||
override val keyCreator: (PaymentAccountStatusProducer.Params) -> String,
|
override val keyCreator: (PaymentAccountStatusProducer.Params) -> String,
|
||||||
) : FlowCachingSupplier<PaymentAccountStatusProducer, PaymentAccountStatusProducer.Params, PaymentAccountStatus>()
|
) : FlowCachingSupplier<PaymentAccountStatusProducer, PaymentAccountStatusProducer.Params, AccountStatus.Payment>() {
|
||||||
|
|
||||||
|
operator fun invoke(userWalletId: UserWalletId): Flow<AccountStatus.Payment> {
|
||||||
|
val params = PaymentAccountStatusProducer.Params(userWalletId)
|
||||||
|
return this.invoke(params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.tangem.domain.pay.model
|
package com.tangem.domain.pay.model
|
||||||
|
|
||||||
|
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||||
import com.tangem.domain.models.kyc.KycStatus
|
import com.tangem.domain.models.kyc.KycStatus
|
||||||
|
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
sealed class MainCustomerInfoContentState {
|
sealed class MainCustomerInfoContentState {
|
||||||
|
|
@ -25,6 +27,7 @@ data class CustomerInfo(
|
||||||
data class ProductInstance(
|
data class ProductInstance(
|
||||||
val id: String,
|
val id: String,
|
||||||
val cardId: String,
|
val cardId: String,
|
||||||
|
val frozenState: TangemPayCardFrozenState,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class CardInfo(
|
data class CardInfo(
|
||||||
|
|
@ -33,5 +36,7 @@ data class CustomerInfo(
|
||||||
val currencyCode: String,
|
val currencyCode: String,
|
||||||
val depositAddress: String?,
|
val depositAddress: String?,
|
||||||
val isPinSet: Boolean,
|
val isPinSet: Boolean,
|
||||||
|
val fiatBalance: PaymentAccountStatusValue.FiatBalance,
|
||||||
|
val cryptoBalance: PaymentAccountStatusValue.CryptoBalance,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -15,8 +15,6 @@ import com.tangem.security.isSecurityExposed
|
||||||
import com.tangem.utils.logging.TangemLogger
|
import com.tangem.utils.logging.TangemLogger
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
private const val TAG = "TangemPayMainScreenCustomerInfoUseCase"
|
|
||||||
|
|
||||||
class TangemPayMainScreenCustomerInfoUseCase(
|
class TangemPayMainScreenCustomerInfoUseCase(
|
||||||
private val onboardingRepository: OnboardingRepository,
|
private val onboardingRepository: OnboardingRepository,
|
||||||
private val customerOrderRepository: CustomerOrderRepository,
|
private val customerOrderRepository: CustomerOrderRepository,
|
||||||
|
|
@ -27,15 +25,15 @@ class TangemPayMainScreenCustomerInfoUseCase(
|
||||||
val state: StateFlow<Map<UserWalletId, Either<TangemPayCustomerInfoError, MainCustomerInfoContentState>>>
|
val state: StateFlow<Map<UserWalletId, Either<TangemPayCustomerInfoError, MainCustomerInfoContentState>>>
|
||||||
field = MutableStateFlow(value = mapOf())
|
field = MutableStateFlow(value = mapOf())
|
||||||
|
|
||||||
|
private val logger = TangemLogger.withTag("TangemPayMainScreenCustomerInfoUseCase")
|
||||||
|
|
||||||
suspend fun fetch(userWalletId: UserWalletId) {
|
suspend fun fetch(userWalletId: UserWalletId) {
|
||||||
TangemLogger.withTag(TAG).i("fetch: ${userWalletId.stringValue}")
|
logger.i("fetch: ${userWalletId.stringValue}")
|
||||||
|
|
||||||
if (deviceSecurity.isSecurityExposed()) {
|
if (deviceSecurity.isSecurityExposed()) {
|
||||||
TangemLogger.withTag(TAG).i("fetch security info: rooted: ${deviceSecurity.isRooted}")
|
logger.i("fetch security info: rooted: ${deviceSecurity.isRooted}")
|
||||||
TangemLogger.withTag(TAG).i("fetch security info: xposed: ${deviceSecurity.isXposed}")
|
logger.i("fetch security info: xposed: ${deviceSecurity.isXposed}")
|
||||||
TangemLogger.withTag(
|
logger.i("fetch security info: bootloader unlocked: ${deviceSecurity.isBootloaderUnlocked}")
|
||||||
TAG,
|
|
||||||
).i("fetch security info: bootloader unlocked: ${deviceSecurity.isBootloaderUnlocked}")
|
|
||||||
|
|
||||||
updateState(userWalletId = userWalletId, either = TangemPayCustomerInfoError.ExposedDeviceError.left())
|
updateState(userWalletId = userWalletId, either = TangemPayCustomerInfoError.ExposedDeviceError.left())
|
||||||
return // fast exit
|
return // fast exit
|
||||||
|
|
@ -44,9 +42,7 @@ class TangemPayMainScreenCustomerInfoUseCase(
|
||||||
onboardingRepository.hasTangemPayInWallet(userWalletId)
|
onboardingRepository.hasTangemPayInWallet(userWalletId)
|
||||||
.fold(
|
.fold(
|
||||||
ifLeft = { error ->
|
ifLeft = { error ->
|
||||||
TangemLogger.withTag(
|
logger.e("Failed checkCustomerWallet for $userWalletId: ${error.javaClass.simpleName}")
|
||||||
TAG,
|
|
||||||
).e("Failed checkCustomerWallet for $userWalletId: ${error.javaClass.simpleName}")
|
|
||||||
if (error is VisaApiError.NotPaeraCustomer) {
|
if (error is VisaApiError.NotPaeraCustomer) {
|
||||||
showOnboardingBannerIfEligible(userWalletId)
|
showOnboardingBannerIfEligible(userWalletId)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -54,7 +50,7 @@ class TangemPayMainScreenCustomerInfoUseCase(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ifRight = { hasTangemPay ->
|
ifRight = { hasTangemPay ->
|
||||||
TangemLogger.withTag(TAG).i("checkCustomerWallet for $userWalletId: $hasTangemPay")
|
logger.i("checkCustomerWallet for $userWalletId: $hasTangemPay")
|
||||||
if (hasTangemPay) {
|
if (hasTangemPay) {
|
||||||
val oldResult = state.value[userWalletId]
|
val oldResult = state.value[userWalletId]
|
||||||
if (oldResult == null) {
|
if (oldResult == null) {
|
||||||
|
|
@ -129,11 +125,11 @@ class TangemPayMainScreenCustomerInfoUseCase(
|
||||||
): Either<TangemPayCustomerInfoError, MainScreenCustomerInfo> {
|
): Either<TangemPayCustomerInfoError, MainScreenCustomerInfo> {
|
||||||
return onboardingRepository.getCustomerInfo(userWalletId)
|
return onboardingRepository.getCustomerInfo(userWalletId)
|
||||||
.mapLeft { error ->
|
.mapLeft { error ->
|
||||||
TangemLogger.withTag(TAG).e("mapErrorForCustomer: $error")
|
logger.e("mapErrorForCustomer: $error")
|
||||||
error.mapErrorForCustomer()
|
error.mapErrorForCustomer()
|
||||||
}
|
}
|
||||||
.map { customerInfo ->
|
.map { customerInfo ->
|
||||||
TangemLogger.withTag(TAG).i("customerInfo")
|
logger.i("customerInfo")
|
||||||
if (customerInfo.productInstance == null) {
|
if (customerInfo.productInstance == null) {
|
||||||
onboardingRepository.createOrder(userWalletId)
|
onboardingRepository.createOrder(userWalletId)
|
||||||
MainScreenCustomerInfo(info = customerInfo, orderStatus = OrderStatus.NEW)
|
MainScreenCustomerInfo(info = customerInfo, orderStatus = OrderStatus.NEW)
|
||||||
|
|
|
||||||
|
|
@ -184,7 +184,7 @@ internal class PortfolioSelectorModel @Inject constructor(
|
||||||
val account = accountStatus.account
|
val account = accountStatus.account
|
||||||
val accountBalance = when (accountStatus) {
|
val accountBalance = when (accountStatus) {
|
||||||
is AccountStatus.CryptoPortfolio -> accountStatus.tokenList.totalFiatBalance
|
is AccountStatus.CryptoPortfolio -> accountStatus.tokenList.totalFiatBalance
|
||||||
is AccountStatus.Payment -> accountStatus.totalFiatBalance
|
is AccountStatus.Payment -> accountStatus.value.totalFiatBalance
|
||||||
}
|
}
|
||||||
val accountItemUM = AccountPortfolioItemUMConverter(
|
val accountItemUM = AccountPortfolioItemUMConverter(
|
||||||
onClick = { selectorController.selectAccount(account.accountId) },
|
onClick = { selectorController.selectAccount(account.accountId) },
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import com.tangem.domain.exchange.RampStateManager
|
||||||
import com.tangem.domain.models.TotalFiatBalance
|
import com.tangem.domain.models.TotalFiatBalance
|
||||||
import com.tangem.domain.models.account.Account
|
import com.tangem.domain.models.account.Account
|
||||||
import com.tangem.domain.models.account.AccountStatus
|
import com.tangem.domain.models.account.AccountStatus
|
||||||
|
import com.tangem.domain.models.account.filterCryptoPortfolio
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||||
|
|
@ -105,7 +106,7 @@ internal class OnrampTokenListModel @Inject constructor(
|
||||||
updateTokenListUM(
|
updateTokenListUM(
|
||||||
SetLoadingAccountTokenListTransformer(
|
SetLoadingAccountTokenListTransformer(
|
||||||
appCurrency = appCurrency,
|
appCurrency = appCurrency,
|
||||||
accountList = accountList.accountStatuses.toList(),
|
accountList = accountList.accountStatuses.filterCryptoPortfolio().toList(),
|
||||||
isAccountsMode = isAccountsMode,
|
isAccountsMode = isAccountsMode,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -237,6 +238,7 @@ internal class OnrampTokenListModel @Inject constructor(
|
||||||
private fun AccountStatusList.filterAccountsByQuery(
|
private fun AccountStatusList.filterAccountsByQuery(
|
||||||
query: String,
|
query: String,
|
||||||
): Map<Account.CryptoPortfolio, List<CryptoCurrencyStatus>> = accountStatuses.asSequence()
|
): Map<Account.CryptoPortfolio, List<CryptoCurrencyStatus>> = accountStatuses.asSequence()
|
||||||
|
.filterCryptoPortfolio()
|
||||||
.associate { accountStatus ->
|
.associate { accountStatus ->
|
||||||
when (accountStatus) {
|
when (accountStatus) {
|
||||||
is AccountStatus.CryptoPortfolio -> {
|
is AccountStatus.CryptoPortfolio -> {
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import com.tangem.domain.exchange.RampStateManager
|
||||||
import com.tangem.domain.express.models.ExpressOperationType
|
import com.tangem.domain.express.models.ExpressOperationType
|
||||||
import com.tangem.domain.models.account.Account
|
import com.tangem.domain.models.account.Account
|
||||||
import com.tangem.domain.models.account.AccountStatus
|
import com.tangem.domain.models.account.AccountStatus
|
||||||
|
import com.tangem.domain.models.account.filterCryptoPortfolio
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.models.network.Network
|
import com.tangem.domain.models.network.Network
|
||||||
|
|
@ -131,7 +132,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
||||||
private suspend fun getAccountCurrencyTokensDataState(currency: CryptoCurrency): TokensDataStateExpress {
|
private suspend fun getAccountCurrencyTokensDataState(currency: CryptoCurrency): TokensDataStateExpress {
|
||||||
val walletAccountCurrencyStatuses = singleAccountStatusListSupplier.getSyncOrNull(
|
val walletAccountCurrencyStatuses = singleAccountStatusListSupplier.getSyncOrNull(
|
||||||
SingleAccountStatusListProducer.Params(userWalletId),
|
SingleAccountStatusListProducer.Params(userWalletId),
|
||||||
)?.accountStatuses.orEmpty()
|
)?.accountStatuses.orEmpty().filterCryptoPortfolio()
|
||||||
|
|
||||||
val walletAccountCurrencyStatusesExceptInitial: Map<Account, List<CryptoCurrencyStatus>> =
|
val walletAccountCurrencyStatusesExceptInitial: Map<Account, List<CryptoCurrencyStatus>> =
|
||||||
walletAccountCurrencyStatuses.mapNotNull { accountStatus ->
|
walletAccountCurrencyStatuses.mapNotNull { accountStatus ->
|
||||||
|
|
|
||||||
|
|
@ -15,4 +15,6 @@ dependencies {
|
||||||
|
|
||||||
/** Compose */
|
/** Compose */
|
||||||
implementation(deps.compose.runtime)
|
implementation(deps.compose.runtime)
|
||||||
|
implementation(deps.compose.foundation)
|
||||||
|
implementation(deps.compose.ui)
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.tangem.features.tangempay.component
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import com.tangem.core.decompose.factory.ComponentFactory
|
||||||
|
import com.tangem.features.tangempay.entity.TangemPayMainUM
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
interface TangemPayMainBlockComponent {
|
||||||
|
|
||||||
|
fun LazyListScope.tangemPayMainContent(
|
||||||
|
state: TangemPayMainUM,
|
||||||
|
isBalanceHidden: Boolean,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
)
|
||||||
|
|
||||||
|
interface Factory : ComponentFactory<Unit, TangemPayMainBlockComponent>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.tangem.features.tangempay.entity
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
sealed class TangemPayMainUM {
|
||||||
|
|
||||||
|
data object Empty : TangemPayMainUM()
|
||||||
|
data object Loading : TangemPayMainUM()
|
||||||
|
data class UnderReview(val subtitle: TextReference, val onClick: () -> Unit) : TangemPayMainUM()
|
||||||
|
data class IssuingCard(val onClick: () -> Unit) : TangemPayMainUM()
|
||||||
|
data class FailedToIssue(val onClick: () -> Unit) : TangemPayMainUM()
|
||||||
|
data class Content(
|
||||||
|
val subtitle: TextReference,
|
||||||
|
val isBalanceFlickering: Boolean,
|
||||||
|
val balance: TextReference,
|
||||||
|
val balanceSubtitle: TextReference,
|
||||||
|
val onClick: () -> Unit,
|
||||||
|
val shouldShowOnlyCacheWarning: Boolean,
|
||||||
|
) : TangemPayMainUM()
|
||||||
|
|
||||||
|
data object TemporaryUnavailable : TangemPayMainUM()
|
||||||
|
data object SyncNeeded : TangemPayMainUM()
|
||||||
|
data object ExposedDevice : TangemPayMainUM()
|
||||||
|
}
|
||||||
|
|
@ -15,10 +15,9 @@ dependencies {
|
||||||
/** Core */
|
/** Core */
|
||||||
implementation(projects.core.decompose)
|
implementation(projects.core.decompose)
|
||||||
implementation(projects.core.ui)
|
implementation(projects.core.ui)
|
||||||
implementation(projects.core.configToggles)
|
|
||||||
|
|
||||||
/** Features api */
|
/** Features api */
|
||||||
implementation(projects.features.tangempay.details.api)
|
implementation(projects.features.tangempay.main.api)
|
||||||
|
|
||||||
/** Compose */
|
/** Compose */
|
||||||
implementation(deps.compose.foundation)
|
implementation(deps.compose.foundation)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package com.tangem.features.tangempay.component
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import com.tangem.core.decompose.context.AppComponentContext
|
||||||
|
import com.tangem.features.tangempay.entity.TangemPayMainUM
|
||||||
|
import com.tangem.features.tangempay.ui.TangemPayMainBlockItem
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
|
||||||
|
private const val TANGEM_PAY_ACCOUNT_CONTENT_TYPE = "TangemPayAccount"
|
||||||
|
|
||||||
|
@Suppress("UnusedPrivateProperty")
|
||||||
|
internal class DefaultTangemPayMainBlockComponent @AssistedInject constructor(
|
||||||
|
@Assisted context: AppComponentContext,
|
||||||
|
@Assisted params: Unit,
|
||||||
|
) : TangemPayMainBlockComponent, AppComponentContext by context {
|
||||||
|
|
||||||
|
override fun LazyListScope.tangemPayMainContent(
|
||||||
|
state: TangemPayMainUM,
|
||||||
|
isBalanceHidden: Boolean,
|
||||||
|
modifier: Modifier,
|
||||||
|
) {
|
||||||
|
item(
|
||||||
|
key = TANGEM_PAY_ACCOUNT_CONTENT_TYPE,
|
||||||
|
contentType = TANGEM_PAY_ACCOUNT_CONTENT_TYPE,
|
||||||
|
) {
|
||||||
|
TangemPayMainBlockItem(state, isBalanceHidden, modifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory : TangemPayMainBlockComponent.Factory {
|
||||||
|
override fun create(context: AppComponentContext, params: Unit): DefaultTangemPayMainBlockComponent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.tangem.features.tangempay.di
|
||||||
|
|
||||||
|
import com.tangem.features.tangempay.component.DefaultTangemPayMainBlockComponent
|
||||||
|
import com.tangem.features.tangempay.component.TangemPayMainBlockComponent
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
internal interface TangemPayMainModule {
|
||||||
|
@Binds
|
||||||
|
fun bindTangemPayMainBlockComponent(
|
||||||
|
factory: DefaultTangemPayMainBlockComponent.Factory,
|
||||||
|
): TangemPayMainBlockComponent.Factory
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,355 @@
|
||||||
|
package com.tangem.features.tangempay.ui
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
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.alpha
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
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 com.tangem.core.ui.components.CircleShimmer
|
||||||
|
import com.tangem.core.ui.components.RectangleShimmer
|
||||||
|
import com.tangem.core.ui.components.SpacerWMax
|
||||||
|
import com.tangem.core.ui.components.block.BlockCard
|
||||||
|
import com.tangem.core.ui.components.inputrow.InputRowImageBase
|
||||||
|
import com.tangem.core.ui.components.text.applyBladeBrush
|
||||||
|
import com.tangem.core.ui.extensions.*
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
import com.tangem.features.tangempay.entity.TangemPayMainUM
|
||||||
|
import com.tangem.features.tangempay.main.impl.R
|
||||||
|
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||||
|
|
||||||
|
private const val DISABLED_ALPHA = 0.6F
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun TangemPayMainBlockItem(state: TangemPayMainUM, isBalanceHidden: Boolean, modifier: Modifier = Modifier) {
|
||||||
|
when (state) {
|
||||||
|
is TangemPayMainUM.Empty -> Unit
|
||||||
|
is TangemPayMainUM.Loading -> TangemPayMainLoadingItem(modifier)
|
||||||
|
is TangemPayMainUM.UnderReview -> TangemPayMainUnderReviewItem(state, modifier)
|
||||||
|
is TangemPayMainUM.IssuingCard -> TangemPayMainIssuingCardItem(state, modifier)
|
||||||
|
is TangemPayMainUM.FailedToIssue -> TangemPayMainFailedIssueItem(state, modifier)
|
||||||
|
is TangemPayMainUM.Content -> TangemPayMainBlockContent(state, isBalanceHidden, modifier)
|
||||||
|
is TangemPayMainUM.TemporaryUnavailable -> TangemPayMainTempUnavailableItem(modifier)
|
||||||
|
is TangemPayMainUM.SyncNeeded -> TangemPayMainSyncNeededItem(modifier)
|
||||||
|
is TangemPayMainUM.ExposedDevice -> TangemPayMainExposedDeviceItem(modifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TangemPayMainBlockContent(
|
||||||
|
state: TangemPayMainUM.Content,
|
||||||
|
isBalanceHidden: Boolean,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
modifier = modifier,
|
||||||
|
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||||
|
color = TangemTheme.colors.background.primary,
|
||||||
|
onClick = state.onClick,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(IntrinsicSize.Min)
|
||||||
|
.padding(horizontal = 12.dp, vertical = 16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.img_visa_36),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(36.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResourceSafe(R.string.tangempay_payment_account),
|
||||||
|
style = TangemTheme.typography.subtitle2,
|
||||||
|
color = TangemTheme.colors.text.primary1,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = state.subtitle.resolveReference(),
|
||||||
|
style = TangemTheme.typography.caption2,
|
||||||
|
color = TangemTheme.colors.text.tertiary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxHeight(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
|
horizontalAlignment = Alignment.End,
|
||||||
|
) {
|
||||||
|
TangemPayFiatAmount(
|
||||||
|
text = state.balance.resolveReference(),
|
||||||
|
isBalanceFlickering = state.isBalanceFlickering,
|
||||||
|
isBalanceFromCache = state.shouldShowOnlyCacheWarning,
|
||||||
|
isBalanceHidden = isBalanceHidden,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = state.balanceSubtitle.resolveReference(),
|
||||||
|
style = TangemTheme.typography.caption2,
|
||||||
|
color = TangemTheme.colors.text.tertiary,
|
||||||
|
textAlign = TextAlign.End,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TangemPayFiatAmount(
|
||||||
|
text: String,
|
||||||
|
isBalanceFlickering: Boolean,
|
||||||
|
isBalanceFromCache: Boolean,
|
||||||
|
isBalanceHidden: Boolean,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
AnimatedVisibility(isBalanceFromCache) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(horizontal = 4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.size(12.dp),
|
||||||
|
painter = painterResource(R.drawable.ic_error_sync_24),
|
||||||
|
tint = TangemTheme.colors.icon.inactive,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = text.orMaskWithStars(isBalanceHidden),
|
||||||
|
style = TangemTheme.typography.body2.applyBladeBrush(
|
||||||
|
isEnabled = isBalanceFlickering,
|
||||||
|
textColor = TangemTheme.colors.text.primary1,
|
||||||
|
),
|
||||||
|
textAlign = TextAlign.End,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TangemPayMainUnderReviewItem(state: TangemPayMainUM.UnderReview, modifier: Modifier = Modifier) {
|
||||||
|
BlockCard(
|
||||||
|
modifier = modifier
|
||||||
|
.clip(RoundedCornerShape(size = TangemTheme.dimens.radius14))
|
||||||
|
.background(TangemTheme.colors.background.primary),
|
||||||
|
onClick = state.onClick,
|
||||||
|
) {
|
||||||
|
InputRowImageBase(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(
|
||||||
|
all = TangemTheme.dimens.spacing12,
|
||||||
|
),
|
||||||
|
subtitle = resourceReference(R.string.tangempay_payment_account),
|
||||||
|
caption = state.subtitle,
|
||||||
|
subtitleColor = TangemTheme.colors.text.primary1,
|
||||||
|
captionColor = TangemTheme.colors.text.tertiary,
|
||||||
|
iconResWebp = R.drawable.img_visa_36,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TangemPayMainTempUnavailableItem(modifier: Modifier = Modifier) {
|
||||||
|
BlockCard(
|
||||||
|
modifier = modifier
|
||||||
|
.clip(RoundedCornerShape(size = TangemTheme.dimens.radius14))
|
||||||
|
.background(TangemTheme.colors.background.primary),
|
||||||
|
enabled = false,
|
||||||
|
) {
|
||||||
|
InputRowImageBase(
|
||||||
|
modifier = Modifier.padding(all = TangemTheme.dimens.spacing12),
|
||||||
|
subtitle = resourceReference(R.string.tangempay_payment_account),
|
||||||
|
caption = TextReference.Str(DASH_SIGN),
|
||||||
|
subtitleColor = TangemTheme.colors.text.tertiary,
|
||||||
|
captionColor = TangemTheme.colors.text.tertiary,
|
||||||
|
iconResWebp = R.drawable.img_visa_36,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TangemPayMainIssuingCardItem(state: TangemPayMainUM.IssuingCard, modifier: Modifier = Modifier) {
|
||||||
|
BlockCard(
|
||||||
|
modifier = modifier
|
||||||
|
.clip(RoundedCornerShape(size = TangemTheme.dimens.radius14))
|
||||||
|
.background(TangemTheme.colors.background.primary),
|
||||||
|
onClick = state.onClick,
|
||||||
|
) {
|
||||||
|
InputRowImageBase(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(all = TangemTheme.dimens.spacing12),
|
||||||
|
subtitle = resourceReference(R.string.tangempay_payment_account),
|
||||||
|
caption = resourceReference(R.string.tangempay_issuing_your_card),
|
||||||
|
subtitleColor = TangemTheme.colors.text.primary1,
|
||||||
|
captionColor = TangemTheme.colors.text.tertiary,
|
||||||
|
iconResWebp = R.drawable.img_visa_36,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TangemPayMainFailedIssueItem(state: TangemPayMainUM.FailedToIssue, modifier: Modifier = Modifier) {
|
||||||
|
BlockCard(
|
||||||
|
modifier = modifier
|
||||||
|
.clip(RoundedCornerShape(size = TangemTheme.dimens.radius14))
|
||||||
|
.background(TangemTheme.colors.background.primary),
|
||||||
|
onClick = state.onClick,
|
||||||
|
) {
|
||||||
|
InputRowImageBase(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(
|
||||||
|
all = TangemTheme.dimens.spacing12,
|
||||||
|
),
|
||||||
|
subtitle = TextReference.Res(R.string.tangempay_payment_account),
|
||||||
|
caption = TextReference.Res(R.string.tangempay_failed_to_issue_card),
|
||||||
|
subtitleColor = TangemTheme.colors.text.primary1,
|
||||||
|
captionColor = TangemTheme.colors.text.tertiary,
|
||||||
|
iconResWebp = com.tangem.core.ui.R.drawable.img_visa_36,
|
||||||
|
iconEndRes = R.drawable.ic_alert_24,
|
||||||
|
endIconTint = TangemTheme.colors.icon.warning,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TangemPayMainSyncNeededItem(modifier: Modifier = Modifier) {
|
||||||
|
BlockCard(
|
||||||
|
modifier = modifier
|
||||||
|
.clip(RoundedCornerShape(size = TangemTheme.dimens.radius14))
|
||||||
|
.background(TangemTheme.colors.background.primary),
|
||||||
|
enabled = false,
|
||||||
|
) {
|
||||||
|
InputRowImageBase(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
all = TangemTheme.dimens.spacing12,
|
||||||
|
),
|
||||||
|
subtitle = resourceReference(R.string.tangempay_payment_account),
|
||||||
|
caption = resourceReference(R.string.tangempay_payment_account_sync_needed),
|
||||||
|
subtitleColor = TangemTheme.colors.text.tertiary,
|
||||||
|
captionColor = TangemTheme.colors.text.tertiary,
|
||||||
|
iconResWebp = R.drawable.img_visa_36,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TangemPayMainExposedDeviceItem(modifier: Modifier = Modifier) {
|
||||||
|
BlockCard(
|
||||||
|
modifier = modifier
|
||||||
|
.clip(RoundedCornerShape(size = TangemTheme.dimens.radius14))
|
||||||
|
.background(TangemTheme.colors.background.primary)
|
||||||
|
.alpha(DISABLED_ALPHA),
|
||||||
|
enabled = false,
|
||||||
|
) {
|
||||||
|
InputRowImageBase(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(
|
||||||
|
all = TangemTheme.dimens.spacing12,
|
||||||
|
),
|
||||||
|
subtitle = resourceReference(R.string.tangempay_payment_account),
|
||||||
|
caption = resourceReference(R.string.tangem_pay_rooted_device_subtitle),
|
||||||
|
subtitleColor = TangemTheme.colors.text.primary1,
|
||||||
|
captionColor = TangemTheme.colors.text.tertiary,
|
||||||
|
iconResWebp = R.drawable.img_visa_36,
|
||||||
|
endIconTint = TangemTheme.colors.icon.warning,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TangemPayMainLoadingItem(modifier: Modifier = Modifier) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(color = TangemTheme.colors.background.primary, shape = TangemTheme.shapes.roundedCornersXMedium)
|
||||||
|
.padding(horizontal = 12.dp, vertical = 16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
CircleShimmer(modifier = Modifier.size(36.dp))
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(start = 12.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
|
) {
|
||||||
|
RectangleShimmer(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 4.dp)
|
||||||
|
.sizeIn(minWidth = 70.dp, minHeight = 12.dp),
|
||||||
|
)
|
||||||
|
RectangleShimmer(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 2.dp)
|
||||||
|
.sizeIn(minWidth = 52.dp, minHeight = 12.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
SpacerWMax()
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||||
|
RectangleShimmer(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 4.dp)
|
||||||
|
.sizeIn(minWidth = 40.dp, minHeight = 12.dp),
|
||||||
|
)
|
||||||
|
RectangleShimmer(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 2.dp)
|
||||||
|
.sizeIn(minWidth = 40.dp, minHeight = 12.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun TangemPayMainItemsPreview(
|
||||||
|
@PreviewParameter(TangemPayMainUMPreviewParameterProvider::class)
|
||||||
|
state: TangemPayMainUM,
|
||||||
|
) {
|
||||||
|
TangemThemePreview {
|
||||||
|
TangemPayMainBlockItem(state = state, isBalanceHidden = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TangemPayMainUMPreviewParameterProvider : CollectionPreviewParameterProvider<TangemPayMainUM>(
|
||||||
|
collection = listOf(
|
||||||
|
TangemPayMainUM.Loading,
|
||||||
|
TangemPayMainUM.SyncNeeded,
|
||||||
|
TangemPayMainUM.TemporaryUnavailable,
|
||||||
|
TangemPayMainUM.ExposedDevice,
|
||||||
|
TangemPayMainUM.FailedToIssue(onClick = {}),
|
||||||
|
TangemPayMainUM.UnderReview(subtitle = resourceReference(R.string.tangempay_kyc_in_progress), onClick = {}),
|
||||||
|
TangemPayMainUM.IssuingCard(onClick = {}),
|
||||||
|
TangemPayMainUM.Content(
|
||||||
|
subtitle = TextReference.Str("*1234"),
|
||||||
|
isBalanceFlickering = true,
|
||||||
|
balance = TextReference.Str("$ 101.56"),
|
||||||
|
balanceSubtitle = TextReference.Str("USDC"),
|
||||||
|
onClick = {},
|
||||||
|
shouldShowOnlyCacheWarning = true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
@ -148,6 +148,7 @@ dependencies {
|
||||||
implementation(projects.features.tangempay.details.api)
|
implementation(projects.features.tangempay.details.api)
|
||||||
implementation(projects.features.feed.api)
|
implementation(projects.features.feed.api)
|
||||||
implementation(projects.features.promoBanners.api)
|
implementation(projects.features.promoBanners.api)
|
||||||
|
implementation(projects.features.tangempay.main.api)
|
||||||
|
|
||||||
/** Common modules */
|
/** Common modules */
|
||||||
implementation(projects.common)
|
implementation(projects.common)
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ import com.tangem.features.biometry.AskBiometryComponent
|
||||||
import com.tangem.features.feed.entry.components.FeedEntryComponent
|
import com.tangem.features.feed.entry.components.FeedEntryComponent
|
||||||
import com.tangem.features.pushnotifications.api.PushNotificationsBottomSheetComponent
|
import com.tangem.features.pushnotifications.api.PushNotificationsBottomSheetComponent
|
||||||
import com.tangem.features.pushnotifications.api.PushNotificationsParams
|
import com.tangem.features.pushnotifications.api.PushNotificationsParams
|
||||||
|
import com.tangem.features.tangempay.component.TangemPayMainBlockComponent
|
||||||
import com.tangem.features.send.v2.api.NetworkSelectionComponent
|
import com.tangem.features.send.v2.api.NetworkSelectionComponent
|
||||||
import com.tangem.features.tokenreceive.TokenReceiveComponent
|
import com.tangem.features.tokenreceive.TokenReceiveComponent
|
||||||
import com.tangem.features.yield.supply.api.YieldSupplyDepositedWarningComponent
|
import com.tangem.features.yield.supply.api.YieldSupplyDepositedWarningComponent
|
||||||
|
|
@ -51,6 +52,7 @@ internal class WalletComponent @AssistedInject constructor(
|
||||||
@Assisted appComponentContext: AppComponentContext,
|
@Assisted appComponentContext: AppComponentContext,
|
||||||
@Assisted navigate: (WalletRoute) -> Unit,
|
@Assisted navigate: (WalletRoute) -> Unit,
|
||||||
feedEntryComponentFactory: FeedEntryComponent.Factory,
|
feedEntryComponentFactory: FeedEntryComponent.Factory,
|
||||||
|
tangemPayMainBlockComponentFactory: TangemPayMainBlockComponent.Factory,
|
||||||
private val renameWalletComponentFactory: RenameWalletComponent.Factory,
|
private val renameWalletComponentFactory: RenameWalletComponent.Factory,
|
||||||
private val askBiometryComponentFactory: AskBiometryComponent.Factory,
|
private val askBiometryComponentFactory: AskBiometryComponent.Factory,
|
||||||
private val pushNotificationsBottomSheetComponent: PushNotificationsBottomSheetComponent.Factory,
|
private val pushNotificationsBottomSheetComponent: PushNotificationsBottomSheetComponent.Factory,
|
||||||
|
|
@ -70,6 +72,12 @@ internal class WalletComponent @AssistedInject constructor(
|
||||||
entryRoute = null,
|
entryRoute = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
private val tangemPayMainBlockComponent by lazy {
|
||||||
|
tangemPayMainBlockComponentFactory.create(
|
||||||
|
context = child("tangemPayMainBlockComponent"),
|
||||||
|
params = Unit,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private val promoBannersBlockComponent: PromoBannersBlockComponent? by lazy {
|
private val promoBannersBlockComponent: PromoBannersBlockComponent? by lazy {
|
||||||
if (!newPromoBannersFeatureToggles.isNewPromoBannersEnabled) return@lazy null
|
if (!newPromoBannersFeatureToggles.isNewPromoBannersEnabled) return@lazy null
|
||||||
|
|
@ -218,10 +226,11 @@ internal class WalletComponent @AssistedInject constructor(
|
||||||
val bottomSheetState = remember { mutableStateOf(BottomSheetState.COLLAPSED) }
|
val bottomSheetState = remember { mutableStateOf(BottomSheetState.COLLAPSED) }
|
||||||
var headerSize by remember { mutableStateOf(0.dp) }
|
var headerSize by remember { mutableStateOf(0.dp) }
|
||||||
val dialog by dialog.subscribeAsState()
|
val dialog by dialog.subscribeAsState()
|
||||||
|
val uiState by model.uiState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
if (designFeatureToggles.isRedesignEnabled) {
|
if (designFeatureToggles.isRedesignEnabled) {
|
||||||
WalletScreen2(
|
WalletScreen2(
|
||||||
state = model.uiState.collectAsStateWithLifecycle().value,
|
state = uiState,
|
||||||
bottomSheetContent = {
|
bottomSheetContent = {
|
||||||
BottomSheetContent(
|
BottomSheetContent(
|
||||||
bottomSheetState = bottomSheetState,
|
bottomSheetState = bottomSheetState,
|
||||||
|
|
@ -234,8 +243,9 @@ internal class WalletComponent @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
WalletScreen(
|
WalletScreen(
|
||||||
state = model.uiState.collectAsStateWithLifecycle().value,
|
state = uiState,
|
||||||
promoBannersBlockComponent = promoBannersBlockComponent,
|
promoBannersBlockComponent = promoBannersBlockComponent,
|
||||||
|
tangemPayComponent = tangemPayMainBlockComponent,
|
||||||
bottomSheetContent = {
|
bottomSheetContent = {
|
||||||
BottomSheetContent(
|
BottomSheetContent(
|
||||||
bottomSheetState = bottomSheetState,
|
bottomSheetState = bottomSheetState,
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,13 @@ import com.arkivanov.decompose.router.slot.activate
|
||||||
import com.arkivanov.decompose.router.slot.dismiss
|
import com.arkivanov.decompose.router.slot.dismiss
|
||||||
import com.tangem.common.routing.AppRoute
|
import com.tangem.common.routing.AppRoute
|
||||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
import com.tangem.core.ui.utils.parseBigDecimal
|
|
||||||
import com.tangem.core.analytics.models.AnalyticsParam
|
import com.tangem.core.analytics.models.AnalyticsParam
|
||||||
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
|
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
|
||||||
import com.tangem.core.analytics.utils.TrackingContextProxy
|
import com.tangem.core.analytics.utils.TrackingContextProxy
|
||||||
import com.tangem.core.decompose.di.ModelScoped
|
import com.tangem.core.decompose.di.ModelScoped
|
||||||
import com.tangem.core.decompose.model.Model
|
import com.tangem.core.decompose.model.Model
|
||||||
import com.tangem.core.decompose.ui.UiMessageSender
|
import com.tangem.core.decompose.ui.UiMessageSender
|
||||||
|
import com.tangem.core.ui.utils.parseBigDecimal
|
||||||
import com.tangem.datasource.local.appsflyer.AppsFlyerStore
|
import com.tangem.datasource.local.appsflyer.AppsFlyerStore
|
||||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||||
|
|
@ -25,20 +25,20 @@ import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||||
import com.tangem.domain.models.wallet.*
|
import com.tangem.domain.models.wallet.*
|
||||||
import com.tangem.domain.notifications.GetIsHuaweiDeviceWithoutGoogleServicesUseCase
|
import com.tangem.domain.notifications.GetIsHuaweiDeviceWithoutGoogleServicesUseCase
|
||||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||||
import com.tangem.domain.qrscanning.models.QrResultSource
|
import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher
|
||||||
import com.tangem.domain.qrscanning.models.SourceType
|
|
||||||
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
|
|
||||||
import com.tangem.domain.qrscanning.models.ClassifiedQrContent
|
|
||||||
import com.tangem.domain.qrscanning.models.QrSendTarget
|
|
||||||
import com.tangem.domain.walletconnect.WcPairService
|
|
||||||
import com.tangem.domain.walletconnect.model.WcPairRequest
|
|
||||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||||
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
|
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
|
||||||
|
import com.tangem.domain.qrscanning.models.ClassifiedQrContent
|
||||||
|
import com.tangem.domain.qrscanning.models.QrResultSource
|
||||||
|
import com.tangem.domain.qrscanning.models.QrSendTarget
|
||||||
|
import com.tangem.domain.qrscanning.models.SourceType
|
||||||
|
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
|
||||||
import com.tangem.domain.qrscanning.usecases.ResolveQrSendTargetsUseCase
|
import com.tangem.domain.qrscanning.usecases.ResolveQrSendTargetsUseCase
|
||||||
import com.tangem.domain.settings.*
|
import com.tangem.domain.settings.*
|
||||||
import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase
|
import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase
|
||||||
|
import com.tangem.domain.walletconnect.WcPairService
|
||||||
|
import com.tangem.domain.walletconnect.model.WcPairRequest
|
||||||
import com.tangem.domain.wallets.usecase.*
|
import com.tangem.domain.wallets.usecase.*
|
||||||
import com.tangem.domain.wallets.usecase.GetWalletIconUseCase
|
|
||||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyUpdateUseCase
|
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyUpdateUseCase
|
||||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||||
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
|
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
|
||||||
|
|
@ -61,6 +61,7 @@ import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.KycRejec
|
||||||
import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider
|
import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider
|
||||||
import com.tangem.features.biometry.AskBiometryComponent
|
import com.tangem.features.biometry.AskBiometryComponent
|
||||||
import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks
|
import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks
|
||||||
|
import com.tangem.features.tangempay.TangemPayFeatureToggles
|
||||||
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionListener
|
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionListener
|
||||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||||
import com.tangem.utils.Provider
|
import com.tangem.utils.Provider
|
||||||
|
|
@ -119,6 +120,8 @@ internal class WalletModel @Inject constructor(
|
||||||
private val listenToQrScanningUseCase: ListenToQrScanningUseCase,
|
private val listenToQrScanningUseCase: ListenToQrScanningUseCase,
|
||||||
private val wcPairService: WcPairService,
|
private val wcPairService: WcPairService,
|
||||||
private val resolveQrSendTargetsUseCase: ResolveQrSendTargetsUseCase,
|
private val resolveQrSendTargetsUseCase: ResolveQrSendTargetsUseCase,
|
||||||
|
private val paymentAccountStatusFetcher: PaymentAccountStatusFetcher,
|
||||||
|
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
|
||||||
private val uiMessageSender: UiMessageSender,
|
private val uiMessageSender: UiMessageSender,
|
||||||
val screenLifecycleProvider: ScreenLifecycleProvider,
|
val screenLifecycleProvider: ScreenLifecycleProvider,
|
||||||
val innerWalletRouter: InnerWalletRouter,
|
val innerWalletRouter: InnerWalletRouter,
|
||||||
|
|
@ -427,14 +430,17 @@ internal class WalletModel @Inject constructor(
|
||||||
updateTangemPayJobHolder.cancel()
|
updateTangemPayJobHolder.cancel()
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId)
|
tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId)
|
||||||
|
paymentAccountStatusFetcher.invoke(PaymentAccountStatusFetcher.Params(userWalletId))
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
delay(TANGEM_PAY_UPDATE_INTERVAL)
|
delay(TANGEM_PAY_UPDATE_INTERVAL)
|
||||||
tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId)
|
tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId)
|
||||||
|
paymentAccountStatusFetcher.invoke(PaymentAccountStatusFetcher.Params(userWalletId))
|
||||||
}
|
}
|
||||||
}.saveIn(updateTangemPayJobHolder)
|
}.saveIn(updateTangemPayJobHolder)
|
||||||
} else {
|
} else {
|
||||||
// Don't refresh customer info periodically if the card was already issued, only update on swipe to refresh
|
// Don't refresh customer info periodically if the card was already issued, only update on swipe to refresh
|
||||||
tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId)
|
tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId)
|
||||||
|
paymentAccountStatusFetcher.invoke(PaymentAccountStatusFetcher.Params(userWalletId))
|
||||||
}
|
}
|
||||||
}.launchIn(modelScope)
|
}.launchIn(modelScope)
|
||||||
}
|
}
|
||||||
|
|
@ -543,6 +549,7 @@ internal class WalletModel @Inject constructor(
|
||||||
walletImageResolver = walletImageResolver,
|
walletImageResolver = walletImageResolver,
|
||||||
isMainScreenQrScanningEnabled = walletFeatureToggles.isMainScreenQrScanningEnabled,
|
isMainScreenQrScanningEnabled = walletFeatureToggles.isMainScreenQrScanningEnabled,
|
||||||
getWalletIconUseCase = getWalletIconUseCase,
|
getWalletIconUseCase = getWalletIconUseCase,
|
||||||
|
isTangemPayRefactorEnabled = tangemPayFeatureToggles.isTangemPayAccountsRefactorEnabled,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -589,6 +596,7 @@ internal class WalletModel @Inject constructor(
|
||||||
clickIntents = clickIntents,
|
clickIntents = clickIntents,
|
||||||
walletImageResolver = walletImageResolver,
|
walletImageResolver = walletImageResolver,
|
||||||
getWalletIconUseCase = getWalletIconUseCase,
|
getWalletIconUseCase = getWalletIconUseCase,
|
||||||
|
isTangemPayRefactorEnabled = tangemPayFeatureToggles.isTangemPayAccountsRefactorEnabled,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -610,6 +618,7 @@ internal class WalletModel @Inject constructor(
|
||||||
clickIntents = clickIntents,
|
clickIntents = clickIntents,
|
||||||
walletImageResolver = walletImageResolver,
|
walletImageResolver = walletImageResolver,
|
||||||
getWalletIconUseCase = getWalletIconUseCase,
|
getWalletIconUseCase = getWalletIconUseCase,
|
||||||
|
isTangemPayRefactorEnabled = tangemPayFeatureToggles.isTangemPayAccountsRefactorEnabled,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -624,6 +633,7 @@ internal class WalletModel @Inject constructor(
|
||||||
clickIntents = clickIntents,
|
clickIntents = clickIntents,
|
||||||
walletImageResolver = walletImageResolver,
|
walletImageResolver = walletImageResolver,
|
||||||
getWalletIconUseCase = getWalletIconUseCase,
|
getWalletIconUseCase = getWalletIconUseCase,
|
||||||
|
isTangemPayRefactorEnabled = tangemPayFeatureToggles.isTangemPayAccountsRefactorEnabled,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -685,6 +695,7 @@ internal class WalletModel @Inject constructor(
|
||||||
clickIntents = clickIntents,
|
clickIntents = clickIntents,
|
||||||
walletImageResolver = walletImageResolver,
|
walletImageResolver = walletImageResolver,
|
||||||
getWalletIconUseCase = getWalletIconUseCase,
|
getWalletIconUseCase = getWalletIconUseCase,
|
||||||
|
isTangemPayRefactorEnabled = tangemPayFeatureToggles.isTangemPayAccountsRefactorEnabled,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.pay.TangemPayDetailsConfig
|
import com.tangem.domain.pay.TangemPayDetailsConfig
|
||||||
import com.tangem.domain.pay.TangemPayEligibilityManager
|
import com.tangem.domain.pay.TangemPayEligibilityManager
|
||||||
import com.tangem.domain.pay.model.TangemPayEntryPoint
|
import com.tangem.domain.pay.model.TangemPayEntryPoint
|
||||||
|
import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher
|
||||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||||
import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase
|
import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase
|
||||||
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
|
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
|
||||||
|
|
@ -30,7 +31,6 @@ import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
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.model.WalletDialogConfig
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayHideOnboardingStateTransformer
|
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
|
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayRefreshShowProgressTransformer
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -77,6 +77,7 @@ internal class TangemPayClickIntentsImplementor @Inject constructor(
|
||||||
private val tangemPayEligibilityManager: TangemPayEligibilityManager,
|
private val tangemPayEligibilityManager: TangemPayEligibilityManager,
|
||||||
private val uiMessageSender: UiMessageSender,
|
private val uiMessageSender: UiMessageSender,
|
||||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||||
|
private val paymentAccountStatusFetcher: PaymentAccountStatusFetcher,
|
||||||
) : BaseWalletClickIntents(), TangemPayIntents {
|
) : BaseWalletClickIntents(), TangemPayIntents {
|
||||||
|
|
||||||
override suspend fun onPullToRefresh() {
|
override suspend fun onPullToRefresh() {
|
||||||
|
|
@ -85,20 +86,28 @@ internal class TangemPayClickIntentsImplementor @Inject constructor(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId)
|
tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId)
|
||||||
|
paymentAccountStatusFetcher.invoke(PaymentAccountStatusFetcher.Params(userWalletId))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRefreshPayToken(userWallet: UserWallet) {
|
override fun onRefreshPayToken(userWallet: UserWallet) {
|
||||||
stateHolder.update(TangemPayRefreshShowProgressTransformer(userWallet.walletId))
|
stateHolder.update(
|
||||||
|
TangemPayRefreshShowProgressTransformer(
|
||||||
|
userWalletId = userWallet.walletId,
|
||||||
|
shouldShowProgress = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
produceInitialDataTangemPay.invoke(userWallet.walletId)
|
produceInitialDataTangemPay.invoke(userWallet.walletId)
|
||||||
.onRight { tangemPayMainScreenCustomerInfoUseCase.fetch(userWallet.walletId) }
|
.onRight {
|
||||||
|
tangemPayMainScreenCustomerInfoUseCase.fetch(userWallet.walletId)
|
||||||
|
paymentAccountStatusFetcher.invoke(PaymentAccountStatusFetcher.Params(userWallet.walletId))
|
||||||
|
}
|
||||||
.onLeft {
|
.onLeft {
|
||||||
stateHolder.update(
|
stateHolder.update(
|
||||||
transformer = TangemPayRefreshNeededStateTransformer(
|
TangemPayRefreshShowProgressTransformer(
|
||||||
userWallet = userWallet,
|
|
||||||
userWalletId = userWallet.walletId,
|
userWalletId = userWallet.walletId,
|
||||||
onRefreshClick = { onRefreshPayToken(userWallet) },
|
shouldShowProgress = false,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -267,7 +276,10 @@ internal class TangemPayClickIntentsImplementor @Inject constructor(
|
||||||
analyticsEventHandler.send(TangemPayAnalyticsEvents.KycCancelled())
|
analyticsEventHandler.send(TangemPayAnalyticsEvents.KycCancelled())
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
tangemPayOnboardingRepository.disableTangemPay(userWalletId)
|
tangemPayOnboardingRepository.disableTangemPay(userWalletId)
|
||||||
.onRight { tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId) }
|
.onRight {
|
||||||
|
tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId)
|
||||||
|
paymentAccountStatusFetcher.invoke(PaymentAccountStatusFetcher.Params(userWalletId))
|
||||||
|
}
|
||||||
.onLeft { uiMessageSender.send(ToastMessage(resourceReference(R.string.common_something_went_wrong))) }
|
.onLeft { uiMessageSender.send(ToastMessage(resourceReference(R.string.common_something_went_wrong))) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import com.tangem.feature.wallet.child.wallet.model.WalletActivationBannerType
|
||||||
import com.tangem.feature.wallet.impl.R
|
import com.tangem.feature.wallet.impl.R
|
||||||
import com.tangem.feature.wallet.presentation.common.WalletPreviewDataLegacy.topBarConfig
|
import com.tangem.feature.wallet.presentation.common.WalletPreviewDataLegacy.topBarConfig
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.model.*
|
import com.tangem.feature.wallet.presentation.wallet.state.model.*
|
||||||
|
import com.tangem.features.tangempay.entity.TangemPayMainUM
|
||||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toPersistentList
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
|
|
@ -217,6 +218,8 @@ internal object WalletScreenPreviewDataLegacy {
|
||||||
onClick = {},
|
onClick = {},
|
||||||
),
|
),
|
||||||
type = WalletType.Cold,
|
type = WalletType.Cold,
|
||||||
|
tangemPayMainUM = TangemPayMainUM.Empty,
|
||||||
|
isTangemPayRefactorEnabled = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,7 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
|
||||||
is WalletNotification.Warning.TangemPayUnreachable -> null
|
is WalletNotification.Warning.TangemPayUnreachable -> null
|
||||||
is WalletNotification.UpgradeHotWalletPromo -> null
|
is WalletNotification.UpgradeHotWalletPromo -> null
|
||||||
is WalletNotification.TokenSyncCompleted -> null
|
is WalletNotification.TokenSyncCompleted -> null
|
||||||
|
is WalletNotification.CreateTangemPayAccount -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@ import com.tangem.common.ui.userwallet.ext.walletInterationIcon
|
||||||
import com.tangem.core.decompose.di.ModelScoped
|
import com.tangem.core.decompose.di.ModelScoped
|
||||||
import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState
|
import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState
|
||||||
import com.tangem.core.ui.extensions.resourceReference
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
import com.tangem.domain.account.models.AccountStatusList
|
||||||
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||||
import com.tangem.domain.card.CardTypesResolver
|
import com.tangem.domain.card.CardTypesResolver
|
||||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||||
import com.tangem.domain.core.lce.Lce
|
|
||||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||||
import com.tangem.domain.hotwallet.CheckHotWalletUpgradeBannerUseCase
|
import com.tangem.domain.hotwallet.CheckHotWalletUpgradeBannerUseCase
|
||||||
import com.tangem.domain.hotwallet.GetAccessCodeSkippedUseCase
|
import com.tangem.domain.hotwallet.GetAccessCodeSkippedUseCase
|
||||||
|
|
@ -18,6 +18,8 @@ import com.tangem.domain.hotwallet.GetUpgradeBannerClosureTimestampUseCase
|
||||||
import com.tangem.domain.hotwallet.ShouldShowUpgradeHotWalletBannerUseCase
|
import com.tangem.domain.hotwallet.ShouldShowUpgradeHotWalletBannerUseCase
|
||||||
import com.tangem.domain.models.StatusSource
|
import com.tangem.domain.models.StatusSource
|
||||||
import com.tangem.domain.models.TotalFiatBalance
|
import com.tangem.domain.models.TotalFiatBalance
|
||||||
|
import com.tangem.domain.models.account.AccountStatus
|
||||||
|
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.models.wallet.UserWallet
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
|
|
@ -25,7 +27,6 @@ import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||||
import com.tangem.domain.promo.ShouldShowPromoWalletUseCase
|
import com.tangem.domain.promo.ShouldShowPromoWalletUseCase
|
||||||
import com.tangem.domain.promo.models.PromoId
|
import com.tangem.domain.promo.models.PromoId
|
||||||
import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
|
import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
|
||||||
import com.tangem.domain.tokens.error.TokenListError
|
|
||||||
import com.tangem.domain.wallets.models.SeedPhraseNotificationsStatus
|
import com.tangem.domain.wallets.models.SeedPhraseNotificationsStatus
|
||||||
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
|
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
|
||||||
import com.tangem.domain.wallets.usecase.SeedPhraseNotificationUseCase
|
import com.tangem.domain.wallets.usecase.SeedPhraseNotificationUseCase
|
||||||
|
|
@ -45,7 +46,6 @@ import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@Deprecated("Remove with main toggle [DesignFeatureToggles.isRedesignEnabled]")
|
@Deprecated("Remove with main toggle [DesignFeatureToggles.isRedesignEnabled]")
|
||||||
|
|
@ -69,13 +69,8 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
||||||
@Suppress("UNCHECKED_CAST", "MagicNumber", "LongMethod", "CastNullableToNonNullableType")
|
@Suppress("UNCHECKED_CAST", "MagicNumber", "LongMethod", "CastNullableToNonNullableType")
|
||||||
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow<ImmutableList<WalletNotification>> {
|
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow<ImmutableList<WalletNotification>> {
|
||||||
val cardTypesResolver = (userWallet as? UserWallet.Cold)?.scanResponse?.cardTypesResolver
|
val cardTypesResolver = (userWallet as? UserWallet.Cold)?.scanResponse?.cardTypesResolver
|
||||||
|
val params = SingleAccountStatusListProducer.Params(userWallet.walletId)
|
||||||
val accountStatusListFlow by lazy {
|
val accountStatusListFlow = accountDependencies.singleAccountStatusListSupplier(params)
|
||||||
val params = SingleAccountStatusListProducer.Params(userWallet.walletId)
|
|
||||||
accountDependencies.singleAccountStatusListSupplier(params)
|
|
||||||
.map { it.totalFiatBalance to it.flattenCurrencies() }
|
|
||||||
.map { Lce.Content(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
return combine(
|
return combine(
|
||||||
accountStatusListFlow,
|
accountStatusListFlow,
|
||||||
|
|
@ -95,9 +90,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
||||||
.distinctUntilChanged(),
|
.distinctUntilChanged(),
|
||||||
) { array -> array }
|
) { array -> array }
|
||||||
.map { array ->
|
.map { array ->
|
||||||
val lceTokens = array[0] as Lce<TokenListError, Pair<TotalFiatBalance, List<CryptoCurrencyStatus>>>
|
val accountStatusList = array[0] as AccountStatusList
|
||||||
val totalFiatBalance = lceTokens.map { it.first }
|
|
||||||
val flattenCurrencies = lceTokens.map { it.second }
|
|
||||||
val isReadyToShowRating = array[1] as Boolean
|
val isReadyToShowRating = array[1] as Boolean
|
||||||
val isNeedToBackup = array[2] as Boolean
|
val isNeedToBackup = array[2] as Boolean
|
||||||
val seedPhraseIssueStatus = array[3] as SeedPhraseNotificationsStatus
|
val seedPhraseIssueStatus = array[3] as SeedPhraseNotificationsStatus
|
||||||
|
|
@ -108,8 +101,13 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
||||||
val shouldShowUpgradeBanner = array[8] as Boolean
|
val shouldShowUpgradeBanner = array[8] as Boolean
|
||||||
val closureTimestamp = array[9] as? Long
|
val closureTimestamp = array[9] as? Long
|
||||||
|
|
||||||
|
val flattenCurrencies = accountStatusList.flattenCurrencies()
|
||||||
|
val paymentAccountStatus = accountStatusList.accountStatuses
|
||||||
|
.filterIsInstance<AccountStatus.Payment>()
|
||||||
|
.firstOrNull()
|
||||||
|
|
||||||
buildList {
|
buildList {
|
||||||
addUsedOutdatedDataNotification(totalFiatBalance)
|
addUsedOutdatedDataNotification(accountStatusList.totalFiatBalance)
|
||||||
|
|
||||||
addCriticalNotifications(userWallet, seedPhraseIssueStatus, clickIntents)
|
addCriticalNotifications(userWallet, seedPhraseIssueStatus, clickIntents)
|
||||||
|
|
||||||
|
|
@ -162,24 +160,54 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
||||||
if (!hasCriticalOrWarning) {
|
if (!hasCriticalOrWarning) {
|
||||||
addRateTheAppNotification(isReadyToShowRating, clickIntents)
|
addRateTheAppNotification(isReadyToShowRating, clickIntents)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add as last warning
|
||||||
|
paymentAccountStatus?.let { paymentAccountStatus ->
|
||||||
|
addTangemPayWarnings(
|
||||||
|
status = paymentAccountStatus,
|
||||||
|
userWallet = userWallet,
|
||||||
|
walletClickIntents = clickIntents,
|
||||||
|
)
|
||||||
|
}
|
||||||
}.toImmutableList()
|
}.toImmutableList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MutableList<WalletNotification>.addUsedOutdatedDataNotification(
|
private fun MutableList<WalletNotification>.addTangemPayWarnings(
|
||||||
totalFiatBalance: Lce<TokenListError, TotalFiatBalance>,
|
status: AccountStatus.Payment,
|
||||||
|
userWallet: UserWallet,
|
||||||
|
walletClickIntents: WalletClickIntents,
|
||||||
) {
|
) {
|
||||||
|
val notification = when (status.value) {
|
||||||
|
is PaymentAccountStatusValue.Error.NotSynced -> WalletNotification.Warning.TangemPayRefreshNeeded(
|
||||||
|
buttonText = when (userWallet) {
|
||||||
|
is UserWallet.Cold -> resourceReference(id = R.string.home_button_scan)
|
||||||
|
is UserWallet.Hot -> resourceReference(id = R.string.tangempay_sync_needed_restore_access)
|
||||||
|
},
|
||||||
|
onRefreshClick = { walletClickIntents.onRefreshPayToken(userWallet) },
|
||||||
|
shouldShowProgress = false,
|
||||||
|
)
|
||||||
|
is PaymentAccountStatusValue.NotCreated -> WalletNotification.CreateTangemPayAccount(
|
||||||
|
onClick = { walletClickIntents.onOnboardingBannerClick(userWallet.walletId) },
|
||||||
|
onCloseClick = { walletClickIntents.onOnboardingBannerCloseClick(userWallet.walletId) },
|
||||||
|
)
|
||||||
|
is PaymentAccountStatusValue.Error.Unavailable -> WalletNotification.Warning.TangemPayUnreachable
|
||||||
|
is PaymentAccountStatusValue.Error.CardIssueFailed,
|
||||||
|
is PaymentAccountStatusValue.Error.ExposedDevice,
|
||||||
|
is PaymentAccountStatusValue.IssuingCard,
|
||||||
|
is PaymentAccountStatusValue.Loaded,
|
||||||
|
is PaymentAccountStatusValue.Loading,
|
||||||
|
is PaymentAccountStatusValue.Locked,
|
||||||
|
is PaymentAccountStatusValue.UnderReview,
|
||||||
|
-> null
|
||||||
|
}
|
||||||
|
notification?.let(::add)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MutableList<WalletNotification>.addUsedOutdatedDataNotification(totalFiatBalance: TotalFiatBalance) {
|
||||||
addIf(
|
addIf(
|
||||||
element = WalletNotification.UsedOutdatedData,
|
element = WalletNotification.UsedOutdatedData,
|
||||||
condition = totalFiatBalance.fold(
|
condition = (totalFiatBalance as? TotalFiatBalance.Loaded)?.source == StatusSource.ONLY_CACHE,
|
||||||
ifLoading = {
|
|
||||||
(it as? TotalFiatBalance.Loaded)?.source == StatusSource.ONLY_CACHE
|
|
||||||
},
|
|
||||||
ifContent = {
|
|
||||||
(it as? TotalFiatBalance.Loaded)?.source == StatusSource.ONLY_CACHE
|
|
||||||
},
|
|
||||||
ifError = { false },
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -254,7 +282,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
||||||
private fun MutableList<WalletNotification>.addInformationalNotifications(
|
private fun MutableList<WalletNotification>.addInformationalNotifications(
|
||||||
userWallet: UserWallet,
|
userWallet: UserWallet,
|
||||||
cardTypesResolver: CardTypesResolver?,
|
cardTypesResolver: CardTypesResolver?,
|
||||||
flattenCurrencies: Lce<TokenListError, List<CryptoCurrencyStatus>>,
|
flattenCurrencies: List<CryptoCurrencyStatus>,
|
||||||
clickIntents: WalletClickIntents,
|
clickIntents: WalletClickIntents,
|
||||||
) {
|
) {
|
||||||
addIf(
|
addIf(
|
||||||
|
|
@ -267,7 +295,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
||||||
|
|
||||||
private fun MutableList<WalletNotification>.addMissingAddressesNotification(
|
private fun MutableList<WalletNotification>.addMissingAddressesNotification(
|
||||||
userWallet: UserWallet,
|
userWallet: UserWallet,
|
||||||
flattenCurrencies: Lce<TokenListError, List<CryptoCurrencyStatus>>,
|
flattenCurrencies: List<CryptoCurrencyStatus>,
|
||||||
clickIntents: WalletClickIntents,
|
clickIntents: WalletClickIntents,
|
||||||
) {
|
) {
|
||||||
val currencies = flattenCurrencies.getMissingAddressCurrencies()
|
val currencies = flattenCurrencies.getMissingAddressCurrencies()
|
||||||
|
|
@ -285,10 +313,8 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Lce<TokenListError, List<CryptoCurrencyStatus>>.getMissingAddressCurrencies(): List<CryptoCurrency> {
|
private fun List<CryptoCurrencyStatus>.getMissingAddressCurrencies(): List<CryptoCurrency> {
|
||||||
val flattenCurrencies = getOrNull(isPartialContentAccepted = true) ?: return emptyList()
|
return this
|
||||||
|
|
||||||
return flattenCurrencies
|
|
||||||
.filter { it.value is CryptoCurrencyStatus.MissedDerivation }
|
.filter { it.value is CryptoCurrencyStatus.MissedDerivation }
|
||||||
.map(CryptoCurrencyStatus::currency)
|
.map(CryptoCurrencyStatus::currency)
|
||||||
}
|
}
|
||||||
|
|
@ -330,7 +356,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
||||||
|
|
||||||
private fun MutableList<WalletNotification>.addWarningNotifications(
|
private fun MutableList<WalletNotification>.addWarningNotifications(
|
||||||
cardTypesResolver: CardTypesResolver?,
|
cardTypesResolver: CardTypesResolver?,
|
||||||
flattenCurrencies: Lce<TokenListError, List<CryptoCurrencyStatus>>,
|
flattenCurrencies: List<CryptoCurrencyStatus>,
|
||||||
isNeedToBackup: Boolean,
|
isNeedToBackup: Boolean,
|
||||||
clickIntents: WalletClickIntents,
|
clickIntents: WalletClickIntents,
|
||||||
) {
|
) {
|
||||||
|
|
@ -355,7 +381,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MutableList<WalletNotification>.addCloreMigrationNotification(
|
private fun MutableList<WalletNotification>.addCloreMigrationNotification(
|
||||||
flattenCurrencies: Lce<TokenListError, List<CryptoCurrencyStatus>>,
|
flattenCurrencies: List<CryptoCurrencyStatus>,
|
||||||
clickIntents: WalletClickIntents,
|
clickIntents: WalletClickIntents,
|
||||||
) {
|
) {
|
||||||
val cloreCurrency = flattenCurrencies.findCloreCurrency() ?: return
|
val cloreCurrency = flattenCurrencies.findCloreCurrency() ?: return
|
||||||
|
|
@ -367,10 +393,8 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Lce<TokenListError, List<CryptoCurrencyStatus>>.findCloreCurrency(): CryptoCurrencyStatus? {
|
private fun List<CryptoCurrencyStatus>.findCloreCurrency(): CryptoCurrencyStatus? {
|
||||||
val currencies = getOrNull(isPartialContentAccepted = true) ?: return null
|
return this.find { currencyStatus ->
|
||||||
|
|
||||||
return currencies.find { currencyStatus ->
|
|
||||||
BlockchainUtils.isClore(currencyStatus.currency.network.rawId)
|
BlockchainUtils.isClore(currencyStatus.currency.network.rawId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -388,10 +412,8 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Lce<TokenListError, List<CryptoCurrencyStatus>>.hasUnreachableNetworks(): Boolean {
|
private fun List<CryptoCurrencyStatus>.hasUnreachableNetworks(): Boolean {
|
||||||
val flattenCurrencies = getOrNull(isPartialContentAccepted = false) ?: return false
|
return this.any { it.value is CryptoCurrencyStatus.Unreachable }
|
||||||
|
|
||||||
return flattenCurrencies.any { it.value is CryptoCurrencyStatus.Unreachable }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove in first iteration of yield supply feature
|
// Remove in first iteration of yield supply feature
|
||||||
|
|
@ -427,7 +449,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
||||||
|
|
||||||
private fun MutableList<WalletNotification>.addFinishWalletActivationNotification(
|
private fun MutableList<WalletNotification>.addFinishWalletActivationNotification(
|
||||||
userWallet: UserWallet,
|
userWallet: UserWallet,
|
||||||
flattenCurrencies: Lce<TokenListError, List<CryptoCurrencyStatus>>,
|
flattenCurrencies: List<CryptoCurrencyStatus>,
|
||||||
clickIntents: WalletClickIntents,
|
clickIntents: WalletClickIntents,
|
||||||
shouldAccessCodeSkipped: Boolean,
|
shouldAccessCodeSkipped: Boolean,
|
||||||
) {
|
) {
|
||||||
|
|
@ -437,12 +459,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
||||||
val isAccessCodeRequired = userWallet.hotWalletId.authType == HotWalletId.AuthType.NoPassword &&
|
val isAccessCodeRequired = userWallet.hotWalletId.authType == HotWalletId.AuthType.NoPassword &&
|
||||||
!shouldAccessCodeSkipped
|
!shouldAccessCodeSkipped
|
||||||
val shouldShowFinishActivation = !isBackupExists || isAccessCodeRequired
|
val shouldShowFinishActivation = !isBackupExists || isAccessCodeRequired
|
||||||
|
val type = flattenCurrencies.getFinishWalletActivationType()
|
||||||
val type = flattenCurrencies.fold(
|
|
||||||
ifLoading = { return },
|
|
||||||
ifContent = { it.getFinishWalletActivationType() },
|
|
||||||
ifError = { WalletActivationBannerType.Attention },
|
|
||||||
)
|
|
||||||
|
|
||||||
addIf(
|
addIf(
|
||||||
element = WalletNotification.FinishWalletActivation(
|
element = WalletNotification.FinishWalletActivation(
|
||||||
|
|
@ -465,15 +482,14 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
||||||
|
|
||||||
private suspend fun MutableList<WalletNotification>.addUpgradeHotWalletPromoNotification(
|
private suspend fun MutableList<WalletNotification>.addUpgradeHotWalletPromoNotification(
|
||||||
userWallet: UserWallet,
|
userWallet: UserWallet,
|
||||||
flattenCurrencies: Lce<TokenListError, List<CryptoCurrencyStatus>>,
|
flattenCurrencies: List<CryptoCurrencyStatus>,
|
||||||
clickIntents: WalletClickIntents,
|
clickIntents: WalletClickIntents,
|
||||||
shouldShowUpgradeBanner: Boolean,
|
shouldShowUpgradeBanner: Boolean,
|
||||||
closureTimestamp: Long?,
|
closureTimestamp: Long?,
|
||||||
) {
|
) {
|
||||||
if (userWallet !is UserWallet.Hot) return
|
if (userWallet !is UserWallet.Hot) return
|
||||||
|
|
||||||
val currencies = flattenCurrencies.getOrNull(isPartialContentAccepted = true).orEmpty()
|
val hasBalance = flattenCurrencies.any { it.value.amount.orZero().isPositive() }
|
||||||
val hasBalance = currencies.any { it.value.amount.orZero().isPositive() }
|
|
||||||
|
|
||||||
val shouldShow = checkHotWalletUpgradeBannerUseCase(
|
val shouldShow = checkHotWalletUpgradeBannerUseCase(
|
||||||
walletId = userWallet.walletId,
|
walletId = userWallet.walletId,
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,6 @@ sealed class WalletNotification(val config: NotificationConfig) {
|
||||||
)
|
)
|
||||||
|
|
||||||
data class TangemPayRefreshNeeded(
|
data class TangemPayRefreshNeeded(
|
||||||
@DrawableRes private val tangemIcon: Int?,
|
|
||||||
private val onRefreshClick: () -> Unit,
|
private val onRefreshClick: () -> Unit,
|
||||||
private val buttonText: TextReference,
|
private val buttonText: TextReference,
|
||||||
private val shouldShowProgress: Boolean,
|
private val shouldShowProgress: Boolean,
|
||||||
|
|
@ -160,7 +159,7 @@ sealed class WalletNotification(val config: NotificationConfig) {
|
||||||
subtitle = resourceReference(id = R.string.tangempay_use_tangem_device_to_restore_payment_account),
|
subtitle = resourceReference(id = R.string.tangempay_use_tangem_device_to_restore_payment_account),
|
||||||
buttonsState = ButtonsState.PrimaryButtonConfig(
|
buttonsState = ButtonsState.PrimaryButtonConfig(
|
||||||
text = buttonText,
|
text = buttonText,
|
||||||
iconResId = tangemIcon,
|
iconResId = R.drawable.ic_tangem_24,
|
||||||
onClick = onRefreshClick,
|
onClick = onRefreshClick,
|
||||||
shouldShowProgress = shouldShowProgress,
|
shouldShowProgress = shouldShowProgress,
|
||||||
),
|
),
|
||||||
|
|
@ -480,4 +479,11 @@ sealed class WalletNotification(val config: NotificationConfig) {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class CreateTangemPayAccount(val onClick: () -> Unit, val onCloseClick: () -> Unit) : WalletNotification(
|
||||||
|
config = NotificationConfig(
|
||||||
|
subtitle = resourceReference(R.string.tangempay_onboarding_banner_description),
|
||||||
|
iconResId = R.drawable.img_tangem_pay_visa_banner,
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.holder.LockedTx
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.model.holder.LockedWalletStateHolder
|
import com.tangem.feature.wallet.presentation.wallet.state.model.holder.LockedWalletStateHolder
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.model.holder.TxHistoryStateHolder
|
import com.tangem.feature.wallet.presentation.wallet.state.model.holder.TxHistoryStateHolder
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.model.holder.WalletStateHolder
|
import com.tangem.feature.wallet.presentation.wallet.state.model.holder.WalletStateHolder
|
||||||
|
import com.tangem.features.tangempay.entity.TangemPayMainUM
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.PersistentList
|
import kotlinx.collections.immutable.PersistentList
|
||||||
|
|
||||||
|
|
@ -24,6 +25,8 @@ internal sealed interface WalletState : WalletStateHolder {
|
||||||
abstract val nftState: WalletNFTItemUM
|
abstract val nftState: WalletNFTItemUM
|
||||||
abstract val type: WalletType
|
abstract val type: WalletType
|
||||||
abstract val tangemPayState: TangemPayState
|
abstract val tangemPayState: TangemPayState
|
||||||
|
abstract val tangemPayMainUM: TangemPayMainUM
|
||||||
|
abstract val isTangemPayRefactorEnabled: Boolean // TANGEM_PAY_ACCOUNTS_REFACTOR_ENABLED
|
||||||
|
|
||||||
data class Content(
|
data class Content(
|
||||||
override val pullToRefreshConfig: PullToRefreshConfig,
|
override val pullToRefreshConfig: PullToRefreshConfig,
|
||||||
|
|
@ -35,6 +38,8 @@ internal sealed interface WalletState : WalletStateHolder {
|
||||||
override val nftState: WalletNFTItemUM,
|
override val nftState: WalletNFTItemUM,
|
||||||
override val type: WalletType,
|
override val type: WalletType,
|
||||||
override val tangemPayState: TangemPayState,
|
override val tangemPayState: TangemPayState,
|
||||||
|
override val tangemPayMainUM: TangemPayMainUM,
|
||||||
|
override val isTangemPayRefactorEnabled: Boolean,
|
||||||
) : MultiCurrency()
|
) : MultiCurrency()
|
||||||
|
|
||||||
data class Locked(
|
data class Locked(
|
||||||
|
|
@ -54,6 +59,8 @@ internal sealed interface WalletState : WalletStateHolder {
|
||||||
override val tokensListState = WalletTokensListState.ContentState.Locked
|
override val tokensListState = WalletTokensListState.ContentState.Locked
|
||||||
override val nftState: WalletNFTItemUM = WalletNFTItemUM.Hidden
|
override val nftState: WalletNFTItemUM = WalletNFTItemUM.Hidden
|
||||||
override val tangemPayState: TangemPayState = TangemPayState.Empty
|
override val tangemPayState: TangemPayState = TangemPayState.Empty
|
||||||
|
override val tangemPayMainUM: TangemPayMainUM = TangemPayMainUM.Empty
|
||||||
|
override val isTangemPayRefactorEnabled: Boolean = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ internal class AddWalletTransformer(
|
||||||
private val clickIntents: WalletClickIntents,
|
private val clickIntents: WalletClickIntents,
|
||||||
private val walletImageResolver: WalletImageResolver,
|
private val walletImageResolver: WalletImageResolver,
|
||||||
private val getWalletIconUseCase: GetWalletIconUseCase,
|
private val getWalletIconUseCase: GetWalletIconUseCase,
|
||||||
|
private val isTangemPayRefactorEnabled: Boolean,
|
||||||
) : WalletScreenStateTransformer {
|
) : WalletScreenStateTransformer {
|
||||||
|
|
||||||
private val walletLoadingStateFactory by lazy {
|
private val walletLoadingStateFactory by lazy {
|
||||||
|
|
@ -20,6 +21,7 @@ internal class AddWalletTransformer(
|
||||||
clickIntents = clickIntents,
|
clickIntents = clickIntents,
|
||||||
walletImageResolver = walletImageResolver,
|
walletImageResolver = walletImageResolver,
|
||||||
getWalletIconUseCase = getWalletIconUseCase,
|
getWalletIconUseCase = getWalletIconUseCase,
|
||||||
|
isTangemPayRefactorEnabled = isTangemPayRefactorEnabled,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ internal class InitializeWalletsTransformer(
|
||||||
private val walletImageResolver: WalletImageResolver,
|
private val walletImageResolver: WalletImageResolver,
|
||||||
private val getWalletIconUseCase: GetWalletIconUseCase,
|
private val getWalletIconUseCase: GetWalletIconUseCase,
|
||||||
private val isMainScreenQrScanningEnabled: Boolean = false,
|
private val isMainScreenQrScanningEnabled: Boolean = false,
|
||||||
|
private val isTangemPayRefactorEnabled: Boolean,
|
||||||
) : WalletScreenStateTransformer {
|
) : WalletScreenStateTransformer {
|
||||||
|
|
||||||
private val walletLoadingStateFactory by lazy {
|
private val walletLoadingStateFactory by lazy {
|
||||||
|
|
@ -35,6 +36,7 @@ internal class InitializeWalletsTransformer(
|
||||||
clickIntents = clickIntents,
|
clickIntents = clickIntents,
|
||||||
walletImageResolver = walletImageResolver,
|
walletImageResolver = walletImageResolver,
|
||||||
getWalletIconUseCase = getWalletIconUseCase,
|
getWalletIconUseCase = getWalletIconUseCase,
|
||||||
|
isTangemPayRefactorEnabled = isTangemPayRefactorEnabled,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ internal class ReinitializeNewWalletTransformer(
|
||||||
private val clickIntents: WalletClickIntents,
|
private val clickIntents: WalletClickIntents,
|
||||||
private val walletImageResolver: WalletImageResolver,
|
private val walletImageResolver: WalletImageResolver,
|
||||||
private val getWalletIconUseCase: GetWalletIconUseCase,
|
private val getWalletIconUseCase: GetWalletIconUseCase,
|
||||||
|
private val isTangemPayRefactorEnabled: Boolean,
|
||||||
) : WalletScreenStateTransformer {
|
) : WalletScreenStateTransformer {
|
||||||
|
|
||||||
private val walletLoadingStateFactory by lazy {
|
private val walletLoadingStateFactory by lazy {
|
||||||
|
|
@ -31,6 +32,7 @@ internal class ReinitializeNewWalletTransformer(
|
||||||
clickIntents = clickIntents,
|
clickIntents = clickIntents,
|
||||||
walletImageResolver = walletImageResolver,
|
walletImageResolver = walletImageResolver,
|
||||||
getWalletIconUseCase = getWalletIconUseCase,
|
getWalletIconUseCase = getWalletIconUseCase,
|
||||||
|
isTangemPayRefactorEnabled = isTangemPayRefactorEnabled,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ internal class ReinitializeWalletTransformer(
|
||||||
private val clickIntents: WalletClickIntents,
|
private val clickIntents: WalletClickIntents,
|
||||||
private val walletImageResolver: WalletImageResolver,
|
private val walletImageResolver: WalletImageResolver,
|
||||||
private val getWalletIconUseCase: GetWalletIconUseCase,
|
private val getWalletIconUseCase: GetWalletIconUseCase,
|
||||||
|
private val isTangemPayRefactorEnabled: Boolean,
|
||||||
) : WalletStateTransformer(userWalletId = userWallet.walletId) {
|
) : WalletStateTransformer(userWalletId = userWallet.walletId) {
|
||||||
|
|
||||||
private val walletLoadingStateFactory by lazy {
|
private val walletLoadingStateFactory by lazy {
|
||||||
|
|
@ -27,6 +28,7 @@ internal class ReinitializeWalletTransformer(
|
||||||
clickIntents = clickIntents,
|
clickIntents = clickIntents,
|
||||||
walletImageResolver = walletImageResolver,
|
walletImageResolver = walletImageResolver,
|
||||||
getWalletIconUseCase = getWalletIconUseCase,
|
getWalletIconUseCase = getWalletIconUseCase,
|
||||||
|
isTangemPayRefactorEnabled = isTangemPayRefactorEnabled,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
||||||
|
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
|
import com.tangem.domain.models.account.AccountStatus
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.wallet.UserWallet
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
import com.tangem.domain.staking.model.StakingAvailability
|
import com.tangem.domain.staking.model.StakingAvailability
|
||||||
|
|
@ -8,10 +9,12 @@ import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.model.*
|
import com.tangem.feature.wallet.presentation.wallet.state.model.*
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletBalanceUMTransformer
|
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletBalanceUMTransformer
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletCardStateConverter
|
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletCardStateConverter
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TangemPayMainBlockConverter
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TokenListStateConverter
|
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TokenListStateConverter
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.WalletTokensListUMConverter
|
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.WalletTokensListUMConverter
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.utils.enableButtons
|
import com.tangem.feature.wallet.presentation.wallet.state.utils.enableButtons
|
||||||
import com.tangem.utils.logging.TangemLogger
|
import com.tangem.utils.logging.TangemLogger
|
||||||
|
import com.tangem.features.tangempay.entity.TangemPayMainUM
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
internal class SetTokenListTransformer(
|
internal class SetTokenListTransformer(
|
||||||
|
|
@ -25,12 +28,17 @@ internal class SetTokenListTransformer(
|
||||||
private val isAccountsModeEnabled: Boolean,
|
private val isAccountsModeEnabled: Boolean,
|
||||||
) : WalletStateTransformer(userWallet.walletId) {
|
) : WalletStateTransformer(userWallet.walletId) {
|
||||||
|
|
||||||
|
private val tangemPayConverter by lazy {
|
||||||
|
TangemPayMainBlockConverter(tangemPayClickIntents = clickIntents)
|
||||||
|
}
|
||||||
|
|
||||||
override fun transform(prevState: WalletState): WalletState {
|
override fun transform(prevState: WalletState): WalletState {
|
||||||
return when (prevState) {
|
return when (prevState) {
|
||||||
is WalletState.MultiCurrency.Content -> {
|
is WalletState.MultiCurrency.Content -> {
|
||||||
prevState.copy(
|
prevState.copy(
|
||||||
walletCardState = prevState.walletCardState.toLoadedState(),
|
walletCardState = prevState.walletCardState.toLoadedState(),
|
||||||
tokensListState = prevState.tokensListState.toLoadedState(),
|
tokensListState = prevState.tokensListState.toLoadedState(),
|
||||||
|
tangemPayMainUM = prevState.tangemPayMainUM.toLoadedState(),
|
||||||
buttons = prevState.enableButtons(),
|
buttons = prevState.enableButtons(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -97,6 +105,17 @@ internal class SetTokenListTransformer(
|
||||||
).convert(value = this)
|
).convert(value = this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun TangemPayMainUM.toLoadedState(): TangemPayMainUM {
|
||||||
|
val paymentAccountStatus = when (params) {
|
||||||
|
is TokenConverterParams.Account -> params.accountList.accountStatuses
|
||||||
|
.filterIsInstance<AccountStatus.Payment>()
|
||||||
|
.firstOrNull()
|
||||||
|
is TokenConverterParams.Wallet -> null
|
||||||
|
} ?: return this
|
||||||
|
|
||||||
|
return tangemPayConverter.convert(paymentAccountStatus)
|
||||||
|
}
|
||||||
|
|
||||||
private fun toLoadedState(): WalletTokensListUM {
|
private fun toLoadedState(): WalletTokensListUM {
|
||||||
if (params !is TokenConverterParams.Account) {
|
if (params !is TokenConverterParams.Account) {
|
||||||
return WalletTokensListUM.Empty(
|
return WalletTokensListUM.Empty(
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ internal class TangemPayRefreshNeededStateTransformer(
|
||||||
override fun transform(prevState: WalletState): WalletState {
|
override fun transform(prevState: WalletState): WalletState {
|
||||||
val tangemPayState = TangemPayState.RefreshNeeded(
|
val tangemPayState = TangemPayState.RefreshNeeded(
|
||||||
notification = TangemPayRefreshNeeded(
|
notification = TangemPayRefreshNeeded(
|
||||||
tangemIcon = R.drawable.ic_tangem_24,
|
|
||||||
buttonText = when (userWallet) {
|
buttonText = when (userWallet) {
|
||||||
is UserWallet.Cold -> resourceReference(id = R.string.home_button_scan)
|
is UserWallet.Cold -> resourceReference(id = R.string.home_button_scan)
|
||||||
is UserWallet.Hot -> resourceReference(id = R.string.tangempay_sync_needed_restore_access)
|
is UserWallet.Hot -> resourceReference(id = R.string.tangempay_sync_needed_restore_access)
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,11 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
|
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
|
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
internal class TangemPayRefreshShowProgressTransformer(
|
internal class TangemPayRefreshShowProgressTransformer(
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
|
private val shouldShowProgress: Boolean,
|
||||||
) : WalletStateTransformer(userWalletId) {
|
) : WalletStateTransformer(userWalletId) {
|
||||||
|
|
||||||
override fun transform(prevState: WalletState): WalletState {
|
override fun transform(prevState: WalletState): WalletState {
|
||||||
|
|
@ -15,11 +17,19 @@ internal class TangemPayRefreshShowProgressTransformer(
|
||||||
val refreshNeededState = multiContentState.tangemPayState as? TangemPayState.RefreshNeeded ?: return prevState
|
val refreshNeededState = multiContentState.tangemPayState as? TangemPayState.RefreshNeeded ?: return prevState
|
||||||
val refreshNotification =
|
val refreshNotification =
|
||||||
refreshNeededState.notification as? WalletNotification.Warning.TangemPayRefreshNeeded ?: return prevState
|
refreshNeededState.notification as? WalletNotification.Warning.TangemPayRefreshNeeded ?: return prevState
|
||||||
|
val newWarnings = prevState.warnings.map { warning ->
|
||||||
|
if (warning is WalletNotification.Warning.TangemPayRefreshNeeded) {
|
||||||
|
warning.copy(shouldShowProgress = shouldShowProgress)
|
||||||
|
} else {
|
||||||
|
warning
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return multiContentState.copy(
|
return multiContentState.copy(
|
||||||
tangemPayState = refreshNeededState.copy(
|
tangemPayState = refreshNeededState.copy(
|
||||||
notification = refreshNotification.copy(shouldShowProgress = true),
|
notification = refreshNotification.copy(shouldShowProgress = shouldShowProgress),
|
||||||
),
|
),
|
||||||
|
warnings = newWarnings.toImmutableList(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ internal class UnlockWalletTransformer(
|
||||||
private val clickIntents: WalletClickIntents,
|
private val clickIntents: WalletClickIntents,
|
||||||
private val walletImageResolver: WalletImageResolver,
|
private val walletImageResolver: WalletImageResolver,
|
||||||
private val getWalletIconUseCase: GetWalletIconUseCase,
|
private val getWalletIconUseCase: GetWalletIconUseCase,
|
||||||
|
private val isTangemPayRefactorEnabled: Boolean,
|
||||||
) : WalletScreenStateTransformer {
|
) : WalletScreenStateTransformer {
|
||||||
|
|
||||||
private val walletLoadingStateFactory by lazy {
|
private val walletLoadingStateFactory by lazy {
|
||||||
|
|
@ -25,6 +26,7 @@ internal class UnlockWalletTransformer(
|
||||||
clickIntents = clickIntents,
|
clickIntents = clickIntents,
|
||||||
walletImageResolver = walletImageResolver,
|
walletImageResolver = walletImageResolver,
|
||||||
getWalletIconUseCase = getWalletIconUseCase,
|
getWalletIconUseCase = getWalletIconUseCase,
|
||||||
|
isTangemPayRefactorEnabled = isTangemPayRefactorEnabled,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
|
||||||
|
|
||||||
|
import com.tangem.common.ui.R
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
|
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||||
|
import com.tangem.core.ui.format.bigdecimal.format
|
||||||
|
import com.tangem.domain.models.StatusSource
|
||||||
|
import com.tangem.domain.models.account.AccountStatus
|
||||||
|
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||||
|
import com.tangem.domain.models.kyc.KycStatus
|
||||||
|
import com.tangem.domain.pay.TangemPayDetailsConfig
|
||||||
|
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||||
|
import com.tangem.feature.wallet.child.wallet.model.intents.TangemPayIntents
|
||||||
|
import com.tangem.features.tangempay.entity.TangemPayMainUM
|
||||||
|
import com.tangem.utils.converter.Converter
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.util.Currency
|
||||||
|
|
||||||
|
private const val POLYGON_CHAIN_ID = 137
|
||||||
|
|
||||||
|
internal class TangemPayMainBlockConverter(
|
||||||
|
private val tangemPayClickIntents: TangemPayIntents,
|
||||||
|
) : Converter<AccountStatus.Payment, TangemPayMainUM> {
|
||||||
|
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||||
|
override fun convert(value: AccountStatus.Payment): TangemPayMainUM {
|
||||||
|
return when (val statusValue = value.value) {
|
||||||
|
is PaymentAccountStatusValue.Error.CardIssueFailed -> TangemPayMainUM.FailedToIssue(
|
||||||
|
onClick = { tangemPayClickIntents.onIssuingFailedClicked(statusValue.customerId) },
|
||||||
|
)
|
||||||
|
is PaymentAccountStatusValue.Error.ExposedDevice -> TangemPayMainUM.ExposedDevice
|
||||||
|
is PaymentAccountStatusValue.Error.NotSynced -> TangemPayMainUM.SyncNeeded
|
||||||
|
is PaymentAccountStatusValue.Error.Unavailable -> TangemPayMainUM.TemporaryUnavailable
|
||||||
|
is PaymentAccountStatusValue.IssuingCard -> TangemPayMainUM.IssuingCard(
|
||||||
|
onClick = { tangemPayClickIntents.onIssuingCardClicked() },
|
||||||
|
)
|
||||||
|
is PaymentAccountStatusValue.UnderReview -> TangemPayMainUM.UnderReview(
|
||||||
|
subtitle = when (statusValue.kycStatus) {
|
||||||
|
KycStatus.REJECTED -> TextReference.Res(R.string.tangempay_kyc_has_failed)
|
||||||
|
else -> TextReference.Res(R.string.tangempay_kyc_in_progress)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
when (statusValue.kycStatus) {
|
||||||
|
KycStatus.REJECTED -> tangemPayClickIntents.onKycRejectedClicked(
|
||||||
|
userWalletId = value.account.userWalletId,
|
||||||
|
customerId = statusValue.customerId,
|
||||||
|
)
|
||||||
|
else -> tangemPayClickIntents.onKycProgressClicked(value.account.userWalletId)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
is PaymentAccountStatusValue.NotCreated -> TangemPayMainUM.Empty
|
||||||
|
is PaymentAccountStatusValue.Loading -> TangemPayMainUM.Loading
|
||||||
|
is PaymentAccountStatusValue.Locked -> TangemPayMainUM.Content(
|
||||||
|
subtitle = stringReference("*${statusValue.lastFourDigits}"),
|
||||||
|
isBalanceFlickering = statusValue.source == StatusSource.CACHE,
|
||||||
|
balance = getBalanceText(
|
||||||
|
currencyCode = statusValue.currencyCode,
|
||||||
|
balance = statusValue.fiatBalance.availableBalance,
|
||||||
|
),
|
||||||
|
balanceSubtitle = stringReference("USDC"), // TODO hardcode for now
|
||||||
|
shouldShowOnlyCacheWarning = statusValue.source == StatusSource.ONLY_CACHE,
|
||||||
|
onClick = {
|
||||||
|
tangemPayClickIntents.openDetails(
|
||||||
|
value.account.userWalletId,
|
||||||
|
TangemPayDetailsConfig(
|
||||||
|
customerId = statusValue.customerId,
|
||||||
|
cardId = statusValue.cardId,
|
||||||
|
isPinSet = statusValue.isPinSet,
|
||||||
|
cardFrozenState = TangemPayCardFrozenState.Frozen,
|
||||||
|
cardNumberEnd = statusValue.lastFourDigits,
|
||||||
|
chainId = POLYGON_CHAIN_ID,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
is PaymentAccountStatusValue.Loaded -> TangemPayMainUM.Content(
|
||||||
|
subtitle = stringReference("*${statusValue.lastFourDigits}"),
|
||||||
|
isBalanceFlickering = statusValue.source == StatusSource.CACHE,
|
||||||
|
balance = getBalanceText(
|
||||||
|
currencyCode = statusValue.currencyCode,
|
||||||
|
balance = statusValue.fiatBalance.availableBalance,
|
||||||
|
),
|
||||||
|
balanceSubtitle = stringReference("USDC"), // TODO hardcode for now
|
||||||
|
shouldShowOnlyCacheWarning = statusValue.source == StatusSource.ONLY_CACHE,
|
||||||
|
onClick = {
|
||||||
|
tangemPayClickIntents.openDetails(
|
||||||
|
value.account.userWalletId,
|
||||||
|
TangemPayDetailsConfig(
|
||||||
|
customerId = statusValue.customerId,
|
||||||
|
cardId = statusValue.cardId,
|
||||||
|
isPinSet = statusValue.isPinSet,
|
||||||
|
cardFrozenState = TangemPayCardFrozenState.Unfrozen,
|
||||||
|
cardNumberEnd = statusValue.lastFourDigits,
|
||||||
|
chainId = POLYGON_CHAIN_ID,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getBalanceText(currencyCode: String, balance: BigDecimal): TextReference {
|
||||||
|
val currency = Currency.getInstance(currencyCode)
|
||||||
|
val formattedBalance = balance.format {
|
||||||
|
fiat(fiatCurrencyCode = currency.currencyCode, fiatCurrencySymbol = currency.symbol)
|
||||||
|
}
|
||||||
|
return stringReference(formattedBalance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,7 @@ import com.tangem.feature.wallet.impl.R
|
||||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
|
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
|
||||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
|
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.model.*
|
import com.tangem.feature.wallet.presentation.wallet.state.model.*
|
||||||
|
import com.tangem.features.tangempay.entity.TangemPayMainUM
|
||||||
import com.tangem.utils.extensions.addIf
|
import com.tangem.utils.extensions.addIf
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.PersistentList
|
import kotlinx.collections.immutable.PersistentList
|
||||||
|
|
@ -33,6 +34,7 @@ internal class WalletLoadingStateFactory(
|
||||||
private val clickIntents: WalletClickIntents,
|
private val clickIntents: WalletClickIntents,
|
||||||
private val walletImageResolver: WalletImageResolver,
|
private val walletImageResolver: WalletImageResolver,
|
||||||
private val getWalletIconUseCase: GetWalletIconUseCase,
|
private val getWalletIconUseCase: GetWalletIconUseCase,
|
||||||
|
private val isTangemPayRefactorEnabled: Boolean,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun create(userWallet: UserWallet): WalletState {
|
fun create(userWallet: UserWallet): WalletState {
|
||||||
|
|
@ -82,6 +84,8 @@ internal class WalletLoadingStateFactory(
|
||||||
nftState = WalletNFTItemUM.Hidden,
|
nftState = WalletNFTItemUM.Hidden,
|
||||||
type = WalletType.Hot,
|
type = WalletType.Hot,
|
||||||
tangemPayState = TangemPayState.Empty,
|
tangemPayState = TangemPayState.Empty,
|
||||||
|
tangemPayMainUM = TangemPayMainUM.Empty,
|
||||||
|
isTangemPayRefactorEnabled = isTangemPayRefactorEnabled,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,6 +100,8 @@ internal class WalletLoadingStateFactory(
|
||||||
nftState = WalletNFTItemUM.Hidden,
|
nftState = WalletNFTItemUM.Hidden,
|
||||||
type = WalletType.Cold,
|
type = WalletType.Cold,
|
||||||
tangemPayState = TangemPayState.Empty,
|
tangemPayState = TangemPayState.Empty,
|
||||||
|
tangemPayMainUM = TangemPayMainUM.Empty,
|
||||||
|
isTangemPayRefactorEnabled = isTangemPayRefactorEnabled,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,6 @@ import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheetCo
|
||||||
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheet
|
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheet
|
||||||
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
|
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
|
||||||
import com.tangem.common.ui.expressStatus.expressTransactionsItems
|
import com.tangem.common.ui.expressStatus.expressTransactionsItems
|
||||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
|
||||||
import com.tangem.core.ui.components.atoms.handComposableComponentHeight
|
import com.tangem.core.ui.components.atoms.handComposableComponentHeight
|
||||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheetDraggableHeaderLegacy
|
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheetDraggableHeaderLegacy
|
||||||
|
|
@ -60,6 +59,7 @@ import com.tangem.core.ui.components.sheetscaffold.*
|
||||||
import com.tangem.core.ui.components.snackbar.CopiedTextSnackbar
|
import com.tangem.core.ui.components.snackbar.CopiedTextSnackbar
|
||||||
import com.tangem.core.ui.components.snackbar.TangemSnackbar
|
import com.tangem.core.ui.components.snackbar.TangemSnackbar
|
||||||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||||
|
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||||
import com.tangem.core.ui.event.StateEvent
|
import com.tangem.core.ui.event.StateEvent
|
||||||
import com.tangem.core.ui.extensions.softLayerShadow
|
import com.tangem.core.ui.extensions.softLayerShadow
|
||||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||||
|
|
@ -84,6 +84,8 @@ import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency
|
||||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.marketPriceBlock
|
import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.marketPriceBlock
|
||||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.TangemPayMainScreenBlock
|
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.TangemPayMainScreenBlock
|
||||||
import com.tangem.feature.wallet.presentation.wallet.ui.utils.changeWalletAnimator
|
import com.tangem.feature.wallet.presentation.wallet.ui.utils.changeWalletAnimator
|
||||||
|
import com.tangem.features.tangempay.component.TangemPayMainBlockComponent
|
||||||
|
import com.tangem.features.tangempay.entity.TangemPayMainUM
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -92,6 +94,7 @@ import kotlin.math.roundToInt
|
||||||
@Composable
|
@Composable
|
||||||
internal fun WalletScreen(
|
internal fun WalletScreen(
|
||||||
state: WalletScreenState,
|
state: WalletScreenState,
|
||||||
|
tangemPayComponent: TangemPayMainBlockComponent,
|
||||||
promoBannersBlockComponent: ComposableContentComponent? = null,
|
promoBannersBlockComponent: ComposableContentComponent? = null,
|
||||||
bottomSheetContent: @Composable (() -> Unit),
|
bottomSheetContent: @Composable (() -> Unit),
|
||||||
bottomSheetHeaderHeightProvider: () -> Dp,
|
bottomSheetHeaderHeightProvider: () -> Dp,
|
||||||
|
|
@ -106,6 +109,7 @@ internal fun WalletScreen(
|
||||||
|
|
||||||
WalletContent(
|
WalletContent(
|
||||||
state = state,
|
state = state,
|
||||||
|
tangemPayComponent = tangemPayComponent,
|
||||||
walletsListState = walletsListState,
|
walletsListState = walletsListState,
|
||||||
snackbarHostState = snackbarHostState,
|
snackbarHostState = snackbarHostState,
|
||||||
isAutoScroll = isAutoScroll,
|
isAutoScroll = isAutoScroll,
|
||||||
|
|
@ -128,6 +132,7 @@ internal fun WalletScreen(
|
||||||
@Composable
|
@Composable
|
||||||
private fun WalletContent(
|
private fun WalletContent(
|
||||||
state: WalletScreenState,
|
state: WalletScreenState,
|
||||||
|
tangemPayComponent: TangemPayMainBlockComponent,
|
||||||
walletsListState: LazyListState,
|
walletsListState: LazyListState,
|
||||||
snackbarHostState: SnackbarHostState,
|
snackbarHostState: SnackbarHostState,
|
||||||
isAutoScroll: State<Boolean>,
|
isAutoScroll: State<Boolean>,
|
||||||
|
|
@ -220,18 +225,12 @@ private fun WalletContent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedWallet is WalletState.MultiCurrency) {
|
tangemPayItem(
|
||||||
item(
|
modifier = itemModifier,
|
||||||
key = "TangemPayMainScreenBlock",
|
state = selectedWallet,
|
||||||
contentType = selectedWallet.tangemPayState::class.java,
|
isHidingMode = state.isHidingMode,
|
||||||
) {
|
tangemPayComponent = tangemPayComponent,
|
||||||
TangemPayMainScreenBlock(
|
)
|
||||||
state = selectedWallet.tangemPayState,
|
|
||||||
isBalanceHidden = state.isHidingMode,
|
|
||||||
modifier = itemModifier,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(selectedWallet as? WalletState.SingleCurrency)?.let { walletState ->
|
(selectedWallet as? WalletState.SingleCurrency)?.let { walletState ->
|
||||||
walletState.marketPriceBlockState?.let { marketPriceBlockState ->
|
walletState.marketPriceBlockState?.let { marketPriceBlockState ->
|
||||||
|
|
@ -749,6 +748,25 @@ internal fun LazyListScope.nftCollections(state: WalletState, itemModifier: Modi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun LazyListScope.tangemPayItem(
|
||||||
|
state: WalletState,
|
||||||
|
isHidingMode: Boolean,
|
||||||
|
tangemPayComponent: TangemPayMainBlockComponent,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
if (state !is WalletState.MultiCurrency) return
|
||||||
|
|
||||||
|
if (state.isTangemPayRefactorEnabled) {
|
||||||
|
with(tangemPayComponent) {
|
||||||
|
tangemPayMainContent(modifier = modifier, state = state.tangemPayMainUM, isBalanceHidden = isHidingMode)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item(key = "TangemPayMainScreenBlock", contentType = state.tangemPayState::class.java) {
|
||||||
|
TangemPayMainScreenBlock(modifier = modifier, state = state.tangemPayState, isBalanceHidden = isHidingMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ShowBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) {
|
private fun ShowBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) {
|
||||||
if (bottomSheetConfig != null) {
|
if (bottomSheetConfig != null) {
|
||||||
|
|
@ -767,6 +785,14 @@ private fun WalletScreen_Preview(@PreviewParameter(WalletScreenPreviewProvider::
|
||||||
TangemThemePreview {
|
TangemThemePreview {
|
||||||
WalletScreen(
|
WalletScreen(
|
||||||
state = data,
|
state = data,
|
||||||
|
tangemPayComponent = object : TangemPayMainBlockComponent {
|
||||||
|
override fun LazyListScope.tangemPayMainContent(
|
||||||
|
state: TangemPayMainUM,
|
||||||
|
isBalanceHidden: Boolean,
|
||||||
|
modifier: Modifier,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
},
|
||||||
bottomSheetContent = {
|
bottomSheetContent = {
|
||||||
Text("Markets Content")
|
Text("Markets Content")
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,13 @@ package com.tangem.feature.wallet.presentation.wallet.ui.components.common
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import com.tangem.common.ui.notifications.CreatePaymentAccountNotification
|
||||||
import com.tangem.core.ui.components.notifications.NoteMigrationNotification
|
import com.tangem.core.ui.components.notifications.NoteMigrationNotification
|
||||||
import com.tangem.core.ui.components.notifications.Notification
|
import com.tangem.core.ui.components.notifications.Notification
|
||||||
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
import com.tangem.core.ui.res.ForceDarkTheme
|
import com.tangem.core.ui.res.ForceDarkTheme
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.feature.wallet.impl.R
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
|
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
|
|
@ -49,6 +52,16 @@ internal fun LazyListScope.notifications(configs: ImmutableList<WalletNotificati
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is WalletNotification.CreateTangemPayAccount -> {
|
||||||
|
CreatePaymentAccountNotification(
|
||||||
|
modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null),
|
||||||
|
onClick = item.onClick,
|
||||||
|
onCloseClick = item.onCloseClick,
|
||||||
|
image = R.drawable.img_tangem_pay_visa_banner,
|
||||||
|
title = resourceReference(R.string.tangempay_onboarding_banner_title),
|
||||||
|
subtitle = resourceReference(R.string.tangempay_onboarding_banner_description),
|
||||||
|
)
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
Notification(
|
Notification(
|
||||||
config = item.config,
|
config = item.config,
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
|
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState.Progress
|
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState.Progress
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification.Warning.TangemPayRefreshNeeded
|
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification.Warning.TangemPayRefreshNeeded
|
||||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.TangemPayCardMainBlock
|
import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.TangemPayCardMainBlock
|
||||||
|
|
||||||
|
|
@ -41,7 +42,6 @@ private fun TangemPayMainScreenBlockPreview() {
|
||||||
TangemPayMainScreenBlock(
|
TangemPayMainScreenBlock(
|
||||||
state = TangemPayState.RefreshNeeded(
|
state = TangemPayState.RefreshNeeded(
|
||||||
TangemPayRefreshNeeded(
|
TangemPayRefreshNeeded(
|
||||||
tangemIcon = R.drawable.ic_tangem_24,
|
|
||||||
buttonText = resourceReference(id = R.string.home_button_scan),
|
buttonText = resourceReference(id = R.string.home_button_scan),
|
||||||
onRefreshClick = {},
|
onRefreshClick = {},
|
||||||
shouldShowProgress = false,
|
shouldShowProgress = false,
|
||||||
|
|
@ -49,13 +49,30 @@ private fun TangemPayMainScreenBlockPreview() {
|
||||||
),
|
),
|
||||||
isBalanceHidden = false,
|
isBalanceHidden = false,
|
||||||
)
|
)
|
||||||
|
TangemPayMainScreenBlock(
|
||||||
|
state = TangemPayState.TemporaryUnavailable(WalletNotification.Warning.TangemPayUnreachable),
|
||||||
|
isBalanceHidden = false,
|
||||||
|
)
|
||||||
|
TangemPayMainScreenBlock(
|
||||||
|
state = TangemPayState.OnboardingBanner(onClick = {}, closeOnClick = {}),
|
||||||
|
isBalanceHidden = false,
|
||||||
|
)
|
||||||
|
|
||||||
TangemPayMainScreenBlock(state = TangemPayState.ExposedDevice, isBalanceHidden = false)
|
TangemPayMainScreenBlock(state = TangemPayState.ExposedDevice, isBalanceHidden = false)
|
||||||
|
TangemPayMainScreenBlock(
|
||||||
|
state = TangemPayState.FailedIssue(
|
||||||
|
title = TextReference.Res(R.string.tangempay_payment_account),
|
||||||
|
description = TextReference.Res(R.string.tangempay_failed_to_issue_card),
|
||||||
|
iconRes = R.drawable.ic_alert_24,
|
||||||
|
onButtonClick = { },
|
||||||
|
),
|
||||||
|
isBalanceHidden = false,
|
||||||
|
)
|
||||||
|
|
||||||
TangemPayMainScreenBlock(
|
TangemPayMainScreenBlock(
|
||||||
Progress(
|
Progress(
|
||||||
title = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_title),
|
title = TextReference.Res(R.string.tangempay_payment_account),
|
||||||
description = TextReference.EMPTY,
|
description = TextReference.Res(R.string.tangempay_kyc_in_progress),
|
||||||
buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button),
|
buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button),
|
||||||
iconRes = R.drawable.ic_promo_kyc_36,
|
iconRes = R.drawable.ic_promo_kyc_36,
|
||||||
onButtonClick = {},
|
onButtonClick = {},
|
||||||
|
|
@ -65,19 +82,8 @@ private fun TangemPayMainScreenBlockPreview() {
|
||||||
|
|
||||||
TangemPayMainScreenBlock(
|
TangemPayMainScreenBlock(
|
||||||
Progress(
|
Progress(
|
||||||
title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
|
title = TextReference.Res(R.string.tangempay_payment_account),
|
||||||
description = TextReference.EMPTY,
|
description = TextReference.Res(R.string.tangempay_issuing_your_card),
|
||||||
buttonText = TextReference.Res(R.string.common_continue),
|
|
||||||
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
|
|
||||||
onButtonClick = {},
|
|
||||||
),
|
|
||||||
isBalanceHidden = false,
|
|
||||||
)
|
|
||||||
|
|
||||||
TangemPayMainScreenBlock(
|
|
||||||
Progress(
|
|
||||||
title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
|
|
||||||
description = TextReference.Res(R.string.tangempay_issue_card_notification_description),
|
|
||||||
buttonText = TextReference.EMPTY,
|
buttonText = TextReference.EMPTY,
|
||||||
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
|
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
|
||||||
onButtonClick = {},
|
onButtonClick = {},
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,6 @@ private fun TangemPayRefreshBlockPreview() {
|
||||||
TangemPayRefreshBlock(
|
TangemPayRefreshBlock(
|
||||||
state = TangemPayState.RefreshNeeded(
|
state = TangemPayState.RefreshNeeded(
|
||||||
TangemPayRefreshNeeded(
|
TangemPayRefreshNeeded(
|
||||||
tangemIcon = R.drawable.ic_tangem_24,
|
|
||||||
buttonText = resourceReference(id = R.string.tangempay_sync_needed_restore_access),
|
buttonText = resourceReference(id = R.string.tangempay_sync_needed_restore_access),
|
||||||
onRefreshClick = {},
|
onRefreshClick = {},
|
||||||
shouldShowProgress = true,
|
shouldShowProgress = true,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue