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

View file

@ -38,7 +38,7 @@
layout="@layout/layout_onboarding_container_top"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.2"
android:layout_weight="@dimen/home_top_weight"
android:clipChildren="false" />
<include
@ -46,7 +46,7 @@
layout="@layout/layout_onboarding_container_bottom"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
android:layout_weight="@dimen/home_bottom_weight" />
</androidx.appcompat.widget.LinearLayoutCompat>

View file

@ -23,9 +23,9 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:menu="@menu/shop"
app:navigationIcon="@drawable/ic_baseline_arrow_back_24"
app:title="@string/onboarding_getting_started"
app:menu="@menu/shop"/>
app:title="@string/onboarding_getting_started" />
</com.google.android.material.appbar.AppBarLayout>
@ -33,6 +33,7 @@
android:id="@+id/onboarding_main_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<include
@ -43,7 +44,6 @@
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@ -51,28 +51,25 @@
android:id="@+id/imv_card_background"
android:layout_width="236dp"
android:layout_height="236dp"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:background="@drawable/shape_circle"
android:backgroundTint="@color/lightGray0"
android:elevation="0dp"
android:scaleType="fitCenter"
app:layout_constraintStart_toStartOf="@id/fl_cards_container"
app:layout_constraintEnd_toEndOf="@id/fl_cards_container"
app:layout_constraintTop_toTopOf="@id/fl_cards_container"
app:layout_constraintBottom_toBottomOf="@id/fl_cards_container"
android:layout_marginBottom="16dp"
/>
app:layout_constraintEnd_toEndOf="@id/fl_cards_container"
app:layout_constraintStart_toStartOf="@id/fl_cards_container"
app:layout_constraintTop_toTopOf="@id/fl_cards_container" />
<FrameLayout
android:id="@+id/fl_cards_container"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:layout_marginTop="0dp"
android:clipChildren="false"
app:layout_constraintBottom_toTopOf="@id/guideline2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.495"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/pb_state">
@ -81,36 +78,33 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:src="@drawable/card_placeholder_black"
android:visibility="gone"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:scaleType="fitCenter"
/>
android:src="@drawable/card_placeholder_black"
android:visibility="gone" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/imv_first_backup_card"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:src="@drawable/card_placeholder_black"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:scaleType="fitCenter"
android:visibility="gone"/>
android:src="@drawable/card_placeholder_black"
android:visibility="gone" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/imv_front_card"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:src="@drawable/card_placeholder_black"
android:transitionName="imv_front_card"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:scaleType="fitCenter"
/>
android:src="@drawable/card_placeholder_black"
android:transitionName="imv_front_card" />
</FrameLayout>
@ -119,12 +113,12 @@
android:layout_width="142dp"
android:layout_height="142dp"
android:src="@drawable/ic_activation_success"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/guideline2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.495"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/pb_state"
android:visibility="gone"/>
app:layout_constraintTop_toBottomOf="@id/pb_state" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/ll_header_body_container"
@ -146,63 +140,62 @@
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:fontFamily="sans-serif-medium"
tools:text="Header"
android:textAlignment="center"
android:textColor="#060606"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.504"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline2" />
app:layout_constraintTop_toTopOf="@+id/guideline2"
tools:text="Добавлена одна резервная карта" />
<TextView
android:id="@+id/tv_body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="64dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="64dp"
tools:text="Body"
android:layout_marginStart="@dimen/home_bottom_tv_body_margin_start"
android:layout_marginTop="@dimen/home_bottom_tv_body_margin_top"
android:layout_marginEnd="@dimen/home_bottom_tv_body_margin_end"
android:textAlignment="center"
android:textColor="#686868"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_header" />
app:layout_constraintTop_toBottomOf="@+id/tv_header"
tools:text="Body" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager_backup_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:visibility="gone"
app:layout_constraintHorizontal_bias="0.504"
app:flow_verticalBias="0"
app:layout_constraintTop_toTopOf="@+id/guideline2"
android:layout_marginBottom="8dp"/>
app:layout_constraintTop_toTopOf="@+id/guideline2" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout_backup_info"
android:layout_width="match_parent"
android:layout_height="10dp"
android:layout_marginBottom="16dp"
android:background="@color/backgroundLightGray"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/view_pager_backup_info"
app:layout_constraintBottom_toTopOf="@id/layout_buttons_common"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/view_pager_backup_info"
app:tabBackground="@drawable/selector_tabs_dots"
app:tabGravity="center"
app:tabIndicator="@null"
app:tabPaddingStart="8dp"
android:layout_marginBottom="16dp"
app:tabPaddingEnd="8dp"
app:tabGravity="center"/>
app:tabPaddingStart="8dp" />
<include
android:id="@+id/layout_buttons_common"
layout="@layout/layout_onboarding_buttons_common"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
/>
app:layout_constraintBottom_toBottomOf="parent" />
<include
android:id="@+id/layout_buttons_add_cards"
@ -218,7 +211,7 @@
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintGuide_percent="0.60"
app:layout_constraintGuide_percent="@dimen/onboarding_wallet_top_guide_line_weight"
app:layout_constraintTop_toTopOf="parent" />

View file

@ -26,7 +26,7 @@
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="@drawable/ic_clear_24"
app:title="@string/shop_title"
app:title="@string/home_button_order"
app:titleCentered="true" />
</com.google.android.material.appbar.AppBarLayout>

View file

@ -8,36 +8,36 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/access_code_title"
style="@style/heading"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="100dp"
android:text="@string/onboarding_access_code_intro_title"
android:textAlignment="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/access_code_title"
style="@style/heading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/access_code_title_margin_top"
android:text="@string/onboarding_access_code_intro_title"
android:textAlignment="center"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<include
android:id="@+id/layout_backup_access_code_info"
layout="@layout/layout_backup_access_code_info"
android:layout_width="match_parent"
android:layout_height="0dp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/access_code_title" />
<include
android:id="@+id/layout_backup_access_code_info"
layout="@layout/layout_backup_access_code_info"
android:layout_width="match_parent"
android:layout_height="0dp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/access_code_title" />
<include
android:id="@+id/layout_backup_access_code_submit"
layout="@layout/layout_backup_access_code_submit"
android:layout_width="match_parent"
android:layout_height="0dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/access_code_title" />
<include
android:id="@+id/layout_backup_access_code_submit"
layout="@layout/layout_backup_access_code_submit"
android:layout_width="match_parent"
android:layout_height="0dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/access_code_title" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -8,18 +8,18 @@
android:id="@+id/iv_access_code_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="25dp"
android:layout_marginTop="@dimen/access_code_info_protect_margin_top"
android:src="@drawable/ic_access_code"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_marginTop="20dp"
android:id="@+id/tv_feature_1_title"
style="@style/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/access_code_info_protect_margin_top"
android:text="@string/onboarding_access_code_feature_1_title"
android:textAlignment="center"
app:layout_constraintStart_toStartOf="@id/tv_feature_1_description"
@ -52,7 +52,7 @@
style="@style/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:layout_marginTop="@dimen/access_code_info_items_space"
android:text="@string/onboarding_access_code_feature_2_title"
android:textAlignment="center"
app:layout_constraintStart_toStartOf="@id/tv_feature_1_description"
@ -85,7 +85,7 @@
style="@style/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:layout_marginTop="@dimen/access_code_info_items_space"
android:text="@string/onboarding_access_code_feature_3_title"
android:textAlignment="center"
app:layout_constraintStart_toStartOf="@id/tv_feature_3_description"
@ -119,9 +119,9 @@
style="@style/TapButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="78dp"
android:layout_marginEnd="78dp"
android:layout_marginBottom="92dp"
android:layout_marginStart="@dimen/access_code_btn_continue_margin_start"
android:layout_marginEnd="@dimen/access_code_btn_continue_margin_start"
android:layout_marginBottom="@dimen/access_code_btn_continue_margin_bottom"
android:text="@string/common_continue"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"

View file

@ -14,8 +14,8 @@
android:layout_marginTop="14dp"
android:paddingStart="32dp"
android:paddingEnd="32dp"
android:textAlignment="center"
android:text="@string/onboarding_access_code_hint"
android:textAlignment="center"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textfield.TextInputLayout
@ -28,12 +28,12 @@
android:layout_marginEnd="16dp"
android:layout_marginBottom="40dp"
android:hint="@string/onboarding_wallet_info_title_third"
android:theme="@style/EditTextThemeOverlay"
app:boxStrokeColor="@color/selector_edit_text"
app:boxStrokeWidth="1dp"
app:endIconDrawable="@drawable/selector_password_toggle"
app:endIconMode="password_toggle"
app:hintTextColor="@color/accent"
android:theme="@style/EditTextThemeOverlay"
app:layout_constraintTop_toBottomOf="@id/tv_access_code_enter_description">
<com.google.android.material.textfield.TextInputEditText
@ -48,16 +48,15 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_access_code_submit"
style="@style/TapButton"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="78dp"
android:layout_marginEnd="78dp"
android:layout_marginBottom="92dp"
android:layout_marginTop="32dp"
app:layout_constraintTop_toTopOf="@id/til_access_code"
app:layout_constraintVertical_bias="1"
android:layout_marginStart="@dimen/access_code_btn_continue_margin_start"
android:layout_marginEnd="@dimen/access_code_btn_continue_margin_end"
android:layout_marginBottom="@dimen/access_code_btn_continue_margin_bottom"
android:text="@string/common_create"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent" />
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,33 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_main_action"
style="@style/OnboardingButton"
android:layout_width="204dp"
android:layout_height="wrap_content"
android:layout_marginBottom="92dp"
tools:text="Main Action"
android:layout_marginBottom="@dimen/home_bottom_fl_scan_card_container_margin_bottom"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintStart_toStartOf="parent"
tools:text="Main Action" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_alternative_action"
style="@style/Widget.AppCompat.Button.Borderless"
android:textSize="16sp"
android:layout_width="204dp"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
tools:text="Alternative action"
android:layout_marginBottom="@dimen/home_bottom_btn_alternative_action_margin_bottom"
android:textAllCaps="false"
android:textColor="@color/tapButtonColorBlack"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintStart_toStartOf="parent"
tools:text="Alternative action" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -10,14 +10,14 @@
android:id="@+id/linearLayoutCompat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginBottom="@dimen/home_bottom_llCompat_margin_bottom"
android:gravity="center"
android:orientation="vertical"
app:layout_constraintBottom_toTopOf="@+id/fl_scan_card_container"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.1">
app:layout_constraintVertical_bias="@dimen/home_bottom_llCompat_constraint_vertical_bias">
<TextView
android:id="@+id/tv_header"
@ -35,9 +35,9 @@
style="@style/TextViewOnboarding.Body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="64dp"
android:layout_marginTop="14dp"
android:layout_marginEnd="64dp"
android:layout_marginStart="@dimen/home_bottom_tv_body_margin_start"
android:layout_marginTop="@dimen/home_bottom_tv_body_margin_top"
android:layout_marginEnd="@dimen/home_bottom_tv_body_margin_end"
android:minLines="3"
app:layout_constraintBottom_toTopOf="@+id/fl_scan_card_container"
app:layout_constraintEnd_toEndOf="parent"
@ -65,7 +65,7 @@
android:id="@+id/fl_scan_card_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="72dp"
android:layout_marginBottom="@dimen/home_bottom_fl_scan_card_container_margin_bottom"
android:clipChildren="false"
android:clipToPadding="false"
app:layout_constraintBottom_toBottomOf="parent"
@ -92,7 +92,7 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_alternative_action"
style="@style/TapButtonText"
android:layout_marginBottom="12dp"
android:layout_marginBottom="@dimen/home_bottom_btn_alternative_action_margin_bottom"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View file

@ -71,6 +71,8 @@
android:layout_height="174dp"
android:layout_gravity="center"
android:scaleType="fitXY"
android:scaleX="@dimen/home_top_back_card_scaleX"
android:scaleY="@dimen/home_top_back_card_scaleY"
android:src="@drawable/card_placeholder_white" />
<androidx.appcompat.widget.AppCompatImageView

View file

@ -6,7 +6,11 @@
android:layout_height="match_parent"
android:clipChildren="false">
<include layout="@layout/view_bg_home" />
<include
layout="@layout/view_bg_home"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/imv_back_card"
@ -16,8 +20,10 @@
android:elevation="0dp"
android:rotation="-21"
android:scaleType="fitXY"
android:scaleX="@dimen/home_top_back_card_scaleX"
android:scaleY="@dimen/home_top_back_card_scaleY"
android:src="@drawable/card_placeholder_white"
android:translationY="@dimen/home_back_card_translationY"
android:translationY="@dimen/home_top_back_card_translationY"
app:layout_constraintEnd_toEndOf="@+id/imv_front_card"
app:layout_constraintStart_toStartOf="@+id/imv_front_card"
app:layout_constraintTop_toBottomOf="@+id/imv_front_card" />
@ -29,8 +35,10 @@
android:elevation="1dp"
android:rotation="-3"
android:scaleType="fitXY"
android:scaleX="@dimen/home_top_front_card_scaleX"
android:scaleY="@dimen/home_top_front_card_scaleY"
android:src="@drawable/card_placeholder_black"
android:translationY="@dimen/home_front_card_translationY"
android:translationY="@dimen/home_top_front_card_translationY"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

View file

@ -25,7 +25,7 @@
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/imv_card_background"
android:layout_width="match_parent"
android:layout_height="230dp"
android:layout_height="@dimen/onboarding_square_background_height"
android:layout_marginStart="32dp"
android:layout_marginTop="@dimen/onboarding_square_background_margin_top"
android:layout_marginEnd="32dp"

View file

@ -25,7 +25,7 @@
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/imv_card_background"
android:layout_width="match_parent"
android:layout_height="230dp"
android:layout_height="@dimen/onboarding_square_background_height"
android:layout_marginStart="32dp"
android:layout_marginTop="@dimen/onboarding_square_background_margin_top"
android:layout_marginEnd="32dp"

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<string name="shop_one_wallet">One Wallet</string>
<string name="shop_3_cards">3 cards</string>
<string name="shop_2_cards">2 cards</string>
<string name="shop_shipping">Shipping</string>
<string name="shop_free">Free</string>
<string name="shop_i_have_a_promo_code">I have a promo code…</string>
<string name="shop_total">Total</string>
<string name="shop_other_payment_methods">Other payment methods</string>
<string name="shop_buy_now">Buy now</string>
<string name="shop_placing_order">Placing your order</string>
<string name="shop_web_checkout_title">Your order</string>
<string name="story_meet_title">Meet\nTangem</string>
<string name="story_meet_buy">Buy</string>
<string name="story_meet_store">Store</string>
<string name="story_meet_send">Send</string>
<string name="story_meet_pay">Pay</string>
<string name="story_meet_exchange">Exchange</string>
<string name="story_meet_lend">Lend</string>
<string name="story_meet_borrow">Borrow</string>
<string name="story_meet_stake">Stake</string>
<string name="story_awe_title">Revolutionary Hardware Wallet</string>
<string name="story_awe_description">Store your crypto assets secure while keeping private keys contained in your card</string>
<string name="story_backup_title">Ultra Secure Backup</string>
<string name="story_backup_description">Up to 3 physical cards to one wallet</string>
<string name="story_currencies_title">Thousands of Currencies</string>
<string name="story_currencies_description">A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously all in one card</string>
<string name="story_web3_title">Web3 Compatible</string>
<string name="story_web3_description">Exchange, buy NFTs, make loans and deposits in more than 100 different decentralized services</string>
<string name="story_finish_title">The Wallet for Everyone</string>
<string name="story_finish_description">Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto.</string>
<string name="home_button_order">Order card</string>
<string name="alert_demo_message">You are currently running in Demo mode. All funds are not real.</string>
<string name="alert_demo_feature_disabled">This feature is disabled in Demo mode.</string>
<string name="alert_demo_tx_send">The transaction has been successfully signed, but not sent to the blockchain because of Demo mode. </string>
</resources>

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<string name="shop_one_wallet">One Wallet</string>
<string name="shop_3_cards">3 cards</string>
<string name="shop_2_cards">2 cards</string>
<string name="shop_shipping">Shipping</string>
<string name="shop_free">Free</string>
<string name="shop_i_have_a_promo_code">I have a promo code…</string>
<string name="shop_total">Total</string>
<string name="shop_other_payment_methods">Other payment methods</string>
<string name="shop_buy_now">Buy now</string>
<string name="shop_placing_order">Placing your order</string>
<string name="shop_web_checkout_title">Your order</string>
<string name="story_meet_title">Meet\nTangem</string>
<string name="story_meet_buy">Buy</string>
<string name="story_meet_store">Store</string>
<string name="story_meet_send">Send</string>
<string name="story_meet_pay">Pay</string>
<string name="story_meet_exchange">Exchange</string>
<string name="story_meet_lend">Lend</string>
<string name="story_meet_borrow">Borrow</string>
<string name="story_meet_stake">Stake</string>
<string name="story_awe_title">Revolutionary Hardware Wallet</string>
<string name="story_awe_description">Store your crypto assets secure while keeping private keys contained in your card</string>
<string name="story_backup_title">Ultra Secure Backup</string>
<string name="story_backup_description">Up to 3 physical cards to one wallet</string>
<string name="story_currencies_title">Thousands of Currencies</string>
<string name="story_currencies_description">A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously all in one card</string>
<string name="story_web3_title">Web3 Compatible</string>
<string name="story_web3_description">Exchange, buy NFTs, make loans and deposits in more than 100 different decentralized services</string>
<string name="story_finish_title">The Wallet for Everyone</string>
<string name="story_finish_description">Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto.</string>
<string name="home_button_order">Order card</string>
<string name="alert_demo_message">You are currently running in Demo mode. All funds are not real.</string>
<string name="alert_demo_feature_disabled">This feature is disabled in Demo mode.</string>
<string name="alert_demo_tx_send">The transaction has been successfully signed, but not sent to the blockchain because of Demo mode. </string>
</resources>

View file

@ -1,9 +1,38 @@
<resources>
<dimen name="home_back_card_translationY">-10dp</dimen>
<dimen name="home_front_card_translationY">0dp</dimen>
<dimen name="device_scale_factor_for_twins_welcome">1.0</dimen>
<!-- Home - TOP -->
<item name="home_top_weight" format="float" type="dimen">1.1</item>
<dimen name="home_top_back_card_translationY">-10dp</dimen>
<item name="home_top_back_card_scaleX" format="float" type="dimen">1</item>
<dimen name="home_top_back_card_scaleY">1</dimen>
<dimen name="home_top_front_card_translationY">0dp</dimen>
<item name="home_top_front_card_scaleY" format="float" type="dimen">1</item>
<item name="home_top_front_card_scaleX" format="float" type="dimen">1</item>
<!-- Home - bottom -->
<item name="home_bottom_weight" format="float" type="dimen">1</item>
<dimen name="home_bottom_llCompat_margin_bottom">16dp</dimen>
<item name="home_bottom_llCompat_constraint_vertical_bias" format="float" type="dimen">0.4</item>>
<dimen name="home_bottom_tv_body_margin_top">14dp</dimen>
<dimen name="home_bottom_tv_body_margin_start">64dp</dimen>
<dimen name="home_bottom_tv_body_margin_end">64dp</dimen>
<dimen name="home_bottom_fl_scan_card_container_margin_bottom">90dp</dimen>
<dimen name="home_bottom_btn_alternative_action_margin_bottom">14dp</dimen>
<!-- Onboarding -->
<dimen name="onboarding_progress_margin_top">16dp</dimen>
<dimen name="onboarding_card_margin_top">106dp</dimen>
<dimen name="onboarding_square_background_height">230dp</dimen>
<dimen name="onboarding_square_background_margin_top">67dp</dimen>
<item name="onboarding_wallet_top_guide_line_weight" type="dimen" format="float">0.56</item>
<!-- Onboarding - Wallet - Access code -->
<dimen name="access_code_title_margin_top">80dp</dimen>
<dimen name="access_code_imv_lock_margin_top">24dp</dimen>
<dimen name="access_code_info_protect_margin_top">20dp</dimen>
<dimen name="access_code_info_items_space">32dp</dimen>
<dimen name="access_code_btn_continue_margin_bottom">80dp</dimen>
</resources>

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<string name="shop_one_wallet">One Wallet</string>
<string name="shop_3_cards">3 cards</string>
<string name="shop_2_cards">2 cards</string>
<string name="shop_shipping">Shipping</string>
<string name="shop_free">Free</string>
<string name="shop_i_have_a_promo_code">I have a promo code…</string>
<string name="shop_total">Total</string>
<string name="shop_other_payment_methods">Other payment methods</string>
<string name="shop_buy_now">Buy now</string>
<string name="shop_placing_order">Placing your order</string>
<string name="shop_web_checkout_title">Your order</string>
<string name="story_meet_title">Meet\nTangem</string>
<string name="story_meet_buy">Buy</string>
<string name="story_meet_store">Store</string>
<string name="story_meet_send">Send</string>
<string name="story_meet_pay">Pay</string>
<string name="story_meet_exchange">Exchange</string>
<string name="story_meet_lend">Lend</string>
<string name="story_meet_borrow">Borrow</string>
<string name="story_meet_stake">Stake</string>
<string name="story_awe_title">Revolutionary Hardware Wallet</string>
<string name="story_awe_description">Store your crypto assets secure while keeping private keys contained in your card</string>
<string name="story_backup_title">Ultra Secure Backup</string>
<string name="story_backup_description">Up to 3 physical cards to one wallet</string>
<string name="story_currencies_title">Thousands of Currencies</string>
<string name="story_currencies_description">A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously all in one card</string>
<string name="story_web3_title">Web3 Compatible</string>
<string name="story_web3_description">Exchange, buy NFTs, make loans and deposits in more than 100 different decentralized services</string>
<string name="story_finish_title">The Wallet for Everyone</string>
<string name="story_finish_description">Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto.</string>
<string name="home_button_order">Order card</string>
<string name="alert_demo_message">You are currently running in Demo mode. All funds are not real.</string>
<string name="alert_demo_feature_disabled">This feature is disabled in Demo mode.</string>
<string name="alert_demo_tx_send">The transaction has been successfully signed, but not sent to the blockchain because of Demo mode. </string>
</resources>

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<string name="shop_one_wallet">Один Кошелек</string>
<string name="shop_3_cards">3 карты</string>
<string name="shop_2_cards">2 карты</string>
<string name="shop_shipping">Доставка</string>
<string name="shop_free">Бесплатно</string>
<string name="shop_i_have_a_promo_code">У меня есть промо-код…</string>
<string name="shop_total">Итого</string>
<string name="shop_other_payment_methods">Другие способы оплаты</string>
<string name="shop_buy_now">Купить сейчас</string>
<string name="shop_placing_order">Оформление вашего заказа</string>
<string name="shop_web_checkout_title">Ваш заказ</string>
<string name="story_meet_title">Встречайте\nTangem</string>
<string name="story_meet_buy">Покупайте</string>
<string name="story_meet_store">Храните</string>
<string name="story_meet_send">Отправляйте</string>
<string name="story_meet_pay">Расплачивайтесь</string>
<string name="story_meet_exchange">Обменивайте</string>
<string name="story_meet_lend">Одалживайте</string>
<string name="story_meet_borrow">Занимайте</string>
<string name="story_meet_stake">Вкладывайте</string>
<string name="story_awe_title">Революционный аппаратный кошелек</string>
<string name="story_awe_description">Держите свои криптосбережения в безопасности - приватные ключи надежно хранятся на карте</string>
<string name="story_backup_title">Ультрабезопасная резервная копия</string>
<string name="story_backup_description">До трех карт с одним кошельком</string>
<string name="story_currencies_title">Тысячи криптовалют</string>
<string name="story_currencies_description">Аппаратный кошелек для ваших биткоинов, эфира и многих других валют одновременно — все в одной карте</string>
<string name="story_web3_title">Поддерживает Web3</string>
<string name="story_web3_description">Обменивайте, покупайте NFT, получайте займы и вклады в более чем 100 различных децентрализованных сервисах</string>
<string name="story_finish_title">Кошелек для каждого</string>
<string name="story_finish_description">Используйте его на ходу, в любом месте, в любое время. Без проводов и аккумуляторов. Как только понадобится крипта, просто приложите карту к телефону.</string>
<string name="home_button_order">Заказать карту</string>
<string name="alert_demo_message">Приложение работает в демонстрационном режиме. Средства на всех счетах ненастоящие.</string>
<string name="alert_demo_feature_disabled">Эта функция недоступна в демонстрационном режиме</string>
<string name="alert_demo_tx_send">Транзакция успешно подписана, но не отправлена в блокчейн, потому что активирован демонстрационный режим.</string>
</resources>

View file

@ -35,10 +35,41 @@
<dimen name="text_size_amount_to_send">32sp</dimen>
<dimen name="dimen16">16dp</dimen>
<dimen name="home_back_card_translationY">-30dp</dimen>
<dimen name="home_front_card_translationY">-20dp</dimen>
<dimen name="onboarding_progress_margin_top">8dp</dimen>
<dimen name="onboarding_card_margin_top">70dp</dimen>
<item name="device_scale_factor_for_twins_welcome" format="float" type="dimen">0.85</item>
<!-- Home - TOP -->
<item name="home_top_weight" format="float" type="dimen">0.9</item>
<dimen name="home_top_back_card_translationY">-60dp</dimen>
<item name="home_top_back_card_scaleX" format="float" type="dimen">0.9</item>
<item name="home_top_back_card_scaleY" format="float" type="dimen">0.9</item>
<dimen name="home_top_front_card_translationY">-40dp</dimen>
<item name="home_top_front_card_scaleY" format="float" type="dimen">0.9</item>
<item name="home_top_front_card_scaleX" format="float" type="dimen">0.9</item>
<!-- Home - bottom -->
<item name="home_bottom_weight" format="float" type="dimen">1</item>
<dimen name="home_bottom_llCompat_margin_bottom">8dp</dimen>
<item name="home_bottom_llCompat_constraint_vertical_bias" format="float" type="dimen">0.1</item>
<dimen name="home_bottom_tv_body_margin_top">10dp</dimen>
<dimen name="home_bottom_tv_body_margin_start">34dp</dimen>
<dimen name="home_bottom_tv_body_margin_end">34dp</dimen>
<dimen name="home_bottom_fl_scan_card_container_margin_bottom">72dp</dimen>
<dimen name="home_bottom_btn_alternative_action_margin_bottom">12dp</dimen>
<!-- Onboarding -->
<dimen name="onboarding_progress_margin_top">0dp</dimen>
<dimen name="onboarding_card_margin_top">50dp</dimen>
<dimen name="onboarding_square_background_height">180dp</dimen>
<dimen name="onboarding_square_background_margin_top">37dp</dimen>
<item name="onboarding_wallet_top_guide_line_weight" type="dimen" format="float">0.48</item>>
<!-- Onboarding - Wallet - Access code -->
<dimen name="access_code_title_margin_top">40dp</dimen>
<dimen name="access_code_imv_lock_margin_top">12dp</dimen>
<dimen name="access_code_info_protect_margin_top">8dp</dimen>
<dimen name="access_code_info_items_space">16dp</dimen>
<dimen name="access_code_btn_continue_margin_bottom">38dp</dimen>
<dimen name="access_code_btn_continue_margin_start">78dp</dimen>
<dimen name="access_code_btn_continue_margin_end">78dp</dimen>
</resources>

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<string name="shop_one_wallet">One Wallet</string>
<string name="shop_3_cards">3 cards</string>
<string name="shop_2_cards">2 cards</string>
<string name="shop_shipping">Shipping</string>
<string name="shop_free">Free</string>
<string name="shop_i_have_a_promo_code">I have a promo code…</string>
<string name="shop_total">Total</string>
<string name="shop_other_payment_methods">Other payment methods</string>
<string name="shop_buy_now">Buy now</string>
<string name="shop_placing_order">Placing your order</string>
<string name="shop_web_checkout_title">Your order</string>
<string name="story_meet_title">Meet\nTangem</string>
<string name="story_meet_buy">Buy</string>
<string name="story_meet_store">Store</string>
<string name="story_meet_send">Send</string>
<string name="story_meet_pay">Pay</string>
<string name="story_meet_exchange">Exchange</string>
<string name="story_meet_lend">Lend</string>
<string name="story_meet_borrow">Borrow</string>
<string name="story_meet_stake">Stake</string>
<string name="story_awe_title">Revolutionary Hardware Wallet</string>
<string name="story_awe_description">Store your crypto assets secure while keeping private keys contained in your card</string>
<string name="story_backup_title">Ultra Secure Backup</string>
<string name="story_backup_description">Up to 3 physical cards to one wallet</string>
<string name="story_currencies_title">Thousands of Currencies</string>
<string name="story_currencies_description">A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously all in one card</string>
<string name="story_web3_title">Web3 Compatible</string>
<string name="story_web3_description">Exchange, buy NFTs, make loans and deposits in more than 100 different decentralized services</string>
<string name="story_finish_title">The Wallet for Everyone</string>
<string name="story_finish_description">Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto.</string>
<string name="home_button_order">Order card</string>
<string name="alert_demo_message">You are currently running in Demo mode. All funds are not real.</string>
<string name="alert_demo_feature_disabled">This feature is disabled in Demo mode.</string>
<string name="alert_demo_tx_send">The transaction has been successfully signed, but not sent to the blockchain because of Demo mode. </string>
</resources>

View file

@ -331,38 +331,4 @@ Amount to pay: %s</string>
<string name="feedback_preface_tx_push_failed" translatable="false">Please tell us more about your issue. Every small detail can help.</string>
<string name="feedback_data_collection_message" translatable="false">Following information is optional. You can erase it if you dont want to share it.</string>
<string name="shop_one_wallet" translatable="false">One Wallet</string>
<string name="shop_3_cards" translatable="false">3 cards</string>
<string name="shop_2_cards" translatable="false">2 cards</string>
<string name="shop_shipping" translatable="false">Shipping</string>
<string name="shop_free" translatable="false">Free</string>
<string name="shop_i_have_a_promo_code" translatable="false">I have a promo code...</string>
<string name="shop_total" translatable="false">Total</string>
<string name="shop_other_payment_methods" translatable="false">Other payment methods</string>
<string name="shop_buy_now" translatable="false">Buy now</string>
<string name="shop_title" translatable="false">Order card</string>
<string name="shop_free_delivery" translatable="false">Delivery (Free shipping)</string>
<string name="story_meet_title" translatable="false">Meet\nTangem</string>
<string name="story_meet_buy" translatable="false">Buy</string>
<string name="story_meet_store" translatable="false">Store</string>
<string name="story_meet_send" translatable="false">Send</string>
<string name="story_meet_pay" translatable="false">Pay</string>
<string name="story_meet_exchange" translatable="false">Exchange</string>
<string name="story_meet_lend" translatable="false">Lend</string>
<string name="story_meet_borrow" translatable="false">Borrow</string>
<string name="story_meet_stake" translatable="false">Stake</string>
<string name="story_awe_title" translatable="false">Revolutionary Hardware Wallet</string>
<string name="story_awe_description" translatable="false">Store your crypto assets secure while keeping private keys contained in your card</string>
<string name="story_backup_title" translatable="false">Ultra Secure Backup</string>
<string name="story_backup_description" translatable="false">Up to 3 physical cards to one wallet</string>
<string name="story_currencies_title" translatable="false">Thousands of Currencies</string>
<string name="story_currencies_description" translatable="false">A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously all in one card</string>
<string name="story_web3_title" translatable="false">Web3 Compatible</string>
<string name="story_web3_description" translatable="false">Exchange, buy NFTs, make loans and deposits in more than 100 different decentralized services</string>
<string name="story_finish_title" translatable="false">The Wallet for Everyone</string>
<string name="story_finish_description" translatable="false">Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto.</string>
</resources>