Updated on 2026-08-14

This commit is contained in:
Tangem 2023-08-15 19:46:44 +03:00
commit 9c32379865
379 changed files with 7260 additions and 3853 deletions

View file

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

@ -1 +1 @@
Subproject commit b791bd4cf6c5eca9778f89e87cd62b72d24f5ce9
Subproject commit 8c1c53b73950698d4acfa4d925b8c14bf6f0da63

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

@ -23,6 +23,7 @@ import com.tangem.datasource.config.FeaturesLocalLoader
import com.tangem.datasource.config.models.Config
import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.domain.DomainLayer
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.common.LogConfig
import com.tangem.domain.wallets.legacy.WalletManagersRepository
@ -161,6 +162,9 @@ class TapApplication : Application(), ImageLoaderFactory {
@Inject
lateinit var blockchainExceptionHandler: BlockchainExceptionHandler
@Inject
lateinit var appCurrencyRepository: AppCurrencyRepository
override fun onCreate() {
super.onCreate()
@ -179,6 +183,7 @@ class TapApplication : Application(), ImageLoaderFactory {
walletConnectSessionsRepository = walletConnectSessionsRepository,
tokenDetailsFeatureToggles = tokenDetailsFeatureToggles,
scanCardProcessor = scanCardProcessor,
appCurrencyRepository = appCurrencyRepository,
),
),
)

View file

@ -16,7 +16,7 @@ fun Blockchain.getGreyedOutIconRes(): Int {
Blockchain.Ethereum, Blockchain.EthereumTestnet -> R.drawable.ic_eth_no_color
Blockchain.EthereumClassic, Blockchain.EthereumClassicTestnet -> R.drawable.ic_eth_no_color
Blockchain.RSK -> R.drawable.ic_rsk_no_color
Blockchain.Cardano, Blockchain.CardanoShelley -> R.drawable.ic_cardano_no_color
Blockchain.Cardano -> R.drawable.ic_cardano_no_color
Blockchain.Tezos -> R.drawable.ic_tezos_no_color
Blockchain.XRP -> R.drawable.ic_xrp_no_color
Blockchain.Stellar -> R.drawable.ic_stellar_no_color
@ -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

@ -8,10 +8,12 @@ import com.tangem.domain.common.LogConfig
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.*
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
@ -24,17 +26,13 @@ import com.tangem.tap.network.exchangeServices.ExchangeService
import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoEnvironment
import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoService
import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService
import com.tangem.tap.preferencesStorage
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import com.tangem.tap.userTokensRepository
import com.tangem.tap.walletCurrenciesManager
import com.tangem.tap.proxy.redux.DaggerGraphState
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.DispatchFunction
import org.rekotlin.Middleware
import java.util.*
import java.util.Locale
object GlobalMiddleware {
val handler = globalMiddlewareHandler
@ -68,13 +66,11 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
}
}
is GlobalAction.RestoreAppCurrency -> {
store.dispatch(
GlobalAction.RestoreAppCurrency.Success(
preferencesStorage.fiatCurrenciesPrefStorage.getAppCurrency()
?.run { FiatCurrency(code, name, symbol) }
?: FiatCurrency.Default,
),
)
if (store.state.daggerGraphState.get(DaggerGraphState::walletFeatureToggles).isRedesignedScreenEnabled) {
restoreAppCurrencyNew()
} else {
restoreAppCurrencyLegacy()
}
}
is GlobalAction.HideWarningMessage -> {
store.state.globalState.warningManager?.let {
@ -193,6 +189,28 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
}
}
private fun restoreAppCurrencyLegacy() {
store.dispatch(
GlobalAction.RestoreAppCurrency.Success(
preferencesStorage.fiatCurrenciesPrefStorage.getAppCurrency()
?.run { FiatCurrency(code, name, symbol) }
?: FiatCurrency.Default,
),
)
}
private fun restoreAppCurrencyNew() {
scope.launch {
val currency = store.state.daggerGraphState.get(DaggerGraphState::appCurrencyRepository)
.getSelectedAppCurrency()
.firstOrNull()
?.run { FiatCurrency(code, name, symbol) }
?: FiatCurrency.Default
store.dispatchWithMain(GlobalAction.RestoreAppCurrency.Success(currency))
}
}
private fun makeSellExchangeService(config: Config): ExchangeService {
return MoonPayService(
apiKey = config.moonPayApiKey,

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,12 @@ 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.configs.CardConfig
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.KeyWalletPublicKey
import com.tangem.operations.CommandResponse
@ -56,7 +58,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 +79,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 +134,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 +149,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 +160,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 +249,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

@ -1,7 +1,7 @@
package com.tangem.tap.domain.tokens
import android.content.Context
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.common.core.TangemSdkError
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.TangemTechService

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,10 @@
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.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 +62,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 +170,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

@ -1,6 +1,7 @@
package com.tangem.tap.domain.walletStores.repository.implementation
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.common.CompletionResult
import com.tangem.common.catching
import com.tangem.common.doOnSuccess

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

@ -4,6 +4,7 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.domain.common.extensions.supportedBlockchains
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.tap.features.customtoken.impl.data.converters.FoundTokenConverter
import com.tangem.tap.features.customtoken.impl.domain.CustomTokenRepository
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
@ -27,8 +28,9 @@ class DefaultCustomTokenRepository(
) : CustomTokenRepository {
override suspend fun findToken(address: String, networkId: String?): FoundToken {
val supportedTokenNetworkIds = requireNotNull(reduxStateHolder.scanResponse?.card)
.supportedBlockchains()
val scanResponse = requireNotNull(reduxStateHolder.scanResponse)
val supportedTokenNetworkIds = requireNotNull(scanResponse.card)
.supportedBlockchains(scanResponse.cardTypesResolver)
.filter(Blockchain::canHandleTokens)
.map(Blockchain::toNetworkId)

View file

@ -7,8 +7,9 @@ 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.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 +69,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 +116,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,15 @@ 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.cardTypesResolver
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
@ -206,10 +207,11 @@ internal class AddCustomTokenViewModel @Inject constructor(
private fun getNetworkSelectorItems(): List<SelectorItem.Title> {
val defaultNetwork = createNetworkSelectorItem(blockchain = Blockchain.Unknown)
val scanResponse = reduxStateHolder.scanResponse
return listOf(defaultNetwork) + Blockchain.values()
.filter { blockchain ->
reduxStateHolder.scanResponse?.card?.supportedBlockchains()?.contains(blockchain) == true &&
blockchain != Blockchain.Cardano
scanResponse?.card?.supportedBlockchains(scanResponse.cardTypesResolver)
?.contains(blockchain) == true
}
.sortedBy(Blockchain::fullName)
.map(::createNetworkSelectorItem)
@ -273,7 +275,7 @@ internal class AddCustomTokenViewModel @Inject constructor(
),
) + Blockchain.values()
.filter { blockchain ->
blockchain.isSupportedInApp() && !blockchain.isTestnet() && blockchain != Blockchain.Cardano
blockchain.isSupportedInApp() && !blockchain.isTestnet()
}
.sortedBy(Blockchain::fullName)
.map(::createDerivationPathSelectorAdditionalItem)
@ -405,7 +407,11 @@ internal class AddCustomTokenViewModel @Inject constructor(
val isSupportedToken = if (!isNetworkSelected()) {
true
} else {
reduxStateHolder.scanResponse?.card?.canHandleToken(networkSelectorValue) ?: false
val scanResponse = reduxStateHolder.scanResponse
scanResponse?.card?.canHandleToken(
blockchain = networkSelectorValue,
cardTypesResolver = scanResponse.cardTypesResolver,
) ?: false
}
return buildSet {
@ -439,8 +445,12 @@ internal class AddCustomTokenViewModel @Inject constructor(
address = uiState.form.contractAddressInputField.value,
blockchain = networkSelectorValue,
)
val isSupportedToken = reduxStateHolder.scanResponse?.card
?.canHandleToken(networkSelectorValue)
val scanResponse = reduxStateHolder.scanResponse
val isSupportedToken = scanResponse?.card
?.canHandleToken(
blockchain = networkSelectorValue,
cardTypesResolver = scanResponse.cardTypesResolver,
)
?: false
uiState.copySealed(
@ -599,7 +609,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,18 @@
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.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 +357,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 +394,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 +407,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 +418,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 +440,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

@ -176,6 +176,7 @@ private fun StoriesTitleText(text: String, isDarkBackground: Boolean) {
.padding(start = 40.dp, end = 40.dp),
text = text,
fontSize = 32.sp,
lineHeight = 38.sp,
fontWeight = FontWeight.SemiBold,
color = if (isDarkBackground) Color.White else Color(0xFF090E13),
textAlign = TextAlign.Center,
@ -198,6 +199,7 @@ private fun StoriesSubtitleText(subtitleText: AnnotatedString) {
fontWeight = FontWeight.Normal,
text = subtitleText,
fontSize = 20.sp,
lineHeight = 26.sp,
color = color,
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

@ -6,6 +6,7 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.domain.common.extensions.supportedBlockchains
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.tap.features.tokens.impl.data.converters.CoinsResponseConverter
import com.tangem.tap.features.tokens.impl.domain.models.Token
import com.tangem.tap.proxy.AppStateHolder
@ -38,7 +39,8 @@ internal class TangemApiTokensPagingSource(
val page = params.key ?: 0
return runCatching(dispatchers.io) {
val supportedBlockchains = reduxStateHolder.scanResponse?.card?.supportedBlockchains()
val scanResponse = reduxStateHolder.scanResponse
val supportedBlockchains = scanResponse?.card?.supportedBlockchains(scanResponse.cardTypesResolver)
?: Blockchain.values().toList()
api.getCoins(

View file

@ -2,18 +2,19 @@ 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.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 +25,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 +48,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 +71,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 +121,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 +134,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 +183,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

@ -16,6 +16,7 @@ import com.tangem.core.ui.extensions.getActiveIconRes
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.domain.common.extensions.canHandleToken
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.tap.common.extensions.fullNameWithoutTestnet
import com.tangem.tap.common.extensions.getGreyedOutIconRes
import com.tangem.tap.common.extensions.getNetworkName
@ -102,14 +103,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,
)
@ -349,8 +350,14 @@ internal class TokensListViewModel @Inject constructor(
toggledNetwork.changeToggleState()
}
} else {
val scanResponse = reduxStateHolder.scanResponse
val isUnsupportedToken =
!(reduxStateHolder.scanResponse?.card?.canHandleToken(token.blockchain) ?: false)
!(
scanResponse?.card?.canHandleToken(
blockchain = token.blockchain,
cardTypesResolver = scanResponse.cardTypesResolver,
) ?: false
)
if (isUnsupportedToken) {
router.openUnsupportedSoltanaNetworkAlert()

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,8 @@ 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.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 +22,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 +31,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 +68,7 @@ object TokensMiddleware {
currencies = convertToCurrencies(
blockchains = blockchainsToRemove,
tokens = tokensToRemove,
derivationStyle = scanResponse.card.derivationStyle,
derivationStyle = scanResponse.derivationStyleProvider.getDerivationStyle(),
),
)
@ -87,7 +83,7 @@ object TokensMiddleware {
val currencyList = convertToCurrencies(
blockchains = blockchainsToAdd,
tokens = tokensToAdd,
derivationStyle = scanResponse.card.derivationStyle,
derivationStyle = scanResponse.derivationStyleProvider.getDerivationStyle(),
)
if (scanResponse.supportsHdWallet()) {
@ -123,10 +119,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 +172,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

@ -0,0 +1,43 @@
package com.tangem.tap.features.wallet.converters
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.store
import com.tangem.utils.converter.Converter
class CryptoCurrencyConverter : Converter<Currency, CryptoCurrency> {
private val cryptoCurrencyFactory by lazy { CryptoCurrencyFactory() }
override fun convert(value: Currency): CryptoCurrency {
return when (value) {
is Currency.Blockchain -> requireNotNull(
cryptoCurrencyFactory.createCoin(
blockchain = value.blockchain,
derivationStyleProvider = requireNotNull(
store.state.globalState
.userWalletsListManager
?.selectedUserWalletSync
?.scanResponse
?.derivationStyleProvider,
),
),
)
is Currency.Token -> requireNotNull(
cryptoCurrencyFactory.createToken(
sdkToken = value.token,
blockchain = value.blockchain,
derivationStyleProvider = requireNotNull(
store.state.globalState
.userWalletsListManager
?.selectedUserWalletSync
?.scanResponse
?.derivationStyleProvider,
),
),
)
}
}
}

View file

@ -1,6 +1,6 @@
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.fromNetworkId

View file

@ -3,12 +3,14 @@ package com.tangem.tap.features.wallet.redux.middlewares
import com.tangem.common.extensions.guard
import com.tangem.core.analytics.Analytics
import com.tangem.data.source.preferences.model.DataSourceCurrency
import com.tangem.data.source.preferences.model.DataSourceFiatCurrency
import com.tangem.data.source.preferences.storage.FiatCurrenciesPrefStorage
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.MainScreen
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TapWalletManager
import com.tangem.tap.features.details.redux.DetailsAction
@ -19,6 +21,8 @@ import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.userWalletsListManager
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import kotlinx.coroutines.launch
import timber.log.Timber
@ -26,8 +30,12 @@ class AppCurrencyMiddleware(
private val walletRepository: WalletRepository,
private val tapWalletManager: TapWalletManager,
private val fiatCurrenciesPrefStorage: FiatCurrenciesPrefStorage,
private val featureToggles: WalletFeatureToggles,
private val appCurrencyRepository: AppCurrencyRepository,
private val appCurrencyProvider: () -> FiatCurrency,
) {
private val showSelectorJobHolder = JobHolder()
fun handle(action: WalletAction.AppCurrencyAction) {
when (action) {
is WalletAction.AppCurrencyAction.ChooseAppCurrency -> showSelector()
@ -36,6 +44,52 @@ class AppCurrencyMiddleware(
}
private fun showSelector() {
if (featureToggles.isRedesignedScreenEnabled) {
showSelectorNew()
} else {
showSelectorLegacy()
}
}
private fun selectCurrency(action: WalletAction.AppCurrencyAction.SelectAppCurrency) {
Analytics.send(MainScreen.MainCurrencyChanged(AnalyticsParam.CurrencyType.FiatCurrency(action.fiatCurrency)))
scope.launch {
appCurrencyRepository.changeAppCurrency(action.fiatCurrency.code)
store.dispatchWithMain(GlobalAction.ChangeAppCurrency(action.fiatCurrency))
store.dispatchWithMain(DetailsAction.ChangeAppCurrency(action.fiatCurrency))
store.dispatchWithMain(WalletSelectorAction.ChangeAppCurrency(action.fiatCurrency))
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
Timber.e("Unable to select currency, no user wallet selected")
return@launch
}
tapWalletManager.loadData(selectedUserWallet, refresh = true)
}
}
private fun showSelectorNew() {
scope.launch {
val currencies = appCurrencyRepository.getAvailableAppCurrencies()
store.dispatchDialogShow(
WalletDialog.CurrencySelectionDialog(
currenciesList = currencies.map { appCurrency ->
FiatCurrency(
code = appCurrency.code,
name = appCurrency.name,
symbol = appCurrency.symbol,
)
},
currentAppCurrency = appCurrencyProvider.invoke(),
),
)
}.saveIn(showSelectorJobHolder)
}
private fun showSelectorLegacy() {
val storedFiatCurrencies = fiatCurrenciesPrefStorage.restore()
if (storedFiatCurrencies.isNotEmpty()) {
store.dispatchDialogShow(
@ -65,23 +119,6 @@ class AppCurrencyMiddleware(
}
}
private fun selectCurrency(action: WalletAction.AppCurrencyAction.SelectAppCurrency) {
Analytics.send(MainScreen.MainCurrencyChanged(AnalyticsParam.CurrencyType.FiatCurrency(action.fiatCurrency)))
fiatCurrenciesPrefStorage.saveAppCurrency(
with(action.fiatCurrency) { DataSourceFiatCurrency(code, name, symbol) },
)
store.dispatch(GlobalAction.ChangeAppCurrency(action.fiatCurrency))
store.dispatch(DetailsAction.ChangeAppCurrency(action.fiatCurrency))
store.dispatch(WalletSelectorAction.ChangeAppCurrency(action.fiatCurrency))
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
Timber.e("Unable to select currency, no user wallet selected")
return
}
scope.launch {
tapWalletManager.loadData(selectedUserWallet, refresh = true)
}
}
private fun List<DataSourceCurrency>.mapToUiModel(): List<FiatCurrency> {
return this.map {
FiatCurrency(

View file

@ -1,5 +1,6 @@
package com.tangem.tap.features.wallet.redux.middlewares
import androidx.core.os.bundleOf
import com.tangem.common.doOnSuccess
import com.tangem.common.extensions.guard
import com.tangem.common.flatMap
@ -7,6 +8,7 @@ import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Token.ButtonRemoveToken
import com.tangem.tap.common.extensions.addContext
@ -15,6 +17,7 @@ import com.tangem.tap.common.extensions.dispatchErrorNotification
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.features.wallet.converters.CryptoCurrencyConverter
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.redux.models.WalletDialog
@ -28,12 +31,19 @@ import kotlinx.coroutines.launch
import timber.log.Timber
class MultiWalletMiddleware {
private val cryptoCurrencyConverter by lazy { CryptoCurrencyConverter() }
@Suppress("LongMethod", "ComplexMethod")
fun handle(action: WalletAction.MultiWallet, walletState: WalletState?) {
when (action) {
is WalletAction.MultiWallet.SelectWallet -> {
if (action.currency != null) {
store.dispatch(NavigationAction.NavigateTo(AppScreen.WalletDetails))
val bundle = bundleOf(
// TODO: [REDACTED_JIRA]
TokenDetailsRouter.SELECTED_CURRENCY_KEY to cryptoCurrencyConverter.convert(action.currency),
)
store.dispatch(NavigationAction.NavigateTo(screen = AppScreen.WalletDetails, bundle = bundle))
}
}
is WalletAction.MultiWallet.TryToRemoveWallet -> {

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
@ -55,6 +55,8 @@ class WalletMiddleware {
walletRepository = store.state.featureRepositoryProvider.walletRepository,
tapWalletManager = store.state.globalState.tapWalletManager,
fiatCurrenciesPrefStorage = preferencesStorage.fiatCurrenciesPrefStorage,
appCurrencyRepository = store.state.daggerGraphState.get(DaggerGraphState::appCurrencyRepository),
featureToggles = store.state.daggerGraphState.get(DaggerGraphState::walletFeatureToggles),
appCurrencyProvider = { store.state.globalState.appCurrency },
)
}

View file

@ -12,7 +12,7 @@ object MultipleAddressUiHelper {
Blockchain.BitcoinTestnet,
Blockchain.Litecoin,
Blockchain.BitcoinCash,
Blockchain.CardanoShelley,
Blockchain.Cardano,
)
fun typeToId(type: AddressType, blockchain: Blockchain): Int {

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

@ -6,6 +6,7 @@ import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.blockchain.extensions.Result
import com.tangem.domain.models.scan.CardDTO
import com.tangem.tap.common.extensions.safeUpdate
@ -96,7 +97,11 @@ suspend fun buyErc20TestnetTokens(card: CardDTO, walletManager: EthereumWalletMa
amountToSend,
destinationAddress,
) as? Result.Success ?: return
val fee = feeResult.data.minimum
val fee = when (val feeForTx = feeResult.data) {
is TransactionFee.Choosable -> feeForTx.minimum
is TransactionFee.Single -> feeForTx.normal
}
val coinValue = walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO
if (coinValue < fee.amount.value) return

View file

@ -119,7 +119,7 @@ internal class MercuryoService(private val environment: MercuryoEnvironment) : E
return when (currencyName) {
"BNB" -> Blockchain.BSC
"ETH" -> Blockchain.Ethereum
"ADA" -> Blockchain.CardanoShelley
"ADA" -> Blockchain.Cardano
else -> Blockchain.values().find { it.currency.lowercase() == currencyName.lowercase() }
}
}

View file

@ -10,8 +10,9 @@ 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.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 +47,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 +71,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 +94,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 +164,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

@ -1,6 +1,6 @@
package com.tangem.tap.proxy.di
import androidx.compose.ui.text.intl.Locale
import com.tangem.common.Provider
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.common.CardTypesResolver
@ -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
@ -81,9 +74,7 @@ class ProxyModule {
}
}
override fun getLocaleProvider(): () -> String = { Locale.current.language }
override fun getWebViewAuthCredentialsProvider(): () -> String? = {
override fun getWebViewAuthCredentialsProvider(): Provider<String?> = Provider {
appStateHolder.mainStore?.state?.globalState?.configManager?.config?.tangemComAuthorization
}
}

View file

@ -2,6 +2,7 @@ package com.tangem.tap.proxy.redux
import com.tangem.datasource.asset.AssetReader
import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.ScanCardUseCase
import com.tangem.domain.card.repository.CardSdkConfigRepository
@ -31,6 +32,7 @@ data class DaggerGraphState(
val tokenDetailsRouter: TokenDetailsRouter? = null,
val scanCardProcessor: ScanCardProcessor? = null,
val cardSdkConfigRepository: CardSdkConfigRepository? = null,
val appCurrencyRepository: AppCurrencyRepository? = null,
) : StateType {
inline fun <reified T> get(getDependency: DaggerGraphState.() -> T?): T {

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

@ -96,6 +96,7 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
mainnetApiKey = configValues.tonCenterKeys.mainnet,
testnetApiKey = configValues.tonCenterKeys.testnet,
),
chiaFireAcademyApiKey = configValues.chiaFireAcademyApiKey,
),
appsFlyerDevKey = configValues.appsFlyer.appsFlyerDevKey,
amplitudeApiKey = configValues.amplitudeApiKey,

View file

@ -38,6 +38,7 @@ class ConfigValueModel(
val kaspaSecondaryApiUrl: String,
val walletConnectProjectId: String,
val tangemComAuthorization: String?,
val chiaFireAcademyApiKey: String?,
)
data class AppsFlyer(

View file

@ -0,0 +1,45 @@
package com.tangem.datasource.di
import android.content.Context
import com.squareup.moshi.Moshi
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
import com.tangem.datasource.local.appcurrency.AvailableAppCurrenciesStore
import com.tangem.datasource.local.appcurrency.SelectedAppCurrencyStore
import com.tangem.datasource.local.appcurrency.implementation.DefaultAvailableAppCurrenciesStore
import com.tangem.datasource.local.appcurrency.implementation.DefaultSelectedAppCurrencyStore
import com.tangem.datasource.local.datastore.RuntimeDataStore
import com.tangem.datasource.local.datastore.SharedPreferencesDataStore
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 AppCurrencyDataModule {
@Provides
@Singleton
fun provideAvailableAppCurrenciesStore(): AvailableAppCurrenciesStore {
return DefaultAvailableAppCurrenciesStore(
dataStore = RuntimeDataStore(),
)
}
@Provides
@Singleton
fun provideSelectedAppCurrencyStore(
@ApplicationContext context: Context,
@NetworkMoshi moshi: Moshi,
): SelectedAppCurrencyStore {
return DefaultSelectedAppCurrencyStore(
dataStore = SharedPreferencesDataStore(
preferencesName = "selected_app_currency",
context = context,
adapter = moshi.adapter(CurrenciesResponse.Currency::class.java),
),
)
}
}

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,12 @@
package com.tangem.datasource.local.appcurrency
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
interface AvailableAppCurrenciesStore {
suspend fun getAllSyncOrNull(): List<CurrenciesResponse.Currency>?
suspend fun getSyncOrNull(key: String): CurrenciesResponse.Currency?
suspend fun store(response: CurrenciesResponse)
}

View file

@ -0,0 +1,26 @@
package com.tangem.datasource.local.appcurrency
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
// TODO: Will be implemented in [REDACTED_TASK_KEY] task
internal class MockSelectedAppCurrencyStore : SelectedAppCurrencyStore {
override fun get(): Flow<CurrenciesResponse.Currency> {
return flowOf(
CurrenciesResponse.Currency(
id = "usd",
code = "USD",
name = "US Dollar",
unit = "$",
type = "fiat",
rateBTC = "",
),
)
}
override suspend fun store(item: CurrenciesResponse.Currency) {
/* no-op */
}
}

View file

@ -0,0 +1,11 @@
package com.tangem.datasource.local.appcurrency
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
import kotlinx.coroutines.flow.Flow
interface SelectedAppCurrencyStore {
fun get(): Flow<CurrenciesResponse.Currency>
suspend fun store(item: CurrenciesResponse.Currency)
}

View file

@ -0,0 +1,22 @@
package com.tangem.datasource.local.appcurrency.implementation
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
import com.tangem.datasource.local.appcurrency.AvailableAppCurrenciesStore
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator
internal class DefaultAvailableAppCurrenciesStore(
private val dataStore: StringKeyDataStore<CurrenciesResponse.Currency>,
) : AvailableAppCurrenciesStore,
StringKeyDataStoreDecorator<String, CurrenciesResponse.Currency>(dataStore) {
override fun provideStringKey(key: String): String {
return key
}
override suspend fun store(response: CurrenciesResponse) {
val currencies = response.currencies.associateBy(CurrenciesResponse.Currency::code)
dataStore.store(currencies)
}
}

View file

@ -0,0 +1,10 @@
package com.tangem.datasource.local.appcurrency.implementation
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
import com.tangem.datasource.local.appcurrency.SelectedAppCurrencyStore
import com.tangem.datasource.local.datastore.core.KeylessDataStoreDecorator
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
internal class DefaultSelectedAppCurrencyStore(
dataStore: StringKeyDataStore<CurrenciesResponse.Currency>,
) : SelectedAppCurrencyStore, KeylessDataStoreDecorator<CurrenciesResponse.Currency>(dataStore)

View file

@ -3,38 +3,49 @@ package com.tangem.datasource.local.datastore
import com.squareup.moshi.JsonAdapter
import com.tangem.datasource.files.FileReader
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
import com.tangem.datasource.local.datastore.model.WriteTrigger
import kotlinx.coroutines.channels.BufferOverflow
import com.tangem.datasource.local.datastore.utils.Trigger
import kotlinx.coroutines.flow.*
import timber.log.Timber
@Deprecated("Use shared preferences data store instead")
internal class FileDataStore<Value : Any>(
private val fileReader: FileReader,
private val adapter: JsonAdapter<Value>,
) : StringKeyDataStore<Value> {
private val writeTrigger = MutableSharedFlow<WriteTrigger>(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST,
)
private val writeTrigger = Trigger()
override fun get(key: String): Flow<Value> {
return writeTrigger
.onEmpty { emit(WriteTrigger) }
.map { getInternal(key) }
.filterNotNull()
.distinctUntilChanged()
}
override fun getAll(): Flow<List<Value>> {
val e = NotImplementedError("`getAll()` function not implemented for `FileDataStore`")
Timber.e(e)
throw e
}
override suspend fun getSyncOrNull(key: String): Value? {
return getInternal(key)
}
override suspend fun getAllSyncOrNull(): List<Value> {
val e = NotImplementedError("`getAllSyncOrNull()` function not implemented for `FileDataStore`")
Timber.e(e)
throw e
}
override suspend fun store(key: String, item: Value) {
try {
val json = adapter.toJson(item)
fileReader.rewriteFile(json, key)
writeTrigger.tryEmit(WriteTrigger)
writeTrigger.trigger()
} catch (e: Throwable) {
Timber.e(e, "Unable to write file: $key")
}
@ -48,10 +59,14 @@ internal class FileDataStore<Value : Any>(
override suspend fun remove(key: String) {
fileReader.removeFile(key)
writeTrigger.trigger()
}
override suspend fun clear() {
// TODO: Implement if needed
val e = NotImplementedError("`clear()` function not implemented for `FileDataStore`")
Timber.e(e)
throw e
}
private fun getInternal(fileName: String): Value? {

View file

@ -13,10 +13,18 @@ internal class RuntimeDataStore<Data : Any> : StringKeyDataStore<Data> {
.filterNotNull()
}
override fun getAll(): Flow<List<Data>> {
return store.map { value -> value.values.toList() }
}
override suspend fun getSyncOrNull(key: String): Data? {
return store.value[key]
}
override suspend fun getAllSyncOrNull(): List<Data> {
return store.value.values.toList()
}
override suspend fun store(key: String, item: Data) {
store.update { value ->
value[key] = item

View file

@ -0,0 +1,99 @@
package com.tangem.datasource.local.datastore
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.content.SharedPreferences
import androidx.core.content.edit
import com.squareup.moshi.JsonAdapter
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
import com.tangem.datasource.local.datastore.utils.Trigger
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
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: SharedPreferences by lazy {
context.getSharedPreferences(preferencesName, MODE_PRIVATE)
}
private val writeTrigger = Trigger()
override fun get(key: String): Flow<Value> {
return writeTrigger
.map { getInternal(key) }
.filterNotNull()
.distinctUntilChanged()
}
override fun getAll(): Flow<List<Value>> {
return writeTrigger
.map { getAllInternal() }
.distinctUntilChanged()
}
override suspend fun getSyncOrNull(key: String): Value? {
return getInternal(key)
}
override suspend fun getAllSyncOrNull(): List<Value> {
return getAllInternal()
}
override suspend fun store(key: String, item: Value) {
try {
val json = adapter.toJson(item)
sharedPreferences.edit { putString(key, json) }
writeTrigger.trigger()
} 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) }
writeTrigger.trigger()
}
override suspend fun clear() {
sharedPreferences.edit { clear() }
writeTrigger.trigger()
}
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
}
}
private fun getAllInternal(): List<Value> {
return sharedPreferences.all.mapNotNull { (key, value) ->
try {
val json = value as? String ?: return@mapNotNull null
adapter.fromJson(json)
} catch (e: Throwable) {
Timber.e(e, "Unable to convert value from JSON: $key")
null
}
}
}
}

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