Updated on 2026-08-14
This commit is contained in:
commit
9559ca0564
11 changed files with 270 additions and 43 deletions
|
|
@ -3,9 +3,11 @@ package com.tangem.tap.common.redux.global
|
|||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.commands.Card
|
||||
import org.rekotlin.Action
|
||||
import java.math.BigDecimal
|
||||
|
||||
sealed class GlobalAction : Action {
|
||||
|
||||
data class LoadCard(val card: Card) : GlobalAction()
|
||||
data class LoadWalletManager(val walletManager: WalletManager) : GlobalAction()
|
||||
data class SetFiatRate(val fiatRates: Pair<String, BigDecimal>) : GlobalAction()
|
||||
}
|
||||
|
|
@ -11,6 +11,11 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
|
|||
|
||||
when (action) {
|
||||
is GlobalAction.LoadCard -> newState = newState.copy(card = action.card)
|
||||
is GlobalAction.SetFiatRate -> {
|
||||
val rates = newState.fiatRates.rates.toMutableMap()
|
||||
rates[action.fiatRates.first] = action.fiatRates.second
|
||||
newState = newState.copy(fiatRates = FiatRates(rates))
|
||||
}
|
||||
is GlobalAction.LoadWalletManager ->
|
||||
newState = newState.copy(walletManager = action.walletManager)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,23 @@ package com.tangem.tap.common.redux.global
|
|||
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.tap.domain.TapWalletManager
|
||||
import org.rekotlin.StateType
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class GlobalState(
|
||||
val card: Card? = null,
|
||||
val walletManager: WalletManager? = null,
|
||||
val tapWalletManager: TapWalletManager = TapWalletManager(),
|
||||
val fiatRates: FiatRates = FiatRates(emptyMap()),
|
||||
) : StateType
|
||||
|
||||
data class FiatRates(
|
||||
val rates: Map<String, BigDecimal>
|
||||
) {
|
||||
fun getRateForCryptoCurrency(currency: String): BigDecimal? {
|
||||
return rates[currency]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
117
app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
Normal file
117
app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.commands.common.network.Result
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.tap.TapConfig
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.wallet.redux.PayIdState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.network.coinmarketcap.CoinMarketCapService
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
||||
class TapWalletManager {
|
||||
private val payIdManager = PayIdManager()
|
||||
private val coinMarketCapService = CoinMarketCapService()
|
||||
|
||||
suspend fun loadWalletData() {
|
||||
val walletManager = store.state.globalState.walletManager
|
||||
if (walletManager == null) {
|
||||
store.dispatch(WalletAction.LoadWallet.Failure)
|
||||
return
|
||||
}
|
||||
updateWallet(walletManager)
|
||||
}
|
||||
|
||||
suspend fun loadPayId() {
|
||||
val result = loadPayIdIfNeeded()
|
||||
result?.let { handlePayIdResult(it) }
|
||||
}
|
||||
|
||||
suspend fun loadFiatRate() {
|
||||
val blockchainCurrency = store.state.globalState.walletManager?.wallet?.blockchain?.currency
|
||||
val tokenCurrency = store.state.globalState.walletManager?.wallet?.token?.symbol
|
||||
|
||||
val blockchainRate = blockchainCurrency?.let { coinMarketCapService.getRate(it) }
|
||||
val tokenRate = tokenCurrency?.let { coinMarketCapService.getRate(it) }
|
||||
|
||||
val results = mutableListOf<Pair<String, Result<BigDecimal>?>>()
|
||||
if (blockchainCurrency != null) results.add(blockchainCurrency to blockchainRate)
|
||||
if (tokenCurrency != null) results.add(tokenCurrency to tokenRate)
|
||||
|
||||
handleFiatRatesResult(results)
|
||||
}
|
||||
|
||||
private suspend fun updateWallet(walletManager: WalletManager) {
|
||||
val result = try {
|
||||
walletManager.update()
|
||||
Result.Success(walletManager.wallet)
|
||||
} catch (exeption: Exception) {
|
||||
Result.Failure(exeption)
|
||||
}
|
||||
handleUpdateWalletResult(result)
|
||||
}
|
||||
|
||||
private suspend fun handleUpdateWalletResult(result: Result<Wallet>) {
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
is Result.Success -> store.dispatch(WalletAction.LoadWallet.Success(result.data))
|
||||
is Result.Failure -> store.dispatch(WalletAction.LoadWallet.Failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private suspend fun loadPayIdIfNeeded(): Result<String?>? {
|
||||
if (!TapConfig.usePayId ||
|
||||
store.state.walletState.payIdData.payIdState == PayIdState.Disabled ||
|
||||
store.state.globalState.walletManager?.wallet?.blockchain?.isPayIdSupported() == false) {
|
||||
return null
|
||||
}
|
||||
val cardId = store.state.globalState.card?.cardId
|
||||
val publicKey = store.state.globalState.card?.cardPublicKey
|
||||
if (cardId == null || publicKey == null) {
|
||||
return null
|
||||
}
|
||||
return payIdManager.getPayId(cardId, publicKey.toHexString())
|
||||
}
|
||||
|
||||
private suspend fun handlePayIdResult(result: Result<String?>) {
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
val payId = result.data
|
||||
if (payId == null) {
|
||||
store.dispatch(WalletAction.LoadPayId.NotCreated)
|
||||
} else {
|
||||
store.dispatch(WalletAction.LoadPayId.Success(payId))
|
||||
}
|
||||
}
|
||||
is Result.Failure -> store.dispatch(WalletAction.LoadPayId.Failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleFiatRatesResult(results: List<Pair<String, Result<BigDecimal>?>>) {
|
||||
withContext(Dispatchers.Main) {
|
||||
results.map {
|
||||
when (it.second) {
|
||||
is Result.Success -> {
|
||||
val rate = it.first to (it.second as Result.Success<BigDecimal>).data
|
||||
store.dispatch(GlobalAction.SetFiatRate(rate))
|
||||
store.dispatch(WalletAction.LoadFiatRate.Success(rate))
|
||||
}
|
||||
is Result.Failure -> store.dispatch(WalletAction.LoadFiatRate.Failure)
|
||||
null -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@ val homeMiddleware: Middleware<AppState> = { dispatch, state ->
|
|||
store.dispatch(GlobalAction.LoadCard(result.data.card))
|
||||
store.dispatch(GlobalAction.LoadWalletManager(result.data.walletManager))
|
||||
store.dispatch(WalletAction.LoadWallet)
|
||||
store.dispatch(WalletAction.LoadFiatRate)
|
||||
store.dispatch(WalletAction.LoadPayId)
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,17 @@ import com.tangem.tap.common.redux.NotificationAction
|
|||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.Action
|
||||
import java.math.BigDecimal
|
||||
|
||||
sealed class WalletAction : Action {
|
||||
object LoadWallet : WalletAction() {
|
||||
data class Success(val wallet: Wallet): WalletAction()
|
||||
object Failure: WalletAction()
|
||||
}
|
||||
object LoadFiatRate : WalletAction() {
|
||||
data class Success(val fiatRates: Pair<String, BigDecimal>) : WalletAction()
|
||||
object Failure: WalletAction()
|
||||
}
|
||||
object LoadPayId : WalletAction() {
|
||||
data class Success(val payId: String): WalletAction()
|
||||
object NotCreated: WalletAction()
|
||||
|
|
|
|||
|
|
@ -6,13 +6,11 @@ import androidx.core.content.ContextCompat
|
|||
import com.tangem.commands.common.network.Result
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.tap.TapConfig
|
||||
import com.tangem.tap.common.extensions.copyToClipboard
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.PayIdManager
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.isPayIdSupported
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
|
|
@ -28,50 +26,17 @@ val walletMiddleware: Middleware<AppState> = { dispatch, state ->
|
|||
when (action) {
|
||||
is WalletAction.LoadWallet -> {
|
||||
scope.launch {
|
||||
val walletManager = store.state.globalState.walletManager
|
||||
if (walletManager == null) {
|
||||
store.dispatch(WalletAction.LoadWallet.Failure)
|
||||
return@launch
|
||||
}
|
||||
try {
|
||||
walletManager.update()
|
||||
} catch (ex: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
store.dispatch(WalletAction.LoadWallet.Failure)
|
||||
// callback(CompletionResult.Failure(BlockchainInternalErrorConverter.convert(ex)))
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
store.dispatch(WalletAction.LoadWallet.Success(walletManager.wallet))
|
||||
}
|
||||
store.state.globalState.tapWalletManager.loadWalletData()
|
||||
}
|
||||
}
|
||||
is WalletAction.LoadPayId -> {
|
||||
if (!TapConfig.usePayId ||
|
||||
store.state.walletState.payIdData.payIdState == PayIdState.Disabled ||
|
||||
store.state.globalState.walletManager?.wallet?.blockchain?.isPayIdSupported() == false) {
|
||||
next(action)
|
||||
}
|
||||
scope.launch {
|
||||
val cardId = store.state.globalState.card?.cardId
|
||||
val publicKey = store.state.globalState.card?.cardPublicKey
|
||||
if (cardId != null && publicKey != null) {
|
||||
val result = PayIdManager().getPayId(cardId, publicKey.toHexString())
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
val payId = result.data
|
||||
if (payId == null) {
|
||||
store.dispatch(WalletAction.LoadPayId.NotCreated)
|
||||
} else {
|
||||
store.dispatch(WalletAction.LoadPayId.Success(payId))
|
||||
}
|
||||
}
|
||||
is Result.Failure -> store.dispatch(WalletAction.LoadPayId.Failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
store.state.globalState.tapWalletManager.loadPayId()
|
||||
}
|
||||
}
|
||||
is WalletAction.LoadFiatRate -> {
|
||||
scope.launch {
|
||||
store.state.globalState.tapWalletManager.loadFiatRate()
|
||||
}
|
||||
}
|
||||
is WalletAction.CreatePayId.CompleteCreatingPayId -> {
|
||||
|
|
@ -107,6 +72,7 @@ val walletMiddleware: Middleware<AppState> = { dispatch, state ->
|
|||
store.dispatch(GlobalAction.LoadCard(result.data.card))
|
||||
store.dispatch(GlobalAction.LoadWalletManager(result.data.walletManager))
|
||||
store.dispatch(WalletAction.LoadWallet)
|
||||
store.dispatch(WalletAction.LoadFiatRate)
|
||||
store.dispatch(WalletAction.LoadPayId)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.features.wallet.redux
|
||||
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.tap.common.extensions.toFiatString
|
||||
import com.tangem.tap.common.extensions.toFormattedString
|
||||
import com.tangem.tap.common.extensions.toQrCode
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
|
|
@ -26,22 +27,45 @@ fun walletReducer(action: Action, state: AppState): WalletState {
|
|||
is WalletAction.LoadWallet.Success -> {
|
||||
val token = action.wallet.amounts[AmountType.Token]
|
||||
val tokenData = if (token != null) {
|
||||
val tokenFiatRate = state.globalState.fiatRates.getRateForCryptoCurrency(token.currencySymbol)
|
||||
val tokenFiatAmount = tokenFiatRate?.let { token.value?.toFiatString(it) }
|
||||
TokenData(
|
||||
token.value?.toFormattedString(action.wallet.blockchain) ?: "",
|
||||
token.currencySymbol)
|
||||
token.currencySymbol, tokenFiatAmount)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val amount = action.wallet.amounts[AmountType.Coin]?.value
|
||||
val fiatRate = state.globalState.fiatRates.getRateForCryptoCurrency(action.wallet.blockchain.currency)
|
||||
val fiatAmount = fiatRate?.let { amount?.toFiatString(it) }
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Done, wallet = action.wallet,
|
||||
currencyData = BalanceWidgetData(
|
||||
BalanceStatus.VerifiedOnline, action.wallet.blockchain.fullName,
|
||||
amount?.toFormattedString(action.wallet.blockchain),
|
||||
token = tokenData,
|
||||
fiatAmount = fiatAmount
|
||||
)
|
||||
)
|
||||
}
|
||||
is WalletAction.LoadFiatRate.Success -> {
|
||||
val rate = action.fiatRates.second
|
||||
val currency = action.fiatRates.first
|
||||
val fiatAmount = if (currency == newState.wallet?.blockchain?.currency) {
|
||||
newState.wallet?.amounts?.get(AmountType.Coin)?.value?.toFiatString(rate)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val tokenFiatAmount = if (currency == newState.wallet?.token?.symbol) {
|
||||
newState.wallet?.amounts?.get(AmountType.Token)?.value?.toFiatString(rate)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
newState = newState.copy(currencyData = newState.currencyData.copy(
|
||||
fiatAmount = fiatAmount,
|
||||
token = newState.currencyData.token?.copy(fiatAmount = tokenFiatAmount)
|
||||
))
|
||||
}
|
||||
is WalletAction.LoadWallet.Failure -> newState = newState.copy(
|
||||
state = ProgressState.Done,
|
||||
currencyData = newState.currencyData.copy(status = BalanceStatus.Unreachable)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.tap.network.coinmarketcap
|
||||
|
||||
import com.tangem.tap.TapConfig
|
||||
import com.tangem.tap.network.createRetrofitInstance
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface CoinMarketCapApi {
|
||||
|
||||
@GET("v1/tools/price-conversion")
|
||||
suspend fun getRateInfo(
|
||||
@Query("amount") amount: Int,
|
||||
@Query("symbol") cryptoId: String
|
||||
): RateInfoResponse
|
||||
|
||||
companion object {
|
||||
private const val baseUrl = "https://pro-api.coinmarketcap.com/"
|
||||
|
||||
fun create(): CoinMarketCapApi {
|
||||
return createRetrofitInstance(
|
||||
baseUrl,
|
||||
listOf(createCoinMarketRequestInterceptor()),
|
||||
).create(CoinMarketCapApi::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createCoinMarketRequestInterceptor(): Interceptor {
|
||||
return object : Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val requestBuilder = chain.request().newBuilder()
|
||||
requestBuilder.addHeader("X-CMC_PRO_API_KEY", TapConfig.coinMarketCapKey)
|
||||
return chain.proceed(requestBuilder.build())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.tap.network.coinmarketcap
|
||||
|
||||
import com.tangem.commands.common.network.Result
|
||||
import com.tangem.commands.common.network.performRequest
|
||||
import java.math.BigDecimal
|
||||
|
||||
class CoinMarketCapService {
|
||||
private val api: CoinMarketCapApi by lazy { CoinMarketCapApi.create() }
|
||||
|
||||
suspend fun getRate(currency: String): Result<BigDecimal> {
|
||||
val response = performRequest { api.getRateInfo(1, currency) }
|
||||
return when (response) {
|
||||
is Result.Success -> Result.Success(response.data.data.quote.usd.price)
|
||||
is Result.Failure -> response
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.tap.network.coinmarketcap
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import java.math.BigDecimal
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RateInfoResponse(
|
||||
val status: Status,
|
||||
val data: RateData
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RateData(
|
||||
val quote: Quote
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Quote(
|
||||
@Json(name = "USD")
|
||||
val usd: CurrencyRate
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CurrencyRate(
|
||||
val price: BigDecimal
|
||||
)
|
||||
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Status(
|
||||
val timestamp: String,
|
||||
@Json(name = "error_code")
|
||||
val errorCode: Int,
|
||||
@Json(name = "error_message")
|
||||
val errorMessage: String?,
|
||||
val elapsed: Int,
|
||||
@Json(name = "credit_count")
|
||||
val creditCount: Int,
|
||||
val notice: String?
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue