Updated on 2026-08-14
This commit is contained in:
parent
b5e2899332
commit
1694ec07be
12 changed files with 465 additions and 143 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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>,
|
||||
)
|
||||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue