Updated on 2026-08-14
This commit is contained in:
commit
aeb60e0cec
49 changed files with 402 additions and 192 deletions
|
|
@ -1,6 +0,0 @@
|
||||||
package com.tangem.tap.common.extensions
|
|
||||||
|
|
||||||
fun String.removePrefixOrNull(prefix: String): String? = when {
|
|
||||||
startsWith(prefix) -> substring(prefix.length)
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
@ -15,7 +15,6 @@ import com.tangem.crypto.hdWallet.DerivationPath
|
||||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||||
import com.tangem.domain.card.common.visa.VisaUtilities
|
import com.tangem.domain.card.common.visa.VisaUtilities
|
||||||
import com.tangem.domain.visa.error.VisaActivationError
|
import com.tangem.domain.visa.error.VisaActivationError
|
||||||
import com.tangem.operations.ScanTask
|
|
||||||
import com.tangem.operations.derivation.DeriveWalletPublicKeyTask
|
import com.tangem.operations.derivation.DeriveWalletPublicKeyTask
|
||||||
import com.tangem.operations.sign.SignHashCommand
|
import com.tangem.operations.sign.SignHashCommand
|
||||||
|
|
||||||
|
|
@ -92,25 +91,7 @@ class TangemPaySignWithdrawalHashTask(
|
||||||
?: targetWalletPublicKey.toDecompressedPublicKey(),
|
?: targetWalletPublicKey.toDecompressedPublicKey(),
|
||||||
).asRSVLegacyEVM().toHexString().lowercase()
|
).asRSVLegacyEVM().toHexString().lowercase()
|
||||||
|
|
||||||
scanCard(
|
callback(CompletionResult.Success(rsvSignature))
|
||||||
session = session,
|
|
||||||
callback = callback,
|
|
||||||
signedData = rsvSignature,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is CompletionResult.Failure -> {
|
|
||||||
callback(CompletionResult.Failure(result.error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun scanCard(signedData: String, session: CardSession, callback: CompletionCallback<String>) {
|
|
||||||
val scanTask = ScanTask()
|
|
||||||
scanTask.run(session) { result ->
|
|
||||||
when (result) {
|
|
||||||
is CompletionResult.Success -> {
|
|
||||||
callback(CompletionResult.Success(signedData))
|
|
||||||
}
|
}
|
||||||
is CompletionResult.Failure -> {
|
is CompletionResult.Failure -> {
|
||||||
callback(CompletionResult.Failure(result.error))
|
callback(CompletionResult.Failure(result.error))
|
||||||
|
|
|
||||||
|
|
@ -660,8 +660,13 @@ internal class ChildFactory @Inject constructor(
|
||||||
createComponentChild(
|
createComponentChild(
|
||||||
context = context,
|
context = context,
|
||||||
params = when (val mode = route.mode) {
|
params = when (val mode = route.mode) {
|
||||||
is AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding -> ContinueOnboarding
|
is AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding -> ContinueOnboarding(
|
||||||
is AppRoute.TangemPayOnboarding.Mode.Deeplink -> Deeplink(deeplink = mode.deeplink)
|
userWalletId = mode.userWalletId,
|
||||||
|
)
|
||||||
|
is AppRoute.TangemPayOnboarding.Mode.Deeplink -> Deeplink(
|
||||||
|
deeplink = mode.deeplink,
|
||||||
|
userWalletId = mode.userWalletId,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
componentFactory = tangemPayOnboardingComponentFactory,
|
componentFactory = tangemPayOnboardingComponentFactory,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -431,10 +431,13 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Deeplink(
|
data class Deeplink(
|
||||||
val deeplink: String,
|
val deeplink: String,
|
||||||
|
val userWalletId: UserWalletId?,
|
||||||
) : Mode()
|
) : Mode()
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
object ContinueOnboarding : Mode()
|
data class ContinueOnboarding(
|
||||||
|
val userWalletId: UserWalletId?,
|
||||||
|
) : Mode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ class TokenItemStateConverter(
|
||||||
private val iconStateProvider: (CryptoCurrencyStatus) -> CurrencyIconState = {
|
private val iconStateProvider: (CryptoCurrencyStatus) -> CurrencyIconState = {
|
||||||
CryptoCurrencyToIconStateConverter().convert(it)
|
CryptoCurrencyToIconStateConverter().convert(it)
|
||||||
},
|
},
|
||||||
private val onApyLabelClick: ((CryptoCurrencyStatus, String) -> Unit)? = null,
|
private val onApyLabelClick: ((CryptoCurrencyStatus, ApySource, String) -> Unit)? = null,
|
||||||
private val titleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.TitleState = { currencyStatus ->
|
private val titleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.TitleState = { currencyStatus ->
|
||||||
createTitleState(
|
createTitleState(
|
||||||
currencyStatus = currencyStatus,
|
currencyStatus = currencyStatus,
|
||||||
|
|
@ -166,7 +166,7 @@ class TokenItemStateConverter(
|
||||||
currencyStatus: CryptoCurrencyStatus,
|
currencyStatus: CryptoCurrencyStatus,
|
||||||
yieldModuleApyMap: Map<String, String>,
|
yieldModuleApyMap: Map<String, String>,
|
||||||
stakingApyMap: Map<String, List<Yield.Validator>>,
|
stakingApyMap: Map<String, List<Yield.Validator>>,
|
||||||
onApyLabelClick: ((CryptoCurrencyStatus, String) -> Unit)?,
|
onApyLabelClick: ((CryptoCurrencyStatus, ApySource, String) -> Unit)?,
|
||||||
): TokenItemState.TitleState {
|
): TokenItemState.TitleState {
|
||||||
return when (val value = currencyStatus.value) {
|
return when (val value = currencyStatus.value) {
|
||||||
is CryptoCurrencyStatus.Loading,
|
is CryptoCurrencyStatus.Loading,
|
||||||
|
|
@ -192,7 +192,7 @@ class TokenItemStateConverter(
|
||||||
earnApy = apyInfo?.text,
|
earnApy = apyInfo?.text,
|
||||||
earnApyIsActive = apyInfo?.isActive == true,
|
earnApyIsActive = apyInfo?.isActive == true,
|
||||||
onApyLabelClick = if (apyInfo?.apy != null && onApyLabelClick != null) {
|
onApyLabelClick = if (apyInfo?.apy != null && onApyLabelClick != null) {
|
||||||
{ onApyLabelClick.invoke(currencyStatus, apyInfo.apy) }
|
{ onApyLabelClick.invoke(currencyStatus, apyInfo.source, apyInfo.apy) }
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
},
|
},
|
||||||
|
|
@ -224,6 +224,7 @@ class TokenItemStateConverter(
|
||||||
),
|
),
|
||||||
isActive = isActive,
|
isActive = isActive,
|
||||||
apy = yieldSupplyApy,
|
apy = yieldSupplyApy,
|
||||||
|
source = ApySource.YIELD_SUPPLY,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -249,6 +250,7 @@ class TokenItemStateConverter(
|
||||||
),
|
),
|
||||||
isActive = stakingInfo.isActive,
|
isActive = stakingInfo.isActive,
|
||||||
apy = apyString,
|
apy = apyString,
|
||||||
|
source = ApySource.STAKING,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -433,5 +435,11 @@ class TokenItemStateConverter(
|
||||||
val text: TextReference?,
|
val text: TextReference?,
|
||||||
val isActive: Boolean,
|
val isActive: Boolean,
|
||||||
val apy: String?,
|
val apy: String?,
|
||||||
|
val source: ApySource,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
enum class ApySource {
|
||||||
|
STAKING,
|
||||||
|
YIELD_SUPPLY,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -9,4 +9,8 @@ fun String.uriValidate(): Boolean {
|
||||||
val regex = DEEPLINK_VALIDATION_REGEX.toRegex()
|
val regex = DEEPLINK_VALIDATION_REGEX.toRegex()
|
||||||
|
|
||||||
return !regex.containsMatchIn(this)
|
return !regex.containsMatchIn(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.addHexPrefix(): String {
|
||||||
|
return if (this.startsWith("0x")) this else "0x$this"
|
||||||
}
|
}
|
||||||
|
|
@ -9,7 +9,6 @@ import com.tangem.core.error.UniversalError
|
||||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||||
import com.tangem.data.common.network.NetworkFactory
|
import com.tangem.data.common.network.NetworkFactory
|
||||||
import com.tangem.data.pay.util.TangemPayErrorConverter
|
import com.tangem.data.pay.util.TangemPayErrorConverter
|
||||||
import com.tangem.data.pay.util.TangemPayWalletsManager
|
|
||||||
import com.tangem.datasource.di.NetworkMoshi
|
import com.tangem.datasource.di.NetworkMoshi
|
||||||
import com.tangem.domain.models.ReceiveAddressModel
|
import com.tangem.domain.models.ReceiveAddressModel
|
||||||
import com.tangem.domain.models.ReceiveAddressModel.NameService
|
import com.tangem.domain.models.ReceiveAddressModel.NameService
|
||||||
|
|
@ -32,7 +31,6 @@ private const val TOKEN_DECIMALS = 6
|
||||||
|
|
||||||
internal class DefaultTangemPaySwapDataFactory @Inject constructor(
|
internal class DefaultTangemPaySwapDataFactory @Inject constructor(
|
||||||
@NetworkMoshi moshi: Moshi,
|
@NetworkMoshi moshi: Moshi,
|
||||||
private val tangemPayWalletsManager: TangemPayWalletsManager,
|
|
||||||
excludedBlockchains: ExcludedBlockchains,
|
excludedBlockchains: ExcludedBlockchains,
|
||||||
) : TangemPaySwapDataFactory {
|
) : TangemPaySwapDataFactory {
|
||||||
|
|
||||||
|
|
@ -63,17 +61,17 @@ internal class DefaultTangemPaySwapDataFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun create(
|
override fun create(
|
||||||
|
userWallet: UserWallet,
|
||||||
depositAddress: String,
|
depositAddress: String,
|
||||||
chainId: Int,
|
chainId: Int,
|
||||||
cryptoBalance: BigDecimal,
|
cryptoBalance: BigDecimal,
|
||||||
fiatBalance: BigDecimal,
|
fiatBalance: BigDecimal,
|
||||||
): Either<UniversalError, TangemPayTopUpData> {
|
): Either<UniversalError, TangemPayTopUpData> {
|
||||||
return catch {
|
return catch {
|
||||||
val wallet = tangemPayWalletsManager.getDefaultWalletForTangemPayBlocking()
|
val currency = getCurrency(userWallet, chainId)
|
||||||
val currency = getCurrency(wallet, chainId)
|
|
||||||
TangemPayTopUpData(
|
TangemPayTopUpData(
|
||||||
currency = currency,
|
currency = currency,
|
||||||
walletId = wallet.walletId,
|
walletId = userWallet.walletId,
|
||||||
cryptoBalance = cryptoBalance,
|
cryptoBalance = cryptoBalance,
|
||||||
fiatBalance = fiatBalance,
|
fiatBalance = fiatBalance,
|
||||||
depositAddress = depositAddress,
|
depositAddress = depositAddress,
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import com.tangem.domain.pay.repository.TangemPaySwapRepository
|
||||||
import com.tangem.domain.visa.error.VisaApiError
|
import com.tangem.domain.visa.error.VisaApiError
|
||||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||||
|
import com.tangem.utils.extensions.addHexPrefix
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.math.RoundingMode
|
import java.math.RoundingMode
|
||||||
import java.util.Currency
|
import java.util.Currency
|
||||||
|
|
@ -66,7 +67,7 @@ internal class DefaultTangemPaySwapRepository @Inject constructor(
|
||||||
recipientAddress = receiverAddress,
|
recipientAddress = receiverAddress,
|
||||||
adminSalt = result.salt,
|
adminSalt = result.salt,
|
||||||
senderAddress = result.senderAddress,
|
senderAddress = result.senderAddress,
|
||||||
adminSignature = signatureResult.signature,
|
adminSignature = signatureResult.signature.addHexPrefix(),
|
||||||
)
|
)
|
||||||
tangemPayApi.withdraw(authHeader = authHeader, body = request)
|
tangemPayApi.withdraw(authHeader = authHeader, body = request)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private const val INITIAL_CURSOR = "initial_cursor_key"
|
private const val INITIAL_CURSOR = "initial_cursor_key"
|
||||||
private const val TAG = "TangemPay: TangemPayTxHistoryRepository:"
|
|
||||||
|
|
||||||
internal class DefaultTangemPayTxHistoryRepository @Inject constructor(
|
internal class DefaultTangemPayTxHistoryRepository @Inject constructor(
|
||||||
private val requestPerformer: TangemPayRequestPerformer,
|
private val requestPerformer: TangemPayRequestPerformer,
|
||||||
|
|
@ -106,12 +105,14 @@ internal class DefaultTangemPayTxHistoryRepository @Inject constructor(
|
||||||
cursor: String?,
|
cursor: String?,
|
||||||
pageSize: Int,
|
pageSize: Int,
|
||||||
) {
|
) {
|
||||||
requestPerformer.runWithErrorLogs(TAG) {
|
requestPerformer.performRequest(userWalletId = userWalletId) { authHeader ->
|
||||||
val result = requestPerformer.request(userWalletId) { authHeader ->
|
visaApi.getTangemPayTxHistory(authHeader = authHeader, limit = pageSize, cursor = cursor)
|
||||||
visaApi.getTangemPayTxHistory(authHeader = authHeader, limit = pageSize, cursor = cursor)
|
}.onLeft {
|
||||||
}.result
|
error(it.toString())
|
||||||
|
}.onRight { response ->
|
||||||
|
val result = response.result
|
||||||
val items = txHistoryItemConverter.convertList(result.transactions).filterNotNull()
|
val items = txHistoryItemConverter.convertList(result.transactions).filterNotNull()
|
||||||
txHistoryItemsStore.store(key = customerWalletAddress, cursor = cursor ?: INITIAL_CURSOR, value = items)
|
txHistoryItemsStore.store(key = customerWalletAddress, cursor = cursor ?: INITIAL_CURSOR, value = items)
|
||||||
}.onLeft { error(it.toString()) }
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -21,11 +21,11 @@ import com.tangem.domain.visa.model.TangemPayAuthTokens
|
||||||
import com.tangem.domain.visa.model.getAuthHeader
|
import com.tangem.domain.visa.model.getAuthHeader
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ internal class TangemPayRequestPerformer @Inject constructor(
|
||||||
private val tangemPayStorage: TangemPayStorage,
|
private val tangemPayStorage: TangemPayStorage,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val customerWalletAddress = MutableStateFlow<String?>(null)
|
private val customerWalletAddresses = ConcurrentHashMap<UserWalletId, String>()
|
||||||
private val tokensMutex = Mutex()
|
private val tokensMutex = Mutex()
|
||||||
private val errorConverter = TangemPayErrorConverter(moshi)
|
private val errorConverter = TangemPayErrorConverter(moshi)
|
||||||
|
|
||||||
|
|
@ -110,7 +110,7 @@ internal class TangemPayRequestPerformer @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getCustomerWalletAddress(userWalletId: UserWalletId): String {
|
suspend fun getCustomerWalletAddress(userWalletId: UserWalletId): String {
|
||||||
val existingAddress = customerWalletAddress.value
|
val existingAddress = customerWalletAddresses[userWalletId]
|
||||||
if (existingAddress != null) {
|
if (existingAddress != null) {
|
||||||
return existingAddress
|
return existingAddress
|
||||||
}
|
}
|
||||||
|
|
@ -118,7 +118,7 @@ internal class TangemPayRequestPerformer @Inject constructor(
|
||||||
userWalletId = userWalletId,
|
userWalletId = userWalletId,
|
||||||
) ?: error("Can not find customer address")
|
) ?: error("Can not find customer address")
|
||||||
|
|
||||||
customerWalletAddress.value = storedAddress
|
customerWalletAddresses[userWalletId] = storedAddress
|
||||||
return storedAddress
|
return storedAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,14 @@ class TangemPayWalletsManager @Inject constructor(
|
||||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@Deprecated("Don't use and put userWallet in features that need it")
|
||||||
suspend fun getDefaultWalletForTangemPay(): UserWallet.Cold {
|
suspend fun getDefaultWalletForTangemPay(): UserWallet.Cold {
|
||||||
val userWalletsFlow = if (useNewRepository()) repository.userWallets else manager.userWallets
|
val userWalletsFlow = if (useNewRepository()) repository.userWallets else manager.userWallets
|
||||||
val userWallets = userWalletsFlow.filter { !it.isNullOrEmpty() }.first()
|
val userWallets = userWalletsFlow.filter { !it.isNullOrEmpty() }.first()
|
||||||
return findColdWallet(userWallets)
|
return findColdWallet(userWallets)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Don't use and put userWallet in features that need it")
|
||||||
fun getDefaultWalletForTangemPayBlocking(): UserWallet.Cold {
|
fun getDefaultWalletForTangemPayBlocking(): UserWallet.Cold {
|
||||||
val userWallets = if (useNewRepository()) repository.userWallets.value else manager.userWalletsSync
|
val userWallets = if (useNewRepository()) repository.userWallets.value else manager.userWalletsSync
|
||||||
return findColdWallet(userWallets)
|
return findColdWallet(userWallets)
|
||||||
|
|
@ -29,7 +31,8 @@ class TangemPayWalletsManager @Inject constructor(
|
||||||
private fun useNewRepository(): Boolean = hotWalletFeatureToggles.isHotWalletEnabled
|
private fun useNewRepository(): Boolean = hotWalletFeatureToggles.isHotWalletEnabled
|
||||||
|
|
||||||
private fun findColdWallet(userWallets: List<UserWallet>?): UserWallet.Cold {
|
private fun findColdWallet(userWallets: List<UserWallet>?): UserWallet.Cold {
|
||||||
return userWallets?.find { it is UserWallet.Cold } as? UserWallet.Cold
|
return userWallets?.find {
|
||||||
?: error("Cannot find cold user wallet")
|
it is UserWallet.Cold && it.isMultiCurrency
|
||||||
|
} as? UserWallet.Cold ?: error("Cannot find cold user wallet")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,12 +4,14 @@ import arrow.core.Either
|
||||||
import com.tangem.core.error.UniversalError
|
import com.tangem.core.error.UniversalError
|
||||||
import com.tangem.domain.models.ReceiveAddressModel
|
import com.tangem.domain.models.ReceiveAddressModel
|
||||||
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.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
interface TangemPaySwapDataFactory {
|
interface TangemPaySwapDataFactory {
|
||||||
|
|
||||||
fun create(
|
fun create(
|
||||||
|
userWallet: UserWallet,
|
||||||
depositAddress: String,
|
depositAddress: String,
|
||||||
chainId: Int,
|
chainId: Int,
|
||||||
cryptoBalance: BigDecimal,
|
cryptoBalance: BigDecimal,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,11 @@ package com.tangem.domain.pay.model
|
||||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
sealed class MainCustomerInfoContentState {
|
||||||
|
object Loading : MainCustomerInfoContentState()
|
||||||
|
data class Content(val info: MainScreenCustomerInfo) : MainCustomerInfoContentState()
|
||||||
|
}
|
||||||
|
|
||||||
data class MainScreenCustomerInfo(
|
data class MainScreenCustomerInfo(
|
||||||
val info: CustomerInfo,
|
val info: CustomerInfo,
|
||||||
val orderStatus: OrderStatus,
|
val orderStatus: OrderStatus,
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,13 @@ package com.tangem.domain.pay.usecase
|
||||||
|
|
||||||
import arrow.core.Either
|
import arrow.core.Either
|
||||||
import arrow.core.left
|
import arrow.core.left
|
||||||
import arrow.core.raise.catch
|
|
||||||
import arrow.core.right
|
import arrow.core.right
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.pay.model.CustomerInfo
|
import com.tangem.domain.pay.model.*
|
||||||
import com.tangem.domain.pay.model.MainScreenCustomerInfo
|
|
||||||
import com.tangem.domain.pay.model.OrderStatus
|
|
||||||
import com.tangem.domain.pay.model.TangemPayCustomerInfoError
|
|
||||||
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 kotlinx.coroutines.flow.*
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
private const val TAG = "TangemPayMainScreenCustomerInfoUseCase"
|
private const val TAG = "TangemPayMainScreenCustomerInfoUseCase"
|
||||||
|
|
@ -26,32 +23,50 @@ class TangemPayMainScreenCustomerInfoUseCase(
|
||||||
private val tangemPayOnboardingRepository: OnboardingRepository,
|
private val tangemPayOnboardingRepository: OnboardingRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend operator fun invoke(
|
val state: StateFlow<Map<UserWalletId, Either<TangemPayCustomerInfoError, MainCustomerInfoContentState>>>
|
||||||
userWalletId: UserWalletId,
|
field = MutableStateFlow(value = mapOf())
|
||||||
): Either<TangemPayCustomerInfoError, MainScreenCustomerInfo> = catch(
|
|
||||||
block = {
|
suspend fun fetch(userWalletId: UserWalletId) {
|
||||||
Timber.tag(TAG).d("checkCustomerWallet")
|
Timber.tag(TAG).i("fetch: $userWalletId")
|
||||||
repository.checkCustomerWallet(userWalletId)
|
repository.checkCustomerWallet(userWalletId)
|
||||||
.fold(
|
.fold(
|
||||||
ifLeft = { error ->
|
ifLeft = { error ->
|
||||||
Timber.tag(TAG).e("Failed to check customer wallet ${error.javaClass.simpleName}")
|
Timber.tag(TAG).e("Failed checkCustomerWallet for $userWalletId: ${error.javaClass.simpleName}")
|
||||||
TangemPayCustomerInfoError.UnknownError.left()
|
updateState(userWalletId, TangemPayCustomerInfoError.UnknownError.left())
|
||||||
},
|
},
|
||||||
ifRight = { hasTangemPay ->
|
ifRight = { hasTangemPay ->
|
||||||
Timber.tag(TAG).i("checkCustomerWallet $hasTangemPay")
|
Timber.tag(TAG).i("checkCustomerWallet for $userWalletId: $hasTangemPay")
|
||||||
if (hasTangemPay) {
|
if (hasTangemPay) {
|
||||||
proceedWithPaeraCustomerResult(userWalletId)
|
val oldResult = state.value[userWalletId]
|
||||||
} else {
|
if (oldResult == null) {
|
||||||
TangemPayCustomerInfoError.UnknownError.left() // ignore if there's no TangemPay
|
updateState(userWalletId, MainCustomerInfoContentState.Loading.right())
|
||||||
}
|
}
|
||||||
},
|
|
||||||
)
|
val result = proceedWithPaeraCustomerResult(userWalletId)
|
||||||
},
|
.map(MainCustomerInfoContentState::Content)
|
||||||
catch = { error ->
|
updateState(userWalletId, result)
|
||||||
Timber.tag(TAG).e(error)
|
} else {
|
||||||
TangemPayCustomerInfoError.UnknownError.left()
|
// ignore if there's no TangemPay
|
||||||
},
|
updateState(userWalletId, TangemPayCustomerInfoError.UnknownError.left())
|
||||||
)
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun invoke(
|
||||||
|
userWalletId: UserWalletId,
|
||||||
|
): Flow<Either<TangemPayCustomerInfoError, MainCustomerInfoContentState>> {
|
||||||
|
return state.mapNotNull { map -> map[userWalletId] }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateState(
|
||||||
|
userWalletId: UserWalletId,
|
||||||
|
either: Either<TangemPayCustomerInfoError, MainCustomerInfoContentState>,
|
||||||
|
) {
|
||||||
|
state.update { currentMap ->
|
||||||
|
currentMap.toMutableMap().apply { this[userWalletId] = either }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun proceedWithPaeraCustomerResult(
|
private suspend fun proceedWithPaeraCustomerResult(
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
|
|
@ -70,14 +85,13 @@ class TangemPayMainScreenCustomerInfoUseCase(
|
||||||
private suspend fun proceedWithoutOrder(
|
private suspend fun proceedWithoutOrder(
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
): Either<TangemPayCustomerInfoError, MainScreenCustomerInfo> {
|
): Either<TangemPayCustomerInfoError, MainScreenCustomerInfo> {
|
||||||
Timber.tag(TAG).d("proceedWithoutOrder")
|
|
||||||
return repository.getCustomerInfo(userWalletId)
|
return repository.getCustomerInfo(userWalletId)
|
||||||
.mapLeft { error ->
|
.mapLeft { error ->
|
||||||
Timber.tag(TAG).e("mapErrorForCustomer: $error")
|
Timber.tag(TAG).e("mapErrorForCustomer: $error")
|
||||||
error.mapErrorForCustomer()
|
error.mapErrorForCustomer()
|
||||||
}
|
}
|
||||||
.map { customerInfo ->
|
.map { customerInfo ->
|
||||||
Timber.tag(TAG).d("customerInfo")
|
Timber.tag(TAG).i("customerInfo")
|
||||||
if (customerInfo.cardInfo == null && customerInfo.isKycApproved) {
|
if (customerInfo.cardInfo == null && customerInfo.isKycApproved) {
|
||||||
// If order id wasn't saved -> start order creation and get customer info
|
// If order id wasn't saved -> start order creation and get customer info
|
||||||
repository.createOrder(userWalletId)
|
repository.createOrder(userWalletId)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,11 @@ import kotlinx.coroutines.flow.update
|
||||||
|
|
||||||
internal typealias ChangedCurrencies = Map<ManagedCryptoCurrency.Token, Set<Network>>
|
internal typealias ChangedCurrencies = Map<ManagedCryptoCurrency.Token, Set<Network>>
|
||||||
|
|
||||||
|
internal data class CurrencyUpdates(
|
||||||
|
val toAdd: ChangedCurrencies = emptyMap(),
|
||||||
|
val toRemove: ChangedCurrencies = emptyMap(),
|
||||||
|
)
|
||||||
|
|
||||||
internal class ChangedCurrenciesManager {
|
internal class ChangedCurrenciesManager {
|
||||||
|
|
||||||
val currenciesToAdd: MutableStateFlow<ChangedCurrencies> = MutableStateFlow(emptyMap())
|
val currenciesToAdd: MutableStateFlow<ChangedCurrencies> = MutableStateFlow(emptyMap())
|
||||||
|
|
|
||||||
|
|
@ -329,6 +329,10 @@ internal class ManageTokensListManager @AssistedInject constructor(
|
||||||
val updatedUiItem = uiBatch.data[currencyIndex].toggleExpanded(
|
val updatedUiItem = uiBatch.data[currencyIndex].toggleExpanded(
|
||||||
currency = currencyBatch.data[currencyIndex],
|
currency = currencyBatch.data[currencyIndex],
|
||||||
isEditable = batches.canEditItems,
|
isEditable = batches.canEditItems,
|
||||||
|
updates = CurrencyUpdates(
|
||||||
|
toAdd = currenciesToAdd.value,
|
||||||
|
toRemove = currenciesToRemove.value,
|
||||||
|
),
|
||||||
onSelectCurrencyNetwork = { networkId, isSelected ->
|
onSelectCurrencyNetwork = { networkId, isSelected ->
|
||||||
selectNetwork(currencyBatch.key, currency, networkId, isSelected)
|
selectNetwork(currencyBatch.key, currency, networkId, isSelected)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -5,20 +5,28 @@ import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork
|
||||||
import com.tangem.domain.models.network.Network
|
import com.tangem.domain.models.network.Network
|
||||||
import com.tangem.features.managetokens.entity.item.CurrencyItemUM.Basic.NetworksUM
|
import com.tangem.features.managetokens.entity.item.CurrencyItemUM.Basic.NetworksUM
|
||||||
import com.tangem.features.managetokens.entity.item.CurrencyNetworkUM
|
import com.tangem.features.managetokens.entity.item.CurrencyNetworkUM
|
||||||
|
import com.tangem.features.managetokens.utils.list.CurrencyUpdates
|
||||||
import com.tangem.features.managetokens.utils.ui.getIconRes
|
import com.tangem.features.managetokens.utils.ui.getIconRes
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
internal fun ManagedCryptoCurrency.Token.toUiNetworksModel(
|
internal fun ManagedCryptoCurrency.Token.toUiNetworksModel(
|
||||||
isExpanded: Boolean,
|
isExpanded: Boolean,
|
||||||
isItemsEditable: Boolean,
|
isItemsEditable: Boolean,
|
||||||
|
updates: CurrencyUpdates,
|
||||||
onSelectedStateChange: (SourceNetwork, Boolean) -> Unit,
|
onSelectedStateChange: (SourceNetwork, Boolean) -> Unit,
|
||||||
onLongTap: (SourceNetwork) -> Unit,
|
onLongTap: (SourceNetwork) -> Unit,
|
||||||
): NetworksUM {
|
): NetworksUM {
|
||||||
return if (isExpanded) {
|
return if (isExpanded) {
|
||||||
NetworksUM.Expanded(
|
NetworksUM.Expanded(
|
||||||
networks = availableNetworks.map {
|
networks = availableNetworks.map { sourceNetwork ->
|
||||||
it.toCurrencyNetworkModel(
|
val isSelectedByDefault = sourceNetwork.network in addedIn
|
||||||
isSelected = it.network in addedIn,
|
val isSelected = when (sourceNetwork.network) {
|
||||||
|
in updates.toAdd[this].orEmpty() -> true
|
||||||
|
in updates.toRemove[this].orEmpty() -> false
|
||||||
|
else -> isSelectedByDefault
|
||||||
|
}
|
||||||
|
sourceNetwork.toCurrencyNetworkModel(
|
||||||
|
isSelected = isSelected,
|
||||||
isEditable = isItemsEditable,
|
isEditable = isItemsEditable,
|
||||||
onSelectedStateChange = onSelectedStateChange,
|
onSelectedStateChange = onSelectedStateChange,
|
||||||
onLongTap = onLongTap,
|
onLongTap = onLongTap,
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,14 @@ import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork
|
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork
|
||||||
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
|
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
|
||||||
import com.tangem.features.managetokens.entity.item.CurrencyItemUM.Basic.NetworksUM
|
import com.tangem.features.managetokens.entity.item.CurrencyItemUM.Basic.NetworksUM
|
||||||
|
import com.tangem.features.managetokens.utils.list.CurrencyUpdates
|
||||||
import com.tangem.features.managetokens.utils.mapper.toUiNetworksModel
|
import com.tangem.features.managetokens.utils.mapper.toUiNetworksModel
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
internal fun CurrencyItemUM.toggleExpanded(
|
internal fun CurrencyItemUM.toggleExpanded(
|
||||||
currency: ManagedCryptoCurrency,
|
currency: ManagedCryptoCurrency,
|
||||||
isEditable: Boolean,
|
isEditable: Boolean,
|
||||||
|
updates: CurrencyUpdates,
|
||||||
onSelectCurrencyNetwork: (SourceNetwork, Boolean) -> Unit,
|
onSelectCurrencyNetwork: (SourceNetwork, Boolean) -> Unit,
|
||||||
onLongTap: (SourceNetwork) -> Unit,
|
onLongTap: (SourceNetwork) -> Unit,
|
||||||
): CurrencyItemUM {
|
): CurrencyItemUM {
|
||||||
|
|
@ -30,6 +32,7 @@ internal fun CurrencyItemUM.toggleExpanded(
|
||||||
networks = currency.toUiNetworksModel(
|
networks = currency.toUiNetworksModel(
|
||||||
isExpanded = isExpanded,
|
isExpanded = isExpanded,
|
||||||
isItemsEditable = isEditable,
|
isItemsEditable = isEditable,
|
||||||
|
updates = updates,
|
||||||
onSelectedStateChange = onSelectCurrencyNetwork,
|
onSelectedStateChange = onSelectCurrencyNetwork,
|
||||||
onLongTap = onLongTap,
|
onLongTap = onLongTap,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,13 @@ internal class OnrampSettingsModel @Inject constructor(
|
||||||
paramsContainer: ParamsContainer,
|
paramsContainer: ParamsContainer,
|
||||||
) : Model() {
|
) : Model() {
|
||||||
|
|
||||||
|
private val params: OnrampSettingsComponent.Params = paramsContainer.require()
|
||||||
|
|
||||||
val state: StateFlow<OnrampSettingsUM>
|
val state: StateFlow<OnrampSettingsUM>
|
||||||
field = MutableStateFlow(getInitialState())
|
field = MutableStateFlow(getInitialState())
|
||||||
|
|
||||||
val bottomSheetNavigation: SlotNavigation<OnrampSettingsConfig> = SlotNavigation()
|
val bottomSheetNavigation: SlotNavigation<OnrampSettingsConfig> = SlotNavigation()
|
||||||
|
|
||||||
private val params: OnrampSettingsComponent.Params = paramsContainer.require()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
analyticsEventHandler.send(OnrampAnalyticsEvent.SettingsOpened)
|
analyticsEventHandler.send(OnrampAnalyticsEvent.SettingsOpened)
|
||||||
subscribeOnUpdateState()
|
subscribeOnUpdateState()
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,7 @@ internal class TangemPayDetailsComponent(
|
||||||
appComponentContext = context,
|
appComponentContext = context,
|
||||||
params = TangemPayTxHistoryDetailsComponent.Params(
|
params = TangemPayTxHistoryDetailsComponent.Params(
|
||||||
transaction = navigation.transaction,
|
transaction = navigation.transaction,
|
||||||
|
isBalanceHidden = navigation.isBalanceHidden,
|
||||||
userWalletId = params.userWalletId,
|
userWalletId = params.userWalletId,
|
||||||
onDismiss = model.bottomSheetNavigation::dismiss,
|
onDismiss = model.bottomSheetNavigation::dismiss,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
package com.tangem.features.tangempay.components.txHistory
|
package com.tangem.features.tangempay.components.txHistory
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.tangem.core.decompose.context.AppComponentContext
|
import com.tangem.core.decompose.context.AppComponentContext
|
||||||
import com.tangem.core.decompose.model.getOrCreateModel
|
import com.tangem.core.decompose.model.getOrCreateModel
|
||||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||||
import com.tangem.core.ui.extensions.*
|
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||||
import com.tangem.features.tangempay.model.TangemPayTxHistoryDetailsModel
|
import com.tangem.features.tangempay.model.TangemPayTxHistoryDetailsModel
|
||||||
|
|
@ -23,11 +24,13 @@ internal class TangemPayTxHistoryDetailsComponent(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun BottomSheet() {
|
override fun BottomSheet() {
|
||||||
TangemPayTxHistoryDetailsContent(state = model.uiState)
|
val state by model.uiState.collectAsStateWithLifecycle()
|
||||||
|
TangemPayTxHistoryDetailsContent(state = state)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Params(
|
data class Params(
|
||||||
val transaction: TangemPayTxHistoryItem,
|
val transaction: TangemPayTxHistoryItem,
|
||||||
|
val isBalanceHidden: Boolean,
|
||||||
val userWalletId: UserWalletId,
|
val userWalletId: UserWalletId,
|
||||||
val onDismiss: () -> Unit,
|
val onDismiss: () -> Unit,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -22,5 +22,8 @@ internal sealed class TangemPayDetailsNavigation {
|
||||||
) : TangemPayDetailsNavigation()
|
) : TangemPayDetailsNavigation()
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class TransactionDetails(val transaction: TangemPayTxHistoryItem) : TangemPayDetailsNavigation()
|
data class TransactionDetails(
|
||||||
|
val transaction: TangemPayTxHistoryItem,
|
||||||
|
val isBalanceHidden: Boolean,
|
||||||
|
) : TangemPayDetailsNavigation()
|
||||||
}
|
}
|
||||||
|
|
@ -41,7 +41,6 @@ internal class TangemPayDetailsStateFactory(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return TangemPayDetailsUM(
|
return TangemPayDetailsUM(
|
||||||
topBarConfig = TangemPayDetailsTopBarConfig(
|
topBarConfig = TangemPayDetailsTopBarConfig(
|
||||||
onBackClick = onBack,
|
onBackClick = onBack,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import com.tangem.core.ui.extensions.TextReference
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
internal data class TangemPayTxHistoryDetailsUM(
|
internal data class TangemPayTxHistoryDetailsUM(
|
||||||
|
val isBalanceHidden: Boolean,
|
||||||
val title: TextReference,
|
val title: TextReference,
|
||||||
val iconState: ImageReference,
|
val iconState: ImageReference,
|
||||||
val transactionTitle: TextReference,
|
val transactionTitle: TextReference,
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ 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.model.ParamsContainer
|
import com.tangem.core.decompose.model.ParamsContainer
|
||||||
import com.tangem.domain.pay.TangemPaySwapDataFactory
|
import com.tangem.domain.pay.TangemPaySwapDataFactory
|
||||||
|
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||||
import com.tangem.features.tangempay.components.TangemPayAddFundsComponent
|
import com.tangem.features.tangempay.components.TangemPayAddFundsComponent
|
||||||
import com.tangem.features.tangempay.entity.TangemPayAddFundsUM
|
import com.tangem.features.tangempay.entity.TangemPayAddFundsUM
|
||||||
import com.tangem.features.tangempay.model.transformers.TangemPayAddFundsUMConverter
|
import com.tangem.features.tangempay.model.transformers.TangemPayAddFundsUMConverter
|
||||||
|
|
@ -17,6 +18,7 @@ internal class TangemPayAddFundsModel @Inject constructor(
|
||||||
paramsContainer: ParamsContainer,
|
paramsContainer: ParamsContainer,
|
||||||
override val dispatchers: CoroutineDispatcherProvider,
|
override val dispatchers: CoroutineDispatcherProvider,
|
||||||
private val tangemPaySwapDataFactory: TangemPaySwapDataFactory,
|
private val tangemPaySwapDataFactory: TangemPaySwapDataFactory,
|
||||||
|
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||||
) : Model() {
|
) : Model() {
|
||||||
|
|
||||||
private val params = paramsContainer.require<TangemPayAddFundsComponent.Params>()
|
private val params = paramsContainer.require<TangemPayAddFundsComponent.Params>()
|
||||||
|
|
@ -24,7 +26,11 @@ internal class TangemPayAddFundsModel @Inject constructor(
|
||||||
val uiState: TangemPayAddFundsUM = getInitialState()
|
val uiState: TangemPayAddFundsUM = getInitialState()
|
||||||
|
|
||||||
private fun getInitialState(): TangemPayAddFundsUM {
|
private fun getInitialState(): TangemPayAddFundsUM {
|
||||||
|
val userWallet = requireNotNull(
|
||||||
|
getUserWalletUseCase(params.walletId).getOrNull(),
|
||||||
|
) { "User wallet not found for id: ${params.walletId}" }
|
||||||
val data = tangemPaySwapDataFactory.create(
|
val data = tangemPaySwapDataFactory.create(
|
||||||
|
userWallet = userWallet,
|
||||||
depositAddress = params.depositAddress,
|
depositAddress = params.depositAddress,
|
||||||
chainId = params.chainId,
|
chainId = params.chainId,
|
||||||
cryptoBalance = params.cryptoBalance,
|
cryptoBalance = params.cryptoBalance,
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,15 @@ import com.tangem.core.ui.extensions.resourceReference
|
||||||
import com.tangem.core.ui.message.SnackbarMessage
|
import com.tangem.core.ui.message.SnackbarMessage
|
||||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||||
import com.tangem.domain.models.TokenReceiveConfig
|
import com.tangem.domain.models.TokenReceiveConfig
|
||||||
import com.tangem.domain.pay.TangemPayTopUpData
|
|
||||||
import com.tangem.domain.pay.TangemPaySwapDataFactory
|
import com.tangem.domain.pay.TangemPaySwapDataFactory
|
||||||
|
import com.tangem.domain.pay.TangemPayTopUpData
|
||||||
import com.tangem.domain.pay.model.TangemPayCardBalance
|
import com.tangem.domain.pay.model.TangemPayCardBalance
|
||||||
import com.tangem.domain.pay.repository.CustomerOrderRepository
|
import com.tangem.domain.pay.repository.CustomerOrderRepository
|
||||||
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
|
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
|
||||||
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
|
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
|
||||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||||
|
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||||
import com.tangem.features.tangempay.TangemPayConstants
|
import com.tangem.features.tangempay.TangemPayConstants
|
||||||
import com.tangem.features.tangempay.components.AddFundsListener
|
import com.tangem.features.tangempay.components.AddFundsListener
|
||||||
import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent
|
import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent
|
||||||
|
|
@ -55,7 +56,7 @@ import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList", "LargeClass")
|
||||||
@Stable
|
@Stable
|
||||||
@ModelScoped
|
@ModelScoped
|
||||||
internal class TangemPayDetailsModel @Inject constructor(
|
internal class TangemPayDetailsModel @Inject constructor(
|
||||||
|
|
@ -71,6 +72,7 @@ internal class TangemPayDetailsModel @Inject constructor(
|
||||||
private val txHistoryUpdateListener: TangemPayTxHistoryUpdateListener,
|
private val txHistoryUpdateListener: TangemPayTxHistoryUpdateListener,
|
||||||
private val tangemPaySwapDataFactory: TangemPaySwapDataFactory,
|
private val tangemPaySwapDataFactory: TangemPaySwapDataFactory,
|
||||||
private val orderRepository: CustomerOrderRepository,
|
private val orderRepository: CustomerOrderRepository,
|
||||||
|
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||||
) : Model(), TangemPayTxHistoryUiActions, TangemPayDetailIntents, AddFundsListener {
|
) : Model(), TangemPayTxHistoryUiActions, TangemPayDetailIntents, AddFundsListener {
|
||||||
|
|
||||||
private val params: TangemPayDetailsContainerComponent.Params = paramsContainer.require()
|
private val params: TangemPayDetailsContainerComponent.Params = paramsContainer.require()
|
||||||
|
|
@ -234,7 +236,11 @@ internal class TangemPayDetailsModel @Inject constructor(
|
||||||
if (hasActiveWithdrawal) {
|
if (hasActiveWithdrawal) {
|
||||||
showBottomSheetError(TangemPayDetailsErrorType.WithdrawInProgress)
|
showBottomSheetError(TangemPayDetailsErrorType.WithdrawInProgress)
|
||||||
} else {
|
} else {
|
||||||
|
val userWallet = requireNotNull(
|
||||||
|
getUserWalletUseCase(params.userWalletId).getOrNull(),
|
||||||
|
) { "User wallet not found: ${params.userWalletId}" }
|
||||||
val data = tangemPaySwapDataFactory.create(
|
val data = tangemPaySwapDataFactory.create(
|
||||||
|
userWallet = userWallet,
|
||||||
depositAddress = depositAddress,
|
depositAddress = depositAddress,
|
||||||
chainId = params.config.chainId,
|
chainId = params.config.chainId,
|
||||||
cryptoBalance = currentBalance.cryptoBalance,
|
cryptoBalance = currentBalance.cryptoBalance,
|
||||||
|
|
@ -367,7 +373,12 @@ internal class TangemPayDetailsModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTransactionClick(item: TangemPayTxHistoryItem) {
|
override fun onTransactionClick(item: TangemPayTxHistoryItem) {
|
||||||
bottomSheetNavigation.activate(TangemPayDetailsNavigation.TransactionDetails(item))
|
bottomSheetNavigation.activate(
|
||||||
|
configuration = TangemPayDetailsNavigation.TransactionDetails(
|
||||||
|
transaction = item,
|
||||||
|
isBalanceHidden = uiState.value.isBalanceHidden,
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClickTermsAndLimits() {
|
override fun onClickTermsAndLimits() {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ 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.model.ParamsContainer
|
import com.tangem.core.decompose.model.ParamsContainer
|
||||||
import com.tangem.core.navigation.url.UrlOpener
|
import com.tangem.core.navigation.url.UrlOpener
|
||||||
|
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||||
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
||||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||||
|
|
@ -14,39 +15,56 @@ import com.tangem.features.tangempay.components.txHistory.TangemPayTxHistoryDeta
|
||||||
import com.tangem.features.tangempay.entity.TangemPayTxHistoryDetailsUM
|
import com.tangem.features.tangempay.entity.TangemPayTxHistoryDetailsUM
|
||||||
import com.tangem.features.tangempay.model.transformers.TangemPayTxHistoryDetailsConverter
|
import com.tangem.features.tangempay.model.transformers.TangemPayTxHistoryDetailsConverter
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
@ModelScoped
|
@ModelScoped
|
||||||
|
@Suppress("LongParameterList")
|
||||||
internal class TangemPayTxHistoryDetailsModel @Inject constructor(
|
internal class TangemPayTxHistoryDetailsModel @Inject constructor(
|
||||||
override val dispatchers: CoroutineDispatcherProvider,
|
override val dispatchers: CoroutineDispatcherProvider,
|
||||||
private val getUserWalletsUseCase: GetWalletsUseCase,
|
private val getUserWalletsUseCase: GetWalletsUseCase,
|
||||||
private val getWalletMetaInfoUseCase: GetWalletMetaInfoUseCase,
|
private val getWalletMetaInfoUseCase: GetWalletMetaInfoUseCase,
|
||||||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||||
private val urlOpener: UrlOpener,
|
private val urlOpener: UrlOpener,
|
||||||
|
private val balanceHidingSettings: GetBalanceHidingSettingsUseCase,
|
||||||
paramsContainer: ParamsContainer,
|
paramsContainer: ParamsContainer,
|
||||||
) : Model() {
|
) : Model() {
|
||||||
|
|
||||||
private val params = paramsContainer.require<TangemPayTxHistoryDetailsComponent.Params>()
|
private val params = paramsContainer.require<TangemPayTxHistoryDetailsComponent.Params>()
|
||||||
val uiState: TangemPayTxHistoryDetailsUM = TangemPayTxHistoryDetailsConverter.convert(
|
val uiState: StateFlow<TangemPayTxHistoryDetailsUM>
|
||||||
TangemPayTxHistoryDetailsConverter.Input(
|
field = MutableStateFlow(
|
||||||
item = params.transaction,
|
value = TangemPayTxHistoryDetailsConverter.convert(
|
||||||
onExplorerClick = ::openExplorer,
|
value = TangemPayTxHistoryDetailsConverter.Input(
|
||||||
onDisputeClick = ::dispute,
|
item = params.transaction,
|
||||||
onDismiss = ::dismiss,
|
isBalanceHidden = params.isBalanceHidden,
|
||||||
),
|
onExplorerClick = ::openExplorer,
|
||||||
)
|
onDisputeClick = ::dispute,
|
||||||
|
onDismiss = ::dismiss,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
init {
|
||||||
|
subscribeToBalanceHiding()
|
||||||
|
}
|
||||||
|
|
||||||
fun dismiss() {
|
fun dismiss() {
|
||||||
params.onDismiss()
|
params.onDismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openExplorer(txHash: String?) {
|
private fun subscribeToBalanceHiding() {
|
||||||
|
balanceHidingSettings.isBalanceHidden()
|
||||||
|
.onEach { isBalanceHidden -> uiState.update { it.copy(isBalanceHidden = isBalanceHidden) } }
|
||||||
|
.launchIn(modelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openExplorer(txHash: String?) {
|
||||||
txHash?.let(urlOpener::openUrlExternalBrowser)
|
txHash?.let(urlOpener::openUrlExternalBrowser)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dispute() {
|
private fun dispute() {
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
val userWalletId = params.userWalletId
|
val userWalletId = params.userWalletId
|
||||||
val userWallet = getUserWalletsUseCase.invokeSync()
|
val userWallet = getUserWalletsUseCase.invokeSync()
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import com.tangem.core.ui.extensions.themedColor
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||||
import com.tangem.features.tangempay.details.impl.R
|
import com.tangem.features.tangempay.details.impl.R
|
||||||
|
import com.tangem.features.tangempay.entity.TangemPayDetailsBalanceBlockState
|
||||||
import com.tangem.features.tangempay.entity.TangemPayDetailsTopBarMenuItem
|
import com.tangem.features.tangempay.entity.TangemPayDetailsTopBarMenuItem
|
||||||
import com.tangem.features.tangempay.entity.TangemPayDetailsTopBarMenuItemType.FreezeCard
|
import com.tangem.features.tangempay.entity.TangemPayDetailsTopBarMenuItemType.FreezeCard
|
||||||
import com.tangem.features.tangempay.entity.TangemPayDetailsTopBarMenuItemType.UnfreezeCard
|
import com.tangem.features.tangempay.entity.TangemPayDetailsTopBarMenuItemType.UnfreezeCard
|
||||||
|
|
@ -31,9 +32,20 @@ internal class TangemPayFreezeUnfreezeStateTransformer(
|
||||||
it.type == FreezeCard || it.type == UnfreezeCard
|
it.type == FreezeCard || it.type == UnfreezeCard
|
||||||
}
|
}
|
||||||
val dropdownMenuItems = createUpdatedMenuItems(filteredItems?.toPersistentList())
|
val dropdownMenuItems = createUpdatedMenuItems(filteredItems?.toPersistentList())
|
||||||
|
val balanceBlockState = if (prevState.balanceBlockState is TangemPayDetailsBalanceBlockState.Content) {
|
||||||
|
val actionButtons = prevState.balanceBlockState.actionButtons.map {
|
||||||
|
it.copy(isEnabled = cardFrozenState == TangemPayCardFrozenState.Unfrozen)
|
||||||
|
}
|
||||||
|
prevState.balanceBlockState.copy(
|
||||||
|
actionButtons = actionButtons.toPersistentList(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
prevState.balanceBlockState
|
||||||
|
}
|
||||||
return prevState.copy(
|
return prevState.copy(
|
||||||
topBarConfig = prevState.topBarConfig.copy(items = dropdownMenuItems),
|
topBarConfig = prevState.topBarConfig.copy(items = dropdownMenuItems),
|
||||||
cardFrozenState = converter.convert(cardFrozenState),
|
cardFrozenState = converter.convert(cardFrozenState),
|
||||||
|
balanceBlockState = balanceBlockState,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ internal object TangemPayTxHistoryDetailsConverter :
|
||||||
override fun convert(value: Input): TangemPayTxHistoryDetailsUM {
|
override fun convert(value: Input): TangemPayTxHistoryDetailsUM {
|
||||||
val transaction = value.item
|
val transaction = value.item
|
||||||
return TangemPayTxHistoryDetailsUM(
|
return TangemPayTxHistoryDetailsUM(
|
||||||
|
isBalanceHidden = value.isBalanceHidden,
|
||||||
title = transaction.extractDate(),
|
title = transaction.extractDate(),
|
||||||
iconState = transaction.extractIcon(),
|
iconState = transaction.extractIcon(),
|
||||||
transactionTitle = transaction.extractTransactionTitle(),
|
transactionTitle = transaction.extractTransactionTitle(),
|
||||||
|
|
@ -254,6 +255,7 @@ internal object TangemPayTxHistoryDetailsConverter :
|
||||||
|
|
||||||
data class Input(
|
data class Input(
|
||||||
val item: TangemPayTxHistoryItem,
|
val item: TangemPayTxHistoryItem,
|
||||||
|
val isBalanceHidden: Boolean,
|
||||||
val onExplorerClick: (String?) -> Unit,
|
val onExplorerClick: (String?) -> Unit,
|
||||||
val onDisputeClick: () -> Unit,
|
val onDisputeClick: () -> Unit,
|
||||||
val onDismiss: () -> Unit,
|
val onDismiss: () -> Unit,
|
||||||
|
|
|
||||||
|
|
@ -99,19 +99,6 @@ internal fun TangemPayDetailsScreen(
|
||||||
}
|
}
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
item(
|
|
||||||
key = TangemPayDetailsBalanceBlockState::class.java,
|
|
||||||
content = {
|
|
||||||
TangemPayDetailsBalanceBlock(
|
|
||||||
modifier = modifier
|
|
||||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
|
||||||
.padding(top = 12.dp)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
state = state.balanceBlockState,
|
|
||||||
isBalanceHidden = state.isBalanceHidden,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if (state.addToWalletBlockState != null) {
|
if (state.addToWalletBlockState != null) {
|
||||||
item(
|
item(
|
||||||
key = AddToWalletBlockState::class.java,
|
key = AddToWalletBlockState::class.java,
|
||||||
|
|
@ -125,6 +112,19 @@ internal fun TangemPayDetailsScreen(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
item(
|
||||||
|
key = TangemPayDetailsBalanceBlockState::class.java,
|
||||||
|
content = {
|
||||||
|
TangemPayDetailsBalanceBlock(
|
||||||
|
modifier = modifier
|
||||||
|
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||||
|
.padding(top = 12.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
state = state.balanceBlockState,
|
||||||
|
isBalanceHidden = state.isBalanceHidden,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
with(txHistoryComponent) { txHistoryContent(listState = listState, state = txHistoryState) }
|
with(txHistoryComponent) { txHistoryContent(listState = listState, state = txHistoryState) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ internal fun TangemPayTxHistoryDetailsContent(state: TangemPayTxHistoryDetailsUM
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.padding(top = 8.dp),
|
modifier = Modifier.padding(top = 8.dp),
|
||||||
text = state.transactionAmount,
|
text = state.transactionAmount.orMaskWithStars(state.isBalanceHidden),
|
||||||
style = TangemTheme.typography.head,
|
style = TangemTheme.typography.head,
|
||||||
color = state.transactionAmountColor.resolveReference(),
|
color = state.transactionAmountColor.resolveReference(),
|
||||||
)
|
)
|
||||||
|
|
@ -158,6 +158,7 @@ private fun TangemPayTxHistoryDetailsContentPreview(
|
||||||
private class TangemPayTxHistoryDetailsUMProvider : CollectionPreviewParameterProvider<TangemPayTxHistoryDetailsUM>(
|
private class TangemPayTxHistoryDetailsUMProvider : CollectionPreviewParameterProvider<TangemPayTxHistoryDetailsUM>(
|
||||||
listOf(
|
listOf(
|
||||||
TangemPayTxHistoryDetailsUM(
|
TangemPayTxHistoryDetailsUM(
|
||||||
|
isBalanceHidden = true,
|
||||||
title = stringReference("12 June • 12:40"),
|
title = stringReference("12 June • 12:40"),
|
||||||
iconState = ImageReference.Res(R.drawable.ic_category_24),
|
iconState = ImageReference.Res(R.drawable.ic_category_24),
|
||||||
transactionTitle = stringReference("Starbucks"),
|
transactionTitle = stringReference("Starbucks"),
|
||||||
|
|
@ -180,6 +181,7 @@ private class TangemPayTxHistoryDetailsUMProvider : CollectionPreviewParameterPr
|
||||||
dismiss = {},
|
dismiss = {},
|
||||||
),
|
),
|
||||||
TangemPayTxHistoryDetailsUM(
|
TangemPayTxHistoryDetailsUM(
|
||||||
|
isBalanceHidden = true,
|
||||||
title = stringReference("12 June • 12:40"),
|
title = stringReference("12 June • 12:40"),
|
||||||
iconState = ImageReference.Res(R.drawable.ic_category_24),
|
iconState = ImageReference.Res(R.drawable.ic_category_24),
|
||||||
transactionTitle = stringReference("Starbucks"),
|
transactionTitle = stringReference("Starbucks"),
|
||||||
|
|
@ -205,6 +207,7 @@ private class TangemPayTxHistoryDetailsUMProvider : CollectionPreviewParameterPr
|
||||||
dismiss = {},
|
dismiss = {},
|
||||||
),
|
),
|
||||||
TangemPayTxHistoryDetailsUM(
|
TangemPayTxHistoryDetailsUM(
|
||||||
|
isBalanceHidden = false,
|
||||||
title = stringReference("12 June • 12:40"),
|
title = stringReference("12 June • 12:40"),
|
||||||
iconState = ImageReference.Res(R.drawable.ic_category_24),
|
iconState = ImageReference.Res(R.drawable.ic_category_24),
|
||||||
transactionTitle = stringReference("Starbucks"),
|
transactionTitle = stringReference("Starbucks"),
|
||||||
|
|
@ -226,6 +229,7 @@ private class TangemPayTxHistoryDetailsUMProvider : CollectionPreviewParameterPr
|
||||||
dismiss = {},
|
dismiss = {},
|
||||||
),
|
),
|
||||||
TangemPayTxHistoryDetailsUM(
|
TangemPayTxHistoryDetailsUM(
|
||||||
|
isBalanceHidden = false,
|
||||||
title = stringReference("12 June • 12:40"),
|
title = stringReference("12 June • 12:40"),
|
||||||
iconState = ImageReference.Res(R.drawable.ic_percent_24),
|
iconState = ImageReference.Res(R.drawable.ic_percent_24),
|
||||||
transactionTitle = stringReference("Fee"),
|
transactionTitle = stringReference("Fee"),
|
||||||
|
|
@ -248,6 +252,7 @@ private class TangemPayTxHistoryDetailsUMProvider : CollectionPreviewParameterPr
|
||||||
dismiss = {},
|
dismiss = {},
|
||||||
),
|
),
|
||||||
TangemPayTxHistoryDetailsUM(
|
TangemPayTxHistoryDetailsUM(
|
||||||
|
isBalanceHidden = false,
|
||||||
title = stringReference("12 June • 12:40"),
|
title = stringReference("12 June • 12:40"),
|
||||||
iconState = ImageReference.Res(R.drawable.ic_arrow_down_24),
|
iconState = ImageReference.Res(R.drawable.ic_arrow_down_24),
|
||||||
transactionTitle = stringReference("Deposit"),
|
transactionTitle = stringReference("Deposit"),
|
||||||
|
|
@ -266,6 +271,7 @@ private class TangemPayTxHistoryDetailsUMProvider : CollectionPreviewParameterPr
|
||||||
dismiss = {},
|
dismiss = {},
|
||||||
),
|
),
|
||||||
TangemPayTxHistoryDetailsUM(
|
TangemPayTxHistoryDetailsUM(
|
||||||
|
isBalanceHidden = false,
|
||||||
title = stringReference("12 June • 12:40"),
|
title = stringReference("12 June • 12:40"),
|
||||||
iconState = ImageReference.Res(R.drawable.ic_arrow_up_24),
|
iconState = ImageReference.Res(R.drawable.ic_arrow_up_24),
|
||||||
transactionTitle = stringReference("Withdrawal"),
|
transactionTitle = stringReference("Withdrawal"),
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@ dependencies {
|
||||||
implementation(projects.core.decompose)
|
implementation(projects.core.decompose)
|
||||||
implementation(projects.core.ui)
|
implementation(projects.core.ui)
|
||||||
|
|
||||||
|
/** Domain */
|
||||||
|
implementation(projects.domain.models)
|
||||||
|
|
||||||
/** Compose */
|
/** Compose */
|
||||||
implementation(deps.compose.runtime)
|
implementation(deps.compose.runtime)
|
||||||
}
|
}
|
||||||
|
|
@ -2,15 +2,22 @@ package com.tangem.features.tangempay.components
|
||||||
|
|
||||||
import com.tangem.core.decompose.factory.ComponentFactory
|
import com.tangem.core.decompose.factory.ComponentFactory
|
||||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
|
||||||
interface TangemPayOnboardingComponent : ComposableContentComponent {
|
interface TangemPayOnboardingComponent : ComposableContentComponent {
|
||||||
|
|
||||||
sealed class Params {
|
sealed class Params {
|
||||||
|
|
||||||
|
abstract val userWalletId: UserWalletId?
|
||||||
|
|
||||||
data class Deeplink(
|
data class Deeplink(
|
||||||
val deeplink: String,
|
val deeplink: String,
|
||||||
|
override val userWalletId: UserWalletId?,
|
||||||
) : Params()
|
) : Params()
|
||||||
|
|
||||||
object ContinueOnboarding : Params()
|
data class ContinueOnboarding(
|
||||||
|
override val userWalletId: UserWalletId?,
|
||||||
|
) : Params()
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Factory : ComponentFactory<Params, TangemPayOnboardingComponent>
|
interface Factory : ComponentFactory<Params, TangemPayOnboardingComponent>
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ dependencies {
|
||||||
|
|
||||||
/** Domain */
|
/** Domain */
|
||||||
implementation(projects.domain.visa)
|
implementation(projects.domain.visa)
|
||||||
|
implementation(projects.domain.wallets)
|
||||||
|
|
||||||
/** Data **/
|
/** Data **/
|
||||||
implementation(projects.data.visa)
|
implementation(projects.data.visa)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
package com.tangem.features.tangempay.deeplink
|
package com.tangem.features.tangempay.deeplink
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import dagger.assisted.Assisted
|
|
||||||
import com.tangem.common.routing.AppRoute
|
import com.tangem.common.routing.AppRoute
|
||||||
import com.tangem.features.tangempay.TangemPayFeatureToggles
|
|
||||||
import com.tangem.common.routing.AppRouter
|
import com.tangem.common.routing.AppRouter
|
||||||
|
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||||
|
import com.tangem.features.tangempay.TangemPayFeatureToggles
|
||||||
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
|
|
||||||
|
|
@ -12,11 +13,16 @@ internal class DefaultOnboardVisaDeepLinkHandler @AssistedInject constructor(
|
||||||
@Assisted uri: Uri,
|
@Assisted uri: Uri,
|
||||||
appRouter: AppRouter,
|
appRouter: AppRouter,
|
||||||
tangemPayFeatureToggles: TangemPayFeatureToggles,
|
tangemPayFeatureToggles: TangemPayFeatureToggles,
|
||||||
|
getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||||
) : OnboardVisaDeepLinkHandler {
|
) : OnboardVisaDeepLinkHandler {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (tangemPayFeatureToggles.isTangemPayEnabled) {
|
if (tangemPayFeatureToggles.isTangemPayEnabled) {
|
||||||
val mode = AppRoute.TangemPayOnboarding.Mode.Deeplink(uri.toString())
|
val userWallet = getSelectedWalletSyncUseCase.invoke().getOrNull()
|
||||||
|
val mode = AppRoute.TangemPayOnboarding.Mode.Deeplink(
|
||||||
|
deeplink = uri.toString(),
|
||||||
|
userWalletId = userWallet?.walletId,
|
||||||
|
)
|
||||||
appRouter.push(AppRoute.TangemPayOnboarding(mode))
|
appRouter.push(AppRoute.TangemPayOnboarding(mode))
|
||||||
} else {
|
} else {
|
||||||
appRouter.push(AppRoute.Home())
|
appRouter.push(AppRoute.Home())
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,13 @@ import com.tangem.core.decompose.model.ParamsContainer
|
||||||
import com.tangem.core.decompose.navigation.Router
|
import com.tangem.core.decompose.navigation.Router
|
||||||
import com.tangem.core.navigation.url.UrlOpener
|
import com.tangem.core.navigation.url.UrlOpener
|
||||||
import com.tangem.data.pay.util.TangemPayWalletsManager
|
import com.tangem.data.pay.util.TangemPayWalletsManager
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||||
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.tangempay.TangemPayAnalyticsEvents
|
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
|
||||||
|
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||||
|
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||||
import com.tangem.features.tangempay.TangemPayConstants
|
import com.tangem.features.tangempay.TangemPayConstants
|
||||||
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent
|
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent
|
||||||
import com.tangem.features.tangempay.model.transformers.TangemPayOnboardingButtonLoadingTransformer
|
import com.tangem.features.tangempay.model.transformers.TangemPayOnboardingButtonLoadingTransformer
|
||||||
|
|
@ -37,6 +41,8 @@ internal class TangemPayOnboardingModel @Inject constructor(
|
||||||
private val produceInitialDataUseCase: ProduceTangemPayInitialDataUseCase,
|
private val produceInitialDataUseCase: ProduceTangemPayInitialDataUseCase,
|
||||||
private val urlOpener: UrlOpener,
|
private val urlOpener: UrlOpener,
|
||||||
private val tangemPayWalletsManager: TangemPayWalletsManager,
|
private val tangemPayWalletsManager: TangemPayWalletsManager,
|
||||||
|
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||||
|
private val getSelectedWalletUseCase: GetSelectedWalletUseCase,
|
||||||
) : Model() {
|
) : Model() {
|
||||||
|
|
||||||
private val params = paramsContainer.require<TangemPayOnboardingComponent.Params>()
|
private val params = paramsContainer.require<TangemPayOnboardingComponent.Params>()
|
||||||
|
|
@ -74,8 +80,9 @@ internal class TangemPayOnboardingModel @Inject constructor(
|
||||||
|
|
||||||
private suspend fun checkCustomerInfo() {
|
private suspend fun checkCustomerInfo() {
|
||||||
// TODO implement selector
|
// TODO implement selector
|
||||||
|
val userWalletId = getUserWalletForPay(params.userWalletId)
|
||||||
repository.getCustomerInfo(
|
repository.getCustomerInfo(
|
||||||
userWalletId = tangemPayWalletsManager.getDefaultWalletForTangemPayBlocking().walletId,
|
userWalletId = userWalletId,
|
||||||
)
|
)
|
||||||
// selector
|
// selector
|
||||||
.onRight { customerInfo ->
|
.onRight { customerInfo ->
|
||||||
|
|
@ -92,6 +99,24 @@ internal class TangemPayOnboardingModel @Inject constructor(
|
||||||
.onLeft { back() }
|
.onLeft { back() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getUserWalletForPay(userWalletId: UserWalletId?): UserWalletId {
|
||||||
|
val userWallet = userWalletId?.let { getUserWalletUseCase(it).getOrNull() }
|
||||||
|
return if (userWallet?.isMultiCurrency == true) {
|
||||||
|
userWallet.walletId
|
||||||
|
} else {
|
||||||
|
tryGetSelectedWalletId()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tryGetSelectedWalletId(): UserWalletId {
|
||||||
|
val selectedWallet = getSelectedWalletUseCase.sync().getOrNull()
|
||||||
|
return if (selectedWallet?.isMultiCurrency == true) {
|
||||||
|
selectedWallet.walletId
|
||||||
|
} else {
|
||||||
|
tangemPayWalletsManager.getDefaultWalletForTangemPayBlocking().walletId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun onTermsClick() {
|
private fun onTermsClick() {
|
||||||
analytics.send(TangemPayAnalyticsEvents.ViewTermsClicked)
|
analytics.send(TangemPayAnalyticsEvents.ViewTermsClicked)
|
||||||
urlOpener.openUrl(TangemPayConstants.TERMS_AND_LIMITS_LINK)
|
urlOpener.openUrl(TangemPayConstants.TERMS_AND_LIMITS_LINK)
|
||||||
|
|
@ -103,7 +128,7 @@ internal class TangemPayOnboardingModel @Inject constructor(
|
||||||
uiState.transformerUpdate(TangemPayOnboardingButtonLoadingTransformer(isLoading = true))
|
uiState.transformerUpdate(TangemPayOnboardingButtonLoadingTransformer(isLoading = true))
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
// TODO implement selector
|
// TODO implement selector
|
||||||
val userWalletId = tangemPayWalletsManager.getDefaultWalletForTangemPayBlocking().walletId
|
val userWalletId = getUserWalletForPay(params.userWalletId)
|
||||||
val result = produceInitialDataUseCase(userWalletId)
|
val result = produceInitialDataUseCase(userWalletId)
|
||||||
if (result.isLeft()) {
|
if (result.isLeft()) {
|
||||||
Timber.e("Error producing initial data: ${result.leftOrNull()?.message}")
|
Timber.e("Error producing initial data: ${result.leftOrNull()?.message}")
|
||||||
|
|
@ -135,7 +160,7 @@ internal class TangemPayOnboardingModel @Inject constructor(
|
||||||
router.replaceAll(
|
router.replaceAll(
|
||||||
AppRoute.Wallet,
|
AppRoute.Wallet,
|
||||||
AppRoute.Kyc(
|
AppRoute.Kyc(
|
||||||
userWalletId = tangemPayWalletsManager.getDefaultWalletForTangemPayBlocking().walletId,
|
userWalletId = getUserWalletForPay(params.userWalletId),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
package com.tangem.feature.wallet.child.wallet.model
|
|
||||||
|
|
||||||
import arrow.core.Either
|
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
|
||||||
import com.tangem.domain.pay.model.MainScreenCustomerInfo
|
|
||||||
import com.tangem.domain.pay.model.TangemPayCustomerInfoError
|
|
||||||
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
internal class TangemPayMainInfoManager @Inject constructor(
|
|
||||||
private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase,
|
|
||||||
) {
|
|
||||||
|
|
||||||
val mainScreenCustomerInfo:
|
|
||||||
StateFlow<Pair<UserWalletId, Either<TangemPayCustomerInfoError, MainScreenCustomerInfo>>?>
|
|
||||||
field = MutableStateFlow(null)
|
|
||||||
|
|
||||||
suspend fun refreshTangemPayInfo(userWalletId: UserWalletId) {
|
|
||||||
val info = tangemPayMainScreenCustomerInfoUseCase(userWalletId)
|
|
||||||
mainScreenCustomerInfo.value = Pair(userWalletId, info)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -22,6 +22,7 @@ import com.tangem.domain.nft.ObserveAndClearNFTCacheIfNeedUseCase
|
||||||
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.pay.repository.OnboardingRepository
|
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||||
|
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
|
||||||
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.wallets.usecase.*
|
import com.tangem.domain.wallets.usecase.*
|
||||||
|
|
@ -96,7 +97,7 @@ internal class WalletModel @Inject constructor(
|
||||||
private val tangemPayOnboardingRepository: OnboardingRepository,
|
private val tangemPayOnboardingRepository: OnboardingRepository,
|
||||||
private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles,
|
private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles,
|
||||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||||
private val tangemPayMainInfoManager: TangemPayMainInfoManager,
|
private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase,
|
||||||
private val trackingContextProxy: TrackingContextProxy,
|
private val trackingContextProxy: TrackingContextProxy,
|
||||||
val screenLifecycleProvider: ScreenLifecycleProvider,
|
val screenLifecycleProvider: ScreenLifecycleProvider,
|
||||||
val innerWalletRouter: InnerWalletRouter,
|
val innerWalletRouter: InnerWalletRouter,
|
||||||
|
|
@ -375,7 +376,10 @@ internal class WalletModel @Inject constructor(
|
||||||
}.distinctUntilChanged(),
|
}.distinctUntilChanged(),
|
||||||
transform = ::Pair,
|
transform = ::Pair,
|
||||||
).onEach { (inBackground, userWalletId) ->
|
).onEach { (inBackground, userWalletId) ->
|
||||||
if (inBackground) return@onEach
|
if (inBackground) {
|
||||||
|
updateTangemPayJobHolder.cancel()
|
||||||
|
return@onEach
|
||||||
|
}
|
||||||
|
|
||||||
val savedCustomerInfo =
|
val savedCustomerInfo =
|
||||||
tangemPayOnboardingRepository.getSavedCustomerInfo(userWalletId)
|
tangemPayOnboardingRepository.getSavedCustomerInfo(userWalletId)
|
||||||
|
|
@ -386,15 +390,15 @@ internal class WalletModel @Inject constructor(
|
||||||
if (isShouldLaunchPeriodicUpdate) {
|
if (isShouldLaunchPeriodicUpdate) {
|
||||||
updateTangemPayJobHolder.cancel()
|
updateTangemPayJobHolder.cancel()
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
tangemPayMainInfoManager.refreshTangemPayInfo(userWalletId)
|
tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId)
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
delay(TANGEM_PAY_UPDATE_INTERVAL)
|
delay(TANGEM_PAY_UPDATE_INTERVAL)
|
||||||
tangemPayMainInfoManager.refreshTangemPayInfo(userWalletId)
|
tangemPayMainScreenCustomerInfoUseCase.fetch(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
|
||||||
tangemPayMainInfoManager.refreshTangemPayInfo(userWalletId)
|
tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId)
|
||||||
}
|
}
|
||||||
}.launchIn(modelScope)
|
}.launchIn(modelScope)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.models.wallet.requireColdWallet
|
import com.tangem.domain.models.wallet.requireColdWallet
|
||||||
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.wallets.usecase.GetUserWalletUseCase
|
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||||
import com.tangem.feature.wallet.child.wallet.model.TangemPayMainInfoManager
|
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||||
import com.tangem.features.tangempay.TangemPayFeatureToggles
|
import com.tangem.features.tangempay.TangemPayFeatureToggles
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -43,7 +43,7 @@ internal class TangemPayClickIntentsImplementor @Inject constructor(
|
||||||
private val getWalletMetainfoUseCase: GetWalletMetaInfoUseCase,
|
private val getWalletMetainfoUseCase: GetWalletMetaInfoUseCase,
|
||||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||||
private val tangemPayInfoManager: TangemPayMainInfoManager,
|
private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase,
|
||||||
private val uiMessageSender: UiMessageSender,
|
private val uiMessageSender: UiMessageSender,
|
||||||
) : BaseWalletClickIntents(), TangemPayIntents {
|
) : BaseWalletClickIntents(), TangemPayIntents {
|
||||||
|
|
||||||
|
|
@ -54,13 +54,13 @@ internal class TangemPayClickIntentsImplementor @Inject constructor(
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tangemPayInfoManager.refreshTangemPayInfo(userWalletId)
|
tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRefreshPayToken(userWalletId: UserWalletId) {
|
override fun onRefreshPayToken(userWalletId: UserWalletId) {
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
produceInitialDataTangemPay.invoke(userWalletId)
|
produceInitialDataTangemPay.invoke(userWalletId)
|
||||||
tangemPayInfoManager.refreshTangemPayInfo(userWalletId)
|
tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@ package com.tangem.feature.wallet.child.wallet.model.intents
|
||||||
|
|
||||||
import arrow.core.getOrElse
|
import arrow.core.getOrElse
|
||||||
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
|
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
|
||||||
|
import com.tangem.common.ui.tokens.TokenItemStateConverter.ApySource
|
||||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
|
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
|
||||||
import com.tangem.core.decompose.di.ModelScoped
|
import com.tangem.core.decompose.di.ModelScoped
|
||||||
import com.tangem.domain.models.account.Account
|
import com.tangem.domain.models.account.Account
|
||||||
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.staking.StakingBalance
|
import com.tangem.domain.models.staking.StakingBalance
|
||||||
import com.tangem.domain.models.wallet.UserWallet
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
|
|
@ -50,7 +50,12 @@ internal interface WalletContentClickIntents {
|
||||||
|
|
||||||
fun onTokenItemLongClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus)
|
fun onTokenItemLongClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||||
|
|
||||||
fun onApyLabelClick(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus, apy: String)
|
fun onApyLabelClick(
|
||||||
|
userWalletId: UserWalletId,
|
||||||
|
currencyStatus: CryptoCurrencyStatus,
|
||||||
|
apySource: ApySource,
|
||||||
|
apy: String,
|
||||||
|
)
|
||||||
|
|
||||||
fun onAccountExpandClick(account: Account)
|
fun onAccountExpandClick(account: Account)
|
||||||
|
|
||||||
|
|
@ -162,11 +167,17 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onApyLabelClick(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus, apy: String) {
|
override fun onApyLabelClick(
|
||||||
val navigationAction = if (currencyStatus.currency is CryptoCurrency.Token) {
|
userWalletId: UserWalletId,
|
||||||
NavigationAction.YieldSupply(currencyStatus.value.yieldSupplyStatus?.isActive == true)
|
currencyStatus: CryptoCurrencyStatus,
|
||||||
} else {
|
apySource: ApySource,
|
||||||
NavigationAction.Staking
|
apy: String,
|
||||||
|
) {
|
||||||
|
val navigationAction = when (apySource) {
|
||||||
|
ApySource.STAKING -> NavigationAction.Staking
|
||||||
|
ApySource.YIELD_SUPPLY -> {
|
||||||
|
NavigationAction.YieldSupply(currencyStatus.value.yieldSupplyStatus?.isActive == true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendApyLabelClickAnalytics(navigationAction, currencyStatus)
|
sendApyLabelClickAnalytics(navigationAction, currencyStatus)
|
||||||
|
|
|
||||||
|
|
@ -111,8 +111,14 @@ internal class DefaultWalletRouter @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openTangemPayOnboarding() {
|
override fun openTangemPayOnboarding(userWalletId: UserWalletId) {
|
||||||
router.push(AppRoute.TangemPayOnboarding(AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding))
|
router.push(
|
||||||
|
AppRoute.TangemPayOnboarding(
|
||||||
|
AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openTangemPayDetails(userWalletId: UserWalletId, config: TangemPayDetailsConfig) {
|
override fun openTangemPayDetails(userWalletId: UserWalletId, config: TangemPayDetailsConfig) {
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ internal interface InnerWalletRouter {
|
||||||
|
|
||||||
fun openTokenReceiveBottomSheet(tokenReceiveConfig: TokenReceiveConfig)
|
fun openTokenReceiveBottomSheet(tokenReceiveConfig: TokenReceiveConfig)
|
||||||
|
|
||||||
fun openTangemPayOnboarding()
|
fun openTangemPayOnboarding(userWalletId: UserWalletId)
|
||||||
|
|
||||||
fun openTangemPayDetails(userWalletId: UserWalletId, config: TangemPayDetailsConfig)
|
fun openTangemPayDetails(userWalletId: UserWalletId, config: TangemPayDetailsConfig)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ internal sealed class TangemPayState {
|
||||||
|
|
||||||
object Empty : TangemPayState()
|
object Empty : TangemPayState()
|
||||||
|
|
||||||
|
data object Loading : TangemPayState()
|
||||||
|
|
||||||
data class Progress(
|
data class Progress(
|
||||||
val title: TextReference,
|
val title: TextReference,
|
||||||
val description: TextReference,
|
val description: TextReference,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
||||||
|
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||||
|
|
||||||
|
internal class TangemPayLoadingStateTransformer(userWalletId: UserWalletId) : WalletStateTransformer(userWalletId) {
|
||||||
|
override fun transform(prevState: WalletState): WalletState {
|
||||||
|
return if (prevState is WalletState.MultiCurrency.Content) {
|
||||||
|
prevState.copy(tangemPayState = TangemPayState.Loading)
|
||||||
|
} else {
|
||||||
|
prevState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -49,9 +49,15 @@ internal class TokenListStateConverter(
|
||||||
clickIntents.onTokenItemLongClick(selectedWallet.walletId, currencyStatus)
|
clickIntents.onTokenItemLongClick(selectedWallet.walletId, currencyStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val onApyLabelClick: (currencyStatus: CryptoCurrencyStatus, apy: String) -> Unit =
|
private val onApyLabelClick:
|
||||||
{ currencyStatus, apy ->
|
(currencyStatus: CryptoCurrencyStatus, apySource: TokenItemStateConverter.ApySource, apy: String) -> Unit =
|
||||||
clickIntents.onApyLabelClick(selectedWallet.walletId, currencyStatus, apy)
|
{ currencyStatus, apySource, apy ->
|
||||||
|
clickIntents.onApyLabelClick(
|
||||||
|
userWalletId = selectedWallet.walletId,
|
||||||
|
currencyStatus = currencyStatus,
|
||||||
|
apySource = apySource,
|
||||||
|
apy = apy,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun tokenStatusConverter(accountId: AccountId? = null) = TokenItemStateConverter(
|
private fun tokenStatusConverter(accountId: AccountId? = null) = TokenItemStateConverter(
|
||||||
|
|
@ -60,7 +66,7 @@ internal class TokenListStateConverter(
|
||||||
stakingApyMap = stakingApyMap,
|
stakingApyMap = stakingApyMap,
|
||||||
onItemClick = { _, status -> onTokenClick(accountId, status) },
|
onItemClick = { _, status -> onTokenClick(accountId, status) },
|
||||||
onItemLongClick = { _, status -> onTokenLongClick(accountId, status) },
|
onItemLongClick = { _, status -> onTokenLongClick(accountId, status) },
|
||||||
onApyLabelClick = { status, apy -> onApyLabelClick(status, apy) },
|
onApyLabelClick = { status, apySource, apy -> onApyLabelClick(status, apySource, apy) },
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun convert(value: WalletTokensListState): WalletTokensListState {
|
override fun convert(value: WalletTokensListState): WalletTokensListState {
|
||||||
|
|
|
||||||
|
|
@ -2,24 +2,24 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers
|
||||||
|
|
||||||
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.domain.pay.model.MainCustomerInfoContentState
|
||||||
import com.tangem.domain.pay.model.MainScreenCustomerInfo
|
import com.tangem.domain.pay.model.MainScreenCustomerInfo
|
||||||
import com.tangem.domain.pay.model.TangemPayCustomerInfoError
|
import com.tangem.domain.pay.model.TangemPayCustomerInfoError
|
||||||
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
|
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
|
||||||
|
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
|
||||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||||
import com.tangem.feature.wallet.child.wallet.model.TangemPayMainInfoManager
|
|
||||||
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
|
||||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletTangemPayAnalyticsEventSender
|
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletTangemPayAnalyticsEventSender
|
||||||
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.transformers.TangemPayHiddenStateTransformer
|
import com.tangem.feature.wallet.presentation.wallet.state.transformers.*
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayRefreshNeededStateTransformer
|
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayUnavailableStateTransformer
|
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayUpdateInfoStateTransformer
|
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
|
|
@ -29,7 +29,7 @@ internal class TangemPayMainSubscriber @AssistedInject constructor(
|
||||||
private val clickIntents: WalletClickIntents,
|
private val clickIntents: WalletClickIntents,
|
||||||
private val innerWalletRouter: InnerWalletRouter,
|
private val innerWalletRouter: InnerWalletRouter,
|
||||||
private val cardDetailsRepository: TangemPayCardDetailsRepository,
|
private val cardDetailsRepository: TangemPayCardDetailsRepository,
|
||||||
private val tangemPayMainInfoManager: TangemPayMainInfoManager,
|
private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase,
|
||||||
private val analytics: WalletTangemPayAnalyticsEventSender,
|
private val analytics: WalletTangemPayAnalyticsEventSender,
|
||||||
) : WalletSubscriber() {
|
) : WalletSubscriber() {
|
||||||
|
|
||||||
|
|
@ -38,11 +38,10 @@ internal class TangemPayMainSubscriber @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun subscribeOnTangemPayInfoUpdates(): Flow<*> {
|
private fun subscribeOnTangemPayInfoUpdates(): Flow<*> {
|
||||||
return tangemPayMainInfoManager.mainScreenCustomerInfo
|
return tangemPayMainScreenCustomerInfoUseCase(userWalletId = userWallet.walletId)
|
||||||
.filterNotNull()
|
|
||||||
.filter { it.first == userWallet.walletId }
|
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.onEach { (userWalletId, mainInfoData) ->
|
.onEach { mainInfoData ->
|
||||||
|
val userWalletId = userWallet.walletId
|
||||||
mainInfoData.onLeft { tangemPayError ->
|
mainInfoData.onLeft { tangemPayError ->
|
||||||
when (tangemPayError) {
|
when (tangemPayError) {
|
||||||
TangemPayCustomerInfoError.RefreshNeededError -> {
|
TangemPayCustomerInfoError.RefreshNeededError -> {
|
||||||
|
|
@ -66,13 +65,23 @@ internal class TangemPayMainSubscriber @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.onRight { data ->
|
}.onRight { contentState -> handleContentState(state = contentState) }
|
||||||
updateTangemPay(data, userWalletId)
|
|
||||||
analytics.send(customerInfo = data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun handleContentState(state: MainCustomerInfoContentState) {
|
||||||
|
val userWalletId = userWallet.walletId
|
||||||
|
when (state) {
|
||||||
|
MainCustomerInfoContentState.Loading -> stateController.update(
|
||||||
|
transformer = TangemPayLoadingStateTransformer(userWalletId),
|
||||||
|
)
|
||||||
|
is MainCustomerInfoContentState.Content -> {
|
||||||
|
updateTangemPay(data = state.info, userWalletId = userWalletId)
|
||||||
|
analytics.send(customerInfo = state.info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun updateTangemPay(data: MainScreenCustomerInfo, userWalletId: UserWalletId) {
|
private suspend fun updateTangemPay(data: MainScreenCustomerInfo, userWalletId: UserWalletId) {
|
||||||
val cardFrozenState =
|
val cardFrozenState =
|
||||||
data.info.productInstance?.cardId?.let { cardDetailsRepository.cardFrozenStateSync(it) }
|
data.info.productInstance?.cardId?.let { cardDetailsRepository.cardFrozenStateSync(it) }
|
||||||
|
|
@ -82,7 +91,7 @@ internal class TangemPayMainSubscriber @AssistedInject constructor(
|
||||||
userWalletId = userWalletId,
|
userWalletId = userWalletId,
|
||||||
value = data,
|
value = data,
|
||||||
cardFrozenState = cardFrozenState,
|
cardFrozenState = cardFrozenState,
|
||||||
onClickKyc = innerWalletRouter::openTangemPayOnboarding,
|
onClickKyc = { innerWalletRouter.openTangemPayOnboarding(userWalletId) },
|
||||||
onIssuingCard = clickIntents::onIssuingCardClicked,
|
onIssuingCard = clickIntents::onIssuingCardClicked,
|
||||||
onIssuingFailed = clickIntents::onIssuingFailedClicked,
|
onIssuingFailed = clickIntents::onIssuingFailedClicked,
|
||||||
openDetails = { config ->
|
openDetails = { config ->
|
||||||
|
|
|
||||||
|
|
@ -224,9 +224,9 @@ private fun WalletContent(
|
||||||
contentType = selectedWallet.tangemPayState::class.java,
|
contentType = selectedWallet.tangemPayState::class.java,
|
||||||
) {
|
) {
|
||||||
TangemPayMainScreenBlock(
|
TangemPayMainScreenBlock(
|
||||||
selectedWallet.tangemPayState,
|
state = selectedWallet.tangemPayState,
|
||||||
isBalanceHidden = state.isHidingMode,
|
isBalanceHidden = state.isHidingMode,
|
||||||
itemModifier,
|
modifier = itemModifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
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.res.TangemTheme
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun TangemPayLoadingScreenBlock(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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,6 +23,7 @@ internal fun TangemPayMainScreenBlock(state: TangemPayState, isBalanceHidden: Bo
|
||||||
is TangemPayState.RefreshNeeded -> TangemPayRefreshBlock(state, modifier)
|
is TangemPayState.RefreshNeeded -> TangemPayRefreshBlock(state, modifier)
|
||||||
is TangemPayState.TemporaryUnavailable -> TangemPayUnavailableBlock(state, modifier)
|
is TangemPayState.TemporaryUnavailable -> TangemPayUnavailableBlock(state, modifier)
|
||||||
is TangemPayState.FailedIssue -> TangemPayFailedIssueState(state, modifier)
|
is TangemPayState.FailedIssue -> TangemPayFailedIssueState(state, modifier)
|
||||||
|
TangemPayState.Loading -> TangemPayLoadingScreenBlock(modifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,6 +33,8 @@ internal fun TangemPayMainScreenBlock(state: TangemPayState, isBalanceHidden: Bo
|
||||||
private fun TangemPayMainScreenBlockPreview() {
|
private fun TangemPayMainScreenBlockPreview() {
|
||||||
TangemThemePreview {
|
TangemThemePreview {
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
|
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
|
||||||
|
TangemPayMainScreenBlock(state = TangemPayState.Loading, isBalanceHidden = false)
|
||||||
|
|
||||||
TangemPayMainScreenBlock(
|
TangemPayMainScreenBlock(
|
||||||
Progress(
|
Progress(
|
||||||
title = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_title),
|
title = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_title),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue