Updated on 2026-08-14

This commit is contained in:
Tangem 2023-08-14 21:45:25 +03:00
commit 4771312415
326 changed files with 6051 additions and 3487 deletions

View file

@ -32,6 +32,7 @@ dependencies {
implementation(projects.domain.settings)
implementation(projects.domain.tokens)
implementation(projects.domain.txhistory)
implementation(projects.domain.appCurrency)
implementation(project(":common"))
implementation(project(":core:analytics"))
@ -51,6 +52,7 @@ dependencies {
implementation(projects.data.settings)
implementation(projects.data.tokens)
implementation(projects.data.txhistory)
implementation(projects.data.appCurrency)
/** Features */
implementation(project(":features:onboarding"))

View file

@ -5,8 +5,8 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.wallets.legacy.asLockable
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.domain.userWalletList.asLockable
import kotlinx.coroutines.*
import timber.log.Timber
import kotlin.time.Duration

View file

@ -46,6 +46,7 @@ fun Blockchain.getGreyedOutIconRes(): Int {
Blockchain.Telos, Blockchain.TelosTestnet -> R.drawable.ic_telos_no_color
Blockchain.AlephZero, Blockchain.AlephZeroTestnet -> R.drawable.ic_azero_no_color
Blockchain.OctaSpace, Blockchain.OctaSpaceTestnet -> R.drawable.ic_octaspace_no_color
Blockchain.Chia, Blockchain.ChiaTestnet -> R.drawable.ic_chia_no_color
else -> R.drawable.ic_tangem_logo
}
}

View file

@ -12,6 +12,9 @@ internal class RuntimeUserWalletsStore(
private val walletsStateHolder: WalletsStateHolder,
) : UserWalletsStore {
override val selectedUserWalletOrNull: UserWallet?
get() = walletsStateHolder.userWalletsListManager?.selectedUserWalletSync
override suspend fun getSyncOrNull(key: UserWalletId): UserWallet? {
return walletsStateHolder.userWalletsListManager
?.userWallets

View file

@ -0,0 +1,22 @@
package com.tangem.tap.di.domain
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent
import dagger.hilt.android.scopes.ViewModelScoped
@Module
@InstallIn(ViewModelComponent::class)
internal object AppCurrencyDomainModule {
@Provides
@ViewModelScoped
fun provideGetSelectedAppCurrencyUseCase(
appCurrencyRepository: AppCurrencyRepository,
): GetSelectedAppCurrencyUseCase {
return GetSelectedAppCurrencyUseCase(appCurrencyRepository)
}
}

View file

@ -1,9 +1,9 @@
package com.tangem.tap.di.domain
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.tokens.repository.TokensRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
@ -18,23 +18,34 @@ internal object TokensDomainModule {
@Provides
@ViewModelScoped
fun provideGetTokenListUseCase(
tokensRepository: TokensRepository,
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
dispatchers: CoroutineDispatcherProvider,
): GetTokenListUseCase {
return GetTokenListUseCase(tokensRepository, quotesRepository, networksRepository, dispatchers)
return GetTokenListUseCase(currenciesRepository, quotesRepository, networksRepository, dispatchers)
}
@Provides
@ViewModelScoped
fun provideGetCurrencyUseCase(
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
dispatchers: CoroutineDispatcherProvider,
): GetCurrencyUseCase {
return GetCurrencyUseCase(currenciesRepository, quotesRepository, networksRepository, dispatchers)
}
@Provides
@ViewModelScoped
fun provideGetPrimaryCurrencyUseCase(
tokensRepository: TokensRepository,
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
dispatchers: CoroutineDispatcherProvider,
): GetPrimaryCurrencyUseCase {
return GetPrimaryCurrencyUseCase(tokensRepository, quotesRepository, networksRepository, dispatchers)
return GetPrimaryCurrencyUseCase(currenciesRepository, quotesRepository, networksRepository, dispatchers)
}
@Provides
@ -55,9 +66,9 @@ internal object TokensDomainModule {
@Provides
@ViewModelScoped
fun provideApplyTokenListSortingUseCase(
tokensRepository: TokensRepository,
currenciesRepository: CurrenciesRepository,
dispatchers: CoroutineDispatcherProvider,
): ApplyTokenListSortingUseCase {
return ApplyTokenListSortingUseCase(tokensRepository, dispatchers)
return ApplyTokenListSortingUseCase(currenciesRepository, dispatchers)
}
}

View file

@ -2,9 +2,7 @@ package com.tangem.tap.di.domain
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.WalletsStateHolder
import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
import com.tangem.domain.wallets.usecase.*
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -21,6 +19,12 @@ internal object WalletsDomainModule {
return GetWalletsUseCase(walletsStateHolder = walletsStateHolder)
}
@Provides
@ViewModelScoped
fun providesGetSelectedWalletUseCase(walletsStateHolder: WalletsStateHolder): GetSelectedWalletUseCase {
return GetSelectedWalletUseCase(walletsStateHolder = walletsStateHolder)
}
@Provides
@ViewModelScoped
fun providesSaveWalletUseCase(walletsStateHolder: WalletsStateHolder): SaveWalletUseCase {
@ -32,4 +36,16 @@ internal object WalletsDomainModule {
fun providesGetExploreUrlUseCase(walletsManagersFacade: WalletManagersFacade): GetExploreUrlUseCase {
return GetExploreUrlUseCase(walletsManagersFacade = walletsManagersFacade)
}
@Provides
@ViewModelScoped
fun providesUnlockWalletUseCase(walletsStateHolder: WalletsStateHolder): UnlockWalletsUseCase {
return UnlockWalletsUseCase(walletsStateHolder = walletsStateHolder)
}
@Provides
@ViewModelScoped
fun providesSelectWalletUseCase(walletsStateHolder: WalletsStateHolder): SelectWalletUseCase {
return SelectWalletUseCase(walletsStateHolder = walletsStateHolder)
}
}

View file

@ -16,6 +16,7 @@ import com.tangem.crypto.bip39.DefaultMnemonic
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.operations.ScanTask
@ -80,7 +81,10 @@ class TangemSdkManager(private val cardSdkConfigRepository: CardSdkConfigReposit
suspend fun createProductWallet(scanResponse: ScanResponse): CompletionResult<CreateProductWalletTaskResponse> {
return runTaskAsync(
CreateProductWalletTask(scanResponse.cardTypesResolver),
CreateProductWalletTask(
cardTypesResolver = scanResponse.cardTypesResolver,
derivationStyleProvider = scanResponse.derivationStyleProvider,
),
scanResponse.card.cardId,
Message(resources.getString(R.string.initial_message_create_wallet_body)),
)
@ -90,15 +94,20 @@ class TangemSdkManager(private val cardSdkConfigRepository: CardSdkConfigReposit
scanResponse: ScanResponse,
mnemonic: String,
): CompletionResult<CreateProductWalletTaskResponse> {
return when (val seedResult = DefaultMnemonic(mnemonic, tangemSdk.wordlist).generateSeed()) {
is CompletionResult.Success -> runTaskAsync(
CreateProductWalletTask(scanResponse.cardTypesResolver, seedResult.data),
scanResponse.card.cardId,
Message(resources.getString(R.string.initial_message_create_wallet_body)),
)
is CompletionResult.Failure -> CompletionResult.Failure(seedResult.error)
val mnemonic = try {
DefaultMnemonic(mnemonic, tangemSdk.wordlist)
} catch (e: TangemSdkError.MnemonicException) {
return CompletionResult.Failure(e)
}
return runTaskAsync(
CreateProductWalletTask(
scanResponse.cardTypesResolver,
derivationStyleProvider = scanResponse.derivationStyleProvider,
mnemonic,
),
scanResponse.card.cardId,
Message(resources.getString(R.string.initial_message_create_wallet_body)),
)
}
private fun sendScanResultsToAnalytics(result: CompletionResult<ScanResponse>) {
@ -246,12 +255,13 @@ class TangemSdkManager(private val cardSdkConfigRepository: CardSdkConfigReposit
}
companion object {
@Deprecated("Use [DefaultCardSdkProvider] instead")
val config = Config(
linkedTerminal = true,
allowUntrustedCards = true,
filter = CardFilter(
allowedCardTypes = FirmwareVersion.FirmwareType.values().toList(),
maxFirmwareVersion = FirmwareVersion(major = 6, minor = 21),
maxFirmwareVersion = FirmwareVersion(major = 6, minor = 33),
),
)
}

View file

@ -26,10 +26,8 @@ sealed class TapError(
val stateError: String,
) : TapError(R.string.common_custom_string, listOf("Unsupported state: $stateError"))
object ScanCardError : TapError(R.string.scan_card_error)
object UnknownBlockchain : TapError(R.string.wallet_error_unsupported_blockchain_subtitle)
object NoInternetConnection : TapError(R.string.wallet_notification_no_internet)
object BlockchainInternalError : TapError(R.string.send_error_blockchain_internal)
object AmountExceedsBalance : TapError(R.string.send_validation_amount_exceeds_balance)
data class AmountLowerExistentialDeposit(
override val args: List<Any>,
@ -43,7 +41,7 @@ sealed class TapError(
object DustChange : TapError(R.string.send_error_dust_change)
sealed class WalletManager {
object CreationError : CustomError("Can't create wallet manager")
object CreationError : CustomError(customMessage = "Can't create wallet manager")
class NoAccountError(amountToCreateAccount: String) : CustomError(amountToCreateAccount)
class InternalError(message: String) : CustomError(message)
object BlockchainIsUnreachableTryLater : TapError(R.string.wallet_balance_blockchain_unreachable_try_later)

View file

@ -8,6 +8,7 @@ import com.tangem.common.doOnFailure
import com.tangem.common.doOnSuccess
import com.tangem.core.analytics.Analytics
import com.tangem.datasource.config.ConfigManager
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.ScanResponse
@ -20,6 +21,8 @@ import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.setContext
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.walletStores.WalletStoresError
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
import com.tangem.tap.domain.walletconnect2.domain.models.Account
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.disclaimer.createDisclaimer
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
@ -92,7 +95,8 @@ class TapWalletManager(
null
}
scope.launch {
store.state.daggerGraphState.walletConnectInteractor?.startListening(
val wcInteractor = store.state.daggerGraphState.walletConnectInteractor ?: return@launch
wcInteractor.startListening(
userWalletId = userWallet.walletId.stringValue,
cardId = cardId,
)
@ -106,6 +110,9 @@ class TapWalletManager(
store.dispatchOnMain(WalletAction.LoadData.Success)
store.state.globalState.topUpController?.loadDataSuccess()
store.dispatchWithMain(WalletAction.Warnings.CheckHashesCount.VerifyOnlineIfNeeded)
val wcInteractor = store.state.daggerGraphState.walletConnectInteractor
wcInteractor?.setUserChains(getAccountsForWc(wcInteractor))
}
.doOnFailure { error ->
val errorAction = when (error) {
@ -135,6 +142,23 @@ class TapWalletManager(
}
}
private fun getAccountsForWc(wcInteractor: WalletConnectInteractor): List<Account> {
return store.state.walletState.walletManagers
.mapNotNull {
val wallet = it.wallet
val chainId = wcInteractor.blockchainHelper.networkIdToChainIdOrNull(
wallet.blockchain.toNetworkId(),
)
chainId?.let {
Account(
chainId,
wallet.address,
wallet.publicKey.derivationPath?.rawPath,
)
}
}
}
fun updateConfigManager(data: ScanResponse) {
val configManager = store.state.globalState.configManager

View file

@ -1,7 +1,7 @@
package com.tangem.tap.domain.configurable.warningMessage
import com.tangem.blockchain.common.Blockchain
import com.tangem.utils.extensions.removeByReplace
import com.tangem.utils.extensions.removeBy
import com.tangem.wallet.R
import java.util.concurrent.CopyOnWriteArrayList
@ -44,12 +44,12 @@ class WarningMessagesManager {
}
fun removeWarnings(origin: WarningMessage.Origin) {
warningsList.removeByReplace { it.origin == origin }
warningsList.removeBy { it.origin == origin }
sortByPriority()
}
fun removeWarnings(messageRes: Int) {
warningsList.removeByReplace { it.messageResId == messageRes }
warningsList.removeBy { it.messageResId == messageRes }
}
fun containsWarning(warning: WarningMessage) = warning in warningsList

View file

@ -1,11 +1,15 @@
package com.tangem.tap.domain.model.builders
import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.common.BlockchainNetwork
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.domain.model.WalletStoreModel
@ -47,7 +51,7 @@ private class BlockchainNetworkWalletStoreBuilderImpl(
}
override fun build(): WalletStoreModel {
val cardDerivationStyle = userWallet.scanResponse.card.derivationStyle
val cardDerivationStyle = userWallet.scanResponse.derivationStyleProvider.getDerivationStyle()
val blockchainWalletData = blockchainNetwork.getBlockchainWalletData(walletManager, cardDerivationStyle)
val tokensWalletsData = blockchainNetwork.getTokensWalletsData(
walletManager = walletManager,

View file

@ -11,10 +11,13 @@ import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.toMapKey
import com.tangem.common.map
import com.tangem.crypto.bip39.Mnemonic
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.DerivationStyleProvider
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.extensions.derivationPath
import com.tangem.domain.common.configs.CardConfig
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.KeyWalletPublicKey
import com.tangem.operations.CommandResponse
@ -56,7 +59,8 @@ private data class CreateWalletResponse(
class CreateProductWalletTask(
private val cardTypesResolver: CardTypesResolver,
private val seed: ByteArray? = null,
private val derivationStyleProvider: DerivationStyleProvider,
private val mnemonic: Mnemonic? = null,
) : CardSessionRunnable<CreateProductWalletTaskResponse> {
override val allowsRequestAccessCodeFromRepository: Boolean = false
@ -76,7 +80,7 @@ class CreateProductWalletTask(
cardTypesResolver.isTangemTwins() ->
throw UnsupportedOperationException("Use the TwinCardsManager to create a wallet")
else -> CreateWalletTangemWallet(seed)
else -> CreateWalletTangemWallet(mnemonic, derivationStyleProvider)
}
commandProcessor.proceed(cardDto, session) {
when (it) {
@ -131,8 +135,12 @@ private class CreateWalletTangemNote(private val cardTypesResolver: CardTypesRes
}
}
/**
* Uses for multiWallet 1st and 2nd
*/
private class CreateWalletTangemWallet(
private val seed: ByteArray?,
private val mnemonic: Mnemonic?,
private val derivationStyleProvider: DerivationStyleProvider,
) : ProductCommandProcessor<CreateProductWalletTaskResponse> {
private var primaryCard: PrimaryCard? = null
@ -142,8 +150,9 @@ private class CreateWalletTangemWallet(
session: CardSession,
callback: (result: CompletionResult<CreateProductWalletTaskResponse>) -> Unit,
) {
val config = CardConfig.createConfig(card)
val walletsOnCard = card.wallets.map { it.curve }.toSet()
val curves = card.supportedCurves.intersect(CURVES_FOR_WALLETS).subtract(walletsOnCard).toList()
val curves = card.supportedCurves.intersect(config.mandatoryCurves.toSet()).subtract(walletsOnCard).toList()
if (curves.isEmpty()) {
val createWalletResponses = card.wallets.map { wallet ->
@ -152,8 +161,7 @@ private class CreateWalletTangemWallet(
proceedWithCreatedWallets(card, createWalletResponses, session, callback)
return
}
CreateWalletsTask(curves, seed).run(session) { result ->
CreateWalletsTask(curves, mnemonic).run(session) { result ->
when (result) {
is CompletionResult.Success -> {
proceedWithCreatedWallets(
@ -242,9 +250,9 @@ private class CreateWalletTangemWallet(
val blockchainsForCurve = getBlockchains(response.cardId, card).filter {
it.getSupportedCurves().contains(response.wallet.curve)
}
val derivationPaths = blockchainsForCurve.mapNotNull {
val derivationPaths = blockchainsForCurve.mapNotNull { blockchain ->
isBlockchainsForCurvesExist = true
it.derivationPath(card.derivationStyle)
blockchain.derivationPath(derivationStyleProvider.getDerivationStyle())
}
if (derivationPaths.isNotEmpty()) {
map[response.wallet.publicKey.toMapKey()] = derivationPaths

View file

@ -5,6 +5,8 @@ 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.crypto.bip39.Mnemonic
import com.tangem.crypto.hdWallet.masterkey.AnyMasterKeyFactory
import com.tangem.operations.CommandResponse
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.operations.wallet.CreateWalletTask
@ -18,7 +20,7 @@ class CreateWalletsResponse(
class CreateWalletsTask(
private val curves: List<EllipticCurve>,
private val seed: ByteArray? = null,
private val mnemonic: Mnemonic? = null,
) : CardSessionRunnable<CreateWalletsResponse> {
private val createdWalletsResponses = mutableListOf<CreateWalletResponse>()
@ -38,7 +40,10 @@ class CreateWalletsTask(
session: CardSession,
callback: (result: CompletionResult<CreateWalletsResponse>) -> Unit,
) {
CreateWalletTask(curve, seed).run(session) { result ->
val extendedPrivateKey = mnemonic?.let {
AnyMasterKeyFactory(mnemonic = it, passphrase = "").makeMasterKey(curve)
}
CreateWalletTask(curve, extendedPrivateKey).run(session) { result ->
when (result) {
is CompletionResult.Success -> {
createdWalletsResponses.add(result.data)

View file

@ -14,13 +14,15 @@ import com.tangem.common.tlv.TlvDecoder
import com.tangem.crypto.CryptoUtils
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.common.BlockchainNetwork
import com.tangem.domain.common.DerivationStyleProvider
import com.tangem.domain.common.TapWorkarounds.isExcluded
import com.tangem.domain.common.TapWorkarounds.isNotSupportedInThatRelease
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.domain.common.TwinsHelper
import com.tangem.domain.common.extensions.getPrimaryCurve
import com.tangem.domain.common.configs.CardConfig
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ProductType
import com.tangem.domain.models.scan.ScanResponse
@ -51,7 +53,7 @@ class ScanProductTask(
}
val cardDto = CardDTO(card)
val error = getErrorIfExcludedCard(cardDto, card)
val error = getErrorIfExcludedCard(cardDto)
if (error != null) {
callback(CompletionResult.Failure(error))
return
@ -81,11 +83,9 @@ class ScanProductTask(
}
}
private fun getErrorIfExcludedCard(cardDto: CardDTO, card: Card): TangemError? {
private fun getErrorIfExcludedCard(cardDto: CardDTO): TangemError? {
if (cardDto.isExcluded) return TapSdkError.CardForDifferentApp
if (cardDto.isNotSupportedInThatRelease) return TapSdkError.CardNotSupportedByRelease
// todo check isImported to prevent using old app with imported wallet, remove before wallet 2.0 enabled ([REDACTED_TASK_KEY])
if (card.wallets.any { it.isImported }) return TapSdkError.CardNotSupportedByRelease
return null
}
}
@ -209,32 +209,24 @@ private class ScanWalletProcessor(
callback: (result: CompletionResult<ScanResponse>) -> Unit,
) {
val productType = ProductType.Wallet
val config = CardConfig.createConfig(card)
scope.launch {
val derivations = collectDerivations(card)
val scanResponse = ScanResponse(
card = card,
productType = productType,
walletData = session.environment.walletData,
primaryCard = primaryCard,
)
val derivations = collectDerivations(card, config, scanResponse.derivationStyleProvider)
if (derivations.isEmpty() || !card.settings.isHDWalletAllowed) {
callback(
CompletionResult.Success(
ScanResponse(
card = card,
productType = productType,
walletData = session.environment.walletData,
primaryCard = primaryCard,
),
),
)
callback(CompletionResult.Success(scanResponse))
return@launch
}
DeriveMultipleWalletPublicKeysTask(derivations).run(session) { result ->
when (result) {
is CompletionResult.Success -> {
val response = ScanResponse(
card = card,
productType = productType,
walletData = session.environment.walletData,
derivedKeys = result.data.entries,
primaryCard = primaryCard,
)
val response = scanResponse.copy(derivedKeys = result.data.entries)
callback(CompletionResult.Success(response))
}
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
@ -243,7 +235,10 @@ private class ScanWalletProcessor(
}
}
private suspend fun getBlockchainsToDerive(card: CardDTO): List<BlockchainNetwork> {
private suspend fun getBlockchainsToDerive(
card: CardDTO,
derivationStyleProvider: DerivationStyleProvider,
): List<BlockchainNetwork> {
val userTokensRepository = userTokensRepository ?: return emptyList()
val blockchainsToDerive = userTokensRepository.loadBlockchainsToDerive(card)
.toMutableList()
@ -251,11 +246,11 @@ private class ScanWalletProcessor(
mutableListOf(
BlockchainNetwork(
blockchain = Blockchain.Bitcoin,
card = card,
derivationStyleProvider = derivationStyleProvider,
),
BlockchainNetwork(
blockchain = Blockchain.Ethereum,
card = card,
derivationStyleProvider = derivationStyleProvider,
),
)
}
@ -265,11 +260,11 @@ private class ScanWalletProcessor(
listOf(
BlockchainNetwork(
blockchain = Blockchain.Ethereum,
card = card,
derivationStyleProvider = derivationStyleProvider,
),
BlockchainNetwork(
blockchain = Blockchain.EthereumTestnet,
card = card,
derivationStyleProvider = derivationStyleProvider,
),
),
)
@ -279,7 +274,7 @@ private class ScanWalletProcessor(
additionalBlockchainsToDerive.map {
BlockchainNetwork(
blockchain = it,
card = card,
derivationStyleProvider = derivationStyleProvider,
)
},
)
@ -295,7 +290,7 @@ private class ScanWalletProcessor(
).map {
BlockchainNetwork(
blockchain = it,
card = card,
derivationStyleProvider = derivationStyleProvider,
)
},
)
@ -303,12 +298,16 @@ private class ScanWalletProcessor(
return blockchainsToDerive.distinct()
}
private suspend fun collectDerivations(card: CardDTO): Map<ByteArrayKey, List<DerivationPath>> {
val blockchains = getBlockchainsToDerive(card)
private suspend fun collectDerivations(
card: CardDTO,
config: CardConfig,
derivationStyleProvider: DerivationStyleProvider,
): Map<ByteArrayKey, List<DerivationPath>> {
val blockchains = getBlockchainsToDerive(card, derivationStyleProvider)
val derivations = mutableMapOf<ByteArrayKey, List<DerivationPath>>()
blockchains.forEach { blockchain ->
val curve = blockchain.blockchain.getPrimaryCurve()
val curve = config.primaryCurve(blockchain.blockchain)
val wallet = card.wallets.firstOrNull { it.curve == curve } ?: return@forEach
if (wallet.chainCode == null) return@forEach

View file

@ -1,27 +0,0 @@
package com.tangem.tap.domain.tokens
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.card.FirmwareVersion
import com.tangem.domain.models.scan.CardDTO
object CurrenciesRepository {
fun getBlockchains(cardFirmware: CardDTO.FirmwareVersion, isTestNet: Boolean = false): List<Blockchain> {
val blockchains = if (cardFirmware < FirmwareVersion.MultiWalletAvailable) {
Blockchain.secp256k1Blockchains(isTestNet)
} else {
Blockchain.secp256k1Blockchains(isTestNet) + Blockchain.ed25519OnlyBlockchains(isTestNet)
}
return excludeUnsupportedBlockchains(blockchains)
}
// Use this list to temporarily exclude a blockchain from the list of tokens.
private fun excludeUnsupportedBlockchains(blockchains: List<Blockchain>): List<Blockchain> {
return blockchains.toMutableList().apply {
removeAll(
listOf(
// Any blockchain
),
)
}
}
}

View file

@ -34,6 +34,11 @@ class CreateSecondTwinWalletTask(
return
}
if (!TwinsHelper.isTwinsCompatible(firstCardId, card.cardId)) {
callback(CompletionResult.Failure(IncompatibleTwinCard))
return
}
session.setMessage(preparingMessage)
PurgeWalletCommand(publicKey).run(session) { response ->
when (response) {

View file

@ -0,0 +1,12 @@
package com.tangem.tap.domain.twins
import com.tangem.common.core.TangemError
import com.tangem.tap.tangemSdkManager
import com.tangem.wallet.R
object IncompatibleTwinCard : TangemError(code = 50005) {
override var customMessage: String = tangemSdkManager.getString(
R.string.twin_error_wrong_twin,
)
override val messageResId: Int? = null
}

View file

@ -2,10 +2,10 @@ package com.tangem.tap.domain.userWalletList.implementation
import com.tangem.common.*
import com.tangem.common.extensions.guard
import com.tangem.domain.wallets.legacy.UserWalletsListError
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.domain.userWalletList.UserWalletsListError
import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey
import com.tangem.tap.domain.userWalletList.repository.SelectedUserWalletRepository
import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository

View file

@ -2,10 +2,10 @@ package com.tangem.tap.domain.userWalletList.implementation
import com.tangem.common.CompletionResult
import com.tangem.common.catching
import com.tangem.domain.wallets.legacy.UserWalletsListError
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.domain.userWalletList.UserWalletsListError
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*

View file

@ -8,8 +8,8 @@ import com.tangem.common.biometric.BiometricManager
import com.tangem.common.biometric.BiometricStorage
import com.tangem.common.core.TangemSdkError
import com.tangem.common.services.secure.SecureStorage
import com.tangem.domain.wallets.legacy.UserWalletsListError
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.domain.userWalletList.UserWalletsListError
import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey
import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository
import kotlinx.coroutines.Dispatchers

View file

@ -12,7 +12,7 @@ import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.domain.userWalletList.model.UserWalletPublicInformation
import com.tangem.tap.domain.userWalletList.repository.UserWalletsPublicInformationRepository
import com.tangem.tap.domain.userWalletList.utils.publicInformation
import com.tangem.utils.extensions.plusOrReplace
import com.tangem.utils.extensions.addOrReplace
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -29,7 +29,7 @@ internal class DefaultUserWalletsPublicInformationRepository(
getAll()
.flatMap { savedInformation ->
val infoToSave = withContext(Dispatchers.Default) {
savedInformation.plusOrReplace(userWallet.publicInformation) {
savedInformation.addOrReplace(userWallet.publicInformation) {
userWallet.walletId == it.walletId
}
}

View file

@ -1,9 +1,11 @@
package com.tangem.tap.domain.walletCurrencies.implementation
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.common.*
import com.tangem.domain.common.BlockchainNetwork
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.DerivationStyleProvider
import com.tangem.domain.common.extensions.derivationPath
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.wallets.legacy.WalletManagersRepository
import com.tangem.domain.wallets.models.UserWallet
@ -61,7 +63,9 @@ internal class DefaultWalletCurrenciesManager(
}
val card = userWallet.scanResponse.card
val currenciesToAddWithMissingBlockchains = currenciesToAdd.addMissingBlockchainsIfNeeded(card)
val currenciesToAddWithMissingBlockchains = currenciesToAdd.addMissingBlockchainsIfNeeded(
userWallet.scanResponse.derivationStyleProvider,
)
listeners.forEach { it.willCurrenciesAdd(userWallet, currenciesToAddWithMissingBlockchains) }
updateWalletStores(
@ -167,13 +171,15 @@ internal class DefaultWalletCurrenciesManager(
return networks
}
private fun List<Currency>.addMissingBlockchainsIfNeeded(card: CardDTO): List<Currency> {
private fun List<Currency>.addMissingBlockchainsIfNeeded(
derivationStyleProvider: DerivationStyleProvider,
): List<Currency> {
if (this.isEmpty()) return this
val currencies = this.asSequence()
return currencies
.groupBy { currency ->
findBlockchainCurrency(currency, currencies, card.derivationStyle)
findBlockchainCurrency(currency, currencies, derivationStyleProvider.getDerivationStyle())
}
.mapValues { (blockchainCurrency, blockchainCurrencies) ->
findBlockchainTokens(blockchainCurrency, blockchainCurrencies)

View file

@ -31,7 +31,7 @@ import com.tangem.tap.features.wallet.models.getPendingTransactions
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.plusOrReplace
import com.tangem.utils.extensions.addOrReplace
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.firstOrNull
import timber.log.Timber
@ -429,7 +429,7 @@ internal class DefaultWalletAmountsRepository(
withContext(Dispatchers.Default) {
WalletManagerStorage.update { prevManagers ->
val newManagersForUserWallet = prevManagers[userWalletId].orEmpty()
.plusOrReplace(walletManager) {
.addOrReplace(walletManager) {
it.wallet.blockchain == walletManager.wallet.blockchain
}

View file

@ -100,6 +100,28 @@ class WalletConnectRepositoryImpl @Inject constructor(
Timber.d("sessionProposal: $sessionProposal")
this@WalletConnectRepositoryImpl.sessionProposal = sessionProposal
val missingNetworks = findMissingNetworks(
namespaces = sessionProposal.requiredNamespaces,
userNamespaces = this@WalletConnectRepositoryImpl.userNamespaces ?: emptyMap(),
)
if (missingNetworks.isNotEmpty()) {
Timber.w("Not added blockchains: $missingNetworks")
scope.launch {
_events.emit(
WalletConnectEvents.SessionApprovalError(
WalletConnectError.ApprovalErrorMissingNetworks(missingNetworks.toList()),
),
)
}
return
}
val optionalWithoutMissingNetworks = removeMissingNetworks(
namespaces = sessionProposal.optionalNamespaces,
userNamespaces = this@WalletConnectRepositoryImpl.userNamespaces ?: emptyMap(),
)
scope.launch {
_events.emit(
WalletConnectEvents.SessionProposal(
@ -108,7 +130,7 @@ class WalletConnectRepositoryImpl @Inject constructor(
sessionProposal.url,
sessionProposal.icons,
sessionProposal.requiredNamespaces.values.flatMap { it.chains ?: emptyList() },
sessionProposal.optionalNamespaces.values.flatMap { it.chains ?: emptyList() },
optionalWithoutMissingNetworks.toList(),
),
)
}
@ -211,6 +233,10 @@ class WalletConnectRepositoryImpl @Inject constructor(
}
}
override fun setUserNamespaces(userNamespaces: Map<NetworkNamespace, List<Account>>) {
this.userNamespaces = userNamespaces
}
override fun pair(uri: String) {
Web3Wallet.pair(Wallet.Params.Pair(uri))
}
@ -220,23 +246,6 @@ class WalletConnectRepositoryImpl @Inject constructor(
val sessionProposal: Wallet.Model.SessionProposal = requireNotNull(this.sessionProposal)
val missingNetworks = findMissingNetworks(
namespaces = sessionProposal.requiredNamespaces,
userNamespaces = userNamespaces,
)
if (missingNetworks.isNotEmpty()) {
Timber.e("Not added blockchains: $missingNetworks")
scope.launch {
_events.emit(
WalletConnectEvents.SessionApprovalError(
WalletConnectError.ApprovalErrorMissingNetworks(missingNetworks.toList()),
),
)
}
return
}
val userChains = userNamespaces.flatMap { namespace ->
namespace.value.map { it.chainId to "${it.chainId}:${it.walletAddress}" }
}.groupBy { pair -> pair.first }
@ -433,4 +442,13 @@ class WalletConnectRepositoryImpl @Inject constructor(
val userChains = userNamespaces.flatMap { it.value.map { account -> account.chainId } }
return requiredChains.subtract(userChains.toSet())
}
private fun removeMissingNetworks(
namespaces: Map<String, Wallet.Model.Namespace.Proposal>,
userNamespaces: Map<NetworkNamespace, List<Account>>,
): Collection<String> {
val wcProvidedChains = namespaces.values.flatMap { it.chains ?: emptyList() }
val userChains = userNamespaces.flatMap { it.value.map { account -> account.chainId } }
return wcProvidedChains.intersect(userChains.toSet())
}
}

View file

@ -44,6 +44,15 @@ class WalletConnectInteractor(
}
}
fun setUserChains(accounts: List<Account>) {
val userNamespaces: Map<NetworkNamespace, List<Account>> = accounts
.groupBy { account ->
blockchainHelper.getNamespaceFromFullChainIdOrNull(account.chainId)
?.let { NetworkNamespace(it) }
}.filterNotNull()
walletConnectRepository.setUserNamespaces(userNamespaces)
}
private suspend fun subscribeToEvents() {
events
.onEach { wcEvent ->

View file

@ -11,6 +11,8 @@ interface WalletConnectRepository {
fun init(projectId: String)
fun setUserNamespaces(userNamespaces: Map<NetworkNamespace, List<Account>>)
fun updateSessions()
fun pair(uri: String)

View file

@ -7,8 +7,10 @@ import com.tangem.common.extensions.guard
import com.tangem.common.extensions.toMapKey
import com.tangem.common.flatMap
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.configs.CardConfig
import com.tangem.domain.common.extensions.derivationPath
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.common.util.hasDerivation
import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.domain.models.scan.ScanResponse
@ -68,10 +70,11 @@ class DefaultCustomTokenInteractor(
currencyList: List<Currency>,
onSuccess: suspend (ScanResponse) -> Unit,
) {
val derivationDataList = listOfNotNull(
getDerivations(EllipticCurve.Secp256k1, scanResponse, currencyList),
getDerivations(EllipticCurve.Ed25519, scanResponse, currencyList),
)
val config = CardConfig.createConfig(scanResponse.card)
val derivationDataList = currencyList.mapNotNull {
val curve = config.primaryCurve(it.blockchain)
curve?.let { getDerivations(curve, scanResponse, currencyList) }
}
val derivations = derivationDataList.associate(TokensMiddleware.DerivationData::derivations)
if (derivations.isEmpty()) {
@ -114,7 +117,7 @@ class DefaultCustomTokenInteractor(
val manageTokensCandidates = currencyList.map { it.blockchain }.distinct().filter {
it.getSupportedCurves().contains(curve)
}.mapNotNull {
it.derivationPath(scanResponse.card.derivationStyle)
it.derivationPath(scanResponse.derivationStyleProvider.getDerivationStyle())
}
val customTokensCandidates = currencyList.filter {

View file

@ -29,7 +29,7 @@ internal fun AddCustomTokenFloatingButton(model: AddCustomTokenFloatingButton, m
.imePadding()
.padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
text = stringResource(id = R.string.common_add),
text = stringResource(id = R.string.custom_token_add_token),
iconResId = R.drawable.ic_plus_24,
enabled = model.isEnabled,
onClick = model.onClick,

View file

@ -11,14 +11,14 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.HDWalletError
import com.tangem.domain.AddCustomTokenError
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.extensions.*
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.features.customtoken.impl.domain.CustomTokenInteractor
@ -599,7 +599,7 @@ internal class AddCustomTokenViewModel @Inject constructor(
if (blockchain == null) return null
val derivationStyle = if (!isDerivationPathSelected()) {
reduxStateHolder.scanResponse?.card?.derivationStyle
reduxStateHolder.scanResponse?.derivationStyleProvider?.getDerivationStyle()
} else {
DerivationStyle.LEGACY
}

View file

@ -15,6 +15,7 @@ import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.userwallets.UserWalletBuilder
import com.tangem.domain.userwallets.UserWalletIdBuilder
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.isLockedSync
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Settings
@ -27,7 +28,6 @@ import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation
import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation
import com.tangem.tap.domain.userWalletList.isLockedSync
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction

View file

@ -1,18 +1,19 @@
package com.tangem.tap.features.details.redux.walletconnect
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.common.extensions.guard
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.BlockchainNetwork
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.DerivationStyleProvider
import com.tangem.domain.common.extensions.derivationPath
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.AppState
@ -357,9 +358,13 @@ class WalletConnectMiddleware {
handleScanResponse(scanResponse = scanResponse, session = session, blockchain = blockchain)
}
private fun getAvailableBlockchains(card: CardDTO, walletState: WalletState): List<Blockchain> {
private fun getAvailableBlockchains(
derivationStyleProvider: DerivationStyleProvider,
walletState: WalletState,
): List<Blockchain> {
return walletState.currencies.filter {
it.isBlockchain() && !it.isCustomCurrency(card.derivationStyle) && it.blockchain.isEvm()
it.isBlockchain() &&
!it.isCustomCurrency(derivationStyleProvider.getDerivationStyle()) && it.blockchain.isEvm()
}.map { it.blockchain }
}
@ -390,7 +395,7 @@ class WalletConnectMiddleware {
walletPublicKey = wallet.publicKey.seedKey,
derivedPublicKey = derivedKey,
derivationPath = wallet.publicKey.derivationPath,
derivationStyle = scanResponse.card.derivationStyle,
derivationStyle = scanResponse.derivationStyleProvider.getDerivationStyle(),
blockchain = wallet.blockchain,
)
@ -403,7 +408,6 @@ class WalletConnectMiddleware {
}
private fun handleScanResponse(scanResponse: ScanResponse, session: WalletConnectSession, blockchain: Blockchain) {
val card = scanResponse.card
if (!scanResponse.cardTypesResolver.isMultiwalletAllowed()) {
store.dispatchOnMain(WalletConnectAction.UnsupportedCard)
return
@ -415,7 +419,11 @@ class WalletConnectMiddleware {
NewWcSessionData(session = updatedSession, scanResponse = scanResponse, blockchain = blockchain),
),
)
val blockchains = if (blockchain.isEvm()) getAvailableBlockchains(card, walletState) else emptyList()
val blockchains = if (blockchain.isEvm()) {
getAvailableBlockchains(scanResponse.derivationStyleProvider, walletState)
} else {
emptyList()
}
store.dispatch(
GlobalAction.ShowDialog(
WalletConnectDialog.ApproveWcSession(session = updatedSession, networks = blockchains),
@ -433,8 +441,9 @@ class WalletConnectMiddleware {
} else {
blockchain
}
val derivation = blockchainToMake.derivationPath(store.state.globalState.scanResponse?.card?.derivationStyle)
?.rawPath
val derivation = blockchainToMake.derivationPath(
style = store.state.globalState.scanResponse?.derivationStyleProvider?.getDerivationStyle(),
)?.rawPath
val blockchainNetwork = BlockchainNetwork(
blockchain = blockchainToMake,
derivationPath = derivation,

View file

@ -2,9 +2,9 @@ package com.tangem.tap.features.details.redux.walletconnect
import com.squareup.moshi.JsonClass
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.redux.StateDialog

View file

@ -12,7 +12,7 @@ import com.tangem.wallet.R
object ClipboardOrScanQrDialog {
fun create(wcUri: String, context: Context): AlertDialog {
return AlertDialog.Builder(context).apply {
setTitle(context.getString(R.string.wallet_connect_title))
setTitle(context.getString(R.string.common_select_action))
setMessage(context.getText(R.string.wallet_connect_clipboard_alert))
setPositiveButton(context.getText(R.string.wallet_connect_paste_from_clipboard)) { _, _ ->
store.dispatch(WalletConnectAction.OpenSession(wcUri))

View file

@ -188,7 +188,7 @@ fun StoriesScreen(
contentDescription = null,
)
Text(
text = stringResource(id = R.string.search_tokens_title),
text = stringResource(id = R.string.common_search_tokens),
fontWeight = FontWeight.Medium,
fontSize = 16.sp,
textAlign = TextAlign.Center,

View file

@ -1,11 +1,6 @@
package com.tangem.tap.features.home.compose.views
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.CircularProgressIndicator
@ -51,7 +46,7 @@ fun HomeButtons(
},
content = {
Text(
text = stringResource(id = R.string.welcome_unlock_card),
text = stringResource(id = R.string.home_button_scan),
fontWeight = FontWeight.Medium,
fontSize = 16.sp,
textAlign = TextAlign.Center,

View file

@ -6,7 +6,6 @@ import com.tangem.common.extensions.guard
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.TapWorkarounds.canSkipBackup
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.common.util.twinsIsTwinned
import com.tangem.domain.models.scan.ProductType
@ -38,10 +37,7 @@ object OnboardingHelper {
}
}
// TODO for Shiba disabled check wallet 2, and only check canSkipBackup, enable when release wallet 2
// ([REDACTED_TASK_KEY])
// response.cardTypesResolver.isWallet2() -> {
!response.card.canSkipBackup -> {
response.cardTypesResolver.isWallet2() -> {
val emptyWallets = response.card.wallets.isEmpty()
val activationInProgress = cardInfoStorage.isActivationInProgress(cardId)
val backupNotActive = response.card.backupStatus?.isActive != true

View file

@ -151,7 +151,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
btnAlternativeAction.isVisible = false
}
tvHeader.setText(R.string.onboarding_top_up_header)
tvHeader.setText(R.string.onboarding_topup_title)
if (state.balanceNonCriticalError == null) {
tvBody.setText(R.string.onboarding_top_up_body)
} else {

View file

@ -6,6 +6,7 @@ import com.tangem.core.analytics.Analytics
import com.tangem.domain.common.BlockchainNetwork
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.postUi
@ -92,7 +93,7 @@ private fun handleOtherCardsAction(action: Action) {
val blockchainNetwork =
BlockchainNetwork(
blockchain = primaryBlockchain,
card = updatedCard,
derivationStyleProvider = updatedResponse.derivationStyleProvider,
)
.updateTokens(
listOfNotNull(primaryToken),
@ -102,11 +103,11 @@ private fun handleOtherCardsAction(action: Action) {
listOf(
BlockchainNetwork(
blockchain = Blockchain.Bitcoin,
card = updatedCard,
derivationStyleProvider = updatedResponse.derivationStyleProvider,
),
BlockchainNetwork(
blockchain = Blockchain.Ethereum,
card = updatedCard,
derivationStyleProvider = updatedResponse.derivationStyleProvider,
),
)
}

View file

@ -11,6 +11,7 @@ import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.common.util.twinsIsTwinned
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.userwallets.UserWalletIdBuilder
import com.tangem.domain.wallets.legacy.isLockedSync
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Onboarding
@ -21,7 +22,6 @@ import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.twins.TwinCardsManager
import com.tangem.tap.domain.userWalletList.isLockedSync
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.features.onboarding.OnboardingDialog
import com.tangem.tap.features.onboarding.OnboardingHelper
@ -50,7 +50,7 @@ private val twinsWalletMiddleware: Middleware<AppState> = { dispatch, state ->
@Suppress("LongMethod", "ComplexMethod", "MagicNumber")
private fun handle(action: Action, dispatch: DispatchFunction) {
val action = action as? TwinCardsAction ?: return
if (action !is TwinCardsAction) return
val globalState = store.state.globalState
val onboardingManager = globalState.onboardingState.onboardingManager

View file

@ -350,7 +350,7 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
btnAlternativeAction.isVisible = false
}
tvHeader.setText(R.string.onboarding_top_up_header)
tvHeader.setText(R.string.onboarding_topup_title)
tvBody.setText(R.string.onboarding_top_up_body)
btnRefreshBalanceWidget.changeState(state.walletBalance.state)

View file

@ -12,6 +12,7 @@ import com.tangem.domain.common.BlockchainNetwork
import com.tangem.domain.common.TapWorkarounds.canSkipBackup
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.userwallets.Artwork
@ -125,7 +126,10 @@ private fun handleWalletAction(action: Action) {
} else {
listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
}.map { blockchain ->
BlockchainNetwork(blockchain, result.data.card)
BlockchainNetwork(
blockchain = blockchain,
derivationStyleProvider = updatedResponse.derivationStyleProvider,
)
}
scope.launch {

View file

@ -3,11 +3,13 @@ package com.tangem.tap.features.onboarding.products.wallet.ui
import androidx.compose.runtime.collectAsState
import com.tangem.feature.onboarding.api.OnboardingSeedPhrase
import com.tangem.feature.onboarding.api.OnboardingSeedPhraseApi
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseScreen
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseViewModel
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletStep
import com.tangem.wallet.R
/**
[REDACTED_AUTHOR]
@ -44,11 +46,27 @@ internal class OnboardingSeedPhraseStateHandler(
walletFragment.binding.onboardingWalletContainer.hide()
walletFragment.bindingSeedPhrase.onboardingSeedPhraseContainer.show()
walletFragment.bindingSeedPhrase.onboardingSeedPhraseContainer.setContent {
val subScreen = viewModel.currentScreen.collectAsState().value
setMainScreenToolbarTitle(walletFragment, subScreen)
onboardingSeedPhraseApi.ScreenContent(
uiState = viewModel.uiState,
subScreen = viewModel.currentScreen.collectAsState().value,
subScreen = subScreen,
progress = viewModel.progress.collectAsState(0).value.toFloat() / onboardingWalletMaxProgress,
)
}
}
private fun setMainScreenToolbarTitle(walletFragment: OnboardingWalletFragment, subScreen: SeedPhraseScreen) {
val titleResId = when (subScreen) {
SeedPhraseScreen.Intro,
SeedPhraseScreen.AboutSeedPhrase,
SeedPhraseScreen.YourSeedPhrase,
SeedPhraseScreen.CheckSeedPhrase,
-> R.string.onboarding_create_wallet_header
SeedPhraseScreen.ImportSeedPhrase -> R.string.onboarding_seed_intro_button_import
}
walletFragment.binding.toolbar.title = walletFragment.getString(titleResId)
}
}

View file

@ -257,7 +257,7 @@ class OnboardingWalletFragment :
prepareBackupView()
tvHeader.text = getText(R.string.onboarding_title_scan_origin_card)
tvBody.text = getString(
R.string.onboarding_subtitle_scan_origin_card,
R.string.onboarding_subtitle_scan_primary,
)
with(layoutButtonsCommon) {

View file

@ -11,6 +11,7 @@ import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.userwallets.UserWalletBuilder
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.isLockable
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.MainScreen
@ -20,7 +21,6 @@ import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation
import com.tangem.tap.domain.userWalletList.isLockable
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.proxy.redux.DaggerGraphState
import kotlinx.coroutines.launch

View file

@ -15,7 +15,6 @@ import com.tangem.core.navigation.NavigationAction
import com.tangem.tap.common.GlobalLayoutStateHandler
import com.tangem.tap.common.KeyboardObserver
import com.tangem.tap.common.extensions.getQuantityString
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.features.BaseStoreFragment
import com.tangem.tap.features.shop.domain.models.ProductState
@ -153,11 +152,12 @@ internal class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSu
animateProductSelection(state.selectedProduct)
handlePriceState(state)
handlePromoCodeState(state)
if (shopifyFeatureToggleManager.isDynamicSalesProductsEnabled) {
handleNotificationBlock(state)
} else {
handleOrderingDelayBlock(isVisible = state.isOrderingDelayBlockVisible)
}
// TODO: https://tangem.slack.com/archives/C01HARKDLQ0/p1691421861756069
// if (shopifyFeatureToggleManager.isDynamicSalesProductsEnabled) {
// handleNotificationBlock(state)
// } else {
// handleOrderingDelayBlock(isVisible = state.isOrderingDelayBlockVisible)
// }
handleButtonsState(state)
}
@ -198,20 +198,20 @@ internal class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSu
pbPromoCode.show(state.promoCodeLoading)
}
private fun handleOrderingDelayBlock(isVisible: Boolean) {
if (isVisible) binding.tvSoldOutDesc.show() else binding.tvSoldOutDesc.hide()
}
private fun handleNotificationBlock(state: ShopState) {
if (isVisible) {
binding.tvSoldOutDesc.show()
getSelectedSalesProduct(state)?.notification?.let { notification ->
binding.tvSoldOutDesc.text = notification.description
}
} else {
binding.tvSoldOutDesc.hide()
}
}
// private fun handleOrderingDelayBlock(isVisible: Boolean) {
// if (isVisible) binding.tvSoldOutDesc.show() else binding.tvSoldOutDesc.hide()
// }
//
// private fun handleNotificationBlock(state: ShopState) {
// if (isVisible) {
// binding.tvSoldOutDesc.show()
// getSelectedSalesProduct(state)?.notification?.let { notification ->
// binding.tvSoldOutDesc.text = notification.description
// }
// } else {
// binding.tvSoldOutDesc.hide()
// }
// }
private fun handleButtonsState(state: ShopState) = with(binding) {
btnPayGooglePay.root.show(state.isGooglePayAvailable)

View file

@ -2,18 +2,20 @@ package com.tangem.tap.features.tokens.impl.domain
import androidx.paging.PagingData
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.common.CompletionResult
import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.toMapKey
import com.tangem.common.flatMap
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.configs.CardConfig
import com.tangem.domain.common.extensions.derivationPath
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.common.util.supportsHdWallet
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.*
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.global.GlobalAction
@ -24,10 +26,6 @@ import com.tangem.tap.features.tokens.legacy.redux.TokenWithBlockchain
import com.tangem.tap.features.tokens.legacy.redux.TokensMiddleware
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import com.tangem.tap.userWalletsListManager
import com.tangem.tap.walletCurrenciesManager
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import timber.log.Timber
@ -51,11 +49,12 @@ internal class DefaultTokensListInteractor(
override suspend fun saveChanges(tokens: List<TokenWithBlockchain>, blockchains: List<Blockchain>) {
val scanResponse = requireNotNull(reduxStateHolder.scanResponse)
val derivationStyle = scanResponse.derivationStyleProvider.getDerivationStyle()
val currentTokens = store.state.tokensState.addedWallets
.toNonCustomTokensWithBlockchains(style = scanResponse.card.derivationStyle)
.toNonCustomTokensWithBlockchains(derivationStyle = derivationStyle)
val currentBlockchains = store.state.tokensState.addedWallets
.toNonCustomBlockchains(derivationStyle = scanResponse.card.derivationStyle)
.toNonCustomBlockchains(derivationStyle = derivationStyle)
val blockchainsToAdd = blockchains.filterNot(currentBlockchains::contains)
val blockchainsToRemove = currentBlockchains.filterNot(blockchains::contains)
@ -73,18 +72,18 @@ internal class DefaultTokensListInteractor(
remove(
tokens = tokensToRemove,
blockchains = blockchainsToRemove,
derivationStyle = scanResponse.card.derivationStyle,
derivationStyle = scanResponse.derivationStyleProvider.getDerivationStyle(),
)
add(tokens = tokensToAdd, blockchains = blockchainsToAdd, scanResponse = scanResponse)
}
private fun List<WalletDataModel>.toNonCustomTokensWithBlockchains(
style: DerivationStyle?,
derivationStyle: DerivationStyle?,
): List<TokenWithBlockchain> {
return this.map(WalletDataModel::currency)
.mapNotNull { currency ->
if (currency !is Currency.Token || currency.isCustomCurrency(style)) return@mapNotNull null
if (currency !is Currency.Token || currency.isCustomCurrency(derivationStyle)) return@mapNotNull null
TokenWithBlockchain(token = currency.token, blockchain = currency.blockchain)
}
.distinct()
@ -123,7 +122,7 @@ internal class DefaultTokensListInteractor(
val currenciesToAdd = convertToCurrencies(
tokens = tokens,
blockchains = blockchains,
derivationStyle = scanResponse.card.derivationStyle,
derivationStyle = scanResponse.derivationStyleProvider.getDerivationStyle(),
)
// TODO("[REDACTED_TASK_KEY] use DerivationManager")
@ -136,10 +135,11 @@ internal class DefaultTokensListInteractor(
}
private suspend fun deriveMissingBlockchains(scanResponse: ScanResponse, currencies: List<Currency>) {
val derivations = listOfNotNull(
getDerivations(EllipticCurve.Secp256k1, scanResponse, currencies),
getDerivations(EllipticCurve.Ed25519, scanResponse, currencies),
).associate(transform = TokensMiddleware.DerivationData::derivations)
val config = CardConfig.createConfig(scanResponse.card)
val derivations = currencies.mapNotNull {
val curve = config.primaryCurve(it.blockchain)
curve?.let { getDerivations(curve, scanResponse, currencies) }
}.associate(transform = TokensMiddleware.DerivationData::derivations)
if (derivations.isEmpty()) {
submitAdd(scanResponse, currencies)
@ -184,7 +184,7 @@ internal class DefaultTokensListInteractor(
.map(Currency::blockchain)
.distinct()
.filter { it.getSupportedCurves().contains(curve) }
.mapNotNull { it.derivationPath(scanResponse.card.derivationStyle) }
.mapNotNull { it.derivationPath(scanResponse.derivationStyleProvider.getDerivationStyle()) }
val customTokensCandidates = currencyList
.filter { it.blockchain.getSupportedCurves().contains(curve) }

View file

@ -234,7 +234,7 @@ private fun Preview_TokensListScreen_Read() {
TokensListScreen(
stateHolder = TokensListStateHolder.ReadContent(
toolbarState = TokensListToolbarState.Title.Read(
titleResId = R.string.search_tokens_title,
titleResId = R.string.common_search_tokens,
onBackButtonClick = {},
onSearchButtonClick = {},
),

View file

@ -11,12 +11,7 @@ import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
@ -186,7 +181,7 @@ private fun Preview_AddTokensToolbar_ReadAccess() {
TangemTheme {
TokensListToolbar(
state = Title.Read(
titleResId = R.string.search_tokens_title,
titleResId = R.string.common_search_tokens,
onBackButtonClick = {},
onSearchButtonClick = {},
),

View file

@ -102,14 +102,14 @@ internal class TokensListViewModel @Inject constructor(
private fun getInitialToolbarState(): TokensListToolbarState {
return if (args.isManageAccess) {
TokensListToolbarState.Title.Manage(
titleResId = R.string.main_manage_tokens,
titleResId = R.string.add_tokens_title,
onBackButtonClick = actionsHandler::onBackButtonClick,
onSearchButtonClick = actionsHandler::onSearchButtonClick,
onAddCustomTokenClick = actionsHandler::onAddCustomTokenClick,
)
} else {
TokensListToolbarState.Title.Read(
titleResId = R.string.search_tokens_title,
titleResId = R.string.common_search_tokens,
onBackButtonClick = actionsHandler::onBackButtonClick,
onSearchButtonClick = actionsHandler::onSearchButtonClick,
)

View file

@ -1,7 +1,7 @@
package com.tangem.tap.features.tokens.legacy.redux
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.tap.domain.model.WalletDataModel
import org.rekotlin.Action

View file

@ -1,7 +1,7 @@
package com.tangem.tap.features.tokens.legacy.redux
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.common.CompletionResult
import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.ByteArrayKey
@ -13,7 +13,9 @@ import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.DomainWrapped
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.configs.CardConfig
import com.tangem.domain.common.extensions.derivationPath
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.common.util.hasDerivation
import com.tangem.domain.common.util.supportsHdWallet
import com.tangem.domain.features.addCustomToken.CustomCurrency
@ -21,7 +23,7 @@ import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.redux.domainStore
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.ManageTokens
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchOnMain
@ -30,11 +32,6 @@ import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import com.tangem.tap.userWalletsListManager
import com.tangem.tap.walletCurrenciesManager
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.rekotlin.Middleware
@ -72,7 +69,7 @@ object TokensMiddleware {
currencies = convertToCurrencies(
blockchains = blockchainsToRemove,
tokens = tokensToRemove,
derivationStyle = scanResponse.card.derivationStyle,
derivationStyle = scanResponse.derivationStyleProvider.getDerivationStyle(),
),
)
@ -87,7 +84,7 @@ object TokensMiddleware {
val currencyList = convertToCurrencies(
blockchains = blockchainsToAdd,
tokens = tokensToAdd,
derivationStyle = scanResponse.card.derivationStyle,
derivationStyle = scanResponse.derivationStyleProvider.getDerivationStyle(),
)
if (scanResponse.supportsHdWallet()) {
@ -123,10 +120,11 @@ object TokensMiddleware {
currencyList: List<Currency>,
onSuccess: (ScanResponse) -> Unit,
) {
val derivationDataList = listOfNotNull(
getDerivations(EllipticCurve.Secp256k1, scanResponse, currencyList),
getDerivations(EllipticCurve.Ed25519, scanResponse, currencyList),
)
val config = CardConfig.createConfig(scanResponse.card)
val derivationDataList = currencyList.mapNotNull {
val curve = config.primaryCurve(it.blockchain)
curve?.let { getDerivations(curve, scanResponse, currencyList) }
}
val derivations = derivationDataList.associate { it.derivations }
if (derivations.isEmpty()) {
onSuccess(scanResponse)
@ -175,7 +173,7 @@ object TokensMiddleware {
val manageTokensCandidates = currencyList.map { it.blockchain }.distinct().filter {
it.getSupportedCurves().contains(curve)
}.mapNotNull {
it.derivationPath(scanResponse.card.derivationStyle)
it.derivationPath(scanResponse.derivationStyleProvider.getDerivationStyle())
}
val customTokensCandidates = currencyList.filter {

View file

@ -1,7 +1,7 @@
package com.tangem.tap.features.tokens.legacy.redux
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.features.wallet.models.Currency

View file

@ -1,8 +1,9 @@
package com.tangem.tap.features.wallet.models
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.domain.common.BlockchainNetwork
import com.tangem.domain.common.extensions.derivationPath
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.toCoinId
import com.tangem.domain.features.addCustomToken.CustomCurrency

View file

@ -13,6 +13,7 @@ import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.domain.userwallets.GetCardImageUseCase
import com.tangem.domain.wallets.legacy.lockIfLockable
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Basic
@ -24,7 +25,6 @@ import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.domain.model.WalletStoreModel
import com.tangem.tap.domain.userWalletList.lockIfLockable
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.send.redux.PrepareSendScreen

View file

@ -1,11 +1,7 @@
package com.tangem.tap.features.wallet.ui
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.*
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.annotation.ColorRes
@ -23,8 +19,8 @@ import com.tangem.common.doOnResult
import com.tangem.common.extensions.guard
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.feature.swap.api.SwapFeatureToggleManager
import com.tangem.feature.swap.domain.SwapInteractor
import com.tangem.sdk.extensions.dpToPx
@ -32,14 +28,7 @@ import com.tangem.tap.common.SnackbarHandler
import com.tangem.tap.common.TestActions
import com.tangem.tap.common.analytics.events.DetailsScreen
import com.tangem.tap.common.analytics.events.Token
import com.tangem.tap.common.extensions.appendIfNotNull
import com.tangem.tap.common.extensions.beginDelayedTransition
import com.tangem.tap.common.extensions.fitChipsByGroupWidth
import com.tangem.tap.common.extensions.getColor
import com.tangem.tap.common.extensions.getString
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.extensions.toQrCode
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.utils.SafeStoreSubscriber
@ -57,15 +46,7 @@ import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter
import com.tangem.tap.features.wallet.ui.adapters.WalletDetailWarningMessagesAdapter
import com.tangem.tap.features.wallet.ui.images.load
import com.tangem.tap.features.wallet.ui.test.TestWallet
import com.tangem.tap.features.wallet.ui.utils.assembleWarnings
import com.tangem.tap.features.wallet.ui.utils.getAvailableActions
import com.tangem.tap.features.wallet.ui.utils.getFormattedCryptoAmount
import com.tangem.tap.features.wallet.ui.utils.getFormattedFiatAmount
import com.tangem.tap.features.wallet.ui.utils.isAvailableToBuy
import com.tangem.tap.features.wallet.ui.utils.isAvailableToSell
import com.tangem.tap.features.wallet.ui.utils.isAvailableToSwap
import com.tangem.tap.features.wallet.ui.utils.mainButton
import com.tangem.tap.features.wallet.ui.utils.shouldShowMultipleAddress
import com.tangem.tap.features.wallet.ui.utils.*
import com.tangem.tap.store
import com.tangem.tap.userWalletsListManagerSafe
import com.tangem.tap.walletCurrenciesManager
@ -336,10 +317,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), SafeSt
private fun handleCurrencyIcon(currency: Currency) = with(binding.lWalletDetails.lBalance) {
ivCurrency.load(
currency = currency,
derivationStyle = store.state.globalState
.scanResponse
?.card
?.derivationStyle,
derivationStyle = store.state.globalState.scanResponse
?.derivationStyleProvider?.getDerivationStyle(),
)
}

View file

@ -7,7 +7,7 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.tangem.core.analytics.Analytics
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.tap.common.analytics.events.Portfolio
import com.tangem.tap.common.extensions.getString
import com.tangem.tap.common.extensions.hide
@ -77,10 +77,8 @@ class WalletAdapter : ListAdapter<WalletDataModel, WalletAdapter.WalletsViewHold
ivCurrency.load(
currency = wallet.currency,
derivationStyle = store.state.globalState
.scanResponse
?.card
?.derivationStyle,
derivationStyle = store.state.globalState.scanResponse
?.derivationStyleProvider?.getDerivationStyle(),
)
lContent.tvCurrency.text = wallet.currency.currencyName

View file

@ -7,7 +7,7 @@ import android.widget.TextView
import androidx.constraintlayout.utils.widget.ImageFilterView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.sdk.extensions.dpToPx
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.wallet.databinding.ViewCurrencyIconBinding

View file

@ -6,7 +6,7 @@ import com.badoo.mvicore.modelWatcher
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.tap.common.analytics.events.MainScreen
import com.tangem.tap.common.analytics.events.Portfolio
import com.tangem.tap.common.entities.FiatCurrency
@ -104,13 +104,13 @@ class MultiWalletView : WalletView() {
watcher.invoke(state)
binding.btnAddToken.setOnClickListener {
val card = store.state.globalState.scanResponse!!.card
Analytics.send(Portfolio.ButtonManageTokens())
store.dispatch(
TokensAction.SetArgs.ManageAccess(
wallets = state.walletsDataFromStores,
derivationStyle = card.derivationStyle,
derivationStyle = store.state.globalState.scanResponse
?.derivationStyleProvider?.getDerivationStyle(),
),
)
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))

View file

@ -8,6 +8,7 @@ import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.userwallets.UserWalletBuilder
import com.tangem.domain.userwallets.UserWalletIdBuilder
import com.tangem.domain.wallets.legacy.unlockIfLockable
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.*
@ -20,7 +21,6 @@ import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.model.TotalFiatBalance
import com.tangem.tap.domain.model.WalletStoreModel
import com.tangem.tap.domain.userWalletList.unlockIfLockable
import com.tangem.tap.proxy.redux.DaggerGraphState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay

View file

@ -4,11 +4,11 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tangem.common.core.TangemError
import com.tangem.core.analytics.Analytics
import com.tangem.domain.wallets.legacy.UserWalletsListError
import com.tangem.domain.wallets.legacy.isLocked
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.common.analytics.events.MyWallets
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.domain.userWalletList.UserWalletsListError
import com.tangem.tap.domain.userWalletList.isLocked
import com.tangem.tap.features.details.ui.cardsettings.TextReference
import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction
import com.tangem.tap.features.walletSelector.redux.WalletSelectorState

View file

@ -251,7 +251,7 @@ private fun LoadedTokensInfo(
SpacerH2()
Text(
text = pluralStringResource(
id = R.plurals.tokens_count,
id = R.plurals.token_count,
count = tokensCount,
tokensCount,
),

View file

@ -9,6 +9,7 @@ import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.userwallets.UserWalletBuilder
import com.tangem.domain.wallets.legacy.unlockIfLockable
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Basic
@ -16,7 +17,6 @@ import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.userWalletList.unlockIfLockable
import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler
import com.tangem.tap.features.signin.redux.SignInAction
import com.tangem.tap.proxy.redux.DaggerGraphState

View file

@ -3,9 +3,9 @@ package com.tangem.tap.features.welcome.ui
import androidx.lifecycle.ViewModel
import com.tangem.common.core.TangemError
import com.tangem.core.analytics.Analytics
import com.tangem.domain.wallets.legacy.UserWalletsListError
import com.tangem.tap.common.analytics.events.SignIn
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.userWalletList.UserWalletsListError
import com.tangem.tap.features.details.ui.cardsettings.TextReference
import com.tangem.tap.features.welcome.redux.WelcomeAction
import com.tangem.tap.features.welcome.redux.WelcomeState

View file

@ -10,8 +10,10 @@ import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.extensions.toMapKey
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.common.BlockchainNetwork
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.configs.CardConfig
import com.tangem.domain.common.extensions.derivationPath
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.common.util.hasDerivation
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.lib.crypto.DerivationManager
@ -46,13 +48,13 @@ class DerivationManagerImpl(
} else {
null
}
val blockchainNetwork = BlockchainNetwork(blockchain, card)
val appCurrency = com.tangem.tap.features.wallet.models.Currency.fromBlockchainNetwork(
blockchainNetwork,
appToken,
)
val scanResponse = appStateHolder.scanResponse
if (scanResponse != null) {
val blockchainNetwork = BlockchainNetwork(blockchain, scanResponse.derivationStyleProvider)
val appCurrency = com.tangem.tap.features.wallet.models.Currency.fromBlockchainNetwork(
blockchainNetwork,
appToken,
)
deriveMissingBlockchains(
scanResponse = scanResponse,
currencyList = listOf(appCurrency),
@ -70,7 +72,7 @@ class DerivationManagerImpl(
val scanResponse = appStateHolder.scanResponse
val blockchain = Blockchain.fromNetworkId(networkId)
if (scanResponse != null && blockchain != null) {
return blockchain.derivationPath(appStateHolder.getActualCard()?.derivationStyle)?.rawPath
return blockchain.derivationPath(scanResponse.derivationStyleProvider.getDerivationStyle())?.rawPath
}
return null
}
@ -93,10 +95,11 @@ class DerivationManagerImpl(
onSuccess: (ScanResponse) -> Unit,
onFailure: (Exception) -> Unit,
) {
val derivationDataList = listOfNotNull(
getDerivations(EllipticCurve.Secp256k1, scanResponse, currencyList),
getDerivations(EllipticCurve.Ed25519, scanResponse, currencyList),
)
val config = CardConfig.createConfig(scanResponse.card)
val derivationDataList = currencyList.mapNotNull {
val curve = config.primaryCurve(it.blockchain)
curve?.let { getDerivations(curve, scanResponse, currencyList) }
}
val derivations = derivationDataList.associate { it.derivations }
if (derivations.isEmpty()) {
onSuccess(scanResponse)
@ -162,7 +165,7 @@ class DerivationManagerImpl(
val manageTokensCandidates = currencyList.map { it.blockchain }.distinct().filter {
it.getSupportedCurves().contains(curve)
}.mapNotNull {
it.derivationPath(scanResponse.card.derivationStyle)
it.derivationPath(scanResponse.derivationStyleProvider.getDerivationStyle())
}
val customTokensCandidates = currencyList.filter {

View file

@ -1,86 +0,0 @@
package com.tangem.tap.proxy
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.TransactionStatus
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.txhistory.TransactionHistoryItem
import com.tangem.blockchain.common.txhistory.TransactionHistoryState
import com.tangem.blockchain.extensions.Result
import com.tangem.domain.common.BlockchainNetwork
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.lib.crypto.TxHistoryManager
import com.tangem.lib.crypto.models.ProxyAmount
import com.tangem.lib.crypto.models.txhistory.ProxyTransactionHistoryItem
import com.tangem.lib.crypto.models.txhistory.ProxyTransactionHistoryState
import com.tangem.lib.crypto.models.txhistory.ProxyTransactionStatus
class TxHistoryManagerImpl(
private val appStateHolder: AppStateHolder,
) : TxHistoryManager {
override suspend fun checkTxHistoryState(networkId: String, derivationPath: String?): ProxyTransactionHistoryState {
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
val walletManager = getActualWalletManager(blockchain, derivationPath)
val state = walletManager.getTransactionHistoryState(address = walletManager.wallet.address)
return state.mapToProxy()
}
override suspend fun getTxHistoryItems(
networkId: String,
derivationPath: String?,
page: Int,
pageSize: Int,
): List<ProxyTransactionHistoryItem> {
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
val walletManager = getActualWalletManager(blockchain, derivationPath)
val itemsResult = walletManager.getTransactionsHistory(
address = walletManager.wallet.address,
page = page,
pageSize = pageSize,
)
return when (itemsResult) {
is Result.Success -> itemsResult.data.items.map { historyItem -> historyItem.mapToProxy() }
is Result.Failure -> error(itemsResult.error.message ?: itemsResult.error.customMessage)
}
}
private fun getActualWalletManager(blockchain: Blockchain, derivationPath: String?): WalletManager {
val blockchainNetwork = BlockchainNetwork(blockchain, derivationPath, emptyList())
val walletManager = appStateHolder.walletState?.getWalletManager(blockchainNetwork)
return requireNotNull(walletManager) { "no wallet manager found" }
}
private fun TransactionHistoryState.mapToProxy(): ProxyTransactionHistoryState {
return when (this) {
TransactionHistoryState.Success.Empty -> ProxyTransactionHistoryState.Success.Empty
is TransactionHistoryState.Failed.FetchError -> ProxyTransactionHistoryState.Failed.FetchError(exception)
TransactionHistoryState.NotImplemented -> ProxyTransactionHistoryState.NotImplemented
is TransactionHistoryState.Success.HasTransactions ->
ProxyTransactionHistoryState.Success.HasTransactions(txCount)
}
}
private fun TransactionHistoryItem.mapToProxy() = ProxyTransactionHistoryItem(
txHash = txHash,
timestamp = timestamp,
direction = when (val direction = direction) {
is TransactionHistoryItem.TransactionDirection.Incoming ->
ProxyTransactionHistoryItem.TransactionDirection.Incoming(direction.from)
is TransactionHistoryItem.TransactionDirection.Outgoing ->
ProxyTransactionHistoryItem.TransactionDirection.Outgoing(direction.to)
},
status = when (status) {
TransactionStatus.Confirmed -> ProxyTransactionStatus.Confirmed
TransactionStatus.Unconfirmed -> ProxyTransactionStatus.Unconfirmed
},
type = when (type) {
TransactionHistoryItem.TransactionType.Transfer -> ProxyTransactionHistoryItem.TransactionType.Transfer
},
amount = ProxyAmount(
currencySymbol = amount.currencySymbol,
value = requireNotNull(amount.value) { "Amount value must not be null" },
decimals = amount.decimals,
),
)
}

View file

@ -8,7 +8,6 @@ import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.feature.learn2earn.domain.api.Learn2earnDependencyProvider
import com.tangem.lib.crypto.DerivationManager
import com.tangem.lib.crypto.TransactionManager
import com.tangem.lib.crypto.TxHistoryManager
import com.tangem.lib.crypto.UserWalletManager
import com.tangem.tap.proxy.*
import dagger.Module
@ -59,12 +58,6 @@ class ProxyModule {
)
}
@Provides
@Singleton
fun provideTxHistoryManager(appStateHolder: AppStateHolder): TxHistoryManager {
return TxHistoryManagerImpl(appStateHolder = appStateHolder)
}
// regions FeatureConsumers
@Provides
@Singleton

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22dp"
android:height="22dp"
android:viewportWidth="22"
android:viewportHeight="22">
<path
android:pathData="M11,0L11,0A11,11 0,0 1,22 11L22,11A11,11 0,0 1,11 22L11,22A11,11 0,0 1,0 11L0,11A11,11 0,0 1,11 0z"
android:fillColor="#EBEBEB"/>
<path
android:pathData="M11.415,6.503C11.372,6.507 11.212,6.521 11.061,6.534C10.799,6.555 10.406,6.603 10.342,6.624C10.325,6.629 10.255,6.642 10.185,6.654C9.672,6.737 9.036,6.949 8.678,7.157C8.613,7.195 8.543,7.233 8.521,7.241C8.44,7.273 7.903,7.658 7.704,7.825C6.237,9.061 5.459,10.99 5.785,12.584C5.825,12.778 5.83,12.78 5.964,12.678C6.179,12.514 6.368,12.389 6.631,12.235C6.785,12.144 7.451,11.811 7.478,11.811C7.482,11.811 7.533,11.789 7.591,11.763C8.174,11.487 9.873,10.889 10.726,10.658C10.758,10.649 10.905,10.608 11.05,10.568C11.86,10.341 11.995,10.311 11.995,10.35C11.995,10.358 11.949,10.383 11.892,10.403C11.613,10.502 10.855,10.816 10.824,10.843C10.814,10.852 10.795,10.86 10.781,10.86C10.768,10.86 10.699,10.888 10.629,10.921C10.559,10.953 10.496,10.981 10.488,10.981C10.482,10.981 10.409,11.012 10.329,11.052C10.248,11.091 10.178,11.123 10.173,11.123C10.168,11.123 10.089,11.159 9.996,11.204C9.903,11.248 9.822,11.285 9.817,11.285C9.734,11.285 7.837,12.304 7.238,12.67C7.102,12.754 6.987,12.822 6.983,12.822C6.977,12.822 6.791,12.942 6.197,13.328C5.534,13.759 4.427,14.564 3.745,15.113C3.664,15.179 3.567,15.256 3.53,15.287C2.939,15.759 2.863,15.845 3.199,15.664C3.954,15.259 4.795,14.854 5.4,14.607C5.77,14.455 6.161,14.342 6.425,14.309L6.535,14.296L6.809,14.578C7.526,15.314 8.297,15.688 9.432,15.849C9.702,15.887 10.568,15.882 10.774,15.841C10.835,15.83 10.967,15.805 11.069,15.788C11.173,15.769 11.283,15.747 11.316,15.738C11.349,15.729 11.432,15.705 11.502,15.687C12.809,15.338 13.928,14.506 14.918,13.144C14.94,13.113 15.028,12.995 15.115,12.88C15.202,12.765 15.291,12.643 15.313,12.61C15.336,12.577 15.391,12.494 15.437,12.427C15.953,11.68 16.683,10.45 17.154,9.536C17.205,9.436 17.288,9.276 17.338,9.182C17.387,9.087 17.547,8.759 17.695,8.454C17.842,8.148 17.971,7.882 17.981,7.862C18.023,7.782 18.007,7.764 17.813,7.684C17.791,7.675 17.645,7.625 17.488,7.572C17.331,7.52 17.154,7.461 17.095,7.44C16.837,7.351 16.408,7.223 15.885,7.081C15.432,6.957 15.308,6.924 15.137,6.886C15.05,6.867 14.957,6.843 14.93,6.834C14.903,6.825 14.828,6.806 14.763,6.795C14.58,6.759 14.5,6.742 14.33,6.704C13.697,6.564 13.148,6.513 12.213,6.503C11.818,6.498 11.459,6.5 11.415,6.503Z"
android:fillColor="#909090"
android:fillType="evenOdd"/>
</vector>

View file

@ -10,7 +10,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="16dp"
android:text="Select"
android:text="@string/wallet_choice_wallet_option_title"
android:textSize="14sp"
android:textStyle="bold" />

View file

@ -264,16 +264,17 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/tv_sold_out_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:background="@drawable/shape_rectangle_rounded_4"
android:padding="16dp"
android:text="@string/shop_sold_out_description"
android:textColor="@color/text_tertiary"
android:textSize="16sp" />
<!--TODO: https://tangem.slack.com/archives/C01HARKDLQ0/p1691421861756069-->
<!--<TextView-->
<!-- android:id="@+id/tv_sold_out_desc"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_marginTop="14dp"-->
<!-- android:background="@drawable/shape_rectangle_rounded_4"-->
<!-- android:padding="16dp"-->
<!-- android:text="@string/shop_sold_out_description"-->
<!-- android:textColor="@color/text_tertiary"-->
<!-- android:textSize="16sp" />-->
</LinearLayout>
<LinearLayout

View file

@ -11,7 +11,7 @@ dependencies {
/** Project */
implementation(projects.core.utils)
implementation(projects.libs.auth)
implementation(projects.domain.core)
implementation(projects.domain.tokens.models)
implementation(projects.domain.wallets.models)
/** Tangem libraries */

View file

@ -60,4 +60,11 @@ interface TangemTechApi {
@Query(value = "locale") locale: String,
@Query(value = "shops") shops: String,
): SalesResponse
@GET("quotes")
suspend fun getQuotes(
@Query("currencyId") currencyId: String,
@Query("coinIds") coinIds: String,
@Query("fields") fields: String = "price,priceChange24h,lastUpdatedAt",
): QuotesResponse
}

View file

@ -0,0 +1,19 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import java.math.BigDecimal
data class QuotesResponse(
@Json(name = "quotes")
val quotes: Map<String, Quote>,
) {
data class Quote(
@Json(name = "price")
val price: BigDecimal,
@Json(name = "priceChange24h")
val priceChange: BigDecimal,
@Json(name = "lastUpdatedAt")
val lastUpdated: String,
)
}

View file

@ -0,0 +1,31 @@
package com.tangem.datasource.di
import android.content.Context
import com.squareup.moshi.Moshi
import com.tangem.datasource.local.datastore.SharedPreferencesDataStore
import com.tangem.datasource.local.quote.DefaultQuotesStore
import com.tangem.datasource.local.quote.QuotesStore
import com.tangem.datasource.local.quote.model.StoredQuote
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object QuotesStoreModule {
@Provides
@Singleton
fun provideQuotesStore(@ApplicationContext context: Context, @NetworkMoshi moshi: Moshi): QuotesStore {
return DefaultQuotesStore(
dataStore = SharedPreferencesDataStore(
preferencesName = "quotes",
context = context,
adapter = moshi.adapter(StoredQuote::class.java),
),
)
}
}

View file

@ -0,0 +1,76 @@
package com.tangem.datasource.local.datastore
import android.content.Context
import android.content.Context.MODE_PRIVATE
import androidx.core.content.edit
import com.squareup.moshi.JsonAdapter
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
import com.tangem.datasource.local.datastore.model.WriteTrigger
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.*
import timber.log.Timber
internal class SharedPreferencesDataStore<Value : Any>(
preferencesName: String,
private val context: Context,
private val adapter: JsonAdapter<Value>,
) : StringKeyDataStore<Value> {
private val sharedPreferences by lazy {
context.getSharedPreferences(preferencesName, MODE_PRIVATE)
}
private val writeTrigger = MutableSharedFlow<WriteTrigger>(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST,
)
override fun get(key: String): Flow<Value> {
return writeTrigger
.onEmpty { emit(WriteTrigger) }
.map { getInternal(key) }
.filterNotNull()
}
override suspend fun getSyncOrNull(key: String): Value? {
return getInternal(key)
}
override suspend fun store(key: String, item: Value) {
try {
val json = adapter.toJson(item)
sharedPreferences.edit { putString(key, json) }
writeTrigger.emit(WriteTrigger)
} catch (e: Throwable) {
Timber.e(e, "Unable to edit preferences: $key")
}
}
override suspend fun store(items: Map<String, Value>) {
items.forEach { (key, item) ->
store(key, item)
}
}
override suspend fun remove(key: String) {
sharedPreferences.edit {
remove(key)
}
}
override suspend fun clear() {
sharedPreferences.edit { clear() }
}
private fun getInternal(key: String): Value? {
return try {
val json = sharedPreferences.getString(key, null) ?: return null
adapter.fromJson(json)
} catch (e: Throwable) {
Timber.e(e, "Unable to get value from preferences: $key")
null
}
}
}

View file

@ -0,0 +1,27 @@
package com.tangem.datasource.local.quote
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
import com.tangem.datasource.local.quote.model.StoredQuote
import com.tangem.domain.tokens.models.CryptoCurrency
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
internal class DefaultQuotesStore(
private val dataStore: StringKeyDataStore<StoredQuote>,
) : QuotesStore {
override fun get(currenciesIds: Set<CryptoCurrency.ID>): Flow<Set<StoredQuote>> {
val flows = currenciesIds.mapNotNull { currencyId ->
dataStore.get(currencyId.rawCurrencyId ?: return@mapNotNull null)
}
return combine(flows) { quotes -> quotes.toSet() }
}
override suspend fun store(response: QuotesResponse) {
response.quotes.forEach { (rawCurrencyId, quote) ->
dataStore.store(rawCurrencyId, StoredQuote(rawCurrencyId, quote))
}
}
}

View file

@ -0,0 +1,13 @@
package com.tangem.datasource.local.quote
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
import com.tangem.datasource.local.quote.model.StoredQuote
import com.tangem.domain.tokens.models.CryptoCurrency
import kotlinx.coroutines.flow.Flow
interface QuotesStore {
fun get(currenciesIds: Set<CryptoCurrency.ID>): Flow<Set<StoredQuote>>
suspend fun store(response: QuotesResponse)
}

View file

@ -0,0 +1,9 @@
package com.tangem.datasource.local.quote.model
import com.squareup.moshi.Json
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
data class StoredQuote(
@Json(name = "rawCurrencyId") val rawCurrencyId: String,
@Json(name = "quote") val quote: QuotesResponse.Quote,
)

View file

@ -5,5 +5,7 @@ import com.tangem.domain.wallets.models.UserWalletId
interface UserWalletsStore {
val selectedUserWalletOrNull: UserWallet?
suspend fun getSyncOrNull(key: UserWalletId): UserWallet?
}

View file

@ -5,7 +5,7 @@ import com.tangem.blockchain.common.WalletManager
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.extensions.plusOrReplace
import com.tangem.utils.extensions.addOrReplace
internal class DefaultWalletManagersStore(
dataStore: StringKeyDataStore<List<WalletManager>>,
@ -32,7 +32,7 @@ internal class DefaultWalletManagersStore(
val walletManagers = getSyncOrNull(userWalletId)
val updatedWalletManagers = walletManagers
?.plusOrReplace(walletManager) {
?.addOrReplace(walletManager) {
it.wallet.blockchain == walletManager.wallet.blockchain &&
it.wallet.publicKey == walletManager.wallet.publicKey
}

View file

@ -2,7 +2,6 @@
<resources>
<string name="common_fee_error">Erhalt der Gebühr fehlgeschlagen</string>
<string name="no_account_generic">Laden Sie %1$s+ %2$s auf um ein Konto zu erstellen</string>
<string name="send_error_blockchain_internal">Interner Fehler der Blockchain</string>
<string name="send_error_dust_amount_format">Minimaler Betrag ist %s</string>
<string name="send_error_dust_change">Restbestand zu klein</string>
<string name="send_error_invalid_fee_value">Falsche Gebühr</string>

View file

@ -10,6 +10,7 @@
<string name="common_cancel">Abbrechen</string>
<string name="common_delete">Entfernen</string>
<string name="common_done">Erledigt</string>
<string name="common_error">Fehler</string>
<string name="common_ok">OK</string>
<string name="common_save_changes">Änderungen speichern</string>
<string name="common_send">Absenden</string>
@ -27,12 +28,14 @@
<string name="details_row_title_signed_hashes">Signiert</string>
<string name="details_title">Details</string>
<string name="disclaimer_title">Nutzungsbedingungen</string>
<string name="home_button_scan">Karte scannen</string>
<string name="initial_message_change_access_code_body">Tippen Sie um den Zugangscode zu ändern</string>
<string name="initial_message_change_passcode_body">Tippen Sie um den Passcode zu ändern</string>
<string name="initial_message_scan_header">Legen Sie die Karte zum Scannen an</string>
<string name="initial_message_sign_header">Tippen um zu signieren</string>
<string name="initial_message_tap_header">Legen Sie die Karte an</string>
<string name="main_processing_full_amount">Der Betrag enthält nicht einige Ihrer Mittel</string>
<string name="onboarding_create_wallet_button_create_wallet">Ein wallet erstellen</string>
<string name="send_amount_label">Betrag</string>
<string name="send_destination_hint_address">Adresse</string>
<string name="send_error_address_same_as_wallet">Die Adresse stimmt mit der Adresse Ihrer Brieftasche überein</string>

View file

@ -2,7 +2,6 @@
<resources>
<string name="common_fee_error">Échec de réception des commissions</string>
<string name="no_account_generic">Pour créer un compte, téléchargez %1$s+ %2$s</string>
<string name="send_error_blockchain_internal">Erreur interne de la blockchain</string>
<string name="send_error_dust_amount_format">Le montant minimal est de %s</string>
<string name="send_error_dust_change">Le reste est trop petit</string>
<string name="send_error_invalid_fee_value">Commission non valide</string>

View file

@ -10,6 +10,7 @@
<string name="common_cancel">Annuler</string>
<string name="common_delete">Supprimer</string>
<string name="common_done">Exécuté</string>
<string name="common_error">Erreur</string>
<string name="common_ok">OK</string>
<string name="common_save_changes">Sauvegarder les modifications</string>
<string name="common_send">Envoyer</string>
@ -27,12 +28,14 @@
<string name="details_row_title_signed_hashes">Signé</string>
<string name="details_title">Référénces</string>
<string name="disclaimer_title">Conditions d\'utilisation</string>
<string name="home_button_scan">Scannez la carte</string>
<string name="initial_message_change_access_code_body">Touchez, pour modifier le code d\'accès</string>
<string name="initial_message_change_passcode_body">Touchez, pour modifier le mot de passe</string>
<string name="initial_message_scan_header">Posez pour scanner</string>
<string name="initial_message_sign_header">Touchez pour signer</string>
<string name="initial_message_tap_header">Posez la carte</string>
<string name="main_processing_full_amount">Le montant n\'inclut pas certains de vos fonds</string>
<string name="onboarding_create_wallet_button_create_wallet">Créer un portefeuille</string>
<string name="send_amount_label">Somme</string>
<string name="send_destination_hint_address">Adresse</string>
<string name="send_error_address_same_as_wallet">L\'adresse est la même que celle de votre portefeuille</string>

View file

@ -2,7 +2,6 @@
<resources>
<string name="common_fee_error">Impossibile ottenere la commissione</string>
<string name="no_account_generic">Scarica %1$s+ %2$s per creare un account</string>
<string name="send_error_blockchain_internal">Errore interno della blockchain</string>
<string name="send_error_dust_amount_format">L\'importo minimo è di %s</string>
<string name="send_error_dust_change">L\'importo residuo è molto basso</string>
<string name="send_error_invalid_fee_value">Commissione non valida</string>

View file

@ -10,6 +10,7 @@
<string name="common_cancel">Annulla</string>
<string name="common_delete">Rimuovere</string>
<string name="common_done">Fatto</string>
<string name="common_error">Errore</string>
<string name="common_ok">OK</string>
<string name="common_save_changes">Mantieni le modifiche</string>
<string name="common_send">Invia</string>
@ -27,12 +28,14 @@
<string name="details_row_title_signed_hashes">Firmato</string>
<string name="details_title">Requisiti</string>
<string name="disclaimer_title">Termini del servizio</string>
<string name="home_button_scan">Scansiona carta</string>
<string name="initial_message_change_access_code_body">Avvicina per modificare il codice di accesso</string>
<string name="initial_message_change_passcode_body">Avvicina per modificare la password</string>
<string name="initial_message_scan_header">Avvicina per scansionare</string>
<string name="initial_message_sign_header">Avvicina per firmare</string>
<string name="initial_message_tap_header">Avvicina la carta</string>
<string name="main_processing_full_amount">L\'importo non include alcuni dei tuoi fondi</string>
<string name="onboarding_create_wallet_button_create_wallet">Crea portafoglio</string>
<string name="send_amount_label">Importo</string>
<string name="send_destination_hint_address">Indirizzo</string>
<string name="send_error_address_same_as_wallet">L\'indirizzo corrisponde all\'indirizzo del tuo portafoglio</string>

View file

@ -6,7 +6,6 @@
<string name="kaspa_withdrawal_message_warning">Из-за ограничений Kaspa в одну транзакцию может поместиться только %1$d UTXO. Это означает, что вы можете отправить только %2$s или меньше. Вам нужно уменьшить сумму.</string>
<string name="no_account_generic">Пополните счет на %1$s+ %2$s, чтобы создать аккаунт</string>
<string name="no_account_polkadot">Аккаунт получателя не активирован. Отправьте %s или более для активации аккаунта.</string>
<string name="send_error_blockchain_internal">Внутренняя ошибка блокчейна</string>
<string name="send_error_dust_amount_format">Минимальная сумма: %s</string>
<string name="send_error_dust_change">Сдача слишком мала</string>
<string name="send_error_invalid_fee_value">Неверная комиссия</string>

View file

@ -1,13 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<string name="add_custom_token_title">Добавить токен</string>
<string name="add_tokens_title">Валюты</string>
<string name="address_qr_code_message_format">Отправляйте только %1$s (%2$s) в сети %3$s на этот адрес. Использование другой сети может привести к утрате средств.</string>
<string name="alert_app_feedback_sent_message">Спасибо за ваш отзыв</string>
<string name="alert_app_feedback_sent_title">Отправлено успешно</string>
<string name="alert_button_request_support">Обратиться в поддержку</string>
<string name="alert_button_send_feedback">Отправить отзыв</string>
<string name="alert_card_signed_transactions">Эта карта ранее пополнялась и подписывала транзакции. Выведите средства как можно быстрее, если вы получили эту карту из ненадежного источника. Если это ваша карта, то не о чем беспокоиться.</string>
<string name="alert_demo_feature_disabled">Эта функция недоступна в демонстрационном режиме</string>
<string name="alert_demo_message">Приложение работает в демонстрационном режиме. Средства на всех счетах ненастоящие.</string>
<string name="alert_developer_card">Карта, которую вы отсканировали, является картой разработчика. Не принимайте её в качестве оплаты.</string>
<string name="alert_failed_to_send_email_title">Не удалось отправить письмо</string>
<string name="alert_failed_to_send_transaction_message">Причина: %s</string>
<string name="alert_failed_to_send_transaction_title">Не могу отправить транзакцию</string>
<string name="alert_manage_tokens_addresses_message">Внимание! Валюты на разных сетях имеют разные адреса. Убедитесь, что адрес соответствует сети, в которой вы отправляете средства.</string>
@ -36,14 +40,22 @@
</plurals>
<string name="card_settings_access_code_recovery_disabled_description">Отключите эту опцию, если не хотите, чтобы эта карта использовалась для сброса кодов доступа на другие карты этого кошелька. Обратите внимание, сброс кода также не будет доступен на этой карте.</string>
<string name="card_settings_access_code_recovery_enabled_description">Использовать эту карту для сброса кода доступа на других картах в этом кошельке</string>
<string name="card_settings_access_code_recovery_footer">Отключить возможность сброса кода доступа на этой карте или других картах этого кошелька</string>
<string name="card_settings_access_code_recovery_title">Восстановление кода доступа</string>
<string name="card_settings_action_sheet_reset">Сбросить</string>
<string name="card_settings_action_sheet_title">Вы уверены, что хотите это сделать?</string>
<string name="card_settings_change_access_code">Смена кода доступа</string>
<string name="card_settings_change_access_code_footer">Код доступа будет изменен только на данной карте</string>
<string name="card_settings_reset_card_to_factory">Заводские настройки</string>
<string name="card_settings_security_mode">Тип безопасности</string>
<string name="card_settings_security_mode_footer">Выбранный способ защиты приложения</string>
<string name="card_settings_title">Настройки карты</string>
<string name="chat_bot_name">Tangem Bot</string>
<string name="chat_button_title">Чат</string>
<string name="chat_user_action_rate_user">Оценить агента</string>
<string name="chat_user_action_send_log">Отправить логи</string>
<string name="chat_user_actions_title">Пожалуйста, выберите действие</string>
<string name="chat_user_rate_agent_title">Пожалуйста, оцените работу агента</string>
<string name="common_accept">Принять</string>
<string name="common_add">Добавить</string>
<string name="common_apply">Применить</string>
@ -56,6 +68,7 @@
<string name="common_camera_denied_alert_message">Вы не предоставили доступ к камере, пожалуйста, измените настройки конфиденциальности.</string>
<string name="common_cancel">Отмена</string>
<string name="common_close">Закрыть</string>
<string name="common_continue">Продолжить</string>
<string name="common_copy">Копировать</string>
<string name="common_copy_address">Скопировать адрес</string>
<string name="common_create">Создать</string>
@ -65,12 +78,17 @@
<string name="common_done">Готово</string>
<string name="common_enable">Включить</string>
<string name="common_enabled">Включено</string>
<string name="common_error">Ошибка</string>
<string name="common_exchange">Обменять</string>
<string name="common_explore_transaction_history">Посмотреть историю транзакций</string>
<string name="common_explorer">Обозреватель</string>
<string name="common_generate_addresses">Сгенерировать адреса</string>
<string name="common_import">Импортировать</string>
<string name="common_like">Нравится</string>
<string name="common_locked">Заблокирован</string>
<string name="common_main_network">Основная сеть</string>
<string name="common_no">Нет</string>
<string name="common_no_address">Нет адреса</string>
<string name="common_no_data">Нет данных</string>
<string name="common_ok">OK</string>
<string name="common_origin_card">Основная карта</string>
@ -79,7 +97,9 @@
<string name="common_reload">Перезагрузить</string>
<string name="common_save_changes">Сохранить изменения</string>
<string name="common_search">Искать</string>
<string name="common_search_tokens">Поиск токенов</string>
<string name="common_seed_phrase">Секретная фраза</string>
<string name="common_select_action">Выберите действие</string>
<string name="common_sell">Продать</string>
<string name="common_send">Отправить</string>
<string name="common_server_unavailable">Сервер недоступен, повторите попытку позднее</string>
@ -99,6 +119,7 @@
<string name="common_yes">Да</string>
<string name="contract_address_copied_message">Адрес контракта скопирован!</string>
<string name="currency_subtitle_expanded">Доступные сети</string>
<string name="custom_token_add_token">Добавить токен</string>
<string name="custom_token_contract_address_input_title">Адрес контракта</string>
<string name="custom_token_creation_error_invalid_contract_address">Адрес контракта некорректен</string>
<string name="custom_token_creation_error_invalid_derivation_path">Путь деривации некорректен</string>
@ -138,7 +159,6 @@
<string name="disclaimer_error_loading">Проверьте подключение с интернетом или переключитесь на другую сеть</string>
<string name="disclaimer_title">Условия использования</string>
<string name="error_update_app">К сожалению, текущая версия приложения не готова к работе с этой картой, проверьте наличие обновлений</string>
<string name="error_wrong_card_type">Данное приложение не предназначено для работы с этой картой или требует обновления</string>
<string name="error_wrong_wallet_tapped">Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком.</string>
<string name="exchange_receive_view_header">Вы получаете</string>
<string name="exchange_send_view_header">Вы отправляете</string>
@ -153,10 +173,13 @@
<string name="feedback_subject_support_tangem">Обращение в поддержку Tangem</string>
<string name="feedback_subject_tx_failed">Не могу отправить транзакцию</string>
<string name="home_button_order">Купить</string>
<string name="home_button_scan">Сканировать</string>
<string name="initial_message_change_access_code_body">Чтобы изменить код доступа, приложите карту как показано выше и не убирайте до окончания операции</string>
<string name="initial_message_change_passcode_body">Чтобы изменить пароль, приложите карту как показано выше и не убирайте до окончания операции</string>
<string name="initial_message_create_wallet_body">Чтобы создать кошелек, приложите карту как показано выше и не убирайте до окончания операции</string>
<string name="initial_message_purge_wallet_body">Чтобы сбросить настройки до заводских, приложите карту как показано выше и не убирайте до окончания операции</string>
<string name="initial_message_scan_header">Приложите, чтобы отсканировать</string>
<string name="initial_message_sign_body">Чтобы подписать транзакцию, приложите карту как показано выше и не убирайте до окончания операции</string>
<string name="initial_message_sign_header">Приложите, чтобы подписать</string>
<string name="initial_message_tap_header">Приложите карту</string>
<string name="internal_error_wallet_manager_not_found">Внутренняя ошибка: не удается найти менеджер кошельков</string>
@ -174,9 +197,16 @@
<string name="main_page_balance">Баланс</string>
<string name="main_processing_full_amount">В сумме учтены не все монеты</string>
<string name="main_promotion_credited">1INCH токены будут зачислены на адрес вашего кошелька в сети %s в течение 48 часов</string>
<string name="main_promotion_no_purchase">По вашему промокоду не было покупки кошелька, а значит вы не можете получить бонус. Купите кошелек Tangem, отсканируйте его в приложении и получите бонус.</string>
<string name="main_scan_card_warning_view_subtitle">Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту</string>
<string name="main_scan_card_warning_view_title">Отсканируйте карту</string>
<string name="main_tokens">Токены</string>
<plurals name="main_warning_missing_derivation_description">
<item quantity="one">Вам надо сгенерировать адрес для %d новой сети, используя вашу карту</item>
<item quantity="few">Вам надо сгенерировать адреса для %d новых сетей, используя вашу карту</item>
<item quantity="many">Вам надо сгенерировать адреса для %d новых сетей, используя вашу карту</item>
<item quantity="other">Вам надо сгенерировать адреса для %d новых сетей, используя вашу карту</item>
</plurals>
<string name="main_warning_missing_derivation_title">Некоторые адреса отсутствуют</string>
<string name="onboarding_access_code_feature_1_description">Вам необходимо установить единый код доступа для защиты всех ваших карт</string>
<string name="onboarding_access_code_feature_1_title">Защита</string>
<string name="onboarding_access_code_feature_2_description">Позже вы сможете установить индивидуальный код доступа для каждой карты</string>
@ -218,7 +248,7 @@
<string name="onboarding_navbar_pin">Код доступа</string>
<string name="onboarding_navbar_register_wallet">Подключиться</string>
<string name="onboarding_navbar_title_creating_backup">Резервная копия</string>
<string name="onboarding_seed_button_read_more">Читать про секретную фразу</string>
<string name="onboarding_seed_button_read_more">Прочитать о секретной фразе</string>
<string name="onboarding_seed_generate_message">Запишите эти 12 слов в порядке, указанном ниже, и сохраните их в надежном месте.</string>
<string name="onboarding_seed_generate_title">Ваша секретная фраза</string>
<string name="onboarding_seed_import_message">Чтобы импортировать кошелек, введите секретную фразу в поле ниже</string>
@ -236,7 +266,7 @@
<string name="onboarding_subtitle_no_backup_cards">Чтобы начать процесс резервного копирования, добавьте одну или две резервные карты.</string>
<string name="onboarding_subtitle_one_backup_card">Вы можете добавить еще одну карту или завершить процесс резервного копирования</string>
<string name="onboarding_subtitle_scan_backup_card_format">Подготовьте резервную карту с номером %s</string>
<string name="onboarding_subtitle_scan_origin_card">Подготовьте основную карту</string>
<string name="onboarding_subtitle_scan_primary">Отсканируйте основную карту, чтобы начать процесс резервного копирования.</string>
<string name="onboarding_subtitle_scan_primary_card_format">Подготовьте основную карту с номером %s</string>
<string name="onboarding_subtitle_success_claim">Поздравляем! Ваша платежная крипто карта теперь активирована!</string>
<string name="onboarding_subtitle_success_tangem_wallet_onboarding">Ваша карта настроена и готова к использованию.</string>
@ -254,7 +284,7 @@
<string name="onboarding_top_up_body_no_account_error">Пополните кошелек более чем на %1$s %2$s, чтобы начать пользоваться картой</string>
<string name="onboarding_top_up_button_but_crypto">Купить криптовалюту</string>
<string name="onboarding_top_up_button_show_wallet_address">Показать адрес кошелька</string>
<string name="onboarding_top_up_header">Пополните свой кошелек</string>
<string name="onboarding_topup_title">Активация кошелька</string>
<string name="onboarding_twin_exit_warning">Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас.</string>
<string name="onboarding_twins_interrupt_warning">Если процесc создания кошелька каким-либо образом прервется, вам придется начинать сначала</string>
<string name="onboarding_wallet_info_subtitle_first">Вы можете сделать резервную копию своих ключей на одной или двух других пустых картах Wallet.</string>
@ -274,12 +304,23 @@
<string name="referral_button_participate">Участвовать</string>
<string name="referral_error_failed_to_load_info">Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже.</string>
<string name="referral_error_failed_to_load_info_with_reason">Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже.</string>
<string name="referral_expected_awards">Грядущие выплаты</string>
<string name="referral_friends_bought_title">Ваши друзья купили</string>
<string name="referral_less">Меньше</string>
<string name="referral_more">Больше</string>
<string name="referral_no_expected_awards">Нет грядущих выплат</string>
<plurals name="referral_number_of_wallets">
<item quantity="one">за %d кошелек</item>
<item quantity="few">за %d кошелька</item>
<item quantity="many">за %d кошельков</item>
<item quantity="other">за %d кошельков</item>
</plurals>
<string name="referral_point_currencies_description">Получите ^^%1$s^^ на ваш адрес в сети %2$s %3$s ^^спустя 30 дней^^ за каждый кошелек, который купит ваш друг</string>
<string name="referral_point_currencies_description_prefix">Получите</string>
<string name="referral_point_currencies_description_suffix">на ваш адрес в сети %1$s%2$s за каждый кошелек, который купит ваш друг</string>
<string name="referral_point_currencies_title">Вы</string>
<string name="referral_point_discount_description_prefix">Получит</string>
<string name="referral_point_discount_description_suffix">при покупке карточки на сайте tangem.com</string>
<string name="referral_point_discount_description_suffix">при покупке кошелька на сайте tangem.com</string>
<string name="referral_point_discount_description_value">%s скидку</string>
<string name="referral_point_discount_title">Ваш друг</string>
<string name="referral_promo_code_copied">Персональный код скопирован!</string>
@ -315,7 +356,6 @@
<string name="scan_card_settings_button">Сканировать</string>
<string name="scan_card_settings_message">Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку.</string>
<string name="scan_card_settings_title">Приготовьте свою карту</string>
<string name="search_tokens_title">Поиск валют</string>
<string name="send_amount_label">Сумма</string>
<string name="send_destination_hint_address">Адрес</string>
<string name="send_error_address_same_as_wallet">Адрес совпадает с адресом кошелька</string>
@ -341,7 +381,7 @@
<string name="shop_i_have_a_promo_code">У меня есть промо-код…</string>
<string name="shop_one_wallet">Tangem Wallet</string>
<string name="shop_other_payment_methods">Другие способы оплаты</string>
<string name="shop_sold_out_description">Из-за высокого количества заказов, которые мы получаем, доставка может быть задержана на срок до 5 недель в зависимости от вашего местоположения</string>
<string name="shop_pre_order_now">Сделать предзаказ</string>
<string name="shop_total">Итого</string>
<string name="solana_rent_warning">Сеть Solana взимает арендную плату в размере %1$s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %2$s, чтобы не платить арендную плату.</string>
<string name="story_awe_description">Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте.</string>
@ -413,12 +453,6 @@
<string name="token_details_unable_hide_alert_message">Токен %1$s является основной валютой в сети %2$s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети.</string>
<string name="token_details_unable_hide_alert_title">Невозможно скрыть %s</string>
<string name="token_item_no_rate">Нет цены</string>
<plurals name="tokens_count">
<item quantity="one">%d токен</item>
<item quantity="few">%d токена</item>
<item quantity="many">%d токенов</item>
<item quantity="other">%d токенов</item>
</plurals>
<string name="transaction_history_contract_address">контракт: %s</string>
<string name="transaction_history_empty_transactions">У вас еще нет транзакций</string>
<string name="transaction_history_error_failed_to_load">Не удалось загрузить историю транзакций.\nНажмите на кнопку перезагрузки, чтобы обновить информацию.</string>
@ -427,6 +461,7 @@
<string name="transaction_history_transaction_to_address">на: %s</string>
<string name="transaction_history_tx_in_progress">В процессе…</string>
<string name="twin_error_same_card">Вы отсканировали ту же карту. Для создания twin-кошелька вам необходимо отсканировать карту с номером %d</string>
<string name="twin_error_wrong_twin">Вы отсканировали не ту twin-карту. Пожалуйста, попробуйте отсканировать другую</string>
<string name="twins_onboarding_description_format">Это карта, которую вы держите в руках. У парной карты номер %s.\n\nОбе карты можно использовать для вывода средств из этого кошелька.</string>
<string name="twins_onboarding_subtitle">Один кошелек. Две карты.</string>
<string name="twins_recreate_button_format">Сканировать карту #%s</string>
@ -435,6 +470,7 @@
<string name="twins_recreate_title_preparing">Подготовка карты</string>
<string name="twins_recreate_toolbar">Tangem Twin</string>
<string name="twins_recreate_warning">Это действие необратимо. У вас не будет доступа к старому кошельку.</string>
<string name="twins_scan_twin_with_number">Приложите twin-карту с номером %s и не убирайте до окончания операции</string>
<string name="user_wallet_list_add_button">Добавить новый кошелек</string>
<string name="user_wallet_list_delete_prompt">Вы уверены, что хотите удалить этот кошелек?</string>
<string name="user_wallet_list_editing_count">%d выбрано</string>
@ -454,6 +490,7 @@
<string name="wallet_balance_tx_in_progress">Транзакция подтверждается…</string>
<string name="wallet_balance_verified">Подтвержденный баланс</string>
<string name="wallet_button_actions">Действия</string>
<string name="wallet_choice_wallet_option_title">Что отправить?</string>
<string name="wallet_choose_trade_action">Вы хотите купить или продать криптовалюту?</string>
<string name="wallet_connect_alert_sign_message">Запрос на подпись сообщения.\n\n%s</string>
<string name="wallet_connect_bnb_sign_message">Dapp %1$s, запрос на\nподпись транзакции с BNB.\n\n%2$s</string>
@ -475,7 +512,10 @@
<string name="wallet_connect_generic_error_with_code">Произошла непредвиденная ошибка. Код ошибки: %d Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки.</string>
<string name="wallet_connect_message_signed">Сообщение было успешно подписано и отправлено в Dapp</string>
<string name="wallet_connect_network_not_found_format">Сеть %s не найдена. Пожалуйста, добавьте её и попробуйте заново.</string>
<string name="wallet_connect_no_sessions_message">Нет открытых сессий WalletConnect</string>
<string name="wallet_connect_no_sessions_title">Упс. Нет сессий.</string>
<string name="wallet_connect_paste_from_clipboard">Вставить из буфера обмена</string>
<string name="wallet_connect_personal_sign_message">Сообщение для %1$s:\n%2$s</string>
<string name="wallet_connect_request_session_start">Запрос на открытие сессии для\n%1$s\n\nСЕТЬ: %2$s\n\nURL: %3$s</string>
<string name="wallet_connect_same_wcuri">Операция не может быть завершена.\n\nВы уже установили сеанс WalletConnect с этими параметрами.</string>
<string name="wallet_connect_scan_new_code">Сканировать новый код</string>
@ -483,6 +523,8 @@
<string name="wallet_connect_scanner_error_unsupported_network">Сеть не поддерживается. Пожалуйста, выберите другую сеть.</string>
<string name="wallet_connect_select_network">Выберите сеть</string>
<string name="wallet_connect_service_no_chain_id">Dapp не предоставил необходимые данные для открытия сессии WalletConnect</string>
<string name="wallet_connect_session_not_found">Не удалось найти сессию для обработки запроса</string>
<string name="wallet_connect_sessions_title">Сессии WalletConnect</string>
<string name="wallet_connect_subtitle">Подключение к Dapps</string>
<string name="wallet_connect_title">WalletConnect</string>
<string name="wallet_connect_transaction_signed">Транзакция успешно подписана и отправлена ​​в Dapp</string>

View file

@ -6,7 +6,6 @@
<string name="kaspa_withdrawal_message_warning">由於 Kaspa 的限制,只有%1$d UTXO 可以放入單次交易中。這意味著您只能發送%2$s或更少數量。您需要減少數量。</string>
<string name="no_account_generic">加載 %1$s+ %2$s 以創建帳戶</string>
<string name="no_account_polkadot">目標帳戶未激活。發送 %s 或更多以激活帳戶</string>
<string name="send_error_blockchain_internal">區塊鏈內部錯誤</string>
<string name="send_error_dust_amount_format">最小數量是 %s</string>
<string name="send_error_dust_change">更動太小</string>
<string name="send_error_invalid_fee_value">無效費用</string>

View file

@ -1,13 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<string name="add_custom_token_title">添加自定義代幣</string>
<string name="add_tokens_title">管理代幣</string>
<string name="address_qr_code_message_format">僅將 %1$s (%2$s) 從 %3$s 網絡發送到此地址。使用其他代幣和網絡可能會導致資金損失</string>
<string name="alert_app_feedback_sent_message">謝謝您的反饋</string>
<string name="alert_app_feedback_sent_title">成功送出</string>
<string name="alert_button_request_support">請求支持</string>
<string name="alert_button_send_feedback">發送反饋</string>
<string name="alert_card_signed_transactions">此卡過去已經充值並簽署過交易。如果您從不受信任的來源收到此卡,請考慮立即提取所有資金。如果是您的卡,則無需擔心</string>
<string name="alert_demo_feature_disabled">此功能不在展示模式中提供</string>
<string name="alert_demo_message">您正在展示模式。所有資產皆不是真的</string>
<string name="alert_developer_card">您掃描的卡是開發卡。不要用它作為付款方式</string>
<string name="alert_failed_to_send_email_title">發送電子郵件失敗</string>
<string name="alert_failed_to_send_transaction_message">原因:%s</string>
<string name="alert_failed_to_send_transaction_title">無法發送交易</string>
<string name="alert_manage_tokens_addresses_message">請注意代幣在不同的網路有不同的地址。請再次檢查正確的地址</string>
@ -32,14 +36,22 @@
</plurals>
<string name="card_settings_access_code_recovery_disabled_description">如果您不希望使用此卡重置此錢包中其他卡上的訪問密碼,請禁用此選項。請注意,這也會阻止您重置此卡上的訪問密碼。</string>
<string name="card_settings_access_code_recovery_enabled_description">允許您使用此卡重置此錢包中其他卡上的訪問密碼</string>
<string name="card_settings_access_code_recovery_footer">禁用重置此卡或此錢包中其他卡上的訪問密碼的功能</string>
<string name="card_settings_access_code_recovery_title">恢復訪問密碼</string>
<string name="card_settings_action_sheet_reset">重置</string>
<string name="card_settings_action_sheet_title">您確定要這麼做嗎?</string>
<string name="card_settings_change_access_code">更改訪問密碼</string>
<string name="card_settings_change_access_code_footer">訪問密碼將僅在此卡上更改</string>
<string name="card_settings_reset_card_to_factory">回復至原廠設置</string>
<string name="card_settings_security_mode">安全模式</string>
<string name="card_settings_security_mode_footer">選定的應用程序保護方法</string>
<string name="card_settings_title">卡片設置</string>
<string name="chat_bot_name">Tangem 機器人</string>
<string name="chat_button_title">支援</string>
<string name="chat_user_action_rate_user">請評價</string>
<string name="chat_user_action_send_log">發送logs</string>
<string name="chat_user_actions_title">請選擇一個操作</string>
<string name="chat_user_rate_agent_title">請評價我們的服務</string>
<string name="common_accept">接受</string>
<string name="common_add">添加</string>
<string name="common_attention">注意</string>
@ -50,6 +62,7 @@
<string name="common_camera_denied_alert_message">您尚未授予相機訪問權限,請更改您的隱私設置</string>
<string name="common_cancel">刪除</string>
<string name="common_close">關閉</string>
<string name="common_continue">繼續</string>
<string name="common_copy">複製</string>
<string name="common_copy_address">複製地址</string>
<string name="common_create">創造</string>
@ -59,7 +72,9 @@
<string name="common_done">完成</string>
<string name="common_enable">允許</string>
<string name="common_enabled">啟用</string>
<string name="common_error">錯誤</string>
<string name="common_exchange">交易</string>
<string name="common_import">導入</string>
<string name="common_like">喜歡</string>
<string name="common_no"></string>
<string name="common_ok">OK</string>
@ -67,6 +82,8 @@
<string name="common_reject">拒絕</string>
<string name="common_save_changes">保存設置</string>
<string name="common_search">搜索</string>
<string name="common_search_tokens">搜尋代幣</string>
<string name="common_select_action">選擇</string>
<string name="common_sell">銷售</string>
<string name="common_send">發送</string>
<string name="common_server_unavailable">伺服器不可用,請稍後在試</string>
@ -84,6 +101,7 @@
<string name="common_yes"></string>
<string name="contract_address_copied_message">已複製代幣地址</string>
<string name="currency_subtitle_expanded">支持的網路</string>
<string name="custom_token_add_token">添加代幣</string>
<string name="custom_token_contract_address_input_title">代幣地址</string>
<string name="custom_token_creation_error_invalid_contract_address">無效地址</string>
<string name="custom_token_creation_error_invalid_derivation_path">衍生路徑錯誤</string>
@ -135,10 +153,13 @@
<string name="feedback_subject_support_tangem">Tangem反饋</string>
<string name="feedback_subject_tx_failed">無法發送交易</string>
<string name="home_button_order">訂購</string>
<string name="home_button_scan">掃描卡片</string>
<string name="initial_message_change_access_code_body">要更改訪問密碼,請完全按照上圖所示連接手機和卡片</string>
<string name="initial_message_change_passcode_body">要更改密碼,請完全按照上圖所示連接手機和卡</string>
<string name="initial_message_create_wallet_body">要創建錢包,請完全按照上圖所示連接手機和卡</string>
<string name="initial_message_purge_wallet_body">要重置為出廠設置,請完全按照上圖所示連接手機和卡片</string>
<string name="initial_message_scan_header">點擊掃描</string>
<string name="initial_message_sign_body">要簽名,請完全按照上圖所示連接手機和卡</string>
<string name="initial_message_sign_header">點擊簽名</string>
<string name="initial_message_tap_header">點按卡片</string>
<string name="internal_error_wallet_manager_not_found">內部錯誤:找不到錢包管理器</string>
@ -150,6 +171,7 @@
<string name="main_processing_full_amount">該金額不包括您的部分資金</string>
<string name="main_scan_card_warning_view_subtitle">要訪問所有的網路您需要掃描卡片</string>
<string name="main_scan_card_warning_view_title">掃描卡片</string>
<string name="main_tokens">代幣</string>
<string name="onboarding_access_code_feature_1_description">您必須設置一個單一的訪問代碼來保護您的所有錢包</string>
<string name="onboarding_access_code_feature_1_title">保護</string>
<string name="onboarding_access_code_feature_2_description">您可以稍後在每張卡上設置單獨的訪問密碼</string>
@ -187,7 +209,7 @@
<string name="onboarding_exit_alert_message">這此情況,您必須要重新開始</string>
<string name="onboarding_exit_alert_title">您想要離開啟用程序嗎?</string>
<string name="onboarding_getting_started">開始</string>
<string name="onboarding_linking_error_card_with_wallets">您要添加的卡上已經創建了另一個錢包。你想重置它並將卡用於新錢包嗎</string>
<string name="onboarding_linking_error_card_with_wallets">您要添加的卡上已經創建了另一個錢包。你想重置它並將卡用於新錢包嗎</string>
<string name="onboarding_navbar_pin">PIN 碼</string>
<string name="onboarding_navbar_register_wallet">連接</string>
<string name="onboarding_navbar_title_creating_backup">創建備份</string>
@ -209,7 +231,7 @@
<string name="onboarding_subtitle_no_backup_cards">要開始備份過程,最多可添加兩張備份卡。</string>
<string name="onboarding_subtitle_one_backup_card">您可以再添加一張卡或完成備份過程</string>
<string name="onboarding_subtitle_scan_backup_card_format">準備編號為 %s 的備份卡</string>
<string name="onboarding_subtitle_scan_origin_card">準備主卡</string>
<string name="onboarding_subtitle_scan_primary">掃描主卡以啟動備份過程</string>
<string name="onboarding_subtitle_scan_primary_card_format">準備編號為 %s 的主卡</string>
<string name="onboarding_subtitle_success_claim">恭喜! 您的第一張支付加密卡已啟用!</string>
<string name="onboarding_subtitle_success_tangem_wallet_onboarding">您的錢包卡已配置完畢,可以使用了</string>
@ -227,7 +249,7 @@
<string name="onboarding_top_up_body_no_account_error">要開始,只需為錢包充值超過 %1$s %2$s</string>
<string name="onboarding_top_up_button_but_crypto">購買加密貨幣</string>
<string name="onboarding_top_up_button_show_wallet_address">顯示錢包地址</string>
<string name="onboarding_top_up_header">充值你的錢包</string>
<string name="onboarding_topup_title">啟動錢包</string>
<string name="onboarding_twin_exit_warning">結對過程已部分完成。你現在不能退出</string>
<string name="onboarding_twins_interrupt_warning">如果創建錢包的過程以任何方式中斷,您將得重新開始</string>
<string name="onboarding_wallet_info_subtitle_first">您最多可以額外備份兩張空白的 Tangem冷錢包</string>
@ -246,7 +268,6 @@
<string name="referral_point_currencies_description_suffix">對於你的朋友在你的 %1$s 網絡地址%2$s上購買的每個錢包</string>
<string name="referral_point_currencies_title"></string>
<string name="referral_point_discount_description_prefix">得到</string>
<string name="referral_point_discount_description_suffix">當在 tangem.com購買卡片</string>
<string name="referral_point_discount_description_value">%s 折扣</string>
<string name="referral_point_discount_title">你的朋友</string>
<string name="referral_promo_code_copied">個人促銷碼已複製!</string>
@ -279,7 +300,6 @@
<string name="scan_card_settings_button">掃描卡片</string>
<string name="scan_card_settings_message">掃描卡片以更改其設置。這些更改只會影響您掃描過的卡,不會影響綁定到您錢包的其他卡。</string>
<string name="scan_card_settings_title">準備好您的卡!</string>
<string name="search_tokens_title">搜尋代幣</string>
<string name="send_amount_label">數量</string>
<string name="send_destination_hint_address">地址</string>
<string name="send_error_address_same_as_wallet">地址與錢包地址相同</string>
@ -366,13 +386,11 @@
<string name="token_details_unable_hide_alert_message">%1$s 代幣是 %2$s 網絡上的主要貨幣,只要列表中還有該網絡上的其他代幣,它就無法被隱藏。</string>
<string name="token_details_unable_hide_alert_title">無法隱藏 %s</string>
<string name="token_item_no_rate">無費用</string>
<plurals name="tokens_count">
<item quantity="other">%d 代幣</item>
</plurals>
<string name="transaction_history_empty_transactions">您還沒有任何交易</string>
<string name="transaction_history_error_failed_to_load">無法加載交易</string>
<string name="transaction_history_tx_in_progress">進行中…</string>
<string name="twin_error_same_card">您掃描了同一張卡片。要創建雙錢包,您需要掃描編號為 %d 的卡</string>
<string name="twin_error_wrong_twin">你掃描錯了胞胎卡。請嘗試另一個</string>
<string name="twins_onboarding_description_format">這一個是你手裡拿著的,另一個是編號為 %s 的,這兩張卡都可以用來從這個錢包中提取資金</string>
<string name="twins_onboarding_subtitle">一個錢包,兩張卡片</string>
<string name="twins_recreate_button_format">掃描卡片 #%s</string>
@ -381,6 +399,7 @@
<string name="twins_recreate_title_preparing">準備卡片</string>
<string name="twins_recreate_toolbar">Tangem Twin</string>
<string name="twins_recreate_warning">這個動作是不可逆的。您將無法訪問舊錢包</string>
<string name="twins_scan_twin_with_number">將您的 iPhone 靠近編號為 %s 的雙胞胎卡</string>
<string name="user_wallet_list_add_button">添加新錢包</string>
<string name="user_wallet_list_delete_prompt">您確定要刪除此錢包?</string>
<string name="user_wallet_list_editing_count">已選擇 %d</string>
@ -400,6 +419,7 @@
<string name="wallet_balance_tx_in_progress">交易進行中</string>
<string name="wallet_balance_verified">檢視餘額</string>
<string name="wallet_button_actions">動作</string>
<string name="wallet_choice_wallet_option_title">選擇錢包選項</string>
<string name="wallet_choose_trade_action">您想要購買或賣出交易貨幣?</string>
<string name="wallet_connect_alert_sign_message">請求籤署消息。%s</string>
<string name="wallet_connect_bnb_sign_message">Dapp %1$s請求\n簽署 BNB 交易。\n%2$s</string>
@ -421,7 +441,10 @@
<string name="wallet_connect_generic_error_with_code">我們遇到了未知錯誤。錯誤代碼:%d。如果問題仍然存在-請隨時聯繫我們的支持人員</string>
<string name="wallet_connect_message_signed">消息已成功簽名並發送至Dapp</string>
<string name="wallet_connect_network_not_found_format">沒有 %s 網路,請先加入後再試一次</string>
<string name="wallet_connect_no_sessions_message">沒有已連結的WalletConnect</string>
<string name="wallet_connect_no_sessions_title">Ooops, 沒有連接</string>
<string name="wallet_connect_paste_from_clipboard">從剪貼板貼上</string>
<string name="wallet_connect_personal_sign_message">給 %1$s 的消息:%2$s</string>
<string name="wallet_connect_request_session_start">請求開始會話\n%1$s\n\n網絡 %2$s\n\n網址%3$s</string>
<string name="wallet_connect_same_wcuri">無法完成執行,您已經使用此參數建立了 WalletConnect 連接</string>
<string name="wallet_connect_scan_new_code">掃描新密碼</string>
@ -429,6 +452,8 @@
<string name="wallet_connect_scanner_error_unsupported_network">不支持此網絡。請選擇其他網絡</string>
<string name="wallet_connect_select_network">選擇網路</string>
<string name="wallet_connect_service_no_chain_id">Dapp 沒有提供必要的數據來建立 WalletConnect 連接</string>
<string name="wallet_connect_session_not_found">找不到請求的連接</string>
<string name="wallet_connect_sessions_title">WalletConnect 連接</string>
<string name="wallet_connect_subtitle">連結到Dapps</string>
<string name="wallet_connect_title">WalletConnect</string>
<string name="wallet_connect_transaction_signed">交易已成功簽署並發送至 Dapp</string>

View file

@ -6,7 +6,6 @@
<string name="kaspa_withdrawal_message_warning">Due to Kaspa limitations only %1$d UTXOs can fit in a single transaction. This means you can only send %2$s or less. You need to reduce the amount.</string>
<string name="no_account_generic">Load %1$s+ %2$s to create account</string>
<string name="no_account_polkadot">Destination account is not active. Send %s or more to activate the account.</string>
<string name="send_error_blockchain_internal">Blockchain internal error</string>
<string name="send_error_dust_amount_format">Minimum amount is %s</string>
<string name="send_error_dust_change">Change is too small</string>
<string name="send_error_invalid_fee_value">Invalid Fee</string>

View file

@ -1,13 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<string name="add_custom_token_title">Add custom token</string>
<string name="add_tokens_title">Manage tokens</string>
<string name="address_qr_code_message_format">Send only %1$s (%2$s) from %3$s network to this address. Using other tokens and networks may result in loss of funds.</string>
<string name="alert_app_feedback_sent_message">Thank you for your feedback</string>
<string name="alert_app_feedback_sent_title">Sent successfully</string>
<string name="alert_button_request_support">Request support</string>
<string name="alert_button_send_feedback">Send feedback</string>
<string name="alert_card_signed_transactions">This card has been already topped up and signed transactions in the past. Consider immediate withdrawal of all funds if you have received this card from an untrusted source. If it\'s your card, there is nothing to worry about.</string>
<string name="alert_demo_feature_disabled">This feature is disabled in Demo mode</string>
<string name="alert_demo_message">You are currently running in Demo mode. All funds are not real.</string>
<string name="alert_developer_card">The card you scanned is a development card. Don\'t accept it as a payment.</string>
<string name="alert_failed_to_send_email_title">Failed to send the email</string>
<string name="alert_failed_to_send_transaction_message">Reason: %s</string>
<string name="alert_failed_to_send_transaction_title">Can\'t send a transaction</string>
<string name="alert_manage_tokens_addresses_message">Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds.</string>
@ -34,14 +38,22 @@
</plurals>
<string name="card_settings_access_code_recovery_disabled_description">Disable this option if you don\'t want this card to be used to reset access codes on other cards in this wallet. Please note that this will also prevent you from resetting the access code on this card.</string>
<string name="card_settings_access_code_recovery_enabled_description">Allows you to use this card to reset access code on other cards in this wallet</string>
<string name="card_settings_access_code_recovery_footer">Disable the ability to reset the access code on this card or other cards in this wallet</string>
<string name="card_settings_access_code_recovery_title">Access code recovery</string>
<string name="card_settings_action_sheet_reset">Reset</string>
<string name="card_settings_action_sheet_title">Are you sure you want to do this?</string>
<string name="card_settings_change_access_code">Change Access Code</string>
<string name="card_settings_change_access_code_footer">Access code will be changed on this card only</string>
<string name="card_settings_reset_card_to_factory">Reset to Factory Settings</string>
<string name="card_settings_security_mode">Security Mode</string>
<string name="card_settings_security_mode_footer">Selected application protection method</string>
<string name="card_settings_title">Card Settings</string>
<string name="chat_bot_name">Tangem Bot</string>
<string name="chat_button_title">Support</string>
<string name="chat_user_action_rate_user">Rate agent</string>
<string name="chat_user_action_send_log">Send logs</string>
<string name="chat_user_actions_title">Please select an action</string>
<string name="chat_user_rate_agent_title">Please, rate the work of the agent</string>
<string name="common_accept">Accept</string>
<string name="common_add">Add</string>
<string name="common_apply">Apply</string>
@ -54,6 +66,7 @@
<string name="common_camera_denied_alert_message">You have not given access to your camera, please adjust your privacy settings</string>
<string name="common_cancel">Cancel</string>
<string name="common_close">Close</string>
<string name="common_continue">Continue</string>
<string name="common_copy">Copy</string>
<string name="common_copy_address">Copy address</string>
<string name="common_create">Create</string>
@ -63,13 +76,18 @@
<string name="common_done">Done</string>
<string name="common_enable">Enable</string>
<string name="common_enabled">Enabled</string>
<string name="common_error">Error</string>
<string name="common_exchange">Exchange</string>
<string name="common_explore_transaction_history">Explore transaction history</string>
<string name="common_explorer">Explorer</string>
<string name="common_generate_addresses">Generate addresses</string>
<string name="common_import">Import</string>
<string name="common_learn_and_earn">Learn &amp; Earn</string>
<string name="common_like">Like</string>
<string name="common_locked">Locked</string>
<string name="common_main_network">Main network</string>
<string name="common_no">No</string>
<string name="common_no_address">No address</string>
<string name="common_no_data">No data</string>
<string name="common_ok">OK</string>
<string name="common_origin_card">Primary Card</string>
@ -78,7 +96,9 @@
<string name="common_reload">Reload</string>
<string name="common_save_changes">Save changes</string>
<string name="common_search">Search</string>
<string name="common_search_tokens">Search tokens</string>
<string name="common_seed_phrase">Seed phrase</string>
<string name="common_select_action">Select action</string>
<string name="common_sell">Sell</string>
<string name="common_send">Send</string>
<string name="common_server_unavailable">The server is not available, please try again later</string>
@ -98,6 +118,7 @@
<string name="common_yes">Yes</string>
<string name="contract_address_copied_message">Contract address copied!</string>
<string name="currency_subtitle_expanded">Available networks</string>
<string name="custom_token_add_token">Add token</string>
<string name="custom_token_contract_address_input_title">Contract address</string>
<string name="custom_token_creation_error_invalid_contract_address">Contract address is invalid</string>
<string name="custom_token_creation_error_invalid_derivation_path">Derivation path is invalid</string>
@ -137,7 +158,6 @@
<string name="disclaimer_error_loading">Check your internet connection or switch to a different network</string>
<string name="disclaimer_title">Terms of Service</string>
<string name="error_update_app">Oops, the current version of the application is not ready to work with this card, please check for updates.</string>
<string name="error_wrong_card_type">This application is not designed to work with this card or needs to be updated</string>
<string name="error_wrong_wallet_tapped">You have used a card from another wallet. Tap the card associated with this wallet</string>
<string name="exchange_receive_view_header">You Receive</string>
<string name="exchange_send_view_header">You Send</string>
@ -152,14 +172,18 @@
<string name="feedback_subject_support_tangem">Tangem feedback</string>
<string name="feedback_subject_tx_failed">Can\'t send a transaction</string>
<string name="home_button_order">Order</string>
<string name="home_button_scan">Scan card</string>
<string name="initial_message_change_access_code_body">To change the access code tap the card as shown above and do not remove until the end of the operation</string>
<string name="initial_message_change_passcode_body">To change the passcode tap the card as shown above and do not remove until the end of the operation</string>
<string name="initial_message_create_wallet_body">To create the wallet tap the card as shown above and do not remove until the end of the operation</string>
<string name="initial_message_purge_wallet_body">To reset to factory settings tap the card as shown above and do not remove until the end of the operation</string>
<string name="initial_message_scan_header">Tap to scan</string>
<string name="initial_message_sign_body">To sign tap the card as shown above and do not remove until the end of the operation</string>
<string name="initial_message_sign_header">Tap to sign</string>
<string name="initial_message_tap_header">Tap the card</string>
<string name="internal_error_wallet_manager_not_found">Internal error: wallet manager not found</string>
<string name="key_invalidated_warning_description">You have updated biometrics, scan your card to enter</string>
<string name="main_empty_tokens_list_message">To begin tracking your crypto assets and transactions, add tokens.</string>
<string name="main_get_bonus_subtitle">You have completed all of the lessons, and are now eligible to receive your 1INCH tokens</string>
<plurals name="main_learn_subtitle">
<item quantity="one">Complete three lessons and receive %d 1INCH token to your wallet</item>
@ -171,9 +195,14 @@
<string name="main_page_balance">Total balance</string>
<string name="main_processing_full_amount">The amount does not include some of your funds</string>
<string name="main_promotion_credited">1INCH tokens will be credited to your %s wallet address within 48 hours</string>
<string name="main_promotion_no_purchase">There was no purchase of a wallet using your promo code, which means you cannot receive a bonus. Buy Tangem wallet, scan it in the app, and get the bonus.</string>
<string name="main_scan_card_warning_view_subtitle">To access all the networks you need to scan the card</string>
<string name="main_scan_card_warning_view_title">Scan your card</string>
<string name="main_tokens">Tokens</string>
<plurals name="main_warning_missing_derivation_description">
<item quantity="one">You need to generate address for %d new network using your card</item>
<item quantity="other">You need to generate addresses for %d new networks using your card</item>
</plurals>
<string name="main_warning_missing_derivation_title">Some addresses are missing</string>
<string name="onboarding_access_code_feature_1_description">You have to set up a single access code to protect all your wallets</string>
<string name="onboarding_access_code_feature_1_title">Protect</string>
<string name="onboarding_access_code_feature_2_description">You can set up an individual access code on each card later</string>
@ -233,7 +262,7 @@
<string name="onboarding_subtitle_no_backup_cards">To start the backup process add up to two backup cards.</string>
<string name="onboarding_subtitle_one_backup_card">You can add one more card or finalize the backup process</string>
<string name="onboarding_subtitle_scan_backup_card_format">Prepare the backup card with number %s</string>
<string name="onboarding_subtitle_scan_origin_card">Prepare the primary card</string>
<string name="onboarding_subtitle_scan_primary">Scan the primary card to start the backup process.</string>
<string name="onboarding_subtitle_scan_primary_card_format">Prepare the primary card with number %s</string>
<string name="onboarding_subtitle_success_claim">Congratulations! Your first payment crypto card has been activated!</string>
<string name="onboarding_subtitle_success_tangem_wallet_onboarding">Your wallet card is configured and ready for use.</string>
@ -251,7 +280,7 @@
<string name="onboarding_top_up_body_no_account_error">To get started, simply top up the wallet with more than %1$s %2$s</string>
<string name="onboarding_top_up_button_but_crypto">Buy crypto</string>
<string name="onboarding_top_up_button_show_wallet_address">Show the wallet\'s address</string>
<string name="onboarding_top_up_header">Top up your wallet</string>
<string name="onboarding_topup_title">Activate a wallet</string>
<string name="onboarding_twin_exit_warning">The twinning process is partly complete. You can\'t exit it now.</string>
<string name="onboarding_twins_interrupt_warning">If the process of creating the wallet gets interrupted in any way, you\'ll have to start over</string>
<string name="onboarding_wallet_info_subtitle_first">You can backup your keys up to two other blank Tangem Wallet cards.</string>
@ -272,12 +301,21 @@
<string name="referral_button_participate">Participate</string>
<string name="referral_error_failed_to_load_info">Failed to load the information about the referral program. Please try again later.</string>
<string name="referral_error_failed_to_load_info_with_reason">Failed to load the information about the referral program. Error code: %s. Please try again later.</string>
<string name="referral_expected_awards">Upcoming payments</string>
<string name="referral_friends_bought_title">Your friends bought</string>
<string name="referral_less">Less</string>
<string name="referral_more">More</string>
<string name="referral_no_expected_awards">No upcoming payments</string>
<plurals name="referral_number_of_wallets">
<item quantity="one">for %d wallet</item>
<item quantity="other">for %d wallets</item>
</plurals>
<string name="referral_point_currencies_description">Will get ^^%1$s^^ for each wallet bought by your friend on your %2$s network address %3$s ^^30 days after^^ that</string>
<string name="referral_point_currencies_description_prefix">Will get</string>
<string name="referral_point_currencies_description_suffix">for each wallet bought by your friend on your %1$s network address%2$s</string>
<string name="referral_point_currencies_title">You</string>
<string name="referral_point_discount_description_prefix">Will get a</string>
<string name="referral_point_discount_description_suffix">when buying a card on tangem.com</string>
<string name="referral_point_discount_description_suffix">when buying a wallet on tangem.com</string>
<string name="referral_point_discount_description_value">%s discount</string>
<string name="referral_point_discount_title">Your friend</string>
<string name="referral_promo_code_copied">Personal code copied!</string>
@ -311,7 +349,6 @@
<string name="scan_card_settings_button">Scan Card</string>
<string name="scan_card_settings_message">Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet.</string>
<string name="scan_card_settings_title">Get your card ready!</string>
<string name="search_tokens_title">Search tokens</string>
<string name="send_amount_label">Amount</string>
<string name="send_destination_hint_address">Address</string>
<string name="send_error_address_same_as_wallet">Address is the same as wallet address</string>
@ -337,7 +374,7 @@
<string name="shop_i_have_a_promo_code">I have a promo code…</string>
<string name="shop_one_wallet">Tangem Wallet</string>
<string name="shop_other_payment_methods">Other payment methods</string>
<string name="shop_sold_out_description">Due to the high volume of orders we are receiving shipping may be delayed up to 5 weeks depending on your location</string>
<string name="shop_pre_order_now">Pre-order now</string>
<string name="shop_total">Total</string>
<string name="solana_rent_warning">Solana network charges a rent of %1$s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %2$s to use it for free.</string>
<string name="story_awe_description">Store your crypto assets secure while keeping private keys contained in your card</string>
@ -407,10 +444,6 @@
<string name="token_details_unable_hide_alert_message">The %1$s token is the main currency on the %2$s network and cannot be hidden as long as you have other tokens on this network in the list.</string>
<string name="token_details_unable_hide_alert_title">Unable to hide %s</string>
<string name="token_item_no_rate">No rate</string>
<plurals name="tokens_count">
<item quantity="one">%d token</item>
<item quantity="other">%d tokens</item>
</plurals>
<string name="transaction_history_contract_address">contract: %s</string>
<string name="transaction_history_empty_transactions">You don\'t have any transactions yet</string>
<string name="transaction_history_error_failed_to_load">Failed to load transaction history.\nClick on reload button to update the information.</string>
@ -419,6 +452,7 @@
<string name="transaction_history_transaction_to_address">to: %s</string>
<string name="transaction_history_tx_in_progress">In progress…</string>
<string name="twin_error_same_card">You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d</string>
<string name="twin_error_wrong_twin">You\'ve scanned wrong twin card. Please try another one</string>
<string name="twins_onboarding_description_format">This one that you are holding in your hands and the other one with number %s.\n\nBoth cards can be used to extract funds from this wallet.</string>
<string name="twins_onboarding_subtitle">One wallet. Two cards.</string>
<string name="twins_recreate_button_format">Scan the card #%s</string>
@ -427,6 +461,7 @@
<string name="twins_recreate_title_preparing">Preparing card</string>
<string name="twins_recreate_toolbar">Tangem Twin</string>
<string name="twins_recreate_warning">This action is irreversible. You will not have access to the old wallet.</string>
<string name="twins_scan_twin_with_number">Tap the twin card with number %s and do not remove until the end of the operation</string>
<string name="user_wallet_list_add_button">Add new wallet</string>
<string name="user_wallet_list_delete_prompt">Are you sure you want to delete this wallet?</string>
<string name="user_wallet_list_editing_count">%d selected</string>
@ -446,6 +481,7 @@
<string name="wallet_balance_tx_in_progress">Transaction is in progress…</string>
<string name="wallet_balance_verified">Verified Balance</string>
<string name="wallet_button_actions">Actions</string>
<string name="wallet_choice_wallet_option_title">Choose wallet option</string>
<string name="wallet_choose_trade_action">Do you want to buy or sell crypto?</string>
<string name="wallet_connect_alert_sign_message">Requesting to sign a message.\n\n%s</string>
<string name="wallet_connect_bnb_sign_message">Dapp %1$s, requesting to\nsign BNB transaction.\n\n%2$s</string>
@ -467,7 +503,10 @@
<string name="wallet_connect_generic_error_with_code">We\'ve encountered unknown error. Error code: %d. If the problem persists — feel free to contact our support</string>
<string name="wallet_connect_message_signed">The message has been successfully signed and sent to the Dapp</string>
<string name="wallet_connect_network_not_found_format">%s network not found. Please, add it first and try again.</string>
<string name="wallet_connect_no_sessions_message">No opened WalletConnect sessions</string>
<string name="wallet_connect_no_sessions_title">Ooops. No Sessions.</string>
<string name="wallet_connect_paste_from_clipboard">Paste from clipboard</string>
<string name="wallet_connect_personal_sign_message">Message for %1$s:\n%2$s</string>
<string name="wallet_connect_request_session_start">Request to start a session for\n%1$s\n\nNETWORK: %2$s\n\nURL: %3$s</string>
<string name="wallet_connect_same_wcuri">The operation couldn\'t be completed.\n\nYou have already established a WalletConnect session with this parameters.</string>
<string name="wallet_connect_scan_new_code">Scan new code</string>
@ -475,6 +514,8 @@
<string name="wallet_connect_scanner_error_unsupported_network">This network is not supported. Please select another network.</string>
<string name="wallet_connect_select_network">Select network</string>
<string name="wallet_connect_service_no_chain_id">Dapp didn\'t provide essential data to establish WalletConnect session</string>
<string name="wallet_connect_session_not_found">Failed to find session for request</string>
<string name="wallet_connect_sessions_title">WalletConnect Sessions</string>
<string name="wallet_connect_subtitle">Connect to Dapps</string>
<string name="wallet_connect_title">WalletConnect</string>
<string name="wallet_connect_transaction_signed">The transaction has been successfully signed and sent to the Dapp</string>

View file

@ -7,12 +7,14 @@ plugins {
dependencies {
/** AndroidX libraries */
implementation(deps.androidx.fragment.ktx)
implementation(deps.androidx.paging.runtime)
/** Compose */
implementation(deps.compose.constraintLayout)
implementation(deps.compose.foundation)
implementation(deps.compose.material)
implementation(deps.compose.material3)
implementation(deps.compose.paging)
implementation(deps.compose.ui.tooling)
/** Other libraries */

View file

@ -31,7 +31,11 @@ fun HorizontalActionChips(
verticalAlignment = Alignment.CenterVertically,
contentPadding = contentPadding,
) {
items(items = buttons, itemContent = { ActionButton(config = it) })
items(
items = buttons,
key = { config -> "${config.text.hashCode()} ${config.iconResId}" },
itemContent = { ActionButton(config = it) },
)
}
}

View file

@ -1,5 +1,7 @@
package com.tangem.core.ui.components.marketprice
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
@ -20,15 +22,22 @@ import androidx.compose.ui.unit.Dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.BigDecimalFormatter
/**
* @see <a href =
* "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=1217-922&mode=design&t=t1PxyisyGJSwzQwg-4"
* Market price block
*
* @param state component state
* @param modifier modifier
*
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=1217-922&mode
* =design&t=t1PxyisyGJSwzQwg-4"
* >Figma component</a>
*/
@Composable
fun MarketPriceBlock(state: MarketPriceBlockState, modifier: Modifier = Modifier) {
var rootWidth by remember { mutableStateOf(value = 0) }
Column(
modifier = modifier
.background(
@ -42,81 +51,139 @@ fun MarketPriceBlock(state: MarketPriceBlockState, modifier: Modifier = Modifier
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing6),
horizontalAlignment = Alignment.Start,
) {
Text(
text = stringResource(id = R.string.wallet_marketplace_block_title, state.currencyName),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.subtitle2,
)
Title(currencyName = state.currencyName)
when (state) {
is MarketPriceBlockState.Loading -> {
RectangleShimmer(
modifier = Modifier.size(width = TangemTheme.dimens.size158, height = TangemTheme.dimens.size20),
)
}
is MarketPriceBlockState.Content -> {
Price(
config = state,
Content(state = state, rootWidth = rootWidth)
}
}
@Composable
private fun Title(currencyName: String) {
Text(
text = stringResource(id = R.string.wallet_marketplace_block_title, currencyName),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.subtitle2,
)
}
@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun Content(state: MarketPriceBlockState, rootWidth: Int) {
AnimatedContent(targetState = state, label = "Update the content") { marketPriceBlockState ->
when (marketPriceBlockState) {
is MarketPriceBlockState.Content,
is MarketPriceBlockState.Error,
-> {
PriceContent(
state = marketPriceBlockState,
priceWidthDp = with(LocalDensity.current) { rootWidth.div(other = 2).toDp() },
)
}
is MarketPriceBlockState.Loading -> LoadingContent()
}
}
}
@Composable
private fun Price(config: MarketPriceBlockState.Content, priceWidthDp: Dp) {
private fun PriceContent(state: MarketPriceBlockState, priceWidthDp: Dp) {
Row(
modifier = Modifier,
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8),
) {
Text(
text = config.price,
modifier = Modifier.widthIn(max = priceWidthDp),
color = TangemTheme.colors.text.primary1,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = TangemTheme.typography.body2,
)
PriceBlock(state = state, priceWidthDp = priceWidthDp)
PriceChangeInPercent(config.priceChangeConfig)
QuoteTimeStatus()
}
}
Text(
text = stringResource(id = R.string.wallet_marketprice_block_update_time),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
)
@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun PriceBlock(state: MarketPriceBlockState, priceWidthDp: Dp) {
val priceModifier = Modifier.widthIn(max = priceWidthDp)
AnimatedContent(targetState = state, label = "Update the price block") { marketPriceBlockState ->
if (marketPriceBlockState is MarketPriceBlockState.Content) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8),
) {
Price(price = marketPriceBlockState.price, modifier = priceModifier)
PriceChangeInPercent(marketPriceBlockState.priceChangeConfig)
}
} else {
Price(price = BigDecimalFormatter.EMPTY_BALANCE_SIGN, modifier = priceModifier)
}
}
}
@Composable
private fun Price(price: String, modifier: Modifier = Modifier) {
Text(
text = price,
modifier = modifier,
color = TangemTheme.colors.text.primary1,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = TangemTheme.typography.body2,
)
}
@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun PriceChangeInPercent(config: PriceChangeConfig) {
AnimatedContent(targetState = config.type, label = "Update price change") { type ->
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing4),
) {
Image(
painter = painterResource(
id = when (type) {
PriceChangeConfig.Type.UP -> R.drawable.img_arrow_up_8
PriceChangeConfig.Type.DOWN -> R.drawable.img_arrow_down_8
},
),
contentDescription = null,
)
Text(
text = config.valueInPercent,
color = when (type) {
PriceChangeConfig.Type.UP -> TangemTheme.colors.text.accent
PriceChangeConfig.Type.DOWN -> TangemTheme.colors.text.warning
},
style = TangemTheme.typography.body2,
)
}
}
}
@Composable
private fun LoadingContent() {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing4),
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8),
) {
Image(
painter = painterResource(
id = when (config.type) {
PriceChangeConfig.Type.UP -> R.drawable.img_arrow_up_8
PriceChangeConfig.Type.DOWN -> R.drawable.img_arrow_down_8
},
RectangleShimmer(
modifier = Modifier.size(
width = TangemTheme.dimens.size158,
height = TangemTheme.dimens.size20,
),
contentDescription = null,
)
Text(
text = config.valueInPercent,
color = when (config.type) {
PriceChangeConfig.Type.UP -> TangemTheme.colors.text.accent
PriceChangeConfig.Type.DOWN -> TangemTheme.colors.text.warning
},
style = TangemTheme.typography.body2,
)
QuoteTimeStatus()
}
}
@Composable
private fun QuoteTimeStatus() {
Text(
text = stringResource(id = R.string.wallet_marketprice_block_update_time),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
)
}
@Preview
@Composable
private fun Preview_MarketPriceBlock_Light(
@ -143,7 +210,7 @@ private class WalletMarketPriceBlockStateProvider : CollectionPreviewParameterPr
collection = listOf(
MarketPriceBlockState.Content(
currencyName = "BTC",
price = "98900",
price = "98900 $",
priceChangeConfig = PriceChangeConfig(
valueInPercent = "5.16%",
type = PriceChangeConfig.Type.DOWN,
@ -151,12 +218,13 @@ private class WalletMarketPriceBlockStateProvider : CollectionPreviewParameterPr
),
MarketPriceBlockState.Content(
currencyName = "BTC",
price = "98900",
price = "98900 $",
priceChangeConfig = PriceChangeConfig(
valueInPercent = "10.89%",
type = PriceChangeConfig.Type.UP,
),
),
MarketPriceBlockState.Loading(currencyName = "BTC"),
MarketPriceBlockState.Error(currencyName = "BTC"),
),
)

View file

@ -7,6 +7,8 @@ sealed interface MarketPriceBlockState {
val currencyName: String
data class Error(override val currencyName: String) : MarketPriceBlockState
data class Loading(override val currencyName: String) : MarketPriceBlockState
data class Content(

View file

@ -21,7 +21,9 @@ import androidx.constraintlayout.compose.Dimension
import com.tangem.core.ui.R
import com.tangem.core.ui.components.CircleShimmer
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.transactions.state.TransactionState
import com.tangem.core.ui.res.TangemTheme
import java.util.UUID
/**
* Transaction component
@ -311,45 +313,53 @@ private fun Preview_TransactionItem_DarkTheme(
private class TransactionItemStateProvider : CollectionPreviewParameterProvider<TransactionState>(
collection = listOf(
TransactionState.Sending(
txHash = UUID.randomUUID().toString(),
address = "33BddS...ga2B",
amount = "-0.500913 BTC",
timestamp = "8:41",
),
TransactionState.Receiving(
txHash = UUID.randomUUID().toString(),
address = "33BddS...ga2B",
amount = "+0.500913 BTC",
timestamp = "8:41",
),
TransactionState.Approving(
txHash = UUID.randomUUID().toString(),
address = "33BddS...ga2B",
amount = "+0.500913 BTC",
timestamp = "8:41",
),
TransactionState.Swapping(
txHash = UUID.randomUUID().toString(),
address = "33BddS...ga2B",
amount = "+0.500913 BTC",
timestamp = "8:41",
),
TransactionState.Send(
txHash = UUID.randomUUID().toString(),
address = "33BddS...ga2B",
amount = "-0.500913 BTC",
timestamp = "8:41",
),
TransactionState.Receive(
txHash = UUID.randomUUID().toString(),
address = "33BddS...ga2B",
amount = "+0.500913 BTC",
timestamp = "8:41",
),
TransactionState.Approved(
txHash = UUID.randomUUID().toString(),
address = "33BddS...ga2B",
amount = "+0.500913 BTC",
timestamp = "8:41",
),
TransactionState.Swapped(
txHash = UUID.randomUUID().toString(),
address = "33BddS...ga2B",
amount = "+0.500913 BTC",
timestamp = "8:41",
),
TransactionState.Loading,
TransactionState.Loading(txHash = UUID.randomUUID().toString()),
),
)

View file

@ -1,5 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency
package com.tangem.core.ui.components.transactions
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListScope
@ -8,42 +9,42 @@ import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.itemsIndexed
import com.tangem.core.ui.components.transactions.empty.EmptyTransactionBlock
import com.tangem.core.ui.components.transactions.empty.EmptyTransactionsBlockState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTxHistoryState
import com.tangem.feature.wallet.presentation.wallet.ui.decorations.walletContentItemDecoration
/**
* LazyList extension for transactions history [WalletTxHistoryState]
* LazyList extension for transactions history [TxHistoryState]
*
* @param state state
* @param txHistoryItems transactions
* @param modifier modifier
*/
internal fun LazyListScope.txHistoryItems(
state: WalletTxHistoryState,
txHistoryItems: LazyPagingItems<WalletTxHistoryState.TxHistoryItemState>?,
fun LazyListScope.txHistoryItems(
state: TxHistoryState,
txHistoryItems: LazyPagingItems<TxHistoryState.TxHistoryItemState>?,
modifier: Modifier = Modifier,
) {
when (state) {
is WalletTxHistoryState.ContentState -> {
is TxHistoryState.ContentState -> {
contentItems(
txHistoryItems = requireNotNull(txHistoryItems),
modifier = modifier,
)
}
is WalletTxHistoryState.Empty -> {
is TxHistoryState.Empty -> {
nonContentItem(
state = EmptyTransactionsBlockState.Empty(onClick = state.onBuyClick),
modifier = modifier,
)
}
is WalletTxHistoryState.Error -> {
is TxHistoryState.Error -> {
nonContentItem(
state = EmptyTransactionsBlockState.FailedToLoad(onClick = state.onReloadClick),
modifier = modifier,
)
}
is WalletTxHistoryState.NotSupported -> {
is TxHistoryState.NotSupported -> {
nonContentItem(
state = EmptyTransactionsBlockState.NotImplemented(onClick = state.onExploreClick),
modifier = modifier,
@ -52,32 +53,42 @@ internal fun LazyListScope.txHistoryItems(
}
}
@OptIn(ExperimentalFoundationApi::class)
private fun LazyListScope.contentItems(
txHistoryItems: LazyPagingItems<WalletTxHistoryState.TxHistoryItemState>,
txHistoryItems: LazyPagingItems<TxHistoryState.TxHistoryItemState>,
modifier: Modifier = Modifier,
) {
itemsIndexed(
items = txHistoryItems,
key = { index, _ -> index },
itemContent = { index, item ->
if (item == null) return@itemsIndexed
key = { _, item ->
when (item) {
is TxHistoryState.TxHistoryItemState.GroupTitle -> item.title
is TxHistoryState.TxHistoryItemState.Title -> item.onExploreClick.hashCode()
is TxHistoryState.TxHistoryItemState.Transaction -> item.state.txHash
}
},
) { index, item ->
if (item == null) return@itemsIndexed
SingleCurrencyContentItem(
state = item,
modifier = modifier.walletContentItemDecoration(
TxHistoryListItem(
state = item,
modifier = modifier
.animateItemPlacement()
.roundedShapeItemDecoration(
currentIndex = index,
lastIndex = txHistoryItems.itemSnapshotList.lastIndex,
),
)
},
)
)
}
}
@OptIn(ExperimentalFoundationApi::class)
private fun LazyListScope.nonContentItem(state: EmptyTransactionsBlockState, modifier: Modifier = Modifier) {
item {
item(key = state::class.java, contentType = state::class.java) {
EmptyTransactionBlock(
state = state,
modifier = modifier
.animateItemPlacement()
.padding(horizontal = TangemTheme.dimens.spacing16, vertical = TangemTheme.dimens.spacing12)
.fillMaxWidth(),
)

View file

@ -0,0 +1,20 @@
package com.tangem.core.ui.components.transactions
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.transactions.state.TxHistoryState
@Composable
internal fun TxHistoryListItem(state: TxHistoryState.TxHistoryItemState, modifier: Modifier = Modifier) {
when (state) {
is TxHistoryState.TxHistoryItemState.GroupTitle -> {
TxHistoryGroupTitle(config = state, modifier = modifier)
}
is TxHistoryState.TxHistoryItemState.Title -> {
TxHistoryTitle(config = state, modifier = modifier)
}
is TxHistoryState.TxHistoryItemState.Transaction -> {
Transaction(state = state.state, modifier = modifier)
}
}
}

View file

@ -1,4 +1,4 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency
package com.tangem.core.ui.components.transactions
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxWidth
@ -8,8 +8,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.components.transactions.state.TxHistoryState.TxHistoryItemState
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTxHistoryState.TxHistoryItemState
/**
* Transactions block group title

Some files were not shown because too many files have changed in this diff Show more