From 8a1239f014dc46d284ed05296a52ca0c2fd7adf3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 14 Apr 2023 20:47:58 +0900 Subject: [PATCH] Updated on 2026-08-14 --- .../tokens/di/TokensListInteractorModule.kt | 1 + .../domain/DefaultTokensListInteractor.kt | 232 +++++++++++++++++- .../tokens/domain/TokensListInteractor.kt | 10 + .../router/DefaultTokensListRouter.kt | 31 +++ .../presentation/router/TokensListRouter.kt | 24 +- .../states/TokensListStateHolder.kt | 25 +- .../presentation/ui/TokensListScreen.kt | 12 +- .../viewmodels/TokensListViewModel.kt | 145 +++++------ .../features/tokens/redux/TokensMiddleware.kt | 2 +- gradle/dependencies.toml | 2 +- 10 files changed, 374 insertions(+), 110 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/di/TokensListInteractorModule.kt b/app/src/main/java/com/tangem/tap/features/tokens/di/TokensListInteractorModule.kt index 8d1f10d327..c27bc5f8e0 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/di/TokensListInteractorModule.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/di/TokensListInteractorModule.kt @@ -35,6 +35,7 @@ internal object TokensListInteractorModule { reduxStateHolder = reduxStateHolder, testnetTokensStorage = testnetTokensStorage, ), + reduxStateHolder = reduxStateHolder, ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/domain/DefaultTokensListInteractor.kt b/app/src/main/java/com/tangem/tap/features/tokens/domain/DefaultTokensListInteractor.kt index 3391af49a2..5012ccebc5 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/domain/DefaultTokensListInteractor.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/domain/DefaultTokensListInteractor.kt @@ -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> { return repository.getAvailableTokens(searchText = searchText.ifBlank(defaultValue = { null })) } + + override suspend fun saveChanges(tokens: List, blockchains: List) { + 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.toNonCustomTokensWithBlockchains( + style: DerivationStyle?, + ): List { + 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.toNonCustomBlockchains(derivationStyle: DerivationStyle?): List { + 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, + blockchains: List, + 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, + blockchains: List, + 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) { + 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, + ): 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) { + 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, + blockchains: List, + derivationStyle: DerivationStyle?, + ): List { + 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, + ) + }, + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/domain/TokensListInteractor.kt b/app/src/main/java/com/tangem/tap/features/tokens/domain/TokensListInteractor.kt index 1fab924630..6ffe49b885 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/domain/TokensListInteractor.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/domain/TokensListInteractor.kt @@ -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> + + /** + * Save added tokens + * + * @param tokens tokens list that need to save + * @param blockchains blockchains list that need to save + */ + suspend fun saveChanges(tokens: List, blockchains: List) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/presentation/router/DefaultTokensListRouter.kt b/app/src/main/java/com/tangem/tap/features/tokens/presentation/router/DefaultTokensListRouter.kt index a016e91a00..f266e0805f 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/presentation/router/DefaultTokensListRouter.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/presentation/router/DefaultTokensListRouter.kt @@ -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, + ), + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/presentation/router/TokensListRouter.kt b/app/src/main/java/com/tangem/tap/features/tokens/presentation/router/TokensListRouter.kt index 1ed3a068dc..ccc7ed37cb 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/presentation/router/TokensListRouter.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/presentation/router/TokensListRouter.kt @@ -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() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/presentation/states/TokensListStateHolder.kt b/app/src/main/java/com/tangem/tap/features/tokens/presentation/states/TokensListStateHolder.kt index c4049ddfcc..a7368f383d 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/presentation/states/TokensListStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/presentation/states/TokensListStateHolder.kt @@ -14,6 +14,9 @@ internal sealed interface TokensListStateHolder { /** Toolbar state */ val toolbarState: TokensListToolbarState + /** Loading state */ + val isLoading: Boolean + /** Tokens list */ val tokens: Flow> @@ -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> = 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>, - 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>, 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>, override val onTokensLoadStateChanged: (LoadState) -> Unit, val onSaveButtonClick: () -> Unit, diff --git a/app/src/main/java/com/tangem/tap/features/tokens/presentation/ui/TokensListScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/presentation/ui/TokensListScreen.kt index 977e5705bf..57f720ee1c 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/presentation/ui/TokensListScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/presentation/ui/TokensListScreen.kt @@ -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( diff --git a/app/src/main/java/com/tangem/tap/features/tokens/presentation/viewmodels/TokensListViewModel.kt b/app/src/main/java/com/tangem/tap/features/tokens/presentation/viewmodels/TokensListViewModel.kt index d09d68b5d5..6917f88b1e 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/presentation/viewmodels/TokensListViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/presentation/viewmodels/TokensListViewModel.kt @@ -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 = args.mainScreenTokenList.toMutableList() - private val addedBlockchainList: MutableList = args.mainScreenBlockchainList.toMutableList() + private val changedTokensList: MutableList = args.mainScreenTokenList.toMutableList() + private val changedBlockchainList: MutableList = 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> { + 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 { diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index 7a3c9a9d17..e275b0c072 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -252,7 +252,7 @@ class TokensMiddleware { return DerivationData(derivations = mapKeyOfWalletPublicKey to toDerive) } - private class DerivationData(val derivations: Pair>) + class DerivationData(val derivations: Pair>) private fun submitAdd(scanResponse: ScanResponse, currencyList: List) { val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard { diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index d134968349..33a5dcf0a4 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -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