Updated on 2026-08-14

This commit is contained in:
Tangem 2020-09-02 12:52:38 +03:00
parent 400d625df1
commit a6141bbbe5
18 changed files with 175 additions and 59 deletions

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

@ -6,6 +6,7 @@ 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
@ -47,12 +48,26 @@ class TapWalletManager {
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 (exeption: Exception) {
Result.Failure(exeption)
} catch (exception: Exception) {
Result.Failure(exception)
}
handleUpdateWalletResult(result)
}

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,11 +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.LoadFiatRate)
store.dispatch(WalletAction.LoadPayId)
store.state.globalState.tapWalletManager.onCardScanned(result.data)
store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
}
}

View file

@ -43,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

@ -8,7 +8,6 @@ import com.tangem.common.CompletionResult
import com.tangem.common.extensions.toHexString
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.scope
@ -39,6 +38,17 @@ val walletMiddleware: Middleware<AppState> = { dispatch, state ->
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)
}
}
}
}
is WalletAction.CreatePayId.CompleteCreatingPayId -> {
scope.launch {
val cardId = store.state.globalState.card?.cardId
@ -68,13 +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.LoadFiatRate)
store.dispatch(WalletAction.LoadPayId)
}
store.state.globalState.tapWalletManager.onCardScanned(result.data)
}
}
}

View file

@ -1,6 +1,7 @@
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
@ -17,12 +18,19 @@ 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]
@ -38,6 +46,8 @@ fun walletReducer(action: Action, state: AppState): WalletState {
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(
@ -45,14 +55,15 @@ fun walletReducer(action: Action, state: AppState): WalletState {
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)
newState.wallet?.amounts?.get(AmountType.Coin)?.value?.toFiatString(rate)
} else {
null
}

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

@ -60,14 +60,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

@ -2,14 +2,16 @@ 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> {
suspend fun getRate(currency: String): Result<BigDecimal> = withContext(Dispatchers.IO) {
val response = performRequest { api.getRateInfo(1, currency) }
return when (response) {
return@withContext when (response) {
is Result.Success -> Result.Success(response.data.data.quote.usd.price)
is Result.Failure -> response
}