Updated on 2026-08-14

This commit is contained in:
Tangem 2023-09-04 19:41:21 +08:00
parent b5e2899332
commit 1694ec07be
12 changed files with 465 additions and 143 deletions

View file

@ -17,13 +17,13 @@ import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.tokens.TokensAction
import com.tangem.feature.learn2earn.presentation.Learn2earnViewModel
import com.tangem.tap.common.analytics.events.IntroductionProcess
import com.tangem.tap.features.home.compose.StoriesScreen
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.home.redux.HomeState
import com.tangem.tap.features.home.redux.Stories
import com.tangem.tap.features.tokens.legacy.redux.TokensAction
import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint
import org.rekotlin.StoreSubscriber

View file

@ -1,22 +0,0 @@
package com.tangem.tap.features.tokens.impl.presentation.models
import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.features.tokens.legacy.redux.TokenWithBlockchain
import com.tangem.tap.store
/**
* Required data for tokens list screen
* FIXME("Necessary to avoid using redux state")
*
[REDACTED_AUTHOR]
*/
class TokensListArgs {
/** Tokens list screen mode */
val isManageAccess: Boolean get() = store.state.tokensState.isManageAccess
/** Tokens list that accessible from the main screen */
val mainScreenTokenList: List<TokenWithBlockchain> get() = store.state.tokensState.addedTokens
/** Blockchains list that accessible from the main screen */
val mainScreenBlockchainList: List<Blockchain> get() = store.state.tokensState.addedBlockchains
}

View file

@ -0,0 +1,9 @@
package com.tangem.tap.features.tokens.impl.presentation.viewmodels
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.tokens.TokenWithBlockchain
internal data class TokensListCryptoCurrencies(
val coins: List<Blockchain>,
val tokens: List<TokenWithBlockchain>,
)

View file

@ -0,0 +1,192 @@
package com.tangem.tap.features.tokens.impl.presentation.viewmodels
import arrow.core.Either
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
import com.tangem.domain.tokens.TokenWithBlockchain
import com.tangem.domain.tokens.TokensAction
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.store
import timber.log.Timber
import kotlin.properties.Delegates
/**
* Class that divide a new and legacy logic when user uses tokens list screen
*
* @property walletFeatureToggles wallet feature toggles
* @property getSelectedWalletUseCase use case that returns selected wallet
* @property getCurrenciesUseCase use case that returns crypto currencies of a specified wallet
*/
internal class TokensListMigration(
private val walletFeatureToggles: WalletFeatureToggles,
private val getSelectedWalletUseCase: GetSelectedWalletUseCase,
private val getCurrenciesUseCase: GetCryptoCurrenciesUseCase,
) {
private var currentNewCoins: List<CryptoCurrency.Coin> by Delegates.notNull()
private var currentNewTokens: List<CryptoCurrency.Token> by Delegates.notNull()
private var currentUserWallet: UserWallet by Delegates.notNull()
private val cryptoCurrencyFactory by lazy { CryptoCurrencyFactory() }
suspend fun getCurrentCryptoCurrencies(): TokensListCryptoCurrencies {
return if (walletFeatureToggles.isRedesignedScreenEnabled) {
getNewCryptoCurrencies()
} else {
getLegacyCryptoCurrencies()
}
}
private suspend fun getNewCryptoCurrencies(): TokensListCryptoCurrencies {
return when (val selectedWalletEither = getSelectedWalletUseCase()) {
is Either.Left -> {
Timber.e(selectedWalletEither.value.toString())
TokensListCryptoCurrencies(coins = emptyList(), tokens = emptyList())
}
is Either.Right -> {
currentUserWallet = selectedWalletEither.value
val derivationStyle = currentUserWallet.scanResponse.derivationStyleProvider.getDerivationStyle()
when (val currenciesEither = getCurrenciesUseCase(userWalletId = selectedWalletEither.value.walletId)) {
is Either.Left -> {
Timber.e(currenciesEither.value.toString())
TokensListCryptoCurrencies(coins = emptyList(), tokens = emptyList())
}
is Either.Right -> {
TokensListCryptoCurrencies(
coins = currenciesEither.value
.filterIsInstance<CryptoCurrency.Coin>()
.filterNot { it.isCustomCurrency(derivationStyle) }
.also { currentNewCoins = it }
.map { Blockchain.fromId(it.network.id.value) },
tokens = currenciesEither.value
.filterIsInstance<CryptoCurrency.Token>()
.filterNot(CryptoCurrency.Token::isCustom)
.also { currentNewTokens = it }
.map { token ->
TokenWithBlockchain(
token = Token(
name = token.name,
symbol = token.symbol,
contractAddress = token.contractAddress,
decimals = token.decimals,
id = token.id.rawCurrencyId,
),
blockchain = Blockchain.fromId(token.network.id.value),
)
},
)
}
}
}
}
}
private fun CryptoCurrency.Coin.isCustomCurrency(derivationStyle: DerivationStyle?): Boolean {
if (derivationPath == null || derivationStyle == null) return false
return derivationPath != Blockchain.fromId(network.id.value).derivationPath(derivationStyle)?.rawPath
}
private fun getLegacyCryptoCurrencies(): TokensListCryptoCurrencies {
val wallets = store.state.walletState.walletsDataFromStores
val derivationStyle = store.state.globalState.scanResponse?.derivationStyleProvider?.getDerivationStyle()
return TokensListCryptoCurrencies(
coins = wallets.toNonCustomBlockchains(derivationStyle),
tokens = wallets.toNonCustomTokensWithBlockchains(derivationStyle),
)
}
private fun List<WalletDataModel>.toNonCustomBlockchains(derivationStyle: DerivationStyle?): List<Blockchain> {
return this
.mapNotNull { walletDataModel ->
if (walletDataModel.currency.isCustomCurrency(derivationStyle)) {
null
} else {
(walletDataModel.currency as? Currency.Blockchain)?.blockchain
}
}
.distinct()
}
private fun List<WalletDataModel>.toNonCustomTokensWithBlockchains(
derivationStyle: DerivationStyle?,
): List<TokenWithBlockchain> {
return this
.mapNotNull { walletDataModel ->
if (walletDataModel.currency !is Currency.Token) return@mapNotNull null
if (walletDataModel.currency.isCustomCurrency(derivationStyle)) return@mapNotNull null
TokenWithBlockchain(walletDataModel.currency.token, walletDataModel.currency.blockchain)
}
.distinct()
}
fun onSaveButtonClick(
currentTokensList: List<TokenWithBlockchain>,
currentBlockchainList: List<Blockchain>,
changedTokensList: MutableList<TokenWithBlockchain>,
changedBlockchainList: List<Blockchain>,
) {
if (walletFeatureToggles.isRedesignedScreenEnabled) {
saveByNewWay(changedTokensList = changedTokensList, changedBlockchainList = changedBlockchainList)
} else {
saveByOldWay(currentTokensList, currentBlockchainList, changedTokensList, changedBlockchainList)
}
}
private fun saveByNewWay(
changedTokensList: MutableList<TokenWithBlockchain>,
changedBlockchainList: List<Blockchain>,
) {
store.dispatch(
action = TokensAction.NewSaveChanges(
currentTokens = currentNewTokens,
currentCoins = currentNewCoins,
changedTokens = changedTokensList.mapNotNull {
cryptoCurrencyFactory.createToken(
sdkToken = it.token,
blockchain = it.blockchain,
derivationStyleProvider = currentUserWallet.scanResponse.derivationStyleProvider,
)
},
changedCoins = changedBlockchainList.mapNotNull {
cryptoCurrencyFactory.createCoin(
blockchain = it,
derivationStyleProvider = currentUserWallet.scanResponse.derivationStyleProvider,
)
},
userWallet = currentUserWallet,
),
)
}
private fun saveByOldWay(
currentTokensList: List<TokenWithBlockchain>,
currentBlockchainList: List<Blockchain>,
changedTokensList: MutableList<TokenWithBlockchain>,
changedBlockchainList: List<Blockchain>,
) {
val scanResponse = store.state.globalState.scanResponse ?: return
store.dispatch(
action = TokensAction.LegacySaveChanges(
currentTokens = currentTokensList,
currentBlockchains = currentBlockchainList,
changedTokens = changedTokensList,
changedBlockchains = changedBlockchainList,
scanResponse = scanResponse,
),
)
}
}

View file

@ -19,6 +19,10 @@ import com.tangem.domain.common.extensions.canHandleToken
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.supportedTokens
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
import com.tangem.domain.tokens.TokenWithBlockchain
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import com.tangem.tap.common.extensions.fullNameWithoutTestnet
import com.tangem.tap.common.extensions.getGreyedOutIconRes
import com.tangem.tap.common.extensions.getNetworkName
@ -26,14 +30,11 @@ import com.tangem.tap.features.tokens.impl.domain.TokensListInteractor
import com.tangem.tap.features.tokens.impl.domain.models.Token
import com.tangem.tap.features.tokens.impl.domain.models.Token.Network
import com.tangem.tap.features.tokens.impl.presentation.models.SupportTokensState
import com.tangem.tap.features.tokens.impl.presentation.models.TokensListArgs
import com.tangem.tap.features.tokens.impl.presentation.router.TokensListRouter
import com.tangem.tap.features.tokens.impl.presentation.states.NetworkItemState
import com.tangem.tap.features.tokens.impl.presentation.states.TokenItemState
import com.tangem.tap.features.tokens.impl.presentation.states.TokensListStateHolder
import com.tangem.tap.features.tokens.impl.presentation.states.TokensListToolbarState
import com.tangem.tap.features.tokens.legacy.redux.TokenWithBlockchain
import com.tangem.tap.features.tokens.legacy.redux.TokensAction
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.store
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
@ -43,9 +44,11 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import timber.log.Timber
import javax.inject.Inject
import kotlin.properties.Delegates
import com.tangem.blockchain.common.Token as BlockchainToken
/**
@ -59,6 +62,7 @@ import com.tangem.blockchain.common.Token as BlockchainToken
*
[REDACTED_AUTHOR]
*/
@Suppress("LongParameterList")
@HiltViewModel
internal class TokensListViewModel @Inject constructor(
private val interactor: TokensListInteractor,
@ -66,9 +70,12 @@ internal class TokensListViewModel @Inject constructor(
private val dispatchers: AppCoroutineDispatcherProvider,
private val reduxStateHolder: AppStateHolder,
analyticsEventHandler: AnalyticsEventHandler,
getCurrenciesUseCase: GetCryptoCurrenciesUseCase,
getSelectedWalletUseCase: GetSelectedWalletUseCase,
walletFeatureToggles: WalletFeatureToggles,
) : ViewModel(), DefaultLifecycleObserver {
private val args = TokensListArgs()
private val isManageAccess = store.state.tokensState.isManageAccess
private val analyticsSender = TokensListAnalyticsSender(analyticsEventHandler)
private val actionsHandler = ActionsHandler(router = router, debouncer = Debouncer())
@ -76,15 +83,36 @@ internal class TokensListViewModel @Inject constructor(
var uiState by mutableStateOf(value = getInitialUiState())
private set
private val changedTokensList: MutableList<TokenWithBlockchain> = args.mainScreenTokenList.toMutableList()
private val changedBlockchainList: MutableList<Blockchain> = args.mainScreenBlockchainList.toMutableList()
private var currentTokensList: List<TokenWithBlockchain> by Delegates.notNull()
private var currentBlockchainList: List<Blockchain> by Delegates.notNull()
private var changedTokensList: MutableList<TokenWithBlockchain> by Delegates.notNull()
private var changedBlockchainList: MutableList<Blockchain> by Delegates.notNull()
private val tokensListMigration = TokensListMigration(
walletFeatureToggles = walletFeatureToggles,
getSelectedWalletUseCase = getSelectedWalletUseCase,
getCurrenciesUseCase = getCurrenciesUseCase,
)
init {
viewModelScope.launch(dispatchers.main) {
val (currentCoins, currentTokens) = tokensListMigration.getCurrentCryptoCurrencies()
currentBlockchainList = currentCoins
currentTokensList = currentTokens
changedBlockchainList = currentCoins.toMutableList()
changedTokensList = currentTokens.toMutableList()
}
}
override fun onCreate(owner: LifecycleOwner) {
if (args.isManageAccess) analyticsSender.sendWhenScreenOpened()
if (isManageAccess) analyticsSender.sendWhenScreenOpened()
}
private fun getInitialUiState(): TokensListStateHolder {
return if (args.isManageAccess) {
return if (isManageAccess) {
TokensListStateHolder.ManageContent(
toolbarState = getInitialToolbarState(),
isLoading = true,
@ -105,7 +133,7 @@ internal class TokensListViewModel @Inject constructor(
}
private fun getInitialToolbarState(): TokensListToolbarState {
return if (args.isManageAccess) {
return if (isManageAccess) {
TokensListToolbarState.Title.Manage(
titleResId = R.string.add_tokens_title,
onBackButtonClick = actionsHandler::onBackButtonClick,
@ -130,7 +158,7 @@ internal class TokensListViewModel @Inject constructor(
return interactor.getTokensList(searchText = searchText).map {
it.map { token ->
if (args.isManageAccess) createManageTokenContent(token) else createReadTokenContent(token)
if (isManageAccess) createManageTokenContent(token) else createReadTokenContent(token)
}
}
}
@ -264,7 +292,12 @@ internal class TokensListViewModel @Inject constructor(
fun onSaveButtonClick() {
analyticsSender.sendWhenSaveButtonClicked()
store.dispatch(TokensAction.SaveChanges(changedTokensList, changedBlockchainList))
tokensListMigration.onSaveButtonClick(
currentTokensList = currentTokensList,
currentBlockchainList = currentBlockchainList,
changedTokensList = changedTokensList,
changedBlockchainList = changedBlockchainList,
)
}
private fun onSearchValueChange(newValue: String) {
@ -291,7 +324,7 @@ internal class TokensListViewModel @Inject constructor(
if (isRemoveAction) {
val isTokenWithSameBlockchainFound = changedTokensList.any { it.blockchain == blockchain }
val isAddedOnMainScreen = args.mainScreenBlockchainList.contains(blockchain)
val isAddedOnMainScreen = currentBlockchainList.contains(blockchain)
if (isTokenWithSameBlockchainFound) {
router.openUnableHideMainTokenAlert(
@ -341,7 +374,7 @@ internal class TokensListViewModel @Inject constructor(
val isRemoveAction = changedTokensList.contains(token)
if (isRemoveAction) {
val isAddedOnMainScreen = args.mainScreenTokenList.contains(token)
val isAddedOnMainScreen = currentTokensList.contains(token)
if (isAddedOnMainScreen) {
router.openRemoveWalletAlert(

View file

@ -1,20 +0,0 @@
package com.tangem.tap.features.tokens.legacy.redux
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.tap.domain.model.WalletDataModel
import org.rekotlin.Action
sealed interface TokensAction : Action {
/** Single way to pass data to the screen */
sealed interface SetArgs : TokensAction {
data class ManageAccess(val wallets: List<WalletDataModel>, val derivationStyle: DerivationStyle?) : SetArgs
object ReadAccess : SetArgs
}
// TODO: [REDACTED_TASK_KEY] Remove this action
data class SaveChanges(val tokens: List<TokenWithBlockchain>, val blockchains: List<Blockchain>) : TokensAction
}

View file

@ -15,6 +15,10 @@ 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.domain.tokens.TokenWithBlockchain
import com.tangem.domain.tokens.TokensAction
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.tap.*
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
@ -23,6 +27,7 @@ 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.features.wallet.models.Currency
import com.tangem.tap.proxy.redux.DaggerGraphState
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.rekotlin.Middleware
@ -34,28 +39,69 @@ object TokensMiddleware {
{ next ->
{ action ->
when (action) {
is TokensAction.SaveChanges -> handleSaveChanges(action)
is TokensAction.LegacySaveChanges -> handleLegacySaveChanges(action)
is TokensAction.NewSaveChanges -> handleNewSaveChanges(action)
}
next(action)
}
}
}
private fun handleSaveChanges(action: TokensAction.SaveChanges) {
private fun handleNewSaveChanges(action: TokensAction.NewSaveChanges) {
scope.launch {
val scanResponse = store.state.globalState.scanResponse ?: return@launch
val scanResponse = action.userWallet.scanResponse
val currentTokens = store.state.tokensState.addedTokens
val currentBlockchains = store.state.tokensState.addedBlockchains
val currentTokens = action.currentTokens
val currentBlockchains = action.currentCoins
val blockchainsToAdd = action.blockchains.filterNot(currentBlockchains::contains)
val blockchainsToRemove =
store.state.tokensState.addedBlockchains.filterNot(action.blockchains::contains)
val blockchainsToAdd = action.changedCoins.filterNot(currentBlockchains::contains)
val blockchainsToRemove = currentBlockchains.filterNot(action.changedCoins::contains)
val tokensToAdd = action.tokens.filterNot(currentTokens::contains)
val tokensToRemove = currentTokens.filterNot { token -> action.tokens.any { it.token == token.token } }
val tokensToAdd = action.changedTokens.filterNot(currentTokens::contains)
val tokensToRemove = currentTokens.filterNot { token -> action.changedTokens.any { it == token } }
removeCurrenciesIfNeeded(
removeNewCurrenciesIfNeeded(
userWalletId = action.userWallet.walletId,
currencies = blockchainsToRemove + tokensToRemove,
)
val isNothingToDoWithTokens = tokensToAdd.isEmpty() && tokensToRemove.isEmpty()
val isNothingToDoWithBlockchain = blockchainsToAdd.isEmpty() && blockchainsToRemove.isEmpty()
if (isNothingToDoWithTokens && isNothingToDoWithBlockchain) {
store.dispatchDebugErrorNotification(message = "Nothing to save")
store.dispatchOnMain(NavigationAction.PopBackTo())
return@launch
}
val currencyList = blockchainsToAdd + tokensToAdd
if (scanResponse.supportsHdWallet()) {
deriveMissingCoins(scanResponse = scanResponse, currencyList = currencyList) {
submitNewAdd(userWalletId = action.userWallet.walletId, currencyList = currencyList)
store.dispatchOnMain(NavigationAction.PopBackTo())
}
} else {
submitNewAdd(userWalletId = action.userWallet.walletId, currencyList = currencyList)
store.dispatchOnMain(NavigationAction.PopBackTo())
}
}
}
private fun handleLegacySaveChanges(action: TokensAction.LegacySaveChanges) {
scope.launch {
val scanResponse = action.scanResponse
val currentTokens = action.currentTokens
val currentBlockchains = action.currentBlockchains
val blockchainsToAdd = action.changedBlockchains.filterNot(currentBlockchains::contains)
val blockchainsToRemove = currentBlockchains.filterNot(action.changedBlockchains::contains)
val tokensToAdd = action.changedTokens.filterNot(currentTokens::contains)
val tokensToRemove =
currentTokens.filterNot { token -> action.changedTokens.any { it.token == token.token } }
removeLegacyCurrenciesIfNeeded(
currencies = convertToCurrencies(
blockchains = blockchainsToRemove,
tokens = tokensToRemove,
@ -79,11 +125,11 @@ object TokensMiddleware {
if (scanResponse.supportsHdWallet()) {
deriveMissingBlockchains(scanResponse, currencyList) {
submitAdd(it, currencyList)
submitLegacyAdd(it, currencyList)
store.dispatchOnMain(NavigationAction.PopBackTo())
}
} else {
submitAdd(scanResponse, currencyList)
submitLegacyAdd(scanResponse, currencyList)
store.dispatchOnMain(NavigationAction.PopBackTo())
}
}
@ -94,15 +140,14 @@ object TokensMiddleware {
tokens: List<TokenWithBlockchain>,
derivationStyle: DerivationStyle?,
): List<Currency> {
return blockchains.map {
Currency.Blockchain(it, it.derivationPath(derivationStyle)?.rawPath)
} + tokens.map {
Currency.Token(
it.token,
it.blockchain,
it.blockchain.derivationPath(derivationStyle)?.rawPath,
)
}
return blockchains.map { Currency.Blockchain(it, it.derivationPath(derivationStyle)?.rawPath) } +
tokens.map {
Currency.Token(
token = it.token,
blockchain = it.blockchain,
derivationPath = it.blockchain.derivationPath(derivationStyle)?.rawPath,
)
}
}
private fun deriveMissingBlockchains(
@ -113,7 +158,7 @@ object TokensMiddleware {
val config = CardConfig.createConfig(scanResponse.card)
val derivationDataList = currencyList.mapNotNull {
val curve = config.primaryCurve(it.blockchain)
curve?.let { getDerivations(curve, scanResponse, currencyList) }
curve?.let { getLegacyDerivations(curve, scanResponse, currencyList) }
}
val derivations = derivationDataList.associate { it.derivations }
if (derivations.isEmpty()) {
@ -153,7 +198,55 @@ object TokensMiddleware {
}
}
private fun getDerivations(
private fun deriveMissingCoins(
scanResponse: ScanResponse,
currencyList: List<CryptoCurrency>,
onSuccess: (ScanResponse) -> Unit,
) {
val config = CardConfig.createConfig(scanResponse.card)
val derivationDataList = currencyList.mapNotNull {
config.primaryCurve(blockchain = Blockchain.fromId(it.network.id.value))
?.let { curve -> getNewDerivations(curve, scanResponse, currencyList) }
}
val derivations = derivationDataList.associate(DerivationData::derivations)
if (derivations.isEmpty()) {
onSuccess(scanResponse)
return
}
scope.launch {
val result = tangemSdkManager.derivePublicKeys(
cardId = null,
derivations = derivations,
)
when (result) {
is CompletionResult.Success -> {
val newDerivedKeys = result.data.entries
val oldDerivedKeys = scanResponse.derivedKeys
val walletKeys = (newDerivedKeys.keys + oldDerivedKeys.keys).toSet()
val updatedDerivedKeys = walletKeys.associateWith { walletKey ->
val oldDerivations = ExtendedPublicKeysMap(oldDerivedKeys[walletKey] ?: emptyMap())
val newDerivations = newDerivedKeys[walletKey] ?: ExtendedPublicKeysMap(emptyMap())
ExtendedPublicKeysMap(oldDerivations + newDerivations)
}
val updatedScanResponse = scanResponse.copy(
derivedKeys = updatedDerivedKeys,
)
store.dispatchOnMain(GlobalAction.SaveScanResponse(updatedScanResponse))
delay(DELAY_SDK_DIALOG_CLOSE)
onSuccess(updatedScanResponse)
}
is CompletionResult.Failure -> {
store.dispatchDebugErrorNotification(TapError.CustomError("Error adding tokens"))
}
}
}
}
private fun getLegacyDerivations(
curve: EllipticCurve,
scanResponse: ScanResponse,
currencyList: List<Currency>,
@ -190,9 +283,48 @@ object TokensMiddleware {
return DerivationData(derivations = mapKeyOfWalletPublicKey to toDerive)
}
private fun getNewDerivations(
curve: EllipticCurve,
scanResponse: ScanResponse,
currencyList: List<CryptoCurrency>,
): DerivationData? {
val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null
val manageTokensCandidates = currencyList
.map { Blockchain.fromId(it.network.id.value) }
.distinct()
.filter { it.getSupportedCurves().contains(curve) }
.mapNotNull { it.derivationPath(scanResponse.derivationStyleProvider.getDerivationStyle()) }
val customTokensCandidates = currencyList
.filter { Blockchain.fromId(it.network.id.value).getSupportedCurves().contains(curve) }
.mapNotNull(CryptoCurrency::derivationPath)
.map(::DerivationPath)
val bothCandidates = (manageTokensCandidates + customTokensCandidates).distinct().toMutableList()
if (bothCandidates.isEmpty()) return null
currencyList.find { it is CryptoCurrency.Coin && Blockchain.fromId(it.network.id.value) == Blockchain.Cardano }
?.let { currency ->
currency.derivationPath?.let {
bothCandidates.add(CardanoUtils.extendedDerivationPath(DerivationPath(it)))
}
}
val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey()
val alreadyDerivedKeys: ExtendedPublicKeysMap =
scanResponse.derivedKeys[mapKeyOfWalletPublicKey] ?: ExtendedPublicKeysMap(emptyMap())
val alreadyDerivedPaths = alreadyDerivedKeys.keys.toList()
val toDerive = bothCandidates.filterNot { alreadyDerivedPaths.contains(it) }
if (toDerive.isEmpty()) return null
return DerivationData(derivations = mapKeyOfWalletPublicKey to toDerive)
}
class DerivationData(val derivations: Pair<ByteArrayKey, List<DerivationPath>>)
private fun submitAdd(scanResponse: ScanResponse, currencyList: List<Currency>) {
private fun submitLegacyAdd(scanResponse: ScanResponse, currencyList: List<Currency>) {
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
Timber.e("Unable to add currencies, no user wallet selected")
return
@ -213,7 +345,15 @@ object TokensMiddleware {
}
}
private suspend fun removeCurrenciesIfNeeded(currencies: List<Currency>) {
private fun submitNewAdd(userWalletId: UserWalletId, currencyList: List<CryptoCurrency>) {
val currenciesRepository = store.state.daggerGraphState.get(DaggerGraphState::currenciesRepository)
scope.launch {
currenciesRepository.addCurrencies(userWalletId = userWalletId, currencies = currencyList)
}
}
private suspend fun removeLegacyCurrenciesIfNeeded(currencies: List<Currency>) {
if (currencies.isEmpty()) return
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
Timber.e("Unable to remove currencies, no user wallet selected")
@ -221,4 +361,11 @@ object TokensMiddleware {
}
walletCurrenciesManager.removeCurrencies(selectedUserWallet, currencies)
}
private suspend fun removeNewCurrenciesIfNeeded(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
if (currencies.isEmpty()) return
val currenciesRepository = store.state.daggerGraphState.get(DaggerGraphState::currenciesRepository)
currenciesRepository.removeCurrencies(userWalletId = userWalletId, currencies = currencies)
}
}

View file

@ -1,11 +1,7 @@
package com.tangem.tap.features.tokens.legacy.redux
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.domain.tokens.TokensAction
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.Currency.Token
import org.rekotlin.Action
object TokensReducer {
@ -16,40 +12,8 @@ private fun internalReduce(action: Action, state: AppState): TokensState {
if (action !is TokensAction) return state.tokensState
return when (action) {
is TokensAction.SetArgs.ManageAccess -> {
state.tokensState.copy(
isManageAccess = true,
addedWallets = action.wallets,
addedBlockchains = action.wallets.toNonCustomBlockchains(action.derivationStyle),
addedTokens = action.wallets.toNonCustomTokensWithBlockchains(action.derivationStyle),
)
}
is TokensAction.SetArgs.ReadAccess -> {
state.tokensState.copy(isManageAccess = false)
}
is TokensAction.SetArgs.ManageAccess -> state.tokensState.copy(isManageAccess = true)
is TokensAction.SetArgs.ReadAccess -> state.tokensState.copy(isManageAccess = false)
else -> state.tokensState
}
}
private fun List<WalletDataModel>.toNonCustomBlockchains(derivationStyle: DerivationStyle?): List<Blockchain> {
return mapNotNull { walletDataModel ->
if (walletDataModel.currency.isCustomCurrency(derivationStyle)) {
null
} else {
(walletDataModel.currency as? Currency.Blockchain)?.blockchain
}
}.distinct()
}
private fun List<WalletDataModel>.toNonCustomTokensWithBlockchains(
derivationStyle: DerivationStyle?,
): List<TokenWithBlockchain> {
return mapNotNull { walletDataModel ->
if (walletDataModel.currency !is Token) return@mapNotNull null
if (walletDataModel.currency.isCustomCurrency(derivationStyle)) return@mapNotNull null
TokenWithBlockchain(walletDataModel.currency.token, walletDataModel.currency.blockchain)
}.distinct()
}

View file

@ -1,16 +1,5 @@
package com.tangem.tap.features.tokens.legacy.redux
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.tap.domain.model.WalletDataModel
import org.rekotlin.StateType
data class TokensState(
val isManageAccess: Boolean = false,
val addedWallets: List<WalletDataModel> = emptyList(),
val addedTokens: List<TokenWithBlockchain> = emptyList(),
val addedBlockchains: List<Blockchain> = emptyList(),
) : StateType
// TODO: [REDACTED_TASK_KEY] Remove this class
data class TokenWithBlockchain(val token: Token, val blockchain: Blockchain)
data class TokensState(val isManageAccess: Boolean = false) : StateType

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.util.derivationStyleProvider
import com.tangem.domain.tokens.TokensAction
import com.tangem.tap.common.analytics.events.MainScreen
import com.tangem.tap.common.analytics.events.Portfolio
import com.tangem.tap.common.entities.FiatCurrency
@ -14,7 +14,6 @@ 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.domain.model.TotalFiatBalance
import com.tangem.tap.features.tokens.legacy.redux.TokensAction
import com.tangem.tap.features.wallet.redux.ErrorType
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletState
@ -106,14 +105,8 @@ class MultiWalletView : WalletView() {
binding.btnAddToken.setOnClickListener {
Analytics.send(Portfolio.ButtonManageTokens())
store.dispatch(
TokensAction.SetArgs.ManageAccess(
wallets = state.walletsDataFromStores,
derivationStyle = store.state.globalState.scanResponse
?.derivationStyleProvider?.getDerivationStyle(),
),
)
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))
store.dispatch(action = TokensAction.SetArgs.ManageAccess)
store.dispatch(action = NavigationAction.NavigateTo(screen = AppScreen.AddTokens))
}
handleErrorStates(state = state, binding = binding, fragment = fragment)
}