Updated on 2026-08-14

This commit is contained in:
Tangem 2022-02-24 13:51:40 +03:00
commit c9fc738114
47 changed files with 1041 additions and 451 deletions

View file

@ -15,6 +15,7 @@ import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.BackupInPro
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.ConfirmDiscardingBackupDialog
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.UnfinishedBackupFoundDialog
import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog
import com.tangem.tap.features.wallet.ui.dialogs.SimpleOkDialog
import com.tangem.tap.store
import com.tangem.wallet.R
import org.rekotlin.StoreSubscriber
@ -48,6 +49,8 @@ class DialogManager : StoreSubscriber<GlobalState> {
if (dialog != null) return
dialog = when (state.dialog) {
is AppDialog.SimpleOkDialog -> SimpleOkDialog.create(state.dialog, context)
is AppDialog.SimpleOkDialogRes -> SimpleOkDialog.create(state.dialog, context)
is AppDialog.ScanFailsDialog -> ScanFailsDialog.create(context)
is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context)
is TwinCardsAction.Wallet.ShowInterruptDialog -> CreateWalletInterruptDialog.create(state.dialog, context)

View file

@ -8,18 +8,26 @@ import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.amountToCreateAccount
import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.domain.topup.TradeCryptoHelper
import com.tangem.tap.features.demo.isDemoWallet
import com.tangem.tap.features.wallet.redux.AddressData
import com.tangem.tap.features.wallet.redux.reducers.createAddressesData
import com.tangem.tap.network.NetworkConnectivity
import com.tangem.tap.store
import timber.log.Timber
/**
[REDACTED_AUTHOR]
*/
suspend fun WalletManager.safeUpdate(): Result<Wallet> = try {
update()
Result.Success(wallet)
if (isDemoWallet()) {
Result.Success(wallet)
} else {
update()
Result.Success(wallet)
}
} catch (exception: Exception) {
Timber.e(exception)
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
Result.Failure(TapError.NoInternetConnection)
} else {

View file

@ -9,6 +9,8 @@ import com.tangem.tap.features.wallet.redux.Currency
interface StateDialog
sealed class AppDialog : StateDialog {
data class SimpleOkDialog(val header: String, val message: String) : AppDialog()
data class SimpleOkDialogRes(val headerId: Int, val messageId: Int) : AppDialog()
object ScanFailsDialog : AppDialog()
data class AddressInfoDialog(
val currency: Currency,

View file

@ -10,9 +10,11 @@ import com.tangem.tap.currenciesRepository
import com.tangem.tap.domain.TapWorkarounds.isStart2Coin
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.extensions.*
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
import com.tangem.tap.domain.extensions.makeWalletManagersForApp
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.domain.tokens.CardCurrencies
import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.network.NetworkConnectivity
@ -207,6 +209,9 @@ class TapWalletManager {
data.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> {
WalletAction.LoadData.Failure(TapError.UnknownBlockchain)
}
data.isDemoCard() -> {
return null
}
data.card.wallets.isEmpty() -> {
WalletAction.EmptyWallet
}

View file

@ -10,7 +10,7 @@ import com.tangem.wallet.R
[REDACTED_AUTHOR]
*/
class WarningMessagesManager(
private val warningLoader: RemoteWarningLoader,
private val warningLoader: RemoteWarningLoader,
) {
private val warningsList: MutableList<WarningMessage> = mutableListOf()
@ -33,15 +33,15 @@ class WarningMessagesManager(
fun getWarnings(location: WarningMessage.Location, forBlockchains: List<Blockchain> = emptyList()): List<WarningMessage> {
return warningsList
.filter { !it.isHidden && it.location.contains(location) }
.filter {
val list = it.blockchainList
when {
list == null -> true
list.containsAny(forBlockchains) -> true
else -> false
}
.filter { !it.isHidden && it.location.contains(location) }
.filter {
val list = it.blockchainList
when {
list == null -> true
list.containsAny(forBlockchains) -> true
else -> false
}
}
}
fun hideWarning(warning: WarningMessage): Boolean {
@ -49,7 +49,7 @@ class WarningMessagesManager(
return when {
foundWarning == null -> false
foundWarning.type == WarningMessage.Type.Temporary
|| foundWarning.type == WarningMessage.Type.AppRating -> {
|| foundWarning.type == WarningMessage.Type.AppRating -> {
if (foundWarning.isHidden) {
false
} else {
@ -80,32 +80,32 @@ class WarningMessagesManager(
companion object {
fun devCardWarning(): WarningMessage = WarningMessage(
"",
"",
type = WarningMessage.Type.Permanent,
priority = WarningMessage.Priority.Critical,
listOf(WarningMessage.Location.MainScreen),
null,
R.string.alert_title,
R.string.alert_developer_card,
WarningMessage.Origin.Local
"",
"",
type = WarningMessage.Type.Permanent,
priority = WarningMessage.Priority.Critical,
listOf(WarningMessage.Location.MainScreen),
null,
R.string.alert_title,
R.string.alert_developer_card,
WarningMessage.Origin.Local
)
fun alreadySignedHashesWarning(): WarningMessage = WarningMessage(
"",
"",
type = WarningMessage.Type.Temporary,
priority = WarningMessage.Priority.Info,
listOf(WarningMessage.Location.MainScreen),
null,
R.string.alert_title,
R.string.alert_card_signed_transactions,
WarningMessage.Origin.Local
"",
"",
type = WarningMessage.Type.Temporary,
priority = WarningMessage.Priority.Info,
listOf(WarningMessage.Location.MainScreen),
null,
R.string.alert_title,
R.string.alert_card_signed_transactions,
WarningMessage.Origin.Local
)
fun signedHashesMultiWalletWarning(): WarningMessage = WarningMessage(
title = "",
message = "",
message = "",
type = WarningMessage.Type.Temporary,
priority = WarningMessage.Priority.Info,
location = listOf(WarningMessage.Location.MainScreen),
@ -117,15 +117,15 @@ class WarningMessagesManager(
)
fun appRatingWarning(): WarningMessage = WarningMessage(
"",
"",
WarningMessage.Type.AppRating,
WarningMessage.Priority.Info,
listOf(WarningMessage.Location.MainScreen),
null,
R.string.warning_rate_app_title,
R.string.warning_rate_app_message,
WarningMessage.Origin.Local
"",
"",
WarningMessage.Type.AppRating,
WarningMessage.Priority.Info,
listOf(WarningMessage.Location.MainScreen),
null,
R.string.warning_rate_app_title,
R.string.warning_rate_app_message,
WarningMessage.Origin.Local
)
fun isAlreadySignedHashesWarning(warning: WarningMessage): Boolean {
@ -133,15 +133,15 @@ class WarningMessagesManager(
}
fun onlineVerificationFailed(): WarningMessage = WarningMessage(
"",
"",
type = WarningMessage.Type.Permanent,
priority = WarningMessage.Priority.Critical,
listOf(WarningMessage.Location.MainScreen),
null,
R.string.warning_failed_to_verify_card_title,
R.string.warning_failed_to_verify_card_message,
WarningMessage.Origin.Local
"",
"",
type = WarningMessage.Type.Permanent,
priority = WarningMessage.Priority.Critical,
listOf(WarningMessage.Location.MainScreen),
null,
R.string.warning_failed_to_verify_card_title,
R.string.warning_failed_to_verify_card_message,
WarningMessage.Origin.Local
)
fun remainingSignaturesNotEnough(remainingSignatures: Int): WarningMessage = WarningMessage(
@ -169,6 +169,18 @@ class WarningMessagesManager(
WarningMessage.Origin.Local
)
fun demoCardWarning(): WarningMessage = WarningMessage(
"",
"",
type = WarningMessage.Type.Permanent,
priority = WarningMessage.Priority.Critical,
listOf(WarningMessage.Location.MainScreen),
null,
R.string.alert_title,
R.string.alert_demo_message,
WarningMessage.Origin.Local
)
const val REMAINING_SIGNATURES_WARNING = 10
}
}

View file

@ -5,8 +5,10 @@ import com.tangem.common.card.EllipticCurve
import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.toMapKey
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.operations.CommandResponse
import com.tangem.operations.backup.PrimaryCard
import com.tangem.operations.backup.StartPrimaryCardLinkingTask
@ -16,10 +18,12 @@ import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.operations.wallet.CreateWalletTask
import com.tangem.tap.domain.ProductType
import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.tasks.product.CreateWalletsTask
import com.tangem.tap.domain.tasks.product.KeyWalletPublicKey
import com.tangem.tap.domain.tasks.product.ProductCommandProcessor
import com.tangem.tap.domain.tasks.product.getCurvesForNonCreatedWallets
import com.tangem.tap.features.demo.DemoHelper
data class CreateProductWalletTaskResponse(
@ -96,25 +100,27 @@ private class CreateWalletTangemNote : ProductCommandProcessor<CreateWalletRespo
private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWalletTaskResponse> {
private lateinit var card: Card
private var primaryCard: PrimaryCard? = null
private var createWalletResponse: CreateWalletResponse? = null
override fun proceed(
card: Card,
session: CardSession,
callback: (result: CompletionResult<CreateProductWalletTaskResponse>) -> Unit,
) {
this.card = card
val curves = card.getCurvesForNonCreatedWallets()
CreateWalletsTask(curves).run(session) { result ->
when (result) {
is CompletionResult.Success -> {
createWalletResponse = result.data.createWalletResponses[0]
val createWalletResponses = result.data.createWalletResponses
when {
card.settings.isBackupAllowed -> {
linkPrimaryCard(session, callback)
linkPrimaryCard(createWalletResponses, session, callback)
}
card.settings.isHDWalletAllowed -> {
deriveKeys(session, callback)
deriveKeys(createWalletResponses, session, callback)
}
else -> {
callback(
@ -124,7 +130,6 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWa
)
}
}
}
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
}
@ -132,6 +137,7 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWa
}
private fun linkPrimaryCard(
createWalletResponse: List<CreateWalletResponse>,
session: CardSession,
callback: (result: CompletionResult<CreateProductWalletTaskResponse>) -> Unit,
) {
@ -139,7 +145,7 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWa
when (result) {
is CompletionResult.Success -> {
primaryCard = result.data
deriveKeys(session, callback)
deriveKeys(createWalletResponse, session, callback)
}
is CompletionResult.Failure -> {
callback(CompletionResult.Failure(result.error))
@ -149,28 +155,26 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWa
}
private fun deriveKeys(
createWalletResponse: List<CreateWalletResponse>,
session: CardSession,
callback: (result: CompletionResult<CreateProductWalletTaskResponse>) -> Unit,
) {
val derivationPaths = listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
.mapNotNull { it.derivationPath() }
val response = createWalletResponse.guard {
val map = mutableMapOf<ByteArrayKey, List<DerivationPath>>()
createWalletResponse.forEach { response ->
val blockchainsForCurve = getBlockchains(response.cardId).filter {
it.getSupportedCurves().contains(response.wallet.curve)
}
val derivationPaths = blockchainsForCurve.mapNotNull { it.derivationPath() }
if (derivationPaths.isNotEmpty()) {
map[response.wallet.publicKey.toMapKey()] = derivationPaths
}
}
if (map.isEmpty()) {
callback(CompletionResult.Failure(TangemSdkError.UnknownError()))
return
}
if (derivationPaths.isNullOrEmpty()) {
callback(
CompletionResult.Success(
CreateProductWalletTaskResponse(
card = session.environment.card!!, primaryCard = primaryCard
)
)
)
return
}
DeriveMultipleWalletPublicKeysTask(mapOf(response.wallet.publicKey.toMapKey() to derivationPaths))
DeriveMultipleWalletPublicKeysTask(map)
.run(session) { result ->
when (result) {
is CompletionResult.Success -> {
@ -188,4 +192,12 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWa
}
}
}
private fun getBlockchains(cardId: String): List<Blockchain> {
return when {
DemoHelper.isDemoCardId(cardId) -> DemoHelper.config.demoBlockchains
card.isTestCard -> listOf(Blockchain.BitcoinTestnet, Blockchain.EthereumTestnet)
else -> listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
}
}
}

View file

@ -13,9 +13,11 @@ import com.tangem.tap.common.extensions.appendIf
import com.tangem.tap.common.extensions.readJsonFileToString
import com.tangem.tap.domain.extensions.getCustomIconUrl
import com.tangem.tap.domain.extensions.setCustomIconUrl
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.network.createMoshi
class CurrenciesRepository(val context: Application) {
private val moshi = createMoshi()
private val blockchainsAdapter: JsonAdapter<List<Blockchain>> = moshi.adapter(
Types.newParameterizedType(List::class.java, Blockchain::class.java)
@ -28,10 +30,13 @@ class CurrenciesRepository(val context: Application) {
)
fun loadCardCurrencies(cardId: String): CardCurrencies? {
val blockchains = loadSavedBlockchains(cardId)
val blockchains = loadSavedBlockchains(cardId).toMutableSet()
if (DemoHelper.isDemoCardId(cardId)) {
blockchains.addAll(DemoHelper.config.demoBlockchains)
}
if (blockchains.isEmpty()) return null
return CardCurrencies(loadSavedTokens(cardId), blockchains)
return CardCurrencies(loadSavedTokens(cardId), blockchains.toList())
}
fun saveCardCurrencies(cardId: String, currencies: CardCurrencies) {
@ -164,11 +169,10 @@ class CurrenciesRepository(val context: Application) {
return excludeUnsupportedBlockchains(blockchains)
}
//TODO: move to the App settings
private fun excludeUnsupportedBlockchains(blockchains: List<Blockchain>): List<Blockchain> {
return blockchains.toMutableList().apply {
removeAll(listOf(
Blockchain.Fantom, Blockchain.FantomTestnet
// Blockchain.Fantom, Blockchain.FantomTestnet
))
}
}

View file

@ -6,6 +6,7 @@ import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.extensions.Result
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TangemSigner
import com.tangem.tap.store
@ -14,7 +15,7 @@ import java.math.BigDecimal
class TopUpManager {
suspend fun topUpTestErc20Tokens(walletManager: EthereumWalletManager, token: Token) {
walletManager.update()
walletManager.safeUpdate()
val amountToSend = Amount(walletManager.wallet.blockchain)
val destinationAddress = token.contractAddress

View file

@ -16,6 +16,7 @@ import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
*/
class TwinsCardWidget(
val leapfrogWidget: LeapfrogWidget,
private val deviceScaleFactor: Float = 1f,
val getTopOfAnchorViewForActivateState: () -> Float
) {
@ -87,20 +88,20 @@ class TwinsCardWidget(
return when (cardNumber) {
TwinCardNumber.First -> {
TwinsCardProperties(
xTranslation = -120f,
yTranslation = -220f,
xTranslation = -120f * deviceScaleFactor,
yTranslation = -220f * deviceScaleFactor,
rotation = -3f,
elevation = 1f,
scale = 1f,
scale = deviceScaleFactor,
)
}
TwinCardNumber.Second -> {
TwinsCardProperties(
xTranslation = 170f,
yTranslation = 170f,
xTranslation = 170f * deviceScaleFactor,
yTranslation = 170f * deviceScaleFactor,
rotation = -3f,
elevation = 0f,
scale = 1f,
scale = deviceScaleFactor,
)
}
}
@ -123,7 +124,7 @@ class TwinsCardWidget(
xTranslation = twinProperties.xTranslation,
yTranslation = twinProperties.yTranslation - topOfAnchorView,
rotation = 0f,
scale = twinProperties.scale - 0.5f,
scale = (twinProperties.scale - 0.5f) * deviceScaleFactor,
)
}

View file

@ -15,6 +15,7 @@ import com.tangem.common.extensions.toHexString
import com.tangem.crypto.CryptoUtils
import com.tangem.operations.sign.SignHashCommand
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.extensions.toFormattedString
import com.tangem.tap.features.details.redux.walletconnect.*
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
@ -35,18 +36,11 @@ class WalletConnectSdkHelper {
id: Long,
type: WcTransactionType,
): WcTransactionData? {
val walletManager = getWalletManager(session) ?: return null
try {
walletManager.update()
} catch (exception: Exception) {
Timber.e(exception)
return null
}
walletManager.safeUpdate()
val wallet = walletManager.wallet
val balance =
wallet.amounts[AmountType.Coin]?.value ?: return null
val balance = wallet.amounts[AmountType.Coin]?.value ?: return null
val gas = transaction.gas?.hexToBigDecimal()
?: transaction.gasLimit?.hexToBigDecimal()

View file

@ -0,0 +1,153 @@
package com.tangem.tap.features.demo
import com.tangem.blockchain.common.*
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.tap.common.extensions.dispatchNotification
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.store
import com.tangem.wallet.BuildConfig
import com.tangem.wallet.R
import org.rekotlin.Action
import java.math.BigDecimal
/**
[REDACTED_AUTHOR]
*/
interface DemoMiddleware {
fun tryHandle(config: DemoConfig, scanResponse: ScanResponse, action: Action): Boolean
}
object DemoHelper {
val config = DemoConfig()
private val demoMiddlewares = listOf(
DemoOnboardingNoteMiddleware(),
)
private val disabledActionFeatures = listOf(
WalletConnectAction.StartWalletConnect::class.java,
WalletAction.TradeCryptoAction.Buy::class.java,
WalletAction.TradeCryptoAction.Sell::class.java,
BackupAction.StartBackup::class.java,
WalletAction.ExploreAddress::class.java
)
fun isDemoCard(scanResponse: ScanResponse): Boolean = isDemoCardId(scanResponse.card.cardId)
fun isDemoCardId(cardId: String): Boolean = config.isDemoCardId(cardId)
fun tryHandle(appState: () -> AppState?, action: Action): Boolean {
val scanResponse = getScanResponse(appState) ?: return false
if (!scanResponse.isDemoCard()) return false
demoMiddlewares.forEach {
if (it.tryHandle(config, scanResponse, action)) return true
}
disabledActionFeatures.firstOrNull { it == action::class.java }?.let {
store.dispatchNotification(R.string.alert_demo_feature_disabled)
return true
}
return false
}
fun injectDemoBalance(walletManager: WalletManager?) {
val manager = walletManager ?: return
val blockchain = walletManager.wallet.blockchain
val amount = config.getBalance(blockchain)
manager.wallet.setAmount(amount)
}
private fun getScanResponse(appState: () -> AppState?): ScanResponse? {
val state = appState() ?: return null
return state.globalState.onboardingState.onboardingManager?.scanResponse
?: state.globalState.scanResponse
}
}
class DemoConfig {
val demoBlockchains = listOf(
Blockchain.Bitcoin,
Blockchain.Ethereum,
Blockchain.Dogecoin,
Blockchain.Solana,
)
val demoCardIds: List<String> by lazy {
val demoIds = (releaseDemoCardIds + testDemoCardIds).toMutableList()
if (BuildConfig.DEBUG) demoIds.addAll(debugTestDemoCardIds)
return@lazy demoIds.distinct()
}
private val walletBalances: Map<Blockchain, Amount> = mapOf(
Blockchain.Bitcoin to Amount(0.028.toBigDecimal(), Blockchain.Bitcoin),
Blockchain.Ethereum to Amount(0.2311.toBigDecimal(), Blockchain.Ethereum),
Blockchain.Dogecoin to Amount(1450.025.toBigDecimal(), Blockchain.Dogecoin),
Blockchain.Solana to Amount(13.246.toBigDecimal(), Blockchain.Solana),
)
fun isDemoCardId(cardId: String): Boolean = demoCardIds.contains(cardId)
fun getBalance(blockchain: Blockchain): Amount = walletBalances[blockchain]?.copy()
?: Amount(BigDecimal.ZERO, blockchain).copy()
private val releaseDemoCardIds = mutableListOf<String>(
)
private val testDemoCardIds = listOf(
"FB20000000000186", // Note ETH
"FB10000000000196", // Note BTC
"FB30000000000176", // Wallet
//TODO: delete bellow ids before 3.28 release
"AB01000000045060", // Note BTC
"AB02000000045028", // Note ETH
"AC79000000000004", // Wallet 4.46
)
private val debugTestDemoCardIds = listOf(
"AB01000000045060", // Note BTC
"AB02000000045028", // Note ETH
"AC79000000000004", // Wallet 4.46
)
}
class DemoTransactionSender(
private val walletManager: WalletManager,
private val sender: TransactionSender = walletManager as TransactionSender
) : TransactionSender {
override suspend fun getFee(amount: Amount, destination: String): Result<List<Amount>> =
sender.getFee(amount, destination)
override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult {
val dataToSign = randomString(32).toByteArray()
val signerResponse = signer.sign(dataToSign, walletManager.wallet.cardId, walletManager.wallet.publicKey)
return SimpleResult.Failure(Exception(ID))
}
private fun randomInt(from: Int, to: Int): Int = kotlin.random.Random.nextInt(from, to)
private fun randomString(length: Int): String {
val charPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9')
return (1..length)
.map { randomInt(0, charPool.size) }
.map(charPool::get)
.joinToString("")
}
companion object {
val ID = DemoTransactionSender::class.java.simpleName
}
}

View file

@ -0,0 +1,58 @@
package com.tangem.tap.features.demo
import com.tangem.common.extensions.guard
import com.tangem.tap.common.extensions.withMainContext
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.scope
import com.tangem.tap.store
import kotlinx.coroutines.launch
import org.rekotlin.Action
/**
[REDACTED_AUTHOR]
*/
internal class DemoOnboardingNoteMiddleware : DemoMiddleware {
override fun tryHandle(config: DemoConfig, scanResponse: ScanResponse, action: Action): Boolean {
val globalState = store.state.globalState
val noteState = store.state.onboardingNoteState
when (action) {
is OnboardingNoteAction.Balance.Update -> {
val walletManager = if (noteState.walletManager != null) {
noteState.walletManager
} else {
val wmFactory = globalState.tapWalletManager.walletManagerFactory
val walletManager = wmFactory.makePrimaryWalletManager(scanResponse).guard {
return false
}
store.dispatch(OnboardingNoteAction.SetWalletManager(walletManager))
walletManager
}
val balanceAmount = config.getBalance(walletManager.wallet.blockchain)
val loadedBalance = noteState.walletBalance.copy(
value = balanceAmount.value!!,
currency = Currency.Blockchain(walletManager.wallet.blockchain),
state = ProgressState.Done,
error = null,
criticalError = null
)
walletManager.wallet.setAmount(balanceAmount)
scope.launch {
withMainContext {
store.dispatch(OnboardingNoteAction.Balance.Set(loadedBalance))
store.dispatch(OnboardingNoteAction.Balance.SetCriticalError(loadedBalance.criticalError))
store.dispatch(OnboardingNoteAction.Balance.SetNonCriticalError(loadedBalance.error))
}
}
return true
}
}
return false
}
}

View file

@ -0,0 +1,12 @@
package com.tangem.tap.features.demo
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.tap.domain.tasks.product.ScanResponse
/**
[REDACTED_AUTHOR]
*/
fun ScanResponse.isDemoCard(): Boolean = DemoHelper.isDemoCardId(card.cardId)
fun WalletManager.isDemoWallet(): Boolean = DemoHelper.isDemoCardId(wallet.cardId)
fun Wallet.isDemoWallet(): Boolean = DemoHelper.isDemoCardId(cardId)

View file

@ -17,9 +17,11 @@ import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.domain.walletconnect.BnbHelper
import com.tangem.tap.domain.walletconnect.WalletConnectManager
import com.tangem.tap.domain.walletconnect.WalletConnectNetworkUtils
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.store
import com.tangem.wallet.R
import org.rekotlin.Action
import org.rekotlin.Middleware
class WalletConnectMiddleware {
@ -28,138 +30,131 @@ class WalletConnectMiddleware {
val walletConnectMiddleware: Middleware<AppState> = { dispatch, state ->
{ next ->
{ action ->
when (action) {
handle(state, action)
next(action)
}
}
}
is WalletConnectAction.RestoreSessions -> {
walletConnectManager.restoreSessions()
}
private fun handle(state: () -> AppState?, action: Action) {
if (DemoHelper.tryHandle(state, action)) return
is WalletConnectAction.HandleDeepLink -> {
if (!action.wcUri.isNullOrBlank()) {
if (WalletConnectManager.isCorrectWcUri(action.wcUri)) {
store.dispatchOnMain(WalletConnectAction.OpenSession(action.wcUri))
}
}
}
is WalletConnectAction.StartWalletConnect -> {
val uri = action.activity.getFromClipboard()?.toString()
if (uri != null && WalletConnectManager.isCorrectWcUri(uri)) {
store.dispatchOnMain(WalletConnectAction.ShowClipboardOrScanQrDialog(uri))
} else {
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.QrScan))
}
}
is WalletConnectAction.ShowClipboardOrScanQrDialog -> {
store.dispatchOnMain(
GlobalAction.ShowDialog(
WalletConnectDialog.ClipboardOrScanQr(
action.wcUri
)
)
)
}
is WalletConnectAction.OpeningSessionTimeout -> {
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.SessionTimeout))
}
is WalletConnectAction.FailureEstablishingSession -> {
if (action.session != null) {
walletConnectManager.disconnect(action.session)
}
}
is WalletConnectAction.UnsupportedCard -> {
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedCard))
}
is WalletConnectAction.OpenSession -> {
walletConnectManager.connect(
wcUri = action.wcUri,
)
}
is WalletConnectAction.RefuseOpeningSession -> {
store.dispatch(
GlobalAction.ShowDialog(
WalletConnectDialog.OpeningSessionRejected
)
)
}
is WalletConnectAction.ScanCard -> {
scanCard(action.session, action.chainId)
}
is WalletConnectAction.ApproveSession -> {
walletConnectManager.approve(action.session)
}
is WalletConnectAction.DisconnectSession -> {
walletConnectManager.disconnect(action.session)
}
is WalletConnectAction.HandleTransactionRequest -> {
walletConnectManager.handleTransactionRequest(
transaction = action.transaction,
session = action.session,
id = action.id,
type = action.type
)
}
is WalletConnectAction.HandlePersonalSignRequest -> {
walletConnectManager.handlePersonalSignRequest(
message = action.message,
session = action.session,
id = action.id
)
}
is WalletConnectAction.RejectRequest -> {
walletConnectManager.rejectRequest(action.session, action.id)
}
is WalletConnectAction.SendTransaction -> {
walletConnectManager.completeTransaction(action.session)
}
is WalletConnectAction.SignMessage -> {
walletConnectManager.sendSignedMessage(action.session)
}
is WalletConnectAction.BinanceTransaction.Trade -> {
val messageData = BnbHelper.createMessageData(action.order)
store.dispatchOnMain(
GlobalAction.ShowDialog(
WalletConnectDialog.BnbTransactionDialog(
data = messageData,
session = action.sessionData.session,
sessionId = action.id,
cardId = action.sessionData.wallet.cardId,
dAppName = action.sessionData.peerMeta.name
)
)
)
}
is WalletConnectAction.BinanceTransaction.Transfer -> {
val messageData = BnbHelper.createMessageData(action.order)
store.dispatchOnMain(
GlobalAction.ShowDialog(
WalletConnectDialog.BnbTransactionDialog(
data = messageData,
session = action.sessionData.session,
sessionId = action.id,
cardId = action.sessionData.wallet.cardId,
dAppName = action.sessionData.peerMeta.name
)
)
)
}
is WalletConnectAction.BinanceTransaction.Sign -> {
walletConnectManager.signBnb(
action.id, action.data, action.sessionData
)
when (action) {
is WalletConnectAction.RestoreSessions -> {
walletConnectManager.restoreSessions()
}
is WalletConnectAction.HandleDeepLink -> {
if (!action.wcUri.isNullOrBlank()) {
if (WalletConnectManager.isCorrectWcUri(action.wcUri)) {
store.dispatchOnMain(WalletConnectAction.OpenSession(action.wcUri))
}
}
next(action)
}
is WalletConnectAction.StartWalletConnect -> {
val uri = action.activity.getFromClipboard()?.toString()
if (uri != null && WalletConnectManager.isCorrectWcUri(uri)) {
store.dispatchOnMain(WalletConnectAction.ShowClipboardOrScanQrDialog(uri))
} else {
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.QrScan))
}
}
is WalletConnectAction.ShowClipboardOrScanQrDialog -> {
store.dispatchOnMain(
GlobalAction.ShowDialog(
WalletConnectDialog.ClipboardOrScanQr(
action.wcUri
)
)
)
}
is WalletConnectAction.OpeningSessionTimeout -> {
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.SessionTimeout))
}
is WalletConnectAction.FailureEstablishingSession -> {
if (action.session != null) {
walletConnectManager.disconnect(action.session)
}
}
is WalletConnectAction.UnsupportedCard -> {
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedCard))
}
is WalletConnectAction.OpenSession -> {
walletConnectManager.connect(
wcUri = action.wcUri,
)
}
is WalletConnectAction.RefuseOpeningSession -> {
store.dispatch(
GlobalAction.ShowDialog(
WalletConnectDialog.OpeningSessionRejected
)
)
}
is WalletConnectAction.ScanCard -> {
scanCard(action.session, action.chainId)
}
is WalletConnectAction.ApproveSession -> {
walletConnectManager.approve(action.session)
}
is WalletConnectAction.DisconnectSession -> {
walletConnectManager.disconnect(action.session)
}
is WalletConnectAction.HandleTransactionRequest -> {
walletConnectManager.handleTransactionRequest(
transaction = action.transaction,
session = action.session,
id = action.id,
type = action.type
)
}
is WalletConnectAction.HandlePersonalSignRequest -> {
walletConnectManager.handlePersonalSignRequest(
message = action.message,
session = action.session,
id = action.id
)
}
is WalletConnectAction.RejectRequest -> {
walletConnectManager.rejectRequest(action.session, action.id)
}
is WalletConnectAction.SendTransaction -> {
walletConnectManager.completeTransaction(action.session)
}
is WalletConnectAction.SignMessage -> {
walletConnectManager.sendSignedMessage(action.session)
}
is WalletConnectAction.BinanceTransaction.Trade -> {
val messageData = BnbHelper.createMessageData(action.order)
store.dispatchOnMain(
GlobalAction.ShowDialog(
WalletConnectDialog.BnbTransactionDialog(
data = messageData,
session = action.sessionData.session,
sessionId = action.id,
cardId = action.sessionData.wallet.cardId,
dAppName = action.sessionData.peerMeta.name
)
)
)
}
is WalletConnectAction.BinanceTransaction.Transfer -> {
val messageData = BnbHelper.createMessageData(action.order)
store.dispatchOnMain(
GlobalAction.ShowDialog(
WalletConnectDialog.BnbTransactionDialog(
data = messageData,
session = action.sessionData.session,
sessionId = action.id,
cardId = action.sessionData.wallet.cardId,
dAppName = action.sessionData.peerMeta.name
)
)
)
}
is WalletConnectAction.BinanceTransaction.Sign -> {
walletConnectManager.signBnb(
action.id, action.data, action.sessionData
)
}
}
}
@ -190,9 +185,9 @@ class WalletConnectMiddleware {
}
val walletManager = getWalletManager(scanResponse, blockchain).guard {
store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(null))
return
}
store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(null))
return
}
val wallet = walletManager.wallet
val derivedKey =
@ -227,7 +222,7 @@ class WalletConnectMiddleware {
): WalletManager? {
val card = scanResponse.card
val factory = store.state.globalState.tapWalletManager.walletManagerFactory
val blockchainToMake = if (blockchain == Blockchain.Ethereum && card.isTestCard) {
val blockchainToMake = if (blockchain == Blockchain.Ethereum && card.isTestCard) {
Blockchain.EthereumTestnet
} else {
blockchain

View file

@ -13,6 +13,7 @@ import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.hasWallets
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.scope
@ -33,14 +34,16 @@ class OnboardingNoteMiddleware {
private val onboardingNoteMiddleware: Middleware<AppState> = { dispatch, state ->
{ next ->
{ action ->
handleNoteAction(action, dispatch)
handleNoteAction(state, action, dispatch)
next(action)
}
}
}
private fun handleNoteAction(action: Action, dispatch: DispatchFunction) {
private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch: DispatchFunction) {
if (action !is OnboardingNoteAction) return
if (DemoHelper.tryHandle(appState, action)) return
val globalState = store.state.globalState
val onboardingManager = globalState.onboardingState.onboardingManager ?: return

View file

@ -1,6 +1,7 @@
package com.tangem.tap.features.onboarding.products.twins.ui
import android.os.Bundle
import android.util.TypedValue
import android.view.View
import android.view.animation.OvershootInterpolator
import androidx.annotation.LayoutRes
@ -69,10 +70,15 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
addBackPressHandler(this)
reconfigureLayoutForTwins(binding.onboardingTopContainer)
twinsWidget =
TwinsCardWidget(LeapfrogWidget(binding.onboardingTopContainer.cardsContainer)) { 285f }
btnRefreshBalanceWidget =
RefreshBalanceWidget(binding.onboardingTopContainer.onboardingMainContainer)
val typedValue = TypedValue()
resources.getValue(R.dimen.device_scale_factor_for_twins_welcome, typedValue, true)
val deviceScaleFactorForWelcomeState = typedValue.float
twinsWidget = TwinsCardWidget(LeapfrogWidget(binding.onboardingTopContainer.cardsContainer), deviceScaleFactorForWelcomeState) {
285f * deviceScaleFactorForWelcomeState
}
btnRefreshBalanceWidget = RefreshBalanceWidget(binding.onboardingTopContainer.onboardingMainContainer)
binding.toolbar.title = getText(R.string.twins_recreate_toolbar)

View file

@ -16,6 +16,7 @@ import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.extensions.hasWallets
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware.Companion.BUY_WALLET_URL
import com.tangem.tap.features.wallet.redux.Artwork
@ -164,14 +165,16 @@ class BackupMiddleware {
val backupMiddleware: Middleware<AppState> = { dispatch, state ->
{ next ->
{ action ->
if (action is BackupAction) handleBackupAction(action)
if (action is BackupAction) handleBackupAction(state, action)
next(action)
}
}
}
}
private fun handleBackupAction(action: BackupAction) {
private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) {
if (DemoHelper.tryHandle(appState, action)) return
val backupState = store.state.onboardingWalletState.backupState
val globalState = store.state.globalState

View file

@ -1,6 +1,7 @@
package com.tangem.tap.features.onboarding.products.wallet.ui
import android.os.Bundle
import android.util.TypedValue
import android.view.*
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
@ -14,6 +15,7 @@ import com.squareup.picasso.Picasso
import com.tangem.common.CardIdFormatter
import com.tangem.common.core.CardIdDisplayFormat
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.PropertyCalculator
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.features.FragmentOnBackPressedHandler
@ -51,8 +53,15 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val leapfrog = LeapfrogWidget(binding.flCardsContainer)
cardsWidget = WalletCardsWidget(leapfrog) { 200f }
val typedValue = TypedValue()
resources.getValue(R.dimen.device_scale_factor_for_twins_welcome, typedValue, true)
val deviceScaleFactor = typedValue.float
val leapfrogCalculator = PropertyCalculator(
yTranslationFactor = 25f * deviceScaleFactor,
)
val leapfrog = LeapfrogWidget(binding.flCardsContainer, leapfrogCalculator)
cardsWidget = WalletCardsWidget(leapfrog, deviceScaleFactor) { 200f * deviceScaleFactor }
startPostponedEnterTransition()
binding.viewPagerBackupInfo.adapter = BackupInfoAdapter()
@ -130,8 +139,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
tvHeader.setText(R.string.onboarding_create_wallet_header)
tvBody.setText(R.string.onboarding_create_wallet_body)
cardsWidget.toFolded()
startPostponedEnterTransition()
cardsWidget.toFolded(false) { startPostponedEnterTransition() }
}
@ -394,7 +402,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
val shopMenuShouldBeVisible =
(backupStep == BackupStep.ScanOriginCard || backupStep == BackupStep.AddBackupCards) &&
backupState.buyAdditionalCardsUrl != null
backupState.buyAdditionalCardsUrl != null
menu.getItem(0).isVisible = shopMenuShouldBeVisible
}

View file

@ -12,6 +12,7 @@ import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
class WalletCardsWidget(
val leapfrogWidget: LeapfrogWidget,
private val deviceScaleFactor: Float = 1f,
val getTopOfAnchorViewForActivateState: () -> Float,
) {
@ -42,7 +43,7 @@ class WalletCardsWidget(
createAnimator(BackupCardType.FIRST_BACKUP, createLeapfrogProperties(BackupCardType.FIRST_BACKUP)),
createAnimator(BackupCardType.SECOND_BACKUP, createLeapfrogProperties(BackupCardType.SECOND_BACKUP))
)
leapfrogWidget.fold { animator.start() }
leapfrogWidget.fold(animate) { animator.start() }
}
fun toFan(animate: Boolean = true, onEnd: () -> Unit = {}) {
@ -70,7 +71,7 @@ class WalletCardsWidget(
)
leapfrogWidget.fold {
animator.doOnEnd {
leapfrogWidget.initViews()
// leapfrogWidget.initViews()
leapfrogWidget.unfold()
}
animator.start()
@ -98,26 +99,26 @@ class WalletCardsWidget(
private fun createWelcomeProperties(cardType: BackupCardType): CardProperties {
return when (cardType) {
BackupCardType.ORIGIN -> CardProperties(
xTranslation = 440f,
yTranslation = 0f,
rotation = 75f,
elevation = 2f,
scale = .9f,
)
xTranslation = 440f * deviceScaleFactor,
yTranslation = 70f,
rotation = 75f,
elevation = 2f,
scale = .85f * deviceScaleFactor,
)
BackupCardType.FIRST_BACKUP -> CardProperties(
xTranslation = 10f,
yTranslation = -100f,
rotation = 100f,
elevation = 1f,
scale = .9f,
)
xTranslation = 10f * deviceScaleFactor,
yTranslation = -30f,
rotation = 100f,
elevation = 1f,
scale = .85f * deviceScaleFactor,
)
BackupCardType.SECOND_BACKUP -> CardProperties(
xTranslation = -440f,
yTranslation = -100f,
rotation = 70f,
elevation = 0f,
scale = .9f,
)
xTranslation = -440f * deviceScaleFactor,
yTranslation = -30f,
rotation = 70f,
elevation = 0f,
scale = .85f * deviceScaleFactor,
)
}
}
@ -125,24 +126,24 @@ class WalletCardsWidget(
return when (cardType) {
BackupCardType.ORIGIN -> CardProperties(
xTranslation = 0f,
yTranslation = 0f,
yTranslation = 30f,
rotation = 5f,
elevation = 2f,
scale = 1f,
scale = 1f * deviceScaleFactor,
)
BackupCardType.FIRST_BACKUP -> CardProperties(
xTranslation = 0f,
yTranslation = -80f,
yTranslation = -50f,
rotation = -5f,
elevation = 1f,
scale = 0.9f,
scale = 0.9f * deviceScaleFactor,
)
BackupCardType.SECOND_BACKUP -> CardProperties(
xTranslation = 10f,
yTranslation = -170f,
yTranslation = -120f,
rotation = -20f,
elevation = 0f,
scale = 0.8f,
scale = 0.8f * deviceScaleFactor,
)
}
}
@ -154,21 +155,21 @@ class WalletCardsWidget(
yTranslation = 0f,
rotation = 0f,
elevation = 2f,
scale = 1f,
scale = 1f * deviceScaleFactor,
)
BackupCardType.FIRST_BACKUP -> CardProperties(
xTranslation = 0f,
yTranslation = 0f,
rotation = 0f,
elevation = 1f,
scale = 0.9f,
scale = 0.9f * deviceScaleFactor,
)
BackupCardType.SECOND_BACKUP -> CardProperties(
xTranslation = 0f,
yTranslation = 0f,
rotation = 0f,
elevation = 0f,
scale = 0.8f,
scale = 0.8f * deviceScaleFactor,
)
}
}

View file

@ -13,6 +13,7 @@ import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.NavigationAction
@ -22,6 +23,8 @@ import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.TapWorkarounds.isStart2Coin
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.extensions.minimalAmount
import com.tangem.tap.features.demo.DemoTransactionSender
import com.tangem.tap.features.demo.isDemoWallet
import com.tangem.tap.features.send.redux.*
import com.tangem.tap.features.send.redux.FeeAction.RequestFee
import com.tangem.tap.features.send.redux.states.ButtonState
@ -32,6 +35,7 @@ import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdk
import com.tangem.wallet.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@ -164,9 +168,12 @@ private fun sendTransaction(
)
)
}
val sendResult = try {
(walletManager as TransactionSender).send(txData, signer)
if (walletManager.isDemoWallet()) {
DemoTransactionSender(walletManager).send(txData, signer)
} else {
(walletManager as TransactionSender).send(txData, signer)
}
} catch (ex: Exception) {
FirebaseCrashlytics.getInstance().recordException(ex)
delay(DELAY_SDK_DIALOG_CLOSE)
@ -238,6 +245,12 @@ private fun sendTransaction(
message.contains("Target account is not created. To create account send 1+ XLM.") -> {
dispatch(SendAction.SendError(TapError.XmlError.AssetAccountNotCreated))
}
message.contains(DemoTransactionSender.ID) -> {
store.dispatchDialogShow(AppDialog.SimpleOkDialogRes(
R.string.common_done,
R.string.alert_demo_tx_send
))
}
else -> {
(sendResult.error as? TangemSdkError)?.let { error ->
store.state.globalState.analyticsHandlers?.logCardSdkError(

View file

@ -6,10 +6,12 @@ import com.tangem.blockchain.common.TransactionStatus
import com.tangem.blockchain.common.Wallet
import com.tangem.tap.common.extensions.toFormattedString
import com.tangem.tap.domain.extensions.toSendableAmounts
import java.math.BigDecimal
data class PendingTransaction(
val address: String?,
val amount: String?,
val amount: BigDecimal?,
val amountUi: String?,
val currency: String,
val type: PendingTransactionType
)
@ -38,6 +40,7 @@ fun TransactionData.toPendingTransaction(walletAddress: String): PendingTransact
return PendingTransaction(
if (address == "unknown") null else address,
this.amount.value,
this.amount.value?.toFormattedString(amount.decimals),
this.amount.currencySymbol,
type
@ -61,8 +64,12 @@ fun List<TransactionData>.toPendingTransactionsForToken(token: Token, walletAddr
return this.mapNotNull { it.toPendingTransactionForToken(token, walletAddress) }
}
fun Wallet.getPendingTransactions(): List<PendingTransaction> {
return recentTransactions.toPendingTransactions(address)
fun Wallet.getPendingTransactions(type: PendingTransactionType? = null): List<PendingTransaction> {
val txs = recentTransactions.toPendingTransactions(address)
return when(type) {
null -> txs
else -> txs.filter { it.type == type }
}
}
fun Wallet.hasPendingTransactions(): Boolean {

View file

@ -13,6 +13,10 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.currenciesRepository
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
import com.tangem.tap.domain.extensions.makeWalletManagersForApp
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.wallet.models.PendingTransactionType
import com.tangem.tap.features.wallet.models.getPendingTransactions
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletState
@ -22,6 +26,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.math.BigDecimal
class MultiWalletMiddleware {
fun handle(
@ -34,6 +39,9 @@ class MultiWalletMiddleware {
is WalletAction.MultiWallet.AddWalletManagers -> {
globalState.feedbackManager?.infoHolder?.setWalletsInfo(action.walletManagers)
action.walletManagers.forEach { checkForRentWarning(it) }
if (globalState.scanResponse?.isDemoCard() == true) {
addDummyBalances(action.walletManagers)
}
}
is WalletAction.MultiWallet.SelectWallet -> {
if (action.walletData != null) {
@ -99,28 +107,25 @@ class MultiWalletMiddleware {
scope.launch {
walletManagers.map { walletManager ->
async(Dispatchers.IO) {
try {
walletManager.update()
val wallet = walletManager.wallet
val coinAmount = wallet.amounts[AmountType.Coin]?.value
if (coinAmount != null && !coinAmount.isZero()) {
scope.launch(Dispatchers.Main) {
if (walletState?.getWalletData(wallet.blockchain) == null) {
store.dispatch(
WalletAction.MultiWallet.AddWalletManagers(
listOfNotNull(walletManager)
)
walletManager.safeUpdate()
val wallet = walletManager.wallet
val coinAmount = wallet.amounts[AmountType.Coin]?.value
if (coinAmount != null && !coinAmount.isZero()) {
scope.launch(Dispatchers.Main) {
if (walletState?.getWalletData(wallet.blockchain) == null) {
store.dispatch(
WalletAction.MultiWallet.AddWalletManagers(
listOfNotNull(walletManager)
)
store.dispatch(
WalletAction.MultiWallet.AddBlockchain(
wallet.blockchain
)
)
store.dispatch(
WalletAction.MultiWallet.AddBlockchain(
wallet.blockchain
)
store.dispatch(WalletAction.LoadWallet.Success(wallet))
}
)
store.dispatch(WalletAction.LoadWallet.Success(wallet))
}
}
} catch (exception: Exception) {
}
}
}
@ -167,6 +172,14 @@ class MultiWalletMiddleware {
}
}
private fun addDummyBalances(walletManagers: List<WalletManager>) {
walletManagers.forEach {
if (it.wallet.fundsAvailable(AmountType.Coin) == BigDecimal.ZERO) {
DemoHelper.injectDemoBalance(it)
}
}
}
private fun addToken(token: Token, walletState: WalletState?, globalState: GlobalState?) {
val scanResponse = globalState?.scanResponse ?: return
@ -206,13 +219,25 @@ class MultiWalletMiddleware {
scope.launch {
when (val result = rentProvider.minimalBalanceForRentExemption()) {
is Result.Success -> {
fun isNeedToShowWarning(balance: BigDecimal, rentExempt: BigDecimal): Boolean = balance >= rentExempt
val balance = walletManager.wallet.fundsAvailable(AmountType.Coin)
val outgoingTxs = walletManager.wallet.getPendingTransactions(PendingTransactionType.Outgoing)
val rentExempt = result.data
val show = if (outgoingTxs.isEmpty()) {
isNeedToShowWarning(balance, rentExempt)
} else {
val outgoingAmount = outgoingTxs.sumOf { it.amount ?: BigDecimal.ZERO }
val rest = balance.minus(outgoingAmount)
isNeedToShowWarning(rest, rentExempt)
}
if (!show) return@launch
val currency = walletManager.wallet.blockchain.currency
val minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency")
val rentExempt = ("${result.data.stripZeroPlainString()} $currency")
store.dispatchOnMain(WalletAction.SetWalletRent(
blockchain = walletManager.wallet.blockchain,
minRent = minRent,
rentExempt = rentExempt
minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency"),
rentExempt = ("${rentExempt.stripZeroPlainString()} $currency")
))
}
is Result.Failure -> {}

View file

@ -3,10 +3,12 @@ package com.tangem.tap.features.wallet.redux.middlewares
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
import com.tangem.blockchain.common.AmountType
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.topup.TopUpManager
import com.tangem.tap.domain.topup.TradeCryptoHelper
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.send.redux.PrepareSendScreen
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.features.wallet.redux.Currency
@ -18,7 +20,9 @@ import timber.log.Timber
class TradeCryptoMiddleware {
fun handle(action: WalletAction.TradeCryptoAction) {
fun handle(state: () -> AppState?, action: WalletAction.TradeCryptoAction) {
if (DemoHelper.tryHandle(state, action)) return
when (action) {
is WalletAction.TradeCryptoAction.Buy -> startExchange(action)
is WalletAction.TradeCryptoAction.Sell -> startExchange(action)

View file

@ -3,13 +3,15 @@ package com.tangem.tap.features.wallet.redux.middlewares
import android.content.Intent
import android.net.Uri
import androidx.core.content.ContextCompat
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.isZero
import com.tangem.common.services.Result
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.tap.*
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppDialog
@ -18,10 +20,14 @@ 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.domain.extensions.toSendableAmounts
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.send.redux.PrepareSendScreen
import com.tangem.tap.features.wallet.redux.*
import com.tangem.tap.network.NetworkStateChanged
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.launch
@ -37,18 +43,20 @@ class WalletMiddleware {
val walletMiddleware: Middleware<AppState> = { dispatch, state ->
{ next ->
{ action ->
handleAction(action, dispatch)
handleAction(state, action, dispatch)
next(action)
}
}
}
private fun handleAction(action: Action, dispatch: DispatchFunction) {
private fun handleAction(state: () -> AppState?, action: Action, dispatch: DispatchFunction) {
if (DemoHelper.tryHandle(state, action)) return
val globalState = store.state.globalState
val walletState = store.state.walletState
when (action) {
is WalletAction.TradeCryptoAction -> tradeCryptoMiddleware.handle(action)
is WalletAction.TradeCryptoAction -> tradeCryptoMiddleware.handle(state, action)
is WalletAction.Warnings -> warningsMiddleware.handle(action, globalState)
is WalletAction.MultiWallet -> multiWalletMiddleware.handle(action, walletState, globalState)
is WalletAction.LoadWallet -> {

View file

@ -17,6 +17,7 @@ import com.tangem.tap.domain.extensions.hasSignedHashes
import com.tangem.tap.domain.extensions.remainingSignatures
import com.tangem.tap.domain.isMultiwalletAllowed
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.network.NetworkConnectivity
import com.tangem.tap.preferencesStorage
@ -99,6 +100,9 @@ class WarningsMiddleware {
addWarningMessage(WarningMessagesManager.onlineVerificationFailed())
}
}
if (scanResponse.isDemoCard()){
addWarningMessage(WarningMessagesManager.demoCardWarning())
}
setWarningMessages()
}
}

View file

@ -63,7 +63,7 @@ class PendingTransactionsAdapter
}
binding.tvPendingTransaction.text = binding.root.getString(transactionDescriptionRes)
transaction.amount?.let { binding.tvPendingTransactionAmount.text = "$it " }
transaction.amountUi?.let { binding.tvPendingTransactionAmount.text = "$it " }
binding.tvPendingTransactionCurrency.text = "${transaction.currency}"
if (transaction.address != null) {

View file

@ -0,0 +1,35 @@
package com.tangem.tap.features.wallet.ui.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.tangem.tap.common.extensions.dispatchDialogHide
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.store
import com.tangem.wallet.R
/**
[REDACTED_AUTHOR]
*/
class SimpleOkDialog {
companion object {
fun create(dialog: AppDialog.SimpleOkDialog, context: Context): AlertDialog {
return AlertDialog.Builder(context).apply {
setTitle(dialog.header)
setMessage(dialog.message)
setPositiveButton(R.string.common_ok) { _, _ -> }
setOnDismissListener { store.dispatchDialogHide() }
}.create()
}
fun create(dialog: AppDialog.SimpleOkDialogRes, context: Context): AlertDialog {
return AlertDialog.Builder(context).apply {
setTitle(context.getString(dialog.headerId))
setMessage(dialog.messageId)
setPositiveButton(R.string.common_ok) { _, _ -> }
setOnDismissListener { store.dispatchDialogHide() }
}.create()
}
}
}