Updated on 2026-08-14

This commit is contained in:
Tangem 2020-09-03 11:44:19 +03:00
commit 564909ffe6
23 changed files with 435 additions and 92 deletions

View file

@ -60,7 +60,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin"
implementation 'androidx.core:core-ktx:1.3.1'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
implementation 'com.google.android.material:material:1.2.0'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'

View file

@ -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()
}

View file

@ -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)

View file

@ -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]
}
}

View file

@ -4,40 +4,44 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.commands.common.network.Result
import com.tangem.tap.network.payid.PayIdService
import com.tangem.tap.network.payid.SetPayIdResponse
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import retrofit2.HttpException
import java.util.*
class PayIdManager {
private val payIdService = PayIdService()
suspend fun getPayId(cardId: String, publicKey: String): Result<String?> {
suspend fun getPayId(cardId: String, publicKey: String): Result<String?> = withContext(Dispatchers.IO) {
val result = payIdService.getPayId(cardId, publicKey)
when (result) {
is Result.Success -> return Result.Success(result.data.payId)
is Result.Success -> return@withContext Result.Success(result.data.payId)
is Result.Failure -> {
(result.error as? HttpException)?.let {
if (it.code() == 404) return Result.Success(null)
if (it.code() == 404) return@withContext Result.Success(null)
}
return result
return@withContext result
}
}
}
suspend fun setPayId(
cardId: String, publicKey: String, payId: String, address: String, blockchain: Blockchain
): Result<SetPayIdResponse> {
): Result<SetPayIdResponse> = withContext(Dispatchers.IO) {
val result = payIdService.setPayId(cardId, publicKey, payId, address, blockchain.getPayIdNetwork())
when (result) {
is Result.Success -> return result
is Result.Success -> return@withContext result
is Result.Failure -> {
(result.error as? HttpException)?.let {
if (it.code() == 409) return Result.Failure(TapError.PayIdAlreadyCreated)
if (it.code() == 409) return@withContext Result.Failure(TapError.PayIdAlreadyCreated)
}
return result
return@withContext result
}
}
}
private fun Blockchain.getPayIdNetwork(): String {
return when (this) {
Blockchain.XRP -> "XRPL"

View file

@ -6,6 +6,7 @@ import com.tangem.commands.CommandResponse
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.CardType
import com.tangem.tangem_sdk_new.extensions.init
import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
import com.tangem.tap.domain.tasks.ScanNoteResponse
import com.tangem.tap.domain.tasks.ScanNoteTask
import kotlinx.coroutines.Dispatchers
@ -23,12 +24,18 @@ class TangemSdkManager(val activity: ComponentActivity) {
return runTaskAsyncReturnOnMain(ScanNoteTask())
}
suspend fun createWallet(): CompletionResult<ScanNoteResponse> {
return runTaskAsyncReturnOnMain(CreateWalletAndRescanTask())
}
private suspend fun <T : CommandResponse> runTaskAsync(
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null
): CompletionResult<T> =
suspendCoroutine { continuation ->
tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage) { result ->
continuation.resume(result)
withContext(Dispatchers.IO) {
suspendCoroutine { continuation ->
tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage) { result ->
continuation.resume(result)
}
}
}

View file

@ -0,0 +1,132 @@
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.domain.tasks.ScanNoteResponse
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)
}
suspend fun onCardScanned(data: ScanNoteResponse) {
withContext(Dispatchers.Main) {
store.dispatch(GlobalAction.LoadCard(data.card))
if (data.walletManager != null) {
store.dispatch(GlobalAction.LoadWalletManager(data.walletManager))
store.dispatch(WalletAction.LoadWallet)
store.dispatch(WalletAction.LoadFiatRate)
store.dispatch(WalletAction.LoadPayId)
} else {
store.dispatch(WalletAction.EmptyWallet)
}
}
}
private suspend fun updateWallet(walletManager: WalletManager) {
val result = try {
walletManager.update()
Result.Success(walletManager.wallet)
} catch (exception: Exception) {
Result.Failure(exception)
}
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 -> {
}
}
}
}
}
}

View file

@ -0,0 +1,28 @@
package com.tangem.tap.domain.tasks
import com.tangem.CardSession
import com.tangem.CardSessionRunnable
import com.tangem.commands.ReadCommand
import com.tangem.common.CompletionResult
import com.tangem.tasks.CreateWalletTask
class CreateWalletAndRescanTask : CardSessionRunnable<ScanNoteResponse> {
override val requiresPin2 = false
override fun run(session: CardSession, callback: (result: CompletionResult<ScanNoteResponse>) -> Unit) {
CreateWalletTask().run(session) { result ->
when (result) {
is CompletionResult.Success -> ReadCommand().run(session) { readResult ->
when (readResult) {
is CompletionResult.Success -> {
callback(readResult.data.toScanNoteCompletionResult())
}
is CompletionResult.Failure -> callback(CompletionResult.Failure(readResult.error))
}
}
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
}
}
}
}

View file

@ -6,11 +6,12 @@ import com.tangem.TangemSdkError
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.commands.Card
import com.tangem.commands.CardStatus
import com.tangem.commands.CommandResponse
import com.tangem.common.CompletionResult
data class ScanNoteResponse(
val walletManager: WalletManager,
val walletManager: WalletManager?,
val card: Card
) : CommandResponse
@ -19,11 +20,16 @@ class ScanNoteTask : CardSessionRunnable<ScanNoteResponse> {
override fun run(session: CardSession, callback: (result: CompletionResult<ScanNoteResponse>) -> Unit) {
val card = session.environment.card
val walletManager = card?.let { WalletManagerFactory.makeWalletManager(it) }
if (card == null || walletManager == null) {
callback(CompletionResult.Failure(TangemSdkError.CardError()))
return
}
callback(CompletionResult.Success(ScanNoteResponse(walletManager, card)))
callback(card.toScanNoteCompletionResult())
}
}
fun Card?.toScanNoteCompletionResult(): CompletionResult<ScanNoteResponse> {
this ?: return CompletionResult.Failure(TangemSdkError.CardError())
if (this.status == CardStatus.Empty) {
return CompletionResult.Success(ScanNoteResponse(null, this))
}
val walletManager = WalletManagerFactory.makeWalletManager(this)
?: return CompletionResult.Failure(TangemSdkError.CardError())
return CompletionResult.Success(ScanNoteResponse(walletManager, this))
}

View file

@ -5,10 +5,8 @@ import android.net.Uri
import androidx.core.content.ContextCompat.startActivity
import com.tangem.common.CompletionResult
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
@ -27,10 +25,7 @@ val homeMiddleware: Middleware<AppState> = { dispatch, state ->
withContext(Dispatchers.Main) {
when (result) {
is CompletionResult.Success -> {
store.dispatch(GlobalAction.LoadCard(result.data.card))
store.dispatch(GlobalAction.LoadWalletManager(result.data.walletManager))
store.dispatch(WalletAction.LoadWallet)
store.dispatch(WalletAction.LoadPayId)
store.state.globalState.tapWalletManager.onCardScanned(result.data)
store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
}
}

View file

@ -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()
@ -38,4 +43,5 @@ sealed class WalletAction : Action {
object HideQrCode : WalletAction()
data class ExploreAddress(val context: Context) : WalletAction()
object CreateWallet : WalletAction()
object EmptyWallet : WalletAction()
}

View file

@ -6,13 +6,10 @@ 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,49 +25,27 @@ 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.CreateWallet -> {
scope.launch {
val result = tangemSdkManager.createWallet()
when (result) {
is CompletionResult.Success -> {
store.state.globalState.tapWalletManager.onCardScanned(result.data)
}
}
}
}
@ -103,12 +78,7 @@ val walletMiddleware: Middleware<AppState> = { dispatch, state ->
val result = tangemSdkManager.scanNote()
when (result) {
is CompletionResult.Success -> {
withContext(Dispatchers.Main) {
store.dispatch(GlobalAction.LoadCard(result.data.card))
store.dispatch(GlobalAction.LoadWalletManager(result.data.walletManager))
store.dispatch(WalletAction.LoadWallet)
store.dispatch(WalletAction.LoadPayId)
}
store.state.globalState.tapWalletManager.onCardScanned(result.data)
}
}
}

View file

@ -1,6 +1,8 @@
package com.tangem.tap.features.wallet.redux
import com.tangem.blockchain.common.AmountType
import com.tangem.common.extensions.isZero
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
@ -16,32 +18,65 @@ fun walletReducer(action: Action, state: AppState): WalletState {
var newState = state.walletState
when (action) {
is WalletAction.EmptyWallet -> newState = WalletState(
state = ProgressState.Done,
currencyData = BalanceWidgetData(BalanceStatus.EmptyCard),
mainButton = WalletMainButton.CreateWalletButton(true)
)
is WalletAction.LoadWallet -> newState = WalletState(
state = ProgressState.Loading,
currencyData = BalanceWidgetData(
BalanceStatus.Loading,
state.globalState.walletManager?.wallet?.blockchain?.fullName
)
),
mainButton = WalletMainButton.SendButton(false)
)
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) }
val sendButtonEnabled = amount?.isZero() == false || token?.value?.isZero() == false
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
),
mainButton = WalletMainButton.SendButton(sendButtonEnabled)
)
}
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)

View file

@ -2,6 +2,7 @@ package com.tangem.tap.features.wallet.redux
import android.graphics.Bitmap
import com.tangem.blockchain.common.Wallet
import com.tangem.tap.common.entities.Button
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
import org.rekotlin.StateType
@ -12,7 +13,8 @@ data class WalletState(
val currencyData: BalanceWidgetData = BalanceWidgetData(),
val payIdData: PayIdData = PayIdData(),
val qrCode: Bitmap? = null,
val creatingPayIdState: CreatingPayIdState? = null
val creatingPayIdState: CreatingPayIdState? = null,
val mainButton: WalletMainButton = WalletMainButton.SendButton(false)
) : StateType
@ -25,4 +27,9 @@ data class PayIdData(
val payId: String? = null
)
enum class CreatingPayIdState { EnterPayId, Waiting }
enum class CreatingPayIdState { EnterPayId, Waiting }
sealed class WalletMainButton(enabled: Boolean) : Button(enabled) {
class SendButton(enabled: Boolean) : WalletMainButton(enabled)
class CreateWalletButton(enabled: Boolean) : WalletMainButton(enabled)
}

View file

@ -5,7 +5,9 @@ import androidx.fragment.app.Fragment
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.card_balance.*
import kotlinx.android.synthetic.main.layout_balance.*
import kotlinx.android.synthetic.main.layout_balance_error.*
import kotlinx.android.synthetic.main.layout_token.view.*
enum class PayIdState {
@ -18,7 +20,8 @@ enum class PayIdState {
enum class BalanceStatus {
VerifiedOnline,
Unreachable,
Loading
Loading,
EmptyCard
}
data class BalanceWidgetData(
@ -45,6 +48,8 @@ class BalanceWidget(
when (data.status) {
BalanceStatus.Loading -> {
fragment.l_balance.show()
fragment.l_balance_error.hide()
fragment.tv_currency.text = data.currency
fragment.tv_amount.text = "-"
fragment.tv_fiat_amount.hide()
@ -52,6 +57,8 @@ class BalanceWidget(
fragment.l_token.hide()
}
BalanceStatus.VerifiedOnline -> {
fragment.l_balance.show()
fragment.l_balance_error.hide()
fragment.tv_currency.text = data.currency
fragment.tv_amount.text = data.amount
fragment.tv_fiat_amount.show()
@ -70,9 +77,16 @@ class BalanceWidget(
}
BalanceStatus.Unreachable -> {
fragment.l_balance.show()
fragment.l_balance_error.hide()
fragment.l_token.hide()
showStatus(R.id.tv_status_error)
}
BalanceStatus.EmptyCard -> {
fragment.l_balance.hide()
fragment.l_balance_error.show()
fragment.tv_error_title.text = fragment.getText(R.string.wallet_empty_card)
fragment.tv_error_descriptions.text = fragment.getText(R.string.wallet_empty_card_description)
}
}

View file

@ -64,14 +64,34 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
override fun newState(state: WalletState) {
if (activity == null) return
state.wallet?.address?.let { tv_address.text = it }
tv_explore?.setOnClickListener {
store.dispatch(WalletAction.ExploreAddress(requireContext()))
if (state.wallet?.address != null) {
l_address?.show()
tv_address.text = state.wallet.address
tv_explore?.setOnClickListener {
store.dispatch(WalletAction.ExploreAddress(requireContext()))
}
} else {
l_address?.hide()
}
btn_copy.setOnClickListener { store.dispatch(WalletAction.CopyAddress(requireContext())) }
btn_show_qr.setOnClickListener { store.dispatch(WalletAction.ShowQrCode) }
val buttonTitle = when (state.mainButton) {
is WalletMainButton.SendButton -> R.string.wallet_button_send
is WalletMainButton.CreateWalletButton -> R.string.wallet_button_create_wallet
}
btn_main.text = getString(buttonTitle)
btn_main.isEnabled = state.mainButton.enabled
btn_main.setOnClickListener {
when (state.mainButton) {
is WalletMainButton.SendButton -> TODO()
is WalletMainButton.CreateWalletButton -> store.dispatch(WalletAction.CreateWallet)
}
}
if (state.qrCode != null && state.wallet?.shareUrl != null) {
qrDialog = QrDialog(requireContext())
qrDialog?.showQr(state.qrCode, state.wallet.shareUrl)

View file

@ -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())
}
}
}

View file

@ -0,0 +1,19 @@
package com.tangem.tap.network.coinmarketcap
import com.tangem.commands.common.network.Result
import com.tangem.commands.common.network.performRequest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.math.BigDecimal
class CoinMarketCapService {
private val api: CoinMarketCapApi by lazy { CoinMarketCapApi.create() }
suspend fun getRate(currency: String): Result<BigDecimal> = withContext(Dispatchers.IO) {
val response = performRequest { api.getRateInfo(1, currency) }
return@withContext when (response) {
is Result.Success -> Result.Success(response.data.data.quote.usd.price)
is Result.Failure -> response
}
}
}

View file

@ -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?
)

View file

@ -2,8 +2,7 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="24dp">
android:layout_height="match_parent">
<ImageView

View file

@ -56,7 +56,7 @@
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_send"
android:id="@+id/btn_main"
style="@style/TapButtonWithIcon"
android:layout_width="0dp"
android:layout_height="48dp"

View file

@ -9,14 +9,15 @@
android:id="@+id/iv_balance_error"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingTop="16dp"
android:layout_width="26dp"
android:layout_height="26dp"
android:scaleType="fitXY"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:src="@drawable/ic_baseline_error_outline_24" />
<TextView
android:id="@+id/tv_error_description"
android:id="@+id/tv_error_title"
app:layout_constraintStart_toEndOf="@id/iv_balance_error"
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/wallet_account_not_created"
@ -29,9 +30,9 @@
android:textStyle="bold" />
<TextView
android:id="@+id/tv_fiat_amount"
android:id="@+id/tv_error_descriptions"
app:layout_constraintStart_toEndOf="@id/iv_balance_error"
app:layout_constraintTop_toBottomOf="@id/tv_error_description"
app:layout_constraintTop_toBottomOf="@id/tv_error_title"
tools:text="Load10+ XLM to create account"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View file

@ -12,12 +12,15 @@
<string name="wallet_toolbar_title" translatable="false">Tangem Tap</string>
<string name="wallet_button_scan">Scan</string>
<string name="wallet_button_send">Send</string>
<string name="wallet_button_create_wallet">Create Wallet</string>
<string name="wallet_explore_address">Explore address</string>
<string name="wallet_create_payid">Create PayID</string>
<string name="wallet_verified_balance">Verified Balance</string>
<string name="wallet_blockchain_is_unreachable">Blockchain is unreachable</string>
<string name="wallet_balance_is_loading">Balance is loading…</string>
<string name="wallet_account_not_created">Account is not created</string>
<string name="wallet_empty_card">Empty Card</string>
<string name="wallet_empty_card_description">Create wallet to start using Tangem card</string>
<string name="wallet_tokens">Tokens</string>
<string name="wallet_dialog_pay_id_button">Create PayID</string>