Updated on 2026-08-14
This commit is contained in:
parent
3c14484840
commit
8a1239f014
10 changed files with 374 additions and 110 deletions
|
|
@ -35,6 +35,7 @@ internal object TokensListInteractorModule {
|
|||
reduxStateHolder = reduxStateHolder,
|
||||
testnetTokensStorage = testnetTokensStorage,
|
||||
),
|
||||
reduxStateHolder = reduxStateHolder,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,245 @@
|
|||
package com.tangem.tap.features.tokens.domain
|
||||
|
||||
import androidx.paging.PagingData
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.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.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
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.tokens.domain.models.Token
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
import com.tangem.tap.features.tokens.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
|
||||
|
||||
/**
|
||||
* Default implementation of tokens list interactor
|
||||
* FIXME("Necessary to avoid using redux actions")
|
||||
*
|
||||
* @property repository repository of tokens list feature
|
||||
* @property repository repository of tokens list feature
|
||||
* @property reduxStateHolder redux state holder
|
||||
*/
|
||||
internal class DefaultTokensListInteractor(private val repository: TokensListRepository) : TokensListInteractor {
|
||||
internal class DefaultTokensListInteractor(
|
||||
private val repository: TokensListRepository,
|
||||
private val reduxStateHolder: AppStateHolder,
|
||||
) : TokensListInteractor {
|
||||
|
||||
override fun getTokensList(searchText: String): Flow<PagingData<Token>> {
|
||||
return repository.getAvailableTokens(searchText = searchText.ifBlank(defaultValue = { null }))
|
||||
}
|
||||
|
||||
override suspend fun saveChanges(tokens: List<TokenWithBlockchain>, blockchains: List<Blockchain>) {
|
||||
val scanResponse = requireNotNull(reduxStateHolder.scanResponse)
|
||||
|
||||
val currentTokens = store.state.tokensState.addedWallets
|
||||
.toNonCustomTokensWithBlockchains(style = scanResponse.card.derivationStyle)
|
||||
|
||||
val currentBlockchains = store.state.tokensState.addedWallets
|
||||
.toNonCustomBlockchains(derivationStyle = scanResponse.card.derivationStyle)
|
||||
|
||||
val blockchainsToAdd = blockchains.filterNot(currentBlockchains::contains)
|
||||
val blockchainsToRemove = currentBlockchains.filterNot(blockchains::contains)
|
||||
|
||||
val tokensToAdd = tokens.filterNot(currentTokens::contains)
|
||||
val tokensToRemove = currentTokens.filterNot { token -> tokens.any { it.token == token.token } }
|
||||
|
||||
val isNothingToDoWithTokens = tokensToAdd.isEmpty() && tokensToRemove.isEmpty()
|
||||
val isNothingToDoWithBlockchain = blockchainsToAdd.isEmpty() && blockchainsToRemove.isEmpty()
|
||||
if (isNothingToDoWithTokens && isNothingToDoWithBlockchain) {
|
||||
store.dispatchDebugErrorNotification(message = "Nothing to save")
|
||||
return
|
||||
}
|
||||
|
||||
remove(
|
||||
tokens = tokensToRemove,
|
||||
blockchains = blockchainsToRemove,
|
||||
derivationStyle = scanResponse.card.derivationStyle,
|
||||
)
|
||||
|
||||
add(tokens = tokensToAdd, blockchains = blockchainsToAdd, scanResponse = scanResponse)
|
||||
}
|
||||
|
||||
private fun List<WalletDataModel>.toNonCustomTokensWithBlockchains(
|
||||
style: DerivationStyle?,
|
||||
): List<TokenWithBlockchain> {
|
||||
return this.map(WalletDataModel::currency)
|
||||
.mapNotNull { currency ->
|
||||
if (currency !is Currency.Token || currency.isCustomCurrency(style)) return@mapNotNull null
|
||||
TokenWithBlockchain(token = currency.token, blockchain = currency.blockchain)
|
||||
}
|
||||
.distinct()
|
||||
}
|
||||
|
||||
private fun List<WalletDataModel>.toNonCustomBlockchains(derivationStyle: DerivationStyle?): List<Blockchain> {
|
||||
return this.map(WalletDataModel::currency)
|
||||
.mapNotNull { currency ->
|
||||
if (currency.isCustomCurrency(derivationStyle)) return@mapNotNull null
|
||||
(currency as? Currency.Blockchain)?.blockchain
|
||||
}
|
||||
.distinct()
|
||||
}
|
||||
|
||||
private suspend fun remove(
|
||||
tokens: List<TokenWithBlockchain>,
|
||||
blockchains: List<Blockchain>,
|
||||
derivationStyle: DerivationStyle?,
|
||||
) {
|
||||
val currencies = convertToCurrencies(tokens, blockchains, derivationStyle)
|
||||
if (currencies.isEmpty()) return
|
||||
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to remove currencies, no user wallet selected")
|
||||
return
|
||||
}
|
||||
|
||||
walletCurrenciesManager.removeCurrencies(userWallet = selectedUserWallet, currenciesToRemove = currencies)
|
||||
}
|
||||
|
||||
private suspend fun add(
|
||||
tokens: List<TokenWithBlockchain>,
|
||||
blockchains: List<Blockchain>,
|
||||
scanResponse: ScanResponse,
|
||||
) {
|
||||
val currenciesToAdd = convertToCurrencies(
|
||||
tokens = tokens,
|
||||
blockchains = blockchains,
|
||||
derivationStyle = scanResponse.card.derivationStyle,
|
||||
)
|
||||
|
||||
// TODO("[REDACTED_TASK_KEY] use DerivationManager")
|
||||
if (scanResponse.supportsHdWallet()) {
|
||||
deriveMissingBlockchains(scanResponse, currenciesToAdd)
|
||||
} else {
|
||||
submitAdd(scanResponse, currenciesToAdd)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
if (derivations.isEmpty()) {
|
||||
submitAdd(scanResponse, currencies)
|
||||
return
|
||||
}
|
||||
|
||||
when (val result = tangemSdkManager.derivePublicKeys(cardId = null, derivations = derivations)) {
|
||||
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(map = oldDerivedKeys[walletKey] ?: emptyMap())
|
||||
val newDerivations = newDerivedKeys[walletKey] ?: ExtendedPublicKeysMap(map = emptyMap())
|
||||
ExtendedPublicKeysMap(map = oldDerivations + newDerivations)
|
||||
}
|
||||
|
||||
val updatedScanResponse = scanResponse.copy(derivedKeys = updatedDerivedKeys)
|
||||
|
||||
store.dispatchOnMain(GlobalAction.SaveScanResponse(updatedScanResponse))
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
|
||||
submitAdd(scanResponse, currencies)
|
||||
return
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
store.dispatchDebugErrorNotification(TapError.CustomError(customMessage = "Error adding tokens"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDerivations(
|
||||
curve: EllipticCurve,
|
||||
scanResponse: ScanResponse,
|
||||
currencyList: List<Currency>,
|
||||
): TokensMiddleware.DerivationData? {
|
||||
val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null
|
||||
|
||||
val manageTokensCandidates = currencyList
|
||||
.map(Currency::blockchain)
|
||||
.distinct()
|
||||
.filter { it.getSupportedCurves().contains(curve) }
|
||||
.mapNotNull { it.derivationPath(scanResponse.card.derivationStyle) }
|
||||
|
||||
val customTokensCandidates = currencyList
|
||||
.filter { it.blockchain.getSupportedCurves().contains(curve) }
|
||||
.mapNotNull(Currency::derivationPath)
|
||||
.map(::DerivationPath)
|
||||
|
||||
val bothCandidates = (manageTokensCandidates + customTokensCandidates).distinct()
|
||||
if (bothCandidates.isEmpty()) return null
|
||||
|
||||
val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey()
|
||||
val alreadyDerivedKeys = scanResponse.derivedKeys[mapKeyOfWalletPublicKey] ?: ExtendedPublicKeysMap(emptyMap())
|
||||
val alreadyDerivedPaths = alreadyDerivedKeys.keys.toList()
|
||||
|
||||
val toDerive = bothCandidates.filterNot(alreadyDerivedPaths::contains)
|
||||
if (toDerive.isEmpty()) return null
|
||||
|
||||
return TokensMiddleware.DerivationData(derivations = mapKeyOfWalletPublicKey to toDerive)
|
||||
}
|
||||
|
||||
private suspend fun submitAdd(scanResponse: ScanResponse, currencies: List<Currency>) {
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to add currencies, no user wallet selected")
|
||||
return
|
||||
}
|
||||
|
||||
userWalletsListManager
|
||||
.update(
|
||||
userWalletId = selectedUserWallet.walletId,
|
||||
update = { userWallet -> userWallet.copy(scanResponse = scanResponse) },
|
||||
)
|
||||
.flatMap { updatedUserWallet ->
|
||||
walletCurrenciesManager.addCurrencies(
|
||||
userWallet = updatedUserWallet,
|
||||
currenciesToAdd = currencies,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertToCurrencies(
|
||||
tokens: List<TokenWithBlockchain>,
|
||||
blockchains: List<Blockchain>,
|
||||
derivationStyle: DerivationStyle?,
|
||||
): List<Currency> {
|
||||
return tokens.map { tokenWithBlockchain ->
|
||||
Currency.Token(
|
||||
token = tokenWithBlockchain.token,
|
||||
blockchain = tokenWithBlockchain.blockchain,
|
||||
derivationPath = tokenWithBlockchain.blockchain.derivationPath(derivationStyle)?.rawPath,
|
||||
)
|
||||
}.plus(
|
||||
blockchains.map { blockchain ->
|
||||
Currency.Blockchain(
|
||||
blockchain = blockchain,
|
||||
derivationPath = blockchain.derivationPath(derivationStyle)?.rawPath,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.tap.features.tokens.domain
|
||||
|
||||
import androidx.paging.PagingData
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.features.tokens.domain.models.Token
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
|
|
@ -13,4 +15,12 @@ internal interface TokensListInteractor {
|
|||
|
||||
/** Get tokens list using filter by text [searchText] */
|
||||
fun getTokensList(searchText: String): Flow<PagingData<Token>>
|
||||
|
||||
/**
|
||||
* Save added tokens
|
||||
*
|
||||
* @param tokens tokens list that need to save
|
||||
* @param blockchains blockchains list that need to save
|
||||
*/
|
||||
suspend fun saveChanges(tokens: List<TokenWithBlockchain>, blockchains: List<Blockchain>)
|
||||
}
|
||||
|
|
@ -1,11 +1,17 @@
|
|||
package com.tangem.tap.features.tokens.presentation.router
|
||||
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchNotification
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
* Default implementation of tokens list router
|
||||
* FIXME("Necessary to avoid using redux actions")
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
|
@ -19,4 +25,29 @@ internal class DefaultTokensListRouter : TokensListRouter {
|
|||
override fun openAddCustomTokenScreen() {
|
||||
store.dispatch(TokensAction.PrepareAndNavigateToAddCustomToken)
|
||||
}
|
||||
|
||||
override fun showAddressCopiedNotification() {
|
||||
store.dispatchNotification(R.string.contract_address_copied_message)
|
||||
}
|
||||
|
||||
override fun openUnableHideMainTokenAlert(tokenName: String, tokenSymbol: String) {
|
||||
store.dispatchDialogShow(
|
||||
dialog = WalletDialog.TokensAreLinkedDialog(currencyTitle = tokenName, currencySymbol = tokenSymbol),
|
||||
)
|
||||
}
|
||||
|
||||
override fun openRemoveWalletAlert(tokenName: String, onOkClick: () -> Unit) {
|
||||
store.dispatchDialogShow(
|
||||
dialog = WalletDialog.RemoveWalletDialog(currencyTitle = tokenName, onOk = onOkClick),
|
||||
)
|
||||
}
|
||||
|
||||
override fun openUnsupportedSoltanaNetworkAlert() {
|
||||
store.dispatchDialogShow(
|
||||
AppDialog.SimpleOkDialogRes(
|
||||
headerId = R.string.common_warning,
|
||||
messageId = R.string.alert_manage_tokens_unsupported_message,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,28 @@ internal interface TokensListRouter {
|
|||
/** Return to last screen */
|
||||
fun popBackStack()
|
||||
|
||||
/** Open screen for adding custom token */
|
||||
/** Open adding custom token screen */
|
||||
fun openAddCustomTokenScreen()
|
||||
|
||||
/** Show notification to inform about copied address */
|
||||
fun showAddressCopiedNotification()
|
||||
|
||||
/**
|
||||
* Open alert if unable to hide the main token
|
||||
*
|
||||
* @param tokenName token name
|
||||
* @param tokenSymbol token brief name
|
||||
*/
|
||||
fun openUnableHideMainTokenAlert(tokenName: String, tokenSymbol: String)
|
||||
|
||||
/**
|
||||
* Open alert to remove wallet
|
||||
*
|
||||
* @param tokenName token name
|
||||
* @param onOkClick lambda be invoked if OkButton is been clicked
|
||||
*/
|
||||
fun openRemoveWalletAlert(tokenName: String, onOkClick: () -> Unit)
|
||||
|
||||
/** Open alert if solana network is unsupported */
|
||||
fun openUnsupportedSoltanaNetworkAlert()
|
||||
}
|
||||
|
|
@ -14,6 +14,9 @@ internal sealed interface TokensListStateHolder {
|
|||
/** Toolbar state */
|
||||
val toolbarState: TokensListToolbarState
|
||||
|
||||
/** Loading state */
|
||||
val isLoading: Boolean
|
||||
|
||||
/** Tokens list */
|
||||
val tokens: Flow<PagingData<TokenItemState>>
|
||||
|
||||
|
|
@ -24,34 +27,22 @@ internal sealed interface TokensListStateHolder {
|
|||
* Util function that allow to make a copy
|
||||
*
|
||||
* @param toolbarState toolbar state
|
||||
* @param isLoading loading state
|
||||
* @param tokens tokens list
|
||||
* @param onTokensLoadStateChanged callback to be invoked when tokens loading state is been changed
|
||||
*/
|
||||
fun copySealed(
|
||||
toolbarState: TokensListToolbarState = this.toolbarState,
|
||||
isLoading: Boolean = this.isLoading,
|
||||
tokens: Flow<PagingData<TokenItemState>> = this.tokens,
|
||||
onTokensLoadStateChanged: (LoadState) -> Unit = this.onTokensLoadStateChanged,
|
||||
): TokensListStateHolder {
|
||||
return when (this) {
|
||||
is ManageContent -> copy(toolbarState, tokens, onTokensLoadStateChanged)
|
||||
is Loading -> copy(toolbarState, tokens, onTokensLoadStateChanged)
|
||||
is ReadContent -> copy(toolbarState, tokens, onTokensLoadStateChanged)
|
||||
is ManageContent -> copy(toolbarState, isLoading, tokens, onTokensLoadStateChanged)
|
||||
is ReadContent -> copy(toolbarState, isLoading, tokens, onTokensLoadStateChanged)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loading state
|
||||
*
|
||||
* @property toolbarState toolbar state
|
||||
* @property tokens tokens list
|
||||
* @property onTokensLoadStateChanged callback to be invoked when tokens loading state is been changed
|
||||
*/
|
||||
data class Loading(
|
||||
override val toolbarState: TokensListToolbarState,
|
||||
override val tokens: Flow<PagingData<TokenItemState>>,
|
||||
override val onTokensLoadStateChanged: (LoadState) -> Unit,
|
||||
) : TokensListStateHolder
|
||||
|
||||
/**
|
||||
* State screen that is available only for read
|
||||
*
|
||||
|
|
@ -61,6 +52,7 @@ internal sealed interface TokensListStateHolder {
|
|||
*/
|
||||
data class ReadContent(
|
||||
override val toolbarState: TokensListToolbarState,
|
||||
override val isLoading: Boolean,
|
||||
override val tokens: Flow<PagingData<TokenItemState>>,
|
||||
override val onTokensLoadStateChanged: (LoadState) -> Unit,
|
||||
) : TokensListStateHolder
|
||||
|
|
@ -75,6 +67,7 @@ internal sealed interface TokensListStateHolder {
|
|||
*/
|
||||
data class ManageContent(
|
||||
override val toolbarState: TokensListToolbarState,
|
||||
override val isLoading: Boolean,
|
||||
override val tokens: Flow<PagingData<TokenItemState>>,
|
||||
override val onTokensLoadStateChanged: (LoadState) -> Unit,
|
||||
val onSaveButtonClick: () -> Unit,
|
||||
|
|
|
|||
|
|
@ -55,15 +55,12 @@ internal fun TokensListScreen(stateHolder: TokensListStateHolder) {
|
|||
) { scaffoldPadding ->
|
||||
val tokens = stateHolder.tokens.collectAsLazyPagingItems()
|
||||
|
||||
TokensListContent(
|
||||
tokens = tokens,
|
||||
scaffoldPadding = scaffoldPadding,
|
||||
)
|
||||
TokensListContent(tokens, scaffoldPadding)
|
||||
|
||||
stateHolder.onTokensLoadStateChanged(tokens.loadState.refresh)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = stateHolder is TokensListStateHolder.Loading,
|
||||
visible = stateHolder.isLoading,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
|
|
@ -122,13 +119,14 @@ private fun SaveChangesButton(onClick: () -> Unit) {
|
|||
private fun Preview_TokensListScreen_Loading() {
|
||||
TangemTheme {
|
||||
TokensListScreen(
|
||||
stateHolder = TokensListStateHolder.Loading(
|
||||
stateHolder = TokensListStateHolder.ReadContent(
|
||||
toolbarState = TokensListToolbarState.Title.Manage(
|
||||
titleResId = R.string.main_manage_tokens,
|
||||
onBackButtonClick = {},
|
||||
onSearchButtonClick = {},
|
||||
onAddCustomTokenClick = {},
|
||||
),
|
||||
isLoading = true,
|
||||
tokens = emptyFlow(),
|
||||
onTokensLoadStateChanged = {},
|
||||
),
|
||||
|
|
@ -148,6 +146,7 @@ private fun Preview_TokensListScreen_Manage() {
|
|||
onSearchButtonClick = {},
|
||||
onAddCustomTokenClick = {},
|
||||
),
|
||||
isLoading = false,
|
||||
tokens = flow {
|
||||
emit(
|
||||
PagingData.from(
|
||||
|
|
@ -176,6 +175,7 @@ private fun Preview_TokensListScreen_Read() {
|
|||
onBackButtonClick = {},
|
||||
onSearchButtonClick = {},
|
||||
),
|
||||
isLoading = false,
|
||||
tokens = flow {
|
||||
emit(
|
||||
PagingData.from(
|
||||
|
|
|
|||
|
|
@ -13,12 +13,9 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
|
|||
import com.tangem.core.ui.extensions.getActiveIconRes
|
||||
import com.tangem.domain.common.extensions.canHandleToken
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchNotification
|
||||
import com.tangem.tap.common.extensions.fullNameWithoutTestnet
|
||||
import com.tangem.tap.common.extensions.getGreyedOutIconRes
|
||||
import com.tangem.tap.common.extensions.getNetworkName
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.features.tokens.domain.TokensListInteractor
|
||||
import com.tangem.tap.features.tokens.domain.models.Token
|
||||
import com.tangem.tap.features.tokens.domain.models.Token.Network
|
||||
|
|
@ -29,10 +26,7 @@ import com.tangem.tap.features.tokens.presentation.states.TokenItemState
|
|||
import com.tangem.tap.features.tokens.presentation.states.TokensListStateHolder
|
||||
import com.tangem.tap.features.tokens.presentation.states.TokensListToolbarState
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.Debouncer
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -40,7 +34,9 @@ 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 kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
import com.tangem.blockchain.common.Token as BlockchainToken
|
||||
|
||||
|
|
@ -72,15 +68,28 @@ internal class TokensListViewModel @Inject constructor(
|
|||
var uiState by mutableStateOf(value = getInitialUiState())
|
||||
private set
|
||||
|
||||
private val addedTokenList: MutableList<TokenWithBlockchain> = args.mainScreenTokenList.toMutableList()
|
||||
private val addedBlockchainList: MutableList<Blockchain> = args.mainScreenBlockchainList.toMutableList()
|
||||
private val changedTokensList: MutableList<TokenWithBlockchain> = args.mainScreenTokenList.toMutableList()
|
||||
private val changedBlockchainList: MutableList<Blockchain> = args.mainScreenBlockchainList.toMutableList()
|
||||
|
||||
private var saveButtonPressed = false
|
||||
|
||||
private fun getInitialUiState(): TokensListStateHolder {
|
||||
return TokensListStateHolder.Loading(
|
||||
toolbarState = getInitialToolbarState(),
|
||||
tokens = getInitialTokensList(),
|
||||
onTokensLoadStateChanged = actionsHandler::onTokensLoadStateChanged,
|
||||
)
|
||||
return if (args.isManageAccess) {
|
||||
TokensListStateHolder.ManageContent(
|
||||
toolbarState = getInitialToolbarState(),
|
||||
isLoading = true,
|
||||
tokens = getInitialTokensList(),
|
||||
onTokensLoadStateChanged = actionsHandler::onTokensLoadStateChanged,
|
||||
onSaveButtonClick = actionsHandler::onSaveButtonClick,
|
||||
)
|
||||
} else {
|
||||
TokensListStateHolder.ReadContent(
|
||||
toolbarState = getInitialToolbarState(),
|
||||
isLoading = true,
|
||||
tokens = getInitialTokensList(),
|
||||
onTokensLoadStateChanged = actionsHandler::onTokensLoadStateChanged,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInitialToolbarState(): TokensListToolbarState {
|
||||
|
|
@ -101,6 +110,8 @@ internal class TokensListViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun getInitialTokensList(searchText: String = ""): Flow<PagingData<TokenItemState>> {
|
||||
if (searchText.isNotEmpty()) analyticsSender.sendWhenTokenSearched()
|
||||
|
||||
return interactor.getTokensList(searchText = searchText).map {
|
||||
it.map { token ->
|
||||
if (args.isManageAccess) createManageTokenContent(token) else createReadTokenContent(token)
|
||||
|
|
@ -177,11 +188,11 @@ internal class TokensListViewModel @Inject constructor(
|
|||
|
||||
private fun isAdded(address: String?, blockchain: Blockchain?): Boolean {
|
||||
return if (address != null) {
|
||||
addedTokenList.any { addedToken ->
|
||||
changedTokensList.any { addedToken ->
|
||||
address == addedToken.token.contractAddress && blockchain == addedToken.blockchain
|
||||
}
|
||||
} else {
|
||||
addedBlockchainList.contains(blockchain)
|
||||
changedBlockchainList.contains(blockchain)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -221,38 +232,22 @@ internal class TokensListViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
// FIXME("Necessary to avoid using redux actions")
|
||||
fun onNetworkClick() {
|
||||
store.dispatchNotification(R.string.contract_address_copied_message)
|
||||
router.showAddressCopiedNotification()
|
||||
}
|
||||
|
||||
fun onTokensLoadStateChanged(state: LoadState) {
|
||||
uiState = when (state) {
|
||||
is LoadState.NotLoading -> {
|
||||
analyticsSender.sendWhenTokenSearched()
|
||||
uiState = uiState.copySealed(isLoading = state is LoadState.Loading || saveButtonPressed)
|
||||
if (saveButtonPressed) saveButtonPressed = false
|
||||
}
|
||||
|
||||
if (args.isManageAccess) {
|
||||
TokensListStateHolder.ManageContent(
|
||||
toolbarState = uiState.toolbarState,
|
||||
tokens = uiState.tokens,
|
||||
onTokensLoadStateChanged = uiState.onTokensLoadStateChanged,
|
||||
onSaveButtonClick = this::onSaveButtonClick,
|
||||
)
|
||||
} else {
|
||||
TokensListStateHolder.ReadContent(
|
||||
toolbarState = uiState.toolbarState,
|
||||
tokens = uiState.tokens,
|
||||
onTokensLoadStateChanged = uiState.onTokensLoadStateChanged,
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
TokensListStateHolder.Loading(
|
||||
toolbarState = uiState.toolbarState,
|
||||
tokens = uiState.tokens,
|
||||
onTokensLoadStateChanged = uiState.onTokensLoadStateChanged,
|
||||
)
|
||||
}
|
||||
fun onSaveButtonClick() {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
saveButtonPressed = true
|
||||
analyticsSender.sendWhenSaveButtonClicked()
|
||||
uiState = uiState.copySealed(isLoading = true)
|
||||
withContext(dispatchers.io) { interactor.saveChanges(changedTokensList, changedBlockchainList) }
|
||||
router.popBackStack()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -276,38 +271,34 @@ internal class TokensListViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun updateBlockchainItem(toggledNetwork: NetworkItemState.ManageContent, blockchain: Blockchain) {
|
||||
val isRemoveAction = addedBlockchainList.contains(blockchain)
|
||||
val isRemoveAction = changedBlockchainList.contains(blockchain)
|
||||
|
||||
if (isRemoveAction) {
|
||||
val isTokenWithSameBlockchainFound = addedTokenList.any { it.blockchain == blockchain }
|
||||
val isTokenWithSameBlockchainFound = changedTokensList.any { it.blockchain == blockchain }
|
||||
val isAddedOnMainScreen = args.mainScreenBlockchainList.contains(blockchain)
|
||||
|
||||
if (isTokenWithSameBlockchainFound) {
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.TokensAreLinkedDialog(
|
||||
currencyTitle = blockchain.name,
|
||||
currencySymbol = blockchain.currency,
|
||||
),
|
||||
router.openUnableHideMainTokenAlert(
|
||||
tokenName = blockchain.name,
|
||||
tokenSymbol = blockchain.currency,
|
||||
)
|
||||
} else if (isAddedOnMainScreen) {
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.RemoveWalletDialog(
|
||||
currencyTitle = blockchain.name,
|
||||
onOk = {
|
||||
analyticsSender.sendWhenBlockchainAdded(blockchain)
|
||||
addedBlockchainList.remove(blockchain)
|
||||
toggledNetwork.changeToggleState()
|
||||
},
|
||||
),
|
||||
router.openRemoveWalletAlert(
|
||||
tokenName = blockchain.name,
|
||||
onOkClick = {
|
||||
analyticsSender.sendWhenBlockchainAdded(blockchain)
|
||||
changedBlockchainList.remove(blockchain)
|
||||
toggledNetwork.changeToggleState()
|
||||
},
|
||||
)
|
||||
} else {
|
||||
analyticsSender.sendWhenBlockchainRemoved(blockchain)
|
||||
addedBlockchainList.remove(blockchain)
|
||||
changedBlockchainList.remove(blockchain)
|
||||
toggledNetwork.changeToggleState()
|
||||
}
|
||||
} else {
|
||||
analyticsSender.sendWhenBlockchainAdded(blockchain)
|
||||
addedBlockchainList.add(blockchain)
|
||||
changedBlockchainList.add(blockchain)
|
||||
toggledNetwork.changeToggleState()
|
||||
}
|
||||
}
|
||||
|
|
@ -327,25 +318,23 @@ internal class TokensListViewModel @Inject constructor(
|
|||
blockchain = toggledNetwork.blockchain,
|
||||
)
|
||||
|
||||
val isRemoveAction = addedTokenList.contains(token)
|
||||
val isRemoveAction = changedTokensList.contains(token)
|
||||
|
||||
if (isRemoveAction) {
|
||||
val isAddedOnMainScreen = args.mainScreenTokenList.contains(token)
|
||||
|
||||
if (isAddedOnMainScreen) {
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.RemoveWalletDialog(
|
||||
currencyTitle = token.token.name,
|
||||
onOk = {
|
||||
analyticsSender.sendWhenTokenRemoved(token.token)
|
||||
addedTokenList.remove(token)
|
||||
toggledNetwork.changeToggleState()
|
||||
},
|
||||
),
|
||||
router.openRemoveWalletAlert(
|
||||
tokenName = token.token.name,
|
||||
onOkClick = {
|
||||
analyticsSender.sendWhenTokenRemoved(token.token)
|
||||
changedTokensList.remove(token)
|
||||
toggledNetwork.changeToggleState()
|
||||
},
|
||||
)
|
||||
} else {
|
||||
analyticsSender.sendWhenTokenRemoved(token.token)
|
||||
addedTokenList.remove(token)
|
||||
changedTokensList.remove(token)
|
||||
toggledNetwork.changeToggleState()
|
||||
}
|
||||
} else {
|
||||
|
|
@ -353,24 +342,14 @@ internal class TokensListViewModel @Inject constructor(
|
|||
!(reduxStateHolder.scanResponse?.card?.canHandleToken(token.blockchain) ?: false)
|
||||
|
||||
if (isUnsupportedToken) {
|
||||
store.dispatchDialogShow(
|
||||
AppDialog.SimpleOkDialogRes(
|
||||
headerId = R.string.common_warning,
|
||||
messageId = R.string.alert_manage_tokens_unsupported_message,
|
||||
),
|
||||
)
|
||||
router.openUnsupportedSoltanaNetworkAlert()
|
||||
} else {
|
||||
analyticsSender.sendWhenTokenAdded(token.token)
|
||||
addedTokenList.add(token)
|
||||
changedTokensList.add(token)
|
||||
toggledNetwork.changeToggleState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSaveButtonClick() {
|
||||
analyticsSender.sendWhenSaveButtonClicked()
|
||||
store.dispatch(TokensAction.SaveChanges(addedTokenList, addedBlockchainList))
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ class TokensMiddleware {
|
|||
return DerivationData(derivations = mapKeyOfWalletPublicKey to toDerive)
|
||||
}
|
||||
|
||||
private class DerivationData(val derivations: Pair<ByteArrayKey, List<DerivationPath>>)
|
||||
class DerivationData(val derivations: Pair<ByteArrayKey, List<DerivationPath>>)
|
||||
|
||||
private fun submitAdd(scanResponse: ScanResponse, currencyList: List<Currency>) {
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ kotlinSerialization = "1.4.1"
|
|||
# endregion Other libraries
|
||||
|
||||
# region Tangem
|
||||
tangemBlockchainSdk = "develop-197"
|
||||
tangemBlockchainSdk = "develop-198"
|
||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "develop-213"
|
||||
# tangemCardSdk = "0.0.1" # Keep it! - used for local builds
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue