From dff86fd4dd6f38a37d5f3638a65742185fc7d212 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 17 Apr 2022 16:24:34 +0300 Subject: [PATCH 1/8] Updated on 2026-08-14 --- .../wallet/redux/middlewares/MultiWalletMiddleware.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 5a0dc52f6c..b11daff69d 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.wallet.redux.middlewares +import com.tangem.blockchain.blockchains.solana.SolanaWalletManager import com.tangem.blockchain.common.* import com.tangem.blockchain.extensions.Result import com.tangem.common.extensions.isZero @@ -235,7 +236,9 @@ class MultiWalletMiddleware { token, blockchainNetwork.blockchain, blockchainNetwork.derivationPath ) })) - walletManager.addTokens(tokens) + // we can't add Solana token's - they are not supported now + if (walletManager !is SolanaWalletManager) walletManager.addTokens(tokens) + currenciesRepository.saveUpdatedCurrency( cardId = scanResponse.card.cardId, blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager) From 5d29cfb18122999649154dbae20958ac88b7ddab Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 17 Apr 2022 17:15:20 +0300 Subject: [PATCH 2/8] Updated on 2026-08-14 --- .../common/redux/global/GlobalMidlleware.kt | 4 ---- .../tap/common/redux/global/GlobalReducer.kt | 6 +++++- .../addCustomToken/compose/DebugActions.kt | 4 ++-- ...iceManager.kt => AddCustomTokenService.kt} | 19 +++---------------- .../addCustomToken/redux/AddCustomTokenHub.kt | 5 +---- .../redux/AddCustomTokenState.kt | 2 +- .../domain/redux/global/DomainGlobalAction.kt | 2 +- .../domain/redux/global/DomainGlobalHub.kt | 8 +++++++- .../domain/redux/global/DomainGlobalState.kt | 9 ++++++++- .../com/tangem/network/common/Interceptors.kt | 4 ++++ 10 files changed, 32 insertions(+), 31 deletions(-) rename domain/src/main/java/com/tangem/domain/features/addCustomToken/{TangemTechServiceManager.kt => AddCustomTokenService.kt} (78%) diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt index 7e1cb3f0cc..722fe8a067 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt @@ -4,8 +4,6 @@ import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.ifNotNull import com.tangem.domain.common.extensions.withMainContext -import com.tangem.domain.redux.domainStore -import com.tangem.domain.redux.global.DomainGlobalAction import com.tangem.tap.* import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain @@ -102,12 +100,10 @@ private val globalMiddlewareHandler: Middleware = { dispatch, appState store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) when (result) { is CompletionResult.Success -> { - domainStore.dispatch(DomainGlobalAction.SetScanResponse(result.data)) tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data) action.onSuccess?.invoke(result.data) } is CompletionResult.Failure -> { - domainStore.dispatch(DomainGlobalAction.SetScanResponse(null)) action.onFailure?.invoke(result.error) } } diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index d84c43379d..67e54a6042 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -1,5 +1,7 @@ package com.tangem.tap.common.redux.global +import com.tangem.domain.redux.domainStore +import com.tangem.domain.redux.global.DomainGlobalAction import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.onboarding.OnboardingManager import com.tangem.tap.preferencesStorage @@ -33,8 +35,10 @@ fun globalReducer(action: Action, state: AppState): GlobalState { is GlobalAction.ScanFailsCounter.Reset -> { globalState.copy(scanCardFailsCounter = 0) } - is GlobalAction.SaveScanNoteResponse -> + is GlobalAction.SaveScanNoteResponse ->{ + domainStore.dispatch(DomainGlobalAction.SaveScanNoteResponse(action.scanResponse)) globalState.copy(scanResponse = action.scanResponse) + } is GlobalAction.ChangeAppCurrency -> { globalState.copy(appCurrency = action.appCurrency) } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/DebugActions.kt b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/DebugActions.kt index 0db334c34b..c9b231783d 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/DebugActions.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/DebugActions.kt @@ -14,7 +14,7 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.VoidCallback import com.tangem.common.services.Result import com.tangem.domain.common.form.Field -import com.tangem.domain.features.addCustomToken.TangemTechServiceManager +import com.tangem.domain.features.addCustomToken.AddCustomTokenService import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction import com.tangem.domain.redux.domainStore import com.tangem.network.api.tangemTech.TangemTechService @@ -147,7 +147,7 @@ private fun CustomActions() { CustomActionButton( name = "Find tokens in several networks", action = { - val manager = TangemTechServiceManager(TangemTechService()) + val manager = AddCustomTokenService(TangemTechService()) val currencies = manager.tokens() val asdfsd = mutableMapOf>() val contractAddresses = currencies.mapNotNull { currency -> diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/TangemTechServiceManager.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt similarity index 78% rename from domain/src/main/java/com/tangem/domain/features/addCustomToken/TangemTechServiceManager.kt rename to domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt index 438e75e47a..464dec0abd 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/TangemTechServiceManager.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt @@ -3,12 +3,11 @@ package com.tangem.domain.features.addCustomToken import com.tangem.common.services.Result import com.tangem.network.api.tangemTech.Coins import com.tangem.network.api.tangemTech.TangemTechService -import com.tangem.network.common.AddHeaderInterceptor /** [REDACTED_AUTHOR] */ -class TangemTechServiceManager( +class AddCustomTokenService( private val tangemTechService: TangemTechService ) { @@ -44,21 +43,9 @@ class TangemTechServiceManager( return when (val result = tangemTechService.coins.tokens()) { is Result.Success -> { val tokens = result.data.tokens - tokens.filter { - it.contracts.isNullOrEmpty() - } + tokens.filter { it.contracts.isNullOrEmpty() } } is Result.Failure -> emptyList() } } - - fun attachAuthKey(authKey: String) { - tangemTechService.addHeaderInterceptors(listOf( - CardPublicKeyHttpInterceptor(authKey), - )) - } -} - -private class CardPublicKeyHttpInterceptor(cardPublicKeyHex: String) : AddHeaderInterceptor(mapOf( - "card_public_key" to cardPublicKeyHex, -)) \ No newline at end of file +} \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt index a127b43776..cfb696268a 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt @@ -4,7 +4,6 @@ import android.webkit.ValueCallback import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationStyle import com.tangem.common.extensions.guard -import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result import com.tangem.domain.DomainDialog import com.tangem.domain.DomainException @@ -22,7 +21,6 @@ import com.tangem.domain.redux.dispatchOnMain import com.tangem.domain.redux.domainStore import com.tangem.domain.redux.global.DomainGlobalAction import com.tangem.network.api.tangemTech.Coins -import com.tangem.network.api.tangemTech.TangemTechService import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -476,8 +474,7 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT } is OnCreate -> { val card = requireNotNull(globalState.scanResponse?.card) - val tangemTechServiceManager = TangemTechServiceManager(TangemTechService()) - tangemTechServiceManager.attachAuthKey(card.cardPublicKey.toHexString()) + val tangemTechServiceManager = AddCustomTokenService(globalState.networkServices.tangemTechService) var derivationPathState = state.screenState.derivationPath derivationPathState = when (card.derivationStyle) { diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt index d8f43f4e00..1a96e9a844 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt @@ -18,7 +18,7 @@ data class AddCustomTokenState( val tokenId: String? = null, val warnings: Set = emptySet(), val screenState: ScreenState = createInitialScreenState(), - val tangemTechServiceManager: TangemTechServiceManager? = null + val tangemTechServiceManager: AddCustomTokenService? = null ) : StateType { inline fun getField(id: FieldId): T = form.getField(id) as T diff --git a/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalAction.kt b/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalAction.kt index a01a16315a..d58d26a864 100644 --- a/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalAction.kt +++ b/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalAction.kt @@ -9,6 +9,6 @@ import org.rekotlin.Action */ //TODO: refactoring: is alias for the GlobalAction sealed class DomainGlobalAction : Action { - data class SetScanResponse(val scanResponse: ScanResponse?) : DomainGlobalAction() + data class SaveScanNoteResponse(val scanResponse: ScanResponse) : DomainGlobalAction() data class ShowDialog(val stateDialog: DomainDialog?) : DomainGlobalAction() } \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalHub.kt b/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalHub.kt index 494dc41e7e..92fc243edd 100644 --- a/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalHub.kt +++ b/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalHub.kt @@ -1,8 +1,10 @@ package com.tangem.domain.redux.global import android.webkit.ValueCallback +import com.tangem.common.extensions.toHexString import com.tangem.domain.redux.BaseStoreHub import com.tangem.domain.redux.DomainState +import com.tangem.network.common.CardPublicKeyHttpInterceptor import org.rekotlin.Action /** @@ -33,7 +35,11 @@ internal class DomainGlobalHub : BaseStoreHub("DomainGlobalHu } override fun reduceAction(action: Action, state: DomainGlobalState): DomainGlobalState = when (action) { - is DomainGlobalAction.SetScanResponse -> { + is DomainGlobalAction.SaveScanNoteResponse -> { + val cardPublicKeyHex = action.scanResponse.card.cardPublicKey.toHexString() + state.networkServices.tangemTechService.addHeaderInterceptors( + listOf(CardPublicKeyHttpInterceptor(cardPublicKeyHex)) + ) state.copy(scanResponse = action.scanResponse) } is DomainGlobalAction.ShowDialog -> { diff --git a/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalState.kt b/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalState.kt index 9bf5d7c15e..c51219beab 100644 --- a/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalState.kt +++ b/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalState.kt @@ -2,12 +2,19 @@ package com.tangem.domain.redux.global import com.tangem.domain.DomainDialog import com.tangem.domain.common.ScanResponse +import com.tangem.network.api.tangemTech.TangemTechService /** [REDACTED_AUTHOR] */ -//TODO: refactoring: is alias for the GlobalState data class DomainGlobalState( + // there is a part of mirrors from the GlobalState val scanResponse: ScanResponse? = null, + // + val networkServices: NetworkServices = NetworkServices(), val dialog: DomainDialog? = null, ) + +data class NetworkServices( + val tangemTechService: TangemTechService = TangemTechService() +) diff --git a/network/src/main/java/com/tangem/network/common/Interceptors.kt b/network/src/main/java/com/tangem/network/common/Interceptors.kt index dff259332e..bc46e5a0a0 100644 --- a/network/src/main/java/com/tangem/network/common/Interceptors.kt +++ b/network/src/main/java/com/tangem/network/common/Interceptors.kt @@ -23,4 +23,8 @@ open class AddHeaderInterceptor( class CacheControlHttpInterceptor(maxAgeSeconds: Int) : AddHeaderInterceptor(mapOf( "Cache-Control" to "max-age=$maxAgeSeconds", +)) + +class CardPublicKeyHttpInterceptor(cardPublicKeyHex: String) : AddHeaderInterceptor(mapOf( + "card_public_key" to cardPublicKeyHex, )) \ No newline at end of file From 797d11bd8c2d910655b4d3c5bc8b5d0acee2dce9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Apr 2022 15:35:49 +0400 Subject: [PATCH 3/8] Updated on 2026-08-14 --- .../com/tangem/tap/domain/tokens/Currency.kt | 26 +++++++++++++++++-- .../domain/common/extensions/Blockchain.kt | 4 +-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/Currency.kt b/app/src/main/java/com/tangem/tap/domain/tokens/Currency.kt index 801a4f263e..3bd2e57ef5 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/Currency.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/Currency.kt @@ -35,7 +35,7 @@ data class Currency( val name: String, val symbol: String, val iconUrl: String, - val contracts: List? + val contracts: List ) { companion object { @@ -45,9 +45,19 @@ data class Currency( name = currency.name, symbol = currency.symbol, iconUrl = getIconUrl(currency.id), - contracts = currency.contracts?.toContracts(isTestNet) + contracts = prepareListOfContracts(currency.contracts, currency.id, isTestNet) ) } + + private fun prepareListOfContracts( + contractsFromJson: List?, + currencyId: String, + isTestNet: Boolean + ): List { + val mainNetwork = Contract.fromCurrencyId(currencyId, isTestNet) + val contracts = contractsFromJson?.toContracts(isTestNet) ?: emptyList() + return (listOfNotNull(mainNetwork) + contracts).distinct() + } } } @@ -72,6 +82,18 @@ data class Contract( ) } + fun fromCurrencyId(currencyId: String, isTestNet: Boolean): Contract? { + val networkId = if (isTestNet) currencyId + TESTNET else currencyId + val blockchain = Blockchain.fromNetworkId(networkId) ?: return null + return Contract( + networkId = networkId, + blockchain = blockchain, + address = blockchain.currency, + decimalCount = blockchain.decimals(), + iconUrl = getIconUrl(networkId) + ) + } + const val TESTNET = "-testnet" } } diff --git a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index 5573c8a4f0..54def43015 100644 --- a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -4,8 +4,8 @@ import com.tangem.blockchain.common.Blockchain fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { return when (networkId) { - "avalanche" -> Blockchain.Avalanche - "avalanche-testnet" -> Blockchain.AvalancheTestnet + "avalanche", "avalanche-2" -> Blockchain.Avalanche + "avalanche-testnet", "avalanche-2-testnet" -> Blockchain.AvalancheTestnet "binancecoin" -> Blockchain.Binance "binancecoin-testnet" -> Blockchain.BinanceTestnet "binance-smart-chain" -> Blockchain.BSC From 99b6c9a5335bd7f991deea074d672c0644cdbdd4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Apr 2022 15:36:59 +0400 Subject: [PATCH 4/8] Updated on 2026-08-14 --- .../features/tokens/ui/compose/CollapsedCurrencyItem.kt | 2 +- .../features/tokens/ui/compose/ExpandedCurrencyItem.kt | 9 +++------ .../tap/features/tokens/ui/compose/ListOfCurrencies.kt | 5 ++++- .../tangem/tap/features/tokens/ui/compose/NetworkItem.kt | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CollapsedCurrencyItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CollapsedCurrencyItem.kt index 21d2de3afd..e4b3810766 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CollapsedCurrencyItem.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CollapsedCurrencyItem.kt @@ -59,7 +59,7 @@ fun CollapsedCurrencyItem( .align(Alignment.CenterVertically) ) { Text( - text = currency.name, + text = currency.fullName, fontSize = 17.sp, fontWeight = FontWeight.Normal, color = Color(0xFF1C1C1E), diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/ExpandedCurrencyItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/ExpandedCurrencyItem.kt index 181bb64187..334e0fb4fb 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/ExpandedCurrencyItem.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/ExpandedCurrencyItem.kt @@ -20,7 +20,6 @@ import androidx.compose.ui.unit.sp import coil.compose.SubcomposeAsyncImage import coil.request.ImageRequest import com.tangem.blockchain.common.Blockchain -import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.tap.domain.tokens.Currency import com.tangem.tap.features.tokens.redux.ContractAddress import com.tangem.tap.features.tokens.redux.TokenWithBlockchain @@ -65,7 +64,7 @@ fun ExpandedCurrencyItem( .align(Alignment.CenterVertically) ) { Text( - text = currency.name, + text = currency.fullName, fontSize = 17.sp, fontWeight = FontWeight.Normal, color = Color(0xFF1C1C1E), @@ -87,9 +86,7 @@ fun ExpandedCurrencyItem( ) } - val blockchains = currency.contracts?.map { it.blockchain } ?: listOfNotNull( - Blockchain.fromNetworkId(currency.id) - ) + val blockchains = currency.contracts.map { it.blockchain } Row { Box( @@ -134,7 +131,7 @@ fun ExpandedCurrencyItem( .padding(top = 6.dp), ) { blockchains.map { blockchain -> - val contract = currency.contracts?.firstOrNull { it.blockchain == blockchain } + val contract = currency.contracts.firstOrNull { it.blockchain == blockchain } val added = if (contract != null && contract.address != currency.symbol) { addedTokens.map { it.token.contractAddress }.contains(contract.address) } else { diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/ListOfCurrencies.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/ListOfCurrencies.kt index d435e6e785..0e7c4e2acf 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/ListOfCurrencies.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/ListOfCurrencies.kt @@ -66,4 +66,7 @@ fun ListOfCurrencies( } } -} \ No newline at end of file +} + +val Currency.fullName: String + get() = "${this.name} (${this.symbol})" \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/NetworkItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/NetworkItem.kt index 50618dcb83..bdb5107ae2 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/NetworkItem.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/NetworkItem.kt @@ -46,7 +46,7 @@ fun NetworkItem( .combinedClickable( enabled = allowToAdd, onLongClick = { - if (contract != null) onNetworkItemClicked(contract.address) + if (!isBlockchain) onNetworkItemClicked(contract!!.address) }, onClick = {}, indication = null, From 904c10fae59fdc498544b0f72241742ba0c4cc7e Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Apr 2022 15:39:25 +0400 Subject: [PATCH 5/8] Updated on 2026-08-14 --- .../tap/common/extensions/Blockchain.kt | 2 + .../com/tangem/tap/domain/TapWalletManager.kt | 6 +- .../features/tokens/redux/TokensMiddleware.kt | 66 ++++++++++++------- .../features/tokens/redux/TokensReducer.kt | 6 +- .../tap/features/tokens/redux/TokensState.kt | 12 ++-- .../tap/features/wallet/redux/WalletState.kt | 29 +------- .../middlewares/MultiWalletMiddleware.kt | 3 +- .../middlewares/TradeCryptoMiddleware.kt | 2 +- .../redux/reducers/MultiWalletReducer.kt | 14 ++-- .../redux/reducers/OnWalletLoadedReducer.kt | 3 +- .../wallet/redux/reducers/WalletReducer.kt | 23 +++++-- .../wallet/ui/adapters/WalletAdapter.kt | 1 + .../main/res/layout/item_currency_wallet.xml | 4 ++ 13 files changed, 91 insertions(+), 80 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt b/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt index a34f96c1b0..fd51dd7e45 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt @@ -22,6 +22,7 @@ fun Blockchain.getRoundIconRes(): Int { Blockchain.Solana, Blockchain.SolanaTestnet -> R.drawable.ic_solana_round Blockchain.Fantom, Blockchain.FantomTestnet -> R.drawable.ic_fantom_round Blockchain.BSC, Blockchain.BSCTestnet, Blockchain.Binance, Blockchain.BinanceTestnet -> R.drawable.ic_bsc_round + Blockchain.Dogecoin -> R.drawable.ic_dogecoin_round else -> R.drawable.ic_tangem_logo } } @@ -44,6 +45,7 @@ fun Blockchain.getGreyedOutIconRes(): Int { Blockchain.Solana, Blockchain.SolanaTestnet -> R.drawable.ic_solana_no_color Blockchain.Fantom, Blockchain.FantomTestnet -> R.drawable.ic_fantom_no_color Blockchain.BSC, Blockchain.BSCTestnet, Blockchain.Binance, Blockchain.BinanceTestnet -> R.drawable.ic_bsc_no_color + Blockchain.Dogecoin -> R.drawable.ic_dogecoin_no_color else -> R.drawable.ic_tangem_logo } } diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index c461b5f57b..4302aa1d35 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -231,7 +231,7 @@ class TapWalletManager { val walletManagers = if ( primaryTokens.isNotEmpty() && primaryWalletManager != null && - primaryBlockchain != null + primaryBlockchain != null && primaryBlockchain != Blockchain.Unknown ) { val blockchainsWithoutPrimary = savedCurrencies.filterNot { it.blockchain == primaryBlockchain } walletManagerFactory.makeWalletManagersForApp( @@ -245,7 +245,9 @@ class TapWalletManager { WalletAction.MultiWallet.AddBlockchains(savedCurrencies, walletManagers), ) savedCurrencies.map { - dispatchOnMain(WalletAction.MultiWallet.AddTokens(it.tokens, it)) + if (it.tokens.isNotEmpty()) { + dispatchOnMain(WalletAction.MultiWallet.AddTokens(it.tokens, it)) + } } } } 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 eb09ad2eb1..96ed135524 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 @@ -2,7 +2,7 @@ package com.tangem.tap.features.tokens.redux import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationParams -import com.tangem.blockchain.common.Token +import com.tangem.blockchain.common.DerivationStyle import com.tangem.common.CompletionResult import com.tangem.common.card.EllipticCurve import com.tangem.common.extensions.ByteArrayKey @@ -68,19 +68,27 @@ class TokensMiddleware { private fun handleSaveChanges(action: TokensAction.SaveChanges) { val scanResponse = store.state.globalState.scanResponse ?: return - //TODO: bad things happens. - val currentTokens = store.state.tokensState.addedWallets.toTokens() - val currentBlockchains = store.state.tokensState.addedWallets.toBlockchains( - store.state.tokensState.derivationStyle + val currentTokens = store.state.tokensState.addedWallets.toNonCustomTokensWithBlockchains( + scanResponse.card.derivationStyle + ) + val currentBlockchains = store.state.tokensState.addedWallets.toNonCustomBlockchains( + scanResponse.card.derivationStyle ) val blockchainsToAdd = action.addedBlockchains.filter { !currentBlockchains.contains(it) } val blockchainsToRemove = currentBlockchains.filter { !action.addedBlockchains.contains(it) } - val tokensToAdd = action.addedTokens.filter { !currentTokens.contains(it.token) } - val tokensToRemove = currentTokens.filter { token -> !action.addedTokens.any { it.token == token } } + val tokensToAdd = action.addedTokens.filter { !currentTokens.contains(it) } + val tokensToRemove = currentTokens.filter { + token -> !action.addedTokens.any { it.token == token.token } + } + val derivationStyle = scanResponse.card.derivationStyle - removeCurrenciesIfNeeded(blockchainsToRemove, tokensToRemove) + removeCurrenciesIfNeeded(convertToCurrencies( + blockchains = blockchainsToRemove, + tokens = tokensToRemove, + derivationStyle = derivationStyle + )) if (tokensToAdd.isEmpty() && blockchainsToAdd.isEmpty()) { store.dispatchDebugErrorNotification("Nothing to save") @@ -88,12 +96,11 @@ class TokensMiddleware { return } - val derivationStyle = scanResponse.card.derivationStyle - val currencyList = blockchainsToAdd.map { - Currency.Blockchain(it, it.derivationPath(derivationStyle)?.rawPath) - } + tokensToAdd.map { - Currency.Token(it.token, it.blockchain, it.blockchain.derivationPath(derivationStyle)?.rawPath) - } + val currencyList = convertToCurrencies( + blockchains = blockchainsToAdd, + tokens = tokensToAdd, + derivationStyle = derivationStyle + ) if (scanResponse.supportsHdWallet()) { deriveMissingBlockchains(scanResponse, currencyList) { submitAdd(it, currencyList) @@ -105,6 +112,22 @@ class TokensMiddleware { } } + private fun convertToCurrencies( + blockchains: List, + tokens: List, + derivationStyle: DerivationStyle? + ): List { + return blockchains.map { + Currency.Blockchain(it, it.derivationPath(derivationStyle)?.rawPath) + } + tokens.map { + Currency.Token( + it.token, + it.blockchain, + it.blockchain.derivationPath(derivationStyle)?.rawPath + ) + } + } + private fun deriveMissingBlockchains( scanResponse: ScanResponse, currencyList: List, @@ -233,17 +256,10 @@ class TokensMiddleware { addActions.forEach { store.dispatchOnMain(it) } } - private fun removeCurrenciesIfNeeded(blockchains: List, tokens: List) { - if (tokens.isNotEmpty()) { - tokens.forEach { token -> - store.state.walletState.getWalletData(token)?.let { - store.dispatch(WalletAction.MultiWallet.RemoveWallet(it)) - } - } - } - if (blockchains.isNotEmpty()) { - blockchains.forEach { blockchain -> - store.state.walletState.getWalletData(blockchain)?.let { + private fun removeCurrenciesIfNeeded(currencies: List) { + if (currencies.isNotEmpty()) { + currencies.forEach { currency -> + store.state.walletState.getWalletData(currency)?.let { store.dispatch(WalletAction.MultiWallet.RemoveWallet(it)) } } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt index b52460d488..6ac0dbd968 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt @@ -22,15 +22,15 @@ private fun internalReduce(action: Action, state: AppState): TokensState { is TokensAction.SetAddedCurrencies -> { tokensState.copy( - addedBlockchains = action.wallets.toBlockchains(action.derivationStyle), - addedTokens = action.wallets.toTokensWithBlockchains(action.derivationStyle), + addedBlockchains = action.wallets.toNonCustomBlockchains(action.derivationStyle), + addedTokens = action.wallets.toNonCustomTokensWithBlockchains(action.derivationStyle), addedWallets = action.wallets, derivationStyle = action.derivationStyle ) } is TokensAction.SetNonRemovableCurrencies -> { tokensState.copy( - nonRemovableBlockchains = action.wallets.toBlockchains(tokensState.derivationStyle), + nonRemovableBlockchains = action.wallets.toNonCustomBlockchains(tokensState.derivationStyle), nonRemovableTokens = action.wallets.toTokensContractAddresses(), ) } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt index 7fc19bf751..5476447c70 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt @@ -27,11 +27,13 @@ fun List.toTokensContractAddresses(): List { return mapNotNull { (it.currency as? com.tangem.tap.features.wallet.redux.Currency.Token)?.token?.contractAddress }.distinct() } -fun List.toTokens(): List { - return mapNotNull { (it.currency as? com.tangem.tap.features.wallet.redux.Currency.Token)?.token }.distinct() +fun List.toNonCustomTokens(derivationStyle: DerivationStyle?): List { + return filter { !it.currency.isCustomCurrency(derivationStyle) } + .mapNotNull { (it.currency as? com.tangem.tap.features.wallet.redux.Currency.Token)?.token } + .distinct() } -fun List.toTokensWithBlockchains(derivationStyle: DerivationStyle?): List { +fun List.toNonCustomTokensWithBlockchains(derivationStyle: DerivationStyle?): List { return mapNotNull { if (it.currency !is com.tangem.tap.features.wallet.redux.Currency.Token) return@mapNotNull null if (it.currency.isCustomCurrency(derivationStyle)) return@mapNotNull null @@ -39,7 +41,7 @@ fun List.toTokensWithBlockchains(derivationStyle: DerivationStyle?): }.distinct() } -fun List.toBlockchains(derivationStyle: DerivationStyle?): List { +fun List.toNonCustomBlockchains(derivationStyle: DerivationStyle?): List { return mapNotNull { if (it.currency.isCustomCurrency(derivationStyle)) { null @@ -58,7 +60,7 @@ fun List.filter(supportedBlockchains: Set?): List get() = wallets.mapNotNull { it.walletManager } - fun getWalletManager(token: Token?): WalletManager? { - if (token == null) return null - return wallets - .mapNotNull { it.walletManager } - .find { walletManager -> - walletManager.cardTokens.any { it.contractAddress == token.contractAddress } - } - } - fun getWalletManager(currency: Currency?): WalletManager? { if (currency?.blockchain == null) return null - return wallets.map { it.walletManager } - .find { it?.wallet?.blockchain == currency.blockchain } + return getWalletStore(currency)?.walletManager } fun getWalletManager(blockchain: BlockchainNetwork): WalletManager? { @@ -131,22 +120,6 @@ data class WalletState( return getWalletStore(currency)?.walletsData?.firstOrNull { it.currency == currency } } - fun getWalletData(token: Token?): WalletData? { - if (token == null) return null - return walletsData.find { - (it.currency as? Currency.Token)?.token == token && - !it.currency.isCustomCurrency(store.state.globalState.scanResponse!!.card.derivationStyle) - } - } - - fun getWalletData(blockchain: Blockchain?): WalletData? { - if (blockchain == null) return null - return walletsData.find { - (it.currency as? Currency.Blockchain)?.blockchain == blockchain && - !it.currency.isCustomCurrency(store.state.globalState.scanResponse!!.card.derivationStyle) - } - } - fun getSelectedWalletData(): WalletData? { return walletsData.find { it.currency == selectedCurrency } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 61c5e422be..d5ac8edcc4 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -91,7 +91,7 @@ class MultiWalletMiddleware { } } is Currency.Token -> { - val walletManager = walletState?.getWalletManager(currency.token) + val walletManager = walletState?.getWalletManager(currency) if (walletManager != null) { walletManager.removeToken(currency.token) cardId?.let { @@ -222,6 +222,7 @@ class MultiWalletMiddleware { tokens: List, blockchainNetwork: BlockchainNetwork, walletState: WalletState?, globalState: GlobalState? ) { + if (tokens.isEmpty()) return val scanResponse = globalState?.scanResponse ?: return val wmFactory = globalState.tapWalletManager.walletManagerFactory diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index 3667a06c35..90796fd143 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -50,7 +50,7 @@ class TradeCryptoMiddleware { if (exchangeAction == CurrencyExchangeManager.Action.Buy && currency is Currency.Token && currency.blockchain.isTestnet() ) { - val walletManager = store.state.walletState.getWalletManager(currency.token) + val walletManager = store.state.walletState.getWalletManager(currency) if (walletManager !is EthereumWalletManager) return scope.launch { exchangeManager.buyErc20Tokens(walletManager, currency.token) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt index 0e140c131a..c3f65f3aa8 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt @@ -99,7 +99,8 @@ class MultiWalletReducer { addTokens(listOf(action.token), action.blockchain, state) } is WalletAction.MultiWallet.TokenLoaded -> { - val pendingTransactions = state.getWalletManager(action.token) + val currency = Currency.fromBlockchainNetwork(action.blockchain, action.token) + val pendingTransactions = state.getWalletManager(currency) ?.wallet?.let { wallet -> wallet.recentTransactions.toPendingTransactions(wallet.address) } ?: emptyList() @@ -113,7 +114,7 @@ class MultiWalletReducer { pendingTransactions.isNotEmpty() -> BalanceStatus.SameCurrencyTransactionInProgress else -> BalanceStatus.VerifiedOnline } - val tokenWalletData = state.getWalletData(action.token) + val tokenWalletData = state.getWalletData(currency) val newTokenWalletData = tokenWalletData?.copy( currencyData = tokenWalletData.currencyData.copy( status = tokenBalanceStatus, @@ -166,11 +167,10 @@ private fun addTokens( fun Token.toWallet(state: WalletState, blockchain: BlockchainNetwork): WalletData? { if (!state.isMultiwalletAllowed) return null - if (state.currencies.any { it is Currency.Token && it.token == this }) { - return null - } + val currency = Currency.fromBlockchainNetwork(blockchain, this) + if (state.currencies.contains(currency)) return null - val walletManager = state.getWalletManager(this)?.wallet + val walletManager = state.getWalletManager(currency)?.wallet val walletAddresses = createAddressList(walletManager) return WalletData( @@ -181,6 +181,6 @@ fun Token.toWallet(state: WalletState, blockchain: BlockchainNetwork): WalletDat ), walletAddresses = walletAddresses, mainButton = WalletMainButton.SendButton(false), - currency = Currency.fromBlockchainNetwork(blockchain, this) + currency = currency ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt index 55977c4d14..acd7c4d8b7 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt @@ -74,7 +74,8 @@ class OnWalletLoadedReducer { ) val tokens = wallet.getTokens().mapNotNull { token -> - val tokenWalletData = walletState.getWalletData(token) + val currency = Currency.fromBlockchainNetwork(blockchainNetwork, token) + val tokenWalletData = walletState.getWalletData(currency) val tokenPendingTransactions = pendingTransactions.filter { it.currency == token.symbol } val tokenBalanceStatus = when { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index 5af1e9c3be..35fab475ae 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -164,7 +164,10 @@ private fun internalReduce(action: Action, state: AppState): WalletState { tradeCryptoState = TradeCryptoState.from(exchangeManager, wallet) ) } - val wallets = newState.updateTradeCryptoState(exchangeManager, newState.replaceSomeWallets(newWallets)) + val wallets = newState.updateTradeCryptoState( + exchangeManager, + newState.replaceSomeWallets(newWallets) + ) val walletStore = newState.getWalletStore(action.blockchain)?.updateWallets(wallets) newState = newState.updateWalletStore(walletStore) } @@ -217,7 +220,11 @@ private fun internalReduce(action: Action, state: AppState): WalletState { ), ) val tokenWallets = action.wallet.getTokens() - .mapNotNull { newState.getWalletData(it) } + .mapNotNull { token -> + walletStore?.blockchainNetwork?.let { + newState.getWalletData(Currency.fromBlockchainNetwork(it, token)) + } + } .map { it.copy( currencyData = it.currencyData.copy( @@ -288,12 +295,14 @@ private fun internalReduce(action: Action, state: AppState): WalletState { ?: return newState val address = walletAddresses.list.firstOrNull { it.type == action.type } ?: return newState - newState = newState.updateWalletData(selectedWalletData?.copy( - walletAddresses = WalletAddresses( - address, - walletAddresses.list + newState = newState.updateWalletData( + selectedWalletData?.copy( + walletAddresses = WalletAddresses( + address, + walletAddresses.list + ) ) - )) + ) } is WalletAction.SetWalletRent -> { var walletData = newState.getWalletData(action.blockchain) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt index fde2ce883d..01d1ceebcf 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt @@ -115,6 +115,7 @@ class WalletAdapter private fun toggleWarning(show: Boolean) { binding.tvExchangeRate.show(!show) + binding.tvCustomCurrency.show(!show) binding.tvStatusErrorMessage.show(show) } } diff --git a/app/src/main/res/layout/item_currency_wallet.xml b/app/src/main/res/layout/item_currency_wallet.xml index ec0f6d62ba..a2ebdd3909 100644 --- a/app/src/main/res/layout/item_currency_wallet.xml +++ b/app/src/main/res/layout/item_currency_wallet.xml @@ -122,8 +122,12 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="16dp" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" android:paddingStart="4dp" android:paddingEnd="4dp" + android:paddingTop="3dp" + android:paddingBottom="3dp" android:textColor="@color/darkGray2" android:textSize="14sp" android:background="@drawable/shape_rectangle_rounded_4" From 65a55cb5e3c9b7e82d7a154daac10b743d0d5e8e Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Apr 2022 17:13:28 +0300 Subject: [PATCH 6/8] Updated on 2026-08-14 --- app/build.gradle | 2 +- .../common/compose/ComposeDialogManager.kt | 6 +-- .../common/compose/OutlinedTextFieldWidget.kt | 29 +++++------- .../moduleMessage/ModuleMessageConverter.kt | 24 ++++++++++ .../domain/DomainErrorConverters.kt} | 32 ++++++------- .../domain/DomainMessageConverter.kt | 20 ++++++++ .../compose/AddCustomTokenScreen.kt | 18 ++++--- common/.gitignore | 1 + common/build.gradle | 9 ++++ .../main/java/com/tangem/common/Validator.kt | 8 ++++ .../tangem/common/module/ModuleException.kt | 14 ++++++ .../com/tangem/common/module/ModuleMessage.kt | 22 +++++++++ domain/build.gradle | 3 +- .../java/com/tangem/domain/DomainError.kt | 47 ++++++++++++------- .../java/com/tangem/domain/DomainException.kt | 18 +++---- .../java/com/tangem/domain/DomainMessage.kt | 15 ------ .../domain/common/form/FieldsValidators.kt | 4 +- .../domain/features/addCustomToken/Errors.kt | 25 ---------- .../redux/AddCustomTokenAction.kt | 12 +++-- .../addCustomToken/redux/AddCustomTokenHub.kt | 45 +++++++++--------- .../redux/AddCustomTokenState.kt | 3 +- network/build.gradle | 8 ++++ settings.gradle | 1 + 23 files changed, 222 insertions(+), 144 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/moduleMessage/ModuleMessageConverter.kt rename app/src/main/java/com/tangem/tap/{features/tokens/addCustomToken/DomainErrorConverter.kt => common/moduleMessage/domain/DomainErrorConverters.kt} (52%) create mode 100644 app/src/main/java/com/tangem/tap/common/moduleMessage/domain/DomainMessageConverter.kt create mode 100644 common/.gitignore create mode 100644 common/build.gradle create mode 100644 common/src/main/java/com/tangem/common/Validator.kt create mode 100644 common/src/main/java/com/tangem/common/module/ModuleException.kt create mode 100644 common/src/main/java/com/tangem/common/module/ModuleMessage.kt delete mode 100644 domain/src/main/java/com/tangem/domain/DomainMessage.kt delete mode 100644 domain/src/main/java/com/tangem/domain/features/addCustomToken/Errors.kt diff --git a/app/build.gradle b/app/build.gradle index 6cc910a506..78402e567b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -69,8 +69,8 @@ repositories { dependencies { implementation fileTree(include: ['*.aar'], dir: 'libs') implementation implementation(project(path: ':domain')) - // TODO: refactoring: only for backwards compatibility with non-relocated services to the network module implementation implementation(project(path: ':network')) + implementation implementation(project(path: ':common')) implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.appcompat:appcompat:1.4.1' diff --git a/app/src/main/java/com/tangem/tap/common/compose/ComposeDialogManager.kt b/app/src/main/java/com/tangem/tap/common/compose/ComposeDialogManager.kt index d498d577f8..5031d9d835 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/ComposeDialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/ComposeDialogManager.kt @@ -20,7 +20,7 @@ import com.tangem.domain.DomainDialog import com.tangem.domain.redux.domainStore import com.tangem.domain.redux.global.DomainGlobalAction import com.tangem.domain.redux.global.DomainGlobalState -import com.tangem.tap.features.tokens.addCustomToken.DomainErrorConverter +import com.tangem.tap.common.moduleMessage.ModuleMessageConverter import com.tangem.tap.features.tokens.addCustomToken.compose.SelectTokenNetworkDialog import com.tangem.wallet.R import org.rekotlin.StoreSubscriber @@ -55,13 +55,13 @@ private fun ShowTheDialog(dialogState: MutableState) { if (dialogState.value == null) return val context = LocalContext.current - val errorConverter = remember { DomainErrorConverter(context) } + val errorConverter = remember { ModuleMessageConverter(context) } val onDismissRequest = { domainStore.dispatch(DomainGlobalAction.ShowDialog(null)) } when (val dialog = dialogState.value) { is DomainDialog.DialogError -> ErrorDialog( title = stringResource(id = R.string.common_error), - body = errorConverter.convertError(dialog.error), + body = errorConverter.convert(dialog.error), onDismissRequest ) is DomainDialog.SelectTokenDialog -> SelectTokenNetworkDialog(dialog, onDismissRequest) diff --git a/app/src/main/java/com/tangem/tap/common/compose/OutlinedTextFieldWidget.kt b/app/src/main/java/com/tangem/tap/common/compose/OutlinedTextFieldWidget.kt index 0d7d4f44e9..3081ef0b45 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/OutlinedTextFieldWidget.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/OutlinedTextFieldWidget.kt @@ -15,16 +15,17 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.tangem.domain.DomainError -import com.tangem.domain.ErrorConverter +import com.tangem.common.module.ModuleError import com.tangem.domain.common.form.Field import com.tangem.tap.common.compose.extensions.stringResourceDefault +import com.tangem.tap.common.moduleMessage.ModuleMessageConverter /** [REDACTED_AUTHOR] @@ -41,8 +42,8 @@ fun OutlinedTextFieldWidget( isEnabled: Boolean = true, isVisible: Boolean = true, isLoading: Boolean = false, - error: DomainError? = null, - errorConverter: ErrorConverter? = null, + error: ModuleError? = null, + errorConverter: ModuleMessageConverter? = null, debounceTextChanges: Long = 400, visualTransformation: VisualTransformation = VisualTransformation.None, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, @@ -79,7 +80,7 @@ private fun OutlinedProgressTextField( placeholder: String = "", isEnabled: Boolean = true, isLoading: Boolean = false, - error: DomainError? = null, + error: ModuleError? = null, debounce: Long = 400, visualTransformation: VisualTransformation = VisualTransformation.None, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, @@ -132,8 +133,8 @@ private fun OutlinedProgressTextField( @Composable private fun AnimatedErrorView( - error: DomainError? = null, - errorConverter: ErrorConverter, + error: ModuleError? = null, + errorConverter: ModuleMessageConverter, ) { AnimatedVisibility( visible = error != null, @@ -141,7 +142,7 @@ private fun AnimatedErrorView( exit = slideOutVertically() + fadeOut(), ) { error?.let { - ErrorView(errorConverter.convertError(it), style = TextStyle(fontSize = 14.sp)) + ErrorView(errorConverter.convert(it), style = TextStyle(fontSize = 14.sp)) } } } @@ -149,20 +150,14 @@ private fun AnimatedErrorView( @Preview @Composable fun OutlinedTextFieldWithErrorTest() { - val converter = remember { - object : ErrorConverter { - override fun convertError(error: DomainError): String { - return "Hello, i'am the error: ${error::class.java.simpleName}" - } - - } - } + val context = LocalContext.current + val converter = remember { ModuleMessageConverter(context) } class SimpleError( override val code: Int = 1, override val message: String = "Error message", override val data: Any? = null, - ) : DomainError + ) : ModuleError val modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/com/tangem/tap/common/moduleMessage/ModuleMessageConverter.kt b/app/src/main/java/com/tangem/tap/common/moduleMessage/ModuleMessageConverter.kt new file mode 100644 index 0000000000..a7247ebe20 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/moduleMessage/ModuleMessageConverter.kt @@ -0,0 +1,24 @@ +package com.tangem.tap.common.moduleMessage + +import android.content.Context +import com.tangem.common.module.ModuleMessage +import com.tangem.common.module.ModuleMessageConverter +import com.tangem.domain.DomainModuleMessage +import com.tangem.tap.common.moduleMessage.domain.DomainMessageConverter + +class ModuleMessageConverter( + private val context: Context +) : ModuleMessageConverter { + + override fun convert(message: ModuleMessage): String { + val convertedMessage = when (message) { + is DomainModuleMessage -> DomainMessageConverter(context).convert(message) + else -> null + } + return convertedMessage ?: convertUnknownMessage(message) + } + + private fun convertUnknownMessage(message: ModuleMessage): String { + return "Unknown message: ${message::class.java.simpleName}" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/DomainErrorConverter.kt b/app/src/main/java/com/tangem/tap/common/moduleMessage/domain/DomainErrorConverters.kt similarity index 52% rename from app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/DomainErrorConverter.kt rename to app/src/main/java/com/tangem/tap/common/moduleMessage/domain/DomainErrorConverters.kt index db6b5aa462..aeb3723a1a 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/DomainErrorConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/moduleMessage/domain/DomainErrorConverters.kt @@ -1,10 +1,9 @@ -package com.tangem.tap.features.tokens.addCustomToken +package com.tangem.tap.common.moduleMessage.domain import android.content.Context +import com.tangem.common.module.ModuleMessageConverter +import com.tangem.domain.AddCustomTokenError import com.tangem.domain.DomainError -import com.tangem.domain.ErrorConverter -import com.tangem.domain.features.addCustomToken.AddCustomTokenError -import com.tangem.domain.features.addCustomToken.AddCustomTokenWarning import com.tangem.wallet.R /** @@ -12,27 +11,23 @@ import com.tangem.wallet.R */ class DomainErrorConverter( private val context: Context -) : ErrorConverter { - - override fun convertError(error: DomainError): String { - val errorMessage = when (error) { - is AddCustomTokenError -> AddCustomTokenConverter(context).convertError(error) - else -> null - } - return errorMessage?.let { it } ?: "Unknown error: ${error::class.java.simpleName}" +) : ModuleMessageConverter { + override fun convert(message: DomainError): String? = when (message) { + is AddCustomTokenError -> AddCustomTokenConverter(context).convert(message) + else -> null } } private class AddCustomTokenConverter( private val context: Context -) : ErrorConverter { +) : ModuleMessageConverter { - override fun convertError(error: DomainError): String { - val customTokenError = (error as? AddCustomTokenError) ?: throw UnsupportedOperationException() + override fun convert(message: DomainError): String? { + val customTokenError = (message as? AddCustomTokenError) ?: throw UnsupportedOperationException() val rawMessage = when (customTokenError) { - AddCustomTokenWarning.PotentialScamToken -> R.string.custom_token_validation_error_not_found - AddCustomTokenWarning.TokenAlreadyAdded -> R.string.custom_token_validation_error_already_added + AddCustomTokenError.Warning.PotentialScamToken -> R.string.custom_token_validation_error_not_found + AddCustomTokenError.Warning.TokenAlreadyAdded -> R.string.custom_token_validation_error_already_added AddCustomTokenError.InvalidContractAddress -> R.string.custom_token_creation_error_invalid_contract_address AddCustomTokenError.NetworkIsNotSelected -> R.string.custom_token_creation_error_network_not_selected AddCustomTokenError.InvalidDerivationPath -> R.string.custom_token_creation_error_invalid_derivation_path @@ -42,10 +37,11 @@ private class AddCustomTokenConverter( AddCustomTokenError.FieldIsEmpty -> R.string.custom_token_creation_error_required_field else -> null } + return when (rawMessage) { is Int -> context.getString(rawMessage) is String -> rawMessage - else -> "Unknown error: ${customTokenError::class.java.simpleName}" + else -> null } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/moduleMessage/domain/DomainMessageConverter.kt b/app/src/main/java/com/tangem/tap/common/moduleMessage/domain/DomainMessageConverter.kt new file mode 100644 index 0000000000..3afb79b219 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/moduleMessage/domain/DomainMessageConverter.kt @@ -0,0 +1,20 @@ +package com.tangem.tap.common.moduleMessage.domain + +import android.content.Context +import com.tangem.common.module.ModuleMessageConverter +import com.tangem.domain.DomainError +import com.tangem.domain.DomainModuleMessage + +/** +[REDACTED_AUTHOR] + */ +class DomainMessageConverter( + private val context: Context +) : ModuleMessageConverter { + override fun convert(message: DomainModuleMessage): String? { + return when (message) { + is DomainError -> DomainErrorConverter(context).convert(message) + else -> null + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt index 83641420f3..ea27e5137f 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt @@ -13,11 +13,9 @@ import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.tangem.domain.ErrorConverter +import com.tangem.domain.AddCustomTokenError import com.tangem.domain.common.form.DataField import com.tangem.domain.common.form.FieldId -import com.tangem.domain.features.addCustomToken.AddCustomTokenError -import com.tangem.domain.features.addCustomToken.AddCustomTokenWarning import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.* import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState @@ -27,7 +25,7 @@ import com.tangem.domain.redux.domainStore import com.tangem.tap.common.compose.ComposeDialogManager import com.tangem.tap.common.compose.ToggledRippleTheme import com.tangem.tap.common.compose.keyboardAsState -import com.tangem.tap.features.tokens.addCustomToken.DomainErrorConverter +import com.tangem.tap.common.moduleMessage.ModuleMessageConverter import com.tangem.wallet.R /** @@ -82,7 +80,7 @@ fun AddCustomTokenScreen(state: MutableState) { @Composable private fun FormFields(state: MutableState) { val context = LocalContext.current - val errorConverter = remember { DomainErrorConverter(context) } + val errorConverter = remember { ModuleMessageConverter(context) } val stateValue = state.value stateValue.form.fieldList.forEach { field -> @@ -99,11 +97,11 @@ private fun FormFields(state: MutableState) { } @Composable -fun Warnings(warnings: List) { +fun Warnings(warnings: List) { if (warnings.isEmpty()) return val context = LocalContext.current - val warningConverter = remember { DomainErrorConverter(context) } + val warningConverter = remember { ModuleMessageConverter(context) } Column { warnings.forEachIndexed { index, item -> @@ -120,7 +118,7 @@ fun Warnings(warnings: List) { ) { Text( modifier = Modifier.padding(16.dp), - text = warningConverter.convertError(item), + text = warningConverter.convert(item), color = colorResource(id = R.color.white), fontSize = 14.sp ) @@ -172,14 +170,14 @@ private fun AddCustomTokenFab( data class ScreenFieldData( val field: DataField<*>, val error: AddCustomTokenError?, - val errorConverter: ErrorConverter, + val errorConverter: ModuleMessageConverter, val viewState: ViewStates.TokenField ) { companion object { fun fromState( field: DataField<*>, state: AddCustomTokenState, - errorConverter: DomainErrorConverter + errorConverter: ModuleMessageConverter ): ScreenFieldData { return ScreenFieldData( field = field, diff --git a/common/.gitignore b/common/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/common/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/common/build.gradle b/common/build.gradle new file mode 100644 index 0000000000..0412225730 --- /dev/null +++ b/common/build.gradle @@ -0,0 +1,9 @@ +plugins { + id 'java-library' + id 'org.jetbrains.kotlin.jvm' +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} \ No newline at end of file diff --git a/common/src/main/java/com/tangem/common/Validator.kt b/common/src/main/java/com/tangem/common/Validator.kt new file mode 100644 index 0000000000..8654e9d07e --- /dev/null +++ b/common/src/main/java/com/tangem/common/Validator.kt @@ -0,0 +1,8 @@ +package com.tangem.common + +/** +[REDACTED_AUTHOR] + */ +interface Validator { + fun validate(data: Data? = null): Error? +} \ No newline at end of file diff --git a/common/src/main/java/com/tangem/common/module/ModuleException.kt b/common/src/main/java/com/tangem/common/module/ModuleException.kt new file mode 100644 index 0000000000..233d47f942 --- /dev/null +++ b/common/src/main/java/com/tangem/common/module/ModuleException.kt @@ -0,0 +1,14 @@ +package com.tangem.common.module + +/** +[REDACTED_AUTHOR] + * A module exception + */ +interface ModuleException { + val message: String +} + +/** + * An exception marked as FbConsumeException should be submitted to Firebase.Crashlytics as a non-fatal issue. + */ +interface FbConsumeException \ No newline at end of file diff --git a/common/src/main/java/com/tangem/common/module/ModuleMessage.kt b/common/src/main/java/com/tangem/common/module/ModuleMessage.kt new file mode 100644 index 0000000000..240e7c8759 --- /dev/null +++ b/common/src/main/java/com/tangem/common/module/ModuleMessage.kt @@ -0,0 +1,22 @@ +package com.tangem.common.module + +/** +[REDACTED_AUTHOR] + * The base object for communication between modules + */ +interface ModuleMessage + +interface ModuleMessageConverter { + fun convert(message: ModuleMessage): R +} + +/** + * @property code describes what feature is the error coming from + * @property message the error description + * @property data any data that can help in the part where this error is being handled + */ +interface ModuleError : ModuleMessage { + val code: Int + val message: String + val data: Any? +} \ No newline at end of file diff --git a/domain/build.gradle b/domain/build.gradle index f64927d8a7..8180476f6b 100644 --- a/domain/build.gradle +++ b/domain/build.gradle @@ -47,13 +47,14 @@ android { dependencies { implementation implementation(project(path: ':network')) + implementation implementation(project(path: ':common')) // Tangem sdk's implementation 'com.tangem:blockchain:develop-71' implementation 'com.tangem.tangem-sdk-kotlin:core:develop-142' implementation 'com.tangem.tangem-sdk-kotlin:android:develop-142' - // Kotlin + // Kotlin coroutines implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2' // State management diff --git a/domain/src/main/java/com/tangem/domain/DomainError.kt b/domain/src/main/java/com/tangem/domain/DomainError.kt index 7f6b14c1ed..1f923c0c90 100644 --- a/domain/src/main/java/com/tangem/domain/DomainError.kt +++ b/domain/src/main/java/com/tangem/domain/DomainError.kt @@ -1,29 +1,42 @@ package com.tangem.domain +import com.tangem.common.module.ModuleError +import com.tangem.common.module.ModuleMessage + /** [REDACTED_AUTHOR] - * @property code describes what feature is the error coming from - * @property message the error description - * @property data any data that can help in the part where this error is being handled + * All DomainError descendants must use their own range of codes, but no more than 999 error codes for each. */ -interface DomainError : DomainMessage { - val code: Int - val message: String - val data: Any? -} +sealed interface DomainModuleMessage : ModuleMessage -open class AnError( +sealed class DomainError( override val code: Int, override val message: String, - override val data: Any? = null, -) : DomainError + override val data: Any?, +) : DomainModuleMessage, ModuleError { -interface ErrorConverter { - fun convertError(error: DomainError): T + companion object { + const val CODE_ADD_CUSTOM_TOKEN = 1000 + } } -interface Validator { - fun validate(data: Data? = null): Error? -} +sealed class AddCustomTokenError( + subCode: Int = 0 +) : DomainError(CODE_ADD_CUSTOM_TOKEN + subCode, this::class.java.simpleName, null) { -const val ERROR_CODE_ADD_CUSTOM_TOKEN = 100 \ No newline at end of file + object FieldIsEmpty : AddCustomTokenError() + object FieldIsNotEmpty : AddCustomTokenError() + object InvalidContractAddress : AddCustomTokenError() + object NetworkIsNotSelected : AddCustomTokenError() + object InvalidDecimalsCount : AddCustomTokenError() + object InvalidDerivationPath : AddCustomTokenError() + + sealed class Network : AddCustomTokenError() { + object CheckAddressRequestError : Network() + } + + sealed class Warning : AddCustomTokenError() { + object PotentialScamToken : Warning() + object TokenAlreadyAdded : Warning() + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/DomainException.kt b/domain/src/main/java/com/tangem/domain/DomainException.kt index f137262717..b6fc362990 100644 --- a/domain/src/main/java/com/tangem/domain/DomainException.kt +++ b/domain/src/main/java/com/tangem/domain/DomainException.kt @@ -1,17 +1,19 @@ package com.tangem.domain +import com.tangem.common.module.FbConsumeException +import com.tangem.common.module.ModuleException + /** [REDACTED_AUTHOR] - * Must be handled by the module or sent to Crashlytics */ -interface DomainInternalException +sealed class AddCustomTokenException(override val message: String) : Throwable(message), ModuleException { -sealed class DomainException(message: String?) : Throwable(message), DomainInternalException { - data class SelectTokeNetworkException(val networkId: String) : DomainException( + data class SelectTokeNetworkException(val networkId: String) : AddCustomTokenException( "Unknown network [$networkId] should not be included in the network selection dialog." - ) + ), FbConsumeException - data class UnAppropriateInitializationException(val of: String, val info: String? = null) : DomainException( - "The [$of], must be properly initialized. Info []" - ) + data class UnAppropriateInitializationException( + val of: String, + val info: String? = null + ) : AddCustomTokenException("The [$of], must be properly initialized. Info [$info]") } \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/DomainMessage.kt b/domain/src/main/java/com/tangem/domain/DomainMessage.kt deleted file mode 100644 index 5ff7266ab9..0000000000 --- a/domain/src/main/java/com/tangem/domain/DomainMessage.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.domain - -/** -[REDACTED_AUTHOR] - */ -sealed interface DomainMessage - -sealed interface DomainNotification : DomainMessage { - interface Toast : DomainNotification {} - - interface Snackbar : DomainNotification {} - - interface Dialog : DomainNotification {} - -} \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/form/FieldsValidators.kt b/domain/src/main/java/com/tangem/domain/common/form/FieldsValidators.kt index 712fa36bd3..431b0ff2af 100644 --- a/domain/src/main/java/com/tangem/domain/common/form/FieldsValidators.kt +++ b/domain/src/main/java/com/tangem/domain/common/form/FieldsValidators.kt @@ -2,8 +2,8 @@ package com.tangem.domain.common.form import com.tangem.blockchain.blockchains.ethereum.EthereumAddressService import com.tangem.blockchain.common.Blockchain -import com.tangem.domain.Validator -import com.tangem.domain.features.addCustomToken.AddCustomTokenError +import com.tangem.common.Validator +import com.tangem.domain.AddCustomTokenError /** [REDACTED_AUTHOR] diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/Errors.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/Errors.kt deleted file mode 100644 index ebf097d964..0000000000 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/Errors.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.domain.features.addCustomToken - -import com.tangem.domain.AnError -import com.tangem.domain.ERROR_CODE_ADD_CUSTOM_TOKEN - -/** -[REDACTED_AUTHOR] - */ -sealed class AddCustomTokenError : AnError(ERROR_CODE_ADD_CUSTOM_TOKEN, "Add custom token - error") { - object FieldIsEmpty : AddCustomTokenError() - object FieldIsNotEmpty : AddCustomTokenError() - object InvalidContractAddress : AddCustomTokenError() - object NetworkIsNotSelected : AddCustomTokenError() - object InvalidDecimalsCount : AddCustomTokenError() - object InvalidDerivationPath : AddCustomTokenError() - - sealed class Network : AddCustomTokenWarning() { - object CheckAddressRequestError : Network() - } -} - -sealed class AddCustomTokenWarning : AddCustomTokenError() { - object PotentialScamToken : AddCustomTokenWarning() - object TokenAlreadyAdded : AddCustomTokenWarning() -} \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenAction.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenAction.kt index dc832d367a..f1eadfeab5 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenAction.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenAction.kt @@ -2,11 +2,10 @@ package com.tangem.domain.features.addCustomToken.redux import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationStyle +import com.tangem.domain.AddCustomTokenError import com.tangem.domain.DomainWrapped import com.tangem.domain.common.form.Field import com.tangem.domain.common.form.FieldId -import com.tangem.domain.features.addCustomToken.AddCustomTokenError -import com.tangem.domain.features.addCustomToken.AddCustomTokenWarning import com.tangem.domain.features.addCustomToken.CustomCurrency import com.tangem.domain.features.addCustomToken.CustomTokenFieldId import org.rekotlin.Action @@ -47,9 +46,12 @@ sealed class AddCustomTokenAction : Action { // warnings sealed class Warning : AddCustomTokenAction() { - data class Add(val warnings: Set) : Warning() - data class Remove(val warnings: Set) : Warning() - data class Replace(val remove: Set, val add: Set) : Warning() + data class Add(val warnings: Set) : Warning() + data class Remove(val warnings: Set) : Warning() + data class Replace( + val remove: Set, + val add: Set + ) : Warning() } // To change the screenState diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt index 799ceaf58f..af0bdd7525 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt @@ -5,8 +5,11 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationStyle import com.tangem.common.extensions.guard import com.tangem.common.services.Result +import com.tangem.domain.AddCustomTokenError +import com.tangem.domain.AddCustomTokenError.Warning.PotentialScamToken +import com.tangem.domain.AddCustomTokenError.Warning.TokenAlreadyAdded +import com.tangem.domain.AddCustomTokenException import com.tangem.domain.DomainDialog -import com.tangem.domain.DomainException import com.tangem.domain.DomainWrapped import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.extensions.fromNetworkId @@ -145,9 +148,9 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT private suspend fun updateWarningAlreadyAdded(isInAppSavedList: Boolean) { if (isInAppSavedList) { - AddCustomTokenWarning.TokenAlreadyAdded.add() + TokenAlreadyAdded.add() } else { - AddCustomTokenWarning.TokenAlreadyAdded.remove() + TokenAlreadyAdded.remove() } } @@ -170,7 +173,7 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT val result = when (foundTokensResult) { is Result.Success -> foundTokensResult.data is Result.Failure -> { -// val warning = AddCustomTokenWarning.Network.CheckAddressRequestError +// val warning = Warning.Network.CheckAddressRequestError // dispatchOnMain(Warning.Add(setOf(warning))) emptyList() } @@ -182,8 +185,8 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT private suspend fun manageFoundTokenChanges(foundTokens: List) { if (foundTokens.isEmpty()) { // token not found - it's completely custom - AddCustomTokenWarning.TokenAlreadyAdded.remove() - AddCustomTokenWarning.PotentialScamToken.add() + TokenAlreadyAdded.remove() + PotentialScamToken.add() dispatchOnMain(SetFoundTokenId(null)) clearTokenFields() @@ -208,33 +211,33 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT if (isInAppSavedTokens) { lockTokenFields() lockAddButton() - AddCustomTokenWarning.PotentialScamToken.replace(AddCustomTokenWarning.TokenAlreadyAdded) + PotentialScamToken.replace(TokenAlreadyAdded) } else { // not in the saved tokens list if (singleTokenContract.active) { lockTokenFields() unlockAddButton() if (hubState.derivationPathIsSelected()) { - AddCustomTokenWarning.PotentialScamToken.add() + PotentialScamToken.add() } else { - AddCustomTokenWarning.TokenAlreadyAdded.remove() - AddCustomTokenWarning.PotentialScamToken.remove() + TokenAlreadyAdded.remove() + PotentialScamToken.remove() } } else { unlockAddButton() - AddCustomTokenWarning.PotentialScamToken.add() + PotentialScamToken.add() } } } else -> { - AddCustomTokenWarning.PotentialScamToken.replace(AddCustomTokenWarning.TokenAlreadyAdded) + PotentialScamToken.replace(TokenAlreadyAdded) val dialog = DomainDialog.SelectTokenDialog( items = foundToken.contracts, networkIdConverter = { networkId -> val blockchain = Blockchain.fromNetworkId(networkId) if (blockchain == null || blockchain == Blockchain.Unknown) { - throw DomainException.SelectTokeNetworkException(networkId) + throw AddCustomTokenException.SelectTokeNetworkException(networkId) } hubState.blockchainToName(blockchain) ?: "" }, @@ -253,8 +256,8 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT } private suspend fun replaceWarnings( - warningsAdd: MutableSet = mutableSetOf(), - warningsRemove: MutableSet = mutableSetOf(), + warningsAdd: MutableSet = mutableSetOf(), + warningsRemove: MutableSet = mutableSetOf(), ) { if (warningsAdd.isNotEmpty() || warningsRemove.isNotEmpty()) { dispatchOnMain(Warning.Replace(warningsRemove.toSet(), warningsAdd.toSet())) @@ -263,7 +266,7 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT private suspend fun updateAddButton() { val state = hubState - if (state.warnings.contains(AddCustomTokenWarning.TokenAlreadyAdded)) { + if (state.warnings.contains(TokenAlreadyAdded)) { lockAddButton() return } @@ -483,19 +486,19 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT dispatchOnMain(action) } - private suspend fun AddCustomTokenWarning.add() { + private suspend fun AddCustomTokenError.Warning.add() { dispatchOnMain(Warning.Add(setOf(this))) } - private suspend fun AddCustomTokenWarning.remove() { + private suspend fun AddCustomTokenError.Warning.remove() { dispatchOnMain(Warning.Remove(setOf(this))) } - private suspend fun AddCustomTokenWarning.replace(to: AddCustomTokenWarning) { + private suspend fun AddCustomTokenError.Warning.replace(to: AddCustomTokenError.Warning) { dispatchOnMain(Warning.Replace(setOf(this), setOf(to))) } -// private suspend fun AddCustomTokenWarning.replace(replace: Boolean, to: AddCustomTokenWarning) { +// private suspend fun Warning.replace(replace: Boolean, to: Warning) { // if (replace) dispatchOnMain(Warning.Replace(setOf(this), setOf(to))) // } @@ -653,7 +656,7 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT @Throws private fun throwUnAppropriateInitialization(objName: String) { - throw DomainException.UnAppropriateInitializationException( + throw AddCustomTokenException.UnAppropriateInitializationException( "AddCustomTokenHub", "$objName must be not NULL" ) } diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt index 82e3b9c068..1d359780bd 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt @@ -2,6 +2,7 @@ package com.tangem.domain.features.addCustomToken.redux import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationStyle +import com.tangem.domain.AddCustomTokenError import com.tangem.domain.DomainWrapped import com.tangem.domain.common.form.* import com.tangem.domain.features.addCustomToken.* @@ -16,7 +17,7 @@ data class AddCustomTokenState( val formValidators: Map> = createFormValidators(), val formErrors: Map = emptyMap(), val tokenId: String? = null, - val warnings: Set = emptySet(), + val warnings: Set = emptySet(), val screenState: ScreenState = createInitialScreenState(), val tangemTechServiceManager: AddCustomTokenService? = null ) : StateType { diff --git a/network/build.gradle b/network/build.gradle index f89fb54c38..b90ff59980 100644 --- a/network/build.gradle +++ b/network/build.gradle @@ -9,9 +9,17 @@ java { } dependencies { + implementation implementation(project(path: ':common')) + // Tangem sdk's implementation 'com.tangem.tangem-sdk-kotlin:core:develop-142' + // Kotlin coroutines + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2' + + // Logs + implementation 'com.jakewharton.timber:timber:4.7.1' + // Network implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.3")) implementation("com.squareup.okhttp3:okhttp") diff --git a/settings.gradle b/settings.gradle index a6c2d48bed..9b33b9462c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,4 @@ include ':app' include ':domain' include ':network' +include ':common' From 268b74cb0efadf42481dd91969bddaee0cf53964 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Apr 2022 17:27:28 +0300 Subject: [PATCH 7/8] Updated on 2026-08-14 --- .../java/com/tangem/domain/DomainError.kt | 10 +++++--- .../api/tangemTech/TangemTechService.kt | 18 ++++++++------- .../com/tangem/network/common/NetworError.kt | 23 +++++++++++++++++++ .../tangem/network/common/NetworkException.kt | 9 ++++++++ 4 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 network/src/main/java/com/tangem/network/common/NetworError.kt create mode 100644 network/src/main/java/com/tangem/network/common/NetworkException.kt diff --git a/domain/src/main/java/com/tangem/domain/DomainError.kt b/domain/src/main/java/com/tangem/domain/DomainError.kt index 1f923c0c90..d7b24eccbf 100644 --- a/domain/src/main/java/com/tangem/domain/DomainError.kt +++ b/domain/src/main/java/com/tangem/domain/DomainError.kt @@ -10,19 +10,23 @@ import com.tangem.common.module.ModuleMessage sealed interface DomainModuleMessage : ModuleMessage sealed class DomainError( - override val code: Int, + subCode: Int, override val message: String, override val data: Any?, ) : DomainModuleMessage, ModuleError { + override val code: Int = ERROR_CODE_DOMAIN + subCode companion object { - const val CODE_ADD_CUSTOM_TOKEN = 1000 + // base code used for all error in the module + const val ERROR_CODE_DOMAIN = 10000 + const val ERROR_CODE_ADD_CUSTOM_TOKEN = 100 +// const val CODE_ANY_OTHER = 200..299 } } sealed class AddCustomTokenError( subCode: Int = 0 -) : DomainError(CODE_ADD_CUSTOM_TOKEN + subCode, this::class.java.simpleName, null) { +) : DomainError(ERROR_CODE_ADD_CUSTOM_TOKEN + subCode, this::class.java.simpleName, null) { object FieldIsEmpty : AddCustomTokenError() object FieldIsNotEmpty : AddCustomTokenError() diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt index 10c0ceb3cc..ac969a6868 100644 --- a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt +++ b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt @@ -5,6 +5,8 @@ import com.tangem.common.services.performRequest import com.tangem.network.common.AddHeaderInterceptor import com.tangem.network.common.CacheControlHttpInterceptor import com.tangem.network.common.createRetrofitInstance +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext /** [REDACTED_AUTHOR] @@ -59,22 +61,22 @@ class CoinsRoute : TangemTechRoute { suspend fun prices( currency: String, ids: List - ): Result { - return performRequest { api.coinsPrices(currency, ids) } + ): Result = withContext(Dispatchers.IO) { + performRequest { api.coinsPrices(currency, ids) } } suspend fun checkAddress( contractAddress: String, networkId: String? = null - ): Result { - return performRequest { api.coinsCheckAddress(contractAddress, networkId) } + ): Result = withContext(Dispatchers.IO) { + performRequest { api.coinsCheckAddress(contractAddress, networkId) } } - suspend fun currencies(): Result { - return performRequest { api.coinsCurrencies() } + suspend fun currencies(): Result = withContext(Dispatchers.IO) { + performRequest { api.coinsCurrencies() } } - suspend fun tokens(): Result { - return performRequest { api.coinsTokens() } + suspend fun tokens(): Result = withContext(Dispatchers.IO) { + performRequest { api.coinsTokens() } } } \ No newline at end of file diff --git a/network/src/main/java/com/tangem/network/common/NetworError.kt b/network/src/main/java/com/tangem/network/common/NetworError.kt new file mode 100644 index 0000000000..bdee8a8729 --- /dev/null +++ b/network/src/main/java/com/tangem/network/common/NetworError.kt @@ -0,0 +1,23 @@ +package com.tangem.network.common + +import com.tangem.common.module.ModuleError +import com.tangem.common.module.ModuleMessage + +/** +[REDACTED_AUTHOR] + */ +sealed interface NetworkModuleMessage : ModuleMessage + +sealed class NetworkError( + subCode: Int, + override val message: String, + override val data: Any?, +) : NetworkModuleMessage, ModuleError { + override val code: Int = ERROR_CODE_NETWORK + subCode + + companion object { + // base code used for all error in the module + const val ERROR_CODE_NETWORK = 20000 + // const val CODE_ANY_OTHER = 100..199 + } +} \ No newline at end of file diff --git a/network/src/main/java/com/tangem/network/common/NetworkException.kt b/network/src/main/java/com/tangem/network/common/NetworkException.kt new file mode 100644 index 0000000000..033e2b9b62 --- /dev/null +++ b/network/src/main/java/com/tangem/network/common/NetworkException.kt @@ -0,0 +1,9 @@ +package com.tangem.network.common + +/** +[REDACTED_AUTHOR] + */ +interface NetworkInternalException + +sealed class NetworkException(message: String?) : Throwable(message), NetworkInternalException { +} \ No newline at end of file From 704315ddafccc757ed5b1b8358026eb1d2cd0cbb Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Apr 2022 22:26:51 +0300 Subject: [PATCH 8/8] Updated on 2026-08-14 --- .../tangem/tap/common/extensions/Specific.kt | 8 +- .../com/tangem/tap/common/redux/AppState.kt | 4 + .../tap/common/redux/global/GlobalState.kt | 2 - .../com/tangem/tap/domain/TapWalletManager.kt | 84 +++++++++++++------ .../features/details/redux/DetailsAction.kt | 6 +- .../details/redux/DetailsMiddleware.kt | 35 ++++---- .../features/details/redux/DetailsReducer.kt | 6 +- .../features/details/redux/DetailsState.kt | 5 +- .../details/ui/CurrencySelectionDialog.kt | 8 +- .../tap/features/wallet/ui/WalletFragment.kt | 3 +- .../coinmarketcap/CoinMarketCapService.kt | 1 + .../persistence/FiatCurrenciesPrefStorage.kt | 36 ++++++++ .../tap/persistence/PreferencesStorage.kt | 38 ++------- .../tap/persistence/UsedCardsPrefStorage.kt | 9 +- .../domain/redux/global/DomainGlobalState.kt | 3 +- .../network/api/tangemTech/Responses.kt | 15 ++-- .../network/api/tangemTech/TangemTechApi.kt | 2 +- .../api/tangemTech/TangemTechService.kt | 7 +- 18 files changed, 169 insertions(+), 103 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/persistence/FiatCurrenciesPrefStorage.kt diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt b/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt index 330457dbf6..1b0d1de783 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt @@ -1,8 +1,8 @@ package com.tangem.tap.common.extensions import com.tangem.common.extensions.isZero +import com.tangem.network.api.tangemTech.Coins import com.tangem.tap.common.redux.global.FiatCurrencyName -import com.tangem.tap.network.coinmarketcap.FiatCurrency import java.math.BigDecimal import java.math.RoundingMode import java.text.DecimalFormat @@ -10,7 +10,7 @@ import java.text.DecimalFormatSymbols import java.util.* fun BigDecimal.toFormattedString( - decimals: Int, roundingMode: RoundingMode = RoundingMode.DOWN, locale: Locale = Locale.US + decimals: Int, roundingMode: RoundingMode = RoundingMode.DOWN, locale: Locale = Locale.US ): String { val symbols = DecimalFormatSymbols(locale) val df = DecimalFormat() @@ -26,7 +26,7 @@ fun BigDecimal.toFormattedCurrencyString( decimals: Int, currency: String, roundingMode: RoundingMode = RoundingMode.DOWN, limitNumberOfDecimals: Boolean = true ): String { - val decimalsForRounding = if (limitNumberOfDecimals){ + val decimalsForRounding = if (limitNumberOfDecimals) { if (decimals > 8) 8 else decimals } else { decimals @@ -52,7 +52,7 @@ fun BigDecimal.toFormattedFiatValue(fiatCurrencyName: FiatCurrencyName): String return "≈ ${fiatCurrencyName}  $this" } -fun FiatCurrency.toFormattedString(): String = "${this.name} (${this.symbol}) - ${this.sign}" +fun Coins.CurrenciesResponse.Currency.toFormattedString(): String = "${this.name} (${this.code}) - ${this.unit}" fun BigDecimal.stripZeroPlainString(): String = this.stripTrailingZeros().toPlainString() diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt index ac955b82a7..6f20496105 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -2,6 +2,7 @@ package com.tangem.tap.common.redux import com.tangem.domain.redux.DomainState import com.tangem.domain.redux.domainStore +import com.tangem.domain.redux.global.NetworkServices import com.tangem.tap.common.redux.global.GlobalMiddleware import com.tangem.tap.common.redux.global.GlobalState import com.tangem.tap.common.redux.navigation.NavigationState @@ -54,6 +55,9 @@ data class AppState( val domainState: DomainState get() = domainStore.state + val domainNetworks: NetworkServices + get() = domainState.globalState.networkServices + companion object { fun getMiddleware(): List> { return listOf( diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt index ef6ffeb391..a58e92b944 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt @@ -10,7 +10,6 @@ import com.tangem.tap.domain.configurable.config.ConfigManager import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.features.feedback.FeedbackManager import com.tangem.tap.features.onboarding.OnboardingManager -import com.tangem.tap.network.coinmarketcap.CoinMarketCapService import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import org.rekotlin.StateType @@ -20,7 +19,6 @@ data class GlobalState( val cardVerifiedOnline: Boolean = false, val tapWalletManager: TapWalletManager = TapWalletManager(), val payIdManager: PayIdManager = PayIdManager(), - val coinMarketCapService: CoinMarketCapService = CoinMarketCapService(), val configManager: ConfigManager? = null, val warningManager: WarningMessagesManager? = null, val feedbackManager: FeedbackManager? = null, diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index c461b5f57b..8d81cc222e 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -2,13 +2,17 @@ package com.tangem.tap.domain import com.tangem.blockchain.blockchains.solana.RentProvider import com.tangem.blockchain.common.* +import com.tangem.common.extensions.guard import com.tangem.common.services.Result import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.TapWorkarounds.isTestCard +import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.common.extensions.withMainContext +import com.tangem.network.api.tangemTech.TangemTechService import com.tangem.tap.common.ThrottlerWithValues +import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.extensions.stripZeroPlainString @@ -26,7 +30,6 @@ import com.tangem.tap.features.wallet.models.getPendingTransactions import com.tangem.tap.features.wallet.redux.Currency import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.network.NetworkConnectivity -import com.tangem.tap.network.coinmarketcap.CoinMarketCapService import com.tangem.tap.store import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -37,7 +40,9 @@ class TapWalletManager { val walletManagerFactory: WalletManagerFactory by lazy { WalletManagerFactory(blockchainSdkConfig) } - private val coinMarketCapService = CoinMarketCapService() + private val tangemTechService: TangemTechService + get() = store.state.domainNetworks.tangemTechService + private val blockchainSdkConfig by lazy { store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig() } @@ -98,20 +103,58 @@ class TapWalletManager { } suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, currencies: List) { + suspend fun handleFiatRatesResult(rates: Map?>) { + rates.forEach { (currency, priceResult) -> + when (priceResult) { + is Result.Success -> { + dispatchOnMain( + WalletAction.LoadFiatRate.Success( + currency to priceResult.data + )) + } + is Result.Failure -> dispatchOnMain(WalletAction.LoadFiatRate.Failure) + null -> {} + } + } + } + // get and submit previous result of equivalents. val throttledResult = currencies.filter { fiatRatesThrottler.isStillThrottled(it) }.map { Pair(it, fiatRatesThrottler.geValue(it)) } - if (throttledResult.isNotEmpty()) handleFiatRatesResult(throttledResult) + if (throttledResult.isNotEmpty()) { + handleFiatRatesResult(throttledResult.toMap()) + } - val toUpdate = currencies.filter { !fiatRatesThrottler.isStillThrottled(it) } - toUpdate.forEach { - val result = coinMarketCapService.getRate(it.currencySymbol, fiatCurrency) - if (result is Result.Success) { - fiatRatesThrottler.updateThrottlingTo(it) - fiatRatesThrottler.setValue(it, result) + val toUpdateCurrencies = currencies.filter { !fiatRatesThrottler.isStillThrottled(it) } + val toUpdateIds = toUpdateCurrencies.mapNotNull { it.id }.distinct() + if (toUpdateIds.isEmpty()) return + + //TODO: refactoring: move fiatRatesThrottler to the TangemTechRepository + when (val result = tangemTechService.coins.prices(fiatCurrency, toUpdateIds)) { + is Result.Success -> { + val missedCurrencies = mutableListOf() + val updatedCurrencies = mutableMapOf?>() + + result.data.prices.forEach { (name, value) -> + val currency = toUpdateCurrencies.firstOrNull { it.id == name }.guard { + missedCurrencies.add(name) + return@forEach + } + val priceResult = Result.Success(value.toBigDecimal()) + updatedCurrencies[currency] = priceResult + + fiatRatesThrottler.updateThrottlingTo(currency) + fiatRatesThrottler.setValue(currency, priceResult) + } + + if (missedCurrencies.isNotEmpty()) { + val missedNames = missedCurrencies.joinToString(", ") + store.dispatchDebugErrorNotification("Not found currencies to update: [$missedNames]") + } + handleFiatRatesResult(updatedCurrencies) } - handleFiatRatesResult(listOf(it to result)) + is Result.Failure -> dispatchOnMain(WalletAction.LoadFiatRate.Failure) } } @@ -318,21 +361,14 @@ class TapWalletManager { is com.tangem.blockchain.extensions.Result.Failure -> {} } } - - private suspend fun handleFiatRatesResult(results: List?>>) { - results.map { - when (it.second) { - is Result.Success -> { - val rate = it.first to (it.second as Result.Success).data - dispatchOnMain(WalletAction.LoadFiatRate.Success(rate)) - } - is Result.Failure -> dispatchOnMain(WalletAction.LoadFiatRate.Failure) - null -> {} - } - } - } } fun Wallet.getFirstToken(): Token? { return getTokens().toList().getOrNull(0) -} \ No newline at end of file +} + +val Currency.id: String? + get() = when (this) { + is Currency.Blockchain -> blockchain.toNetworkId() + is Currency.Token -> token.id + } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt index 1c087a518a..2077ee8e4d 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt @@ -4,11 +4,12 @@ import com.tangem.blockchain.common.Wallet import com.tangem.common.card.Card import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TwinCardNumber +import com.tangem.network.api.tangemTech.Coins +import com.tangem.network.api.tangemTech.TangemTechService import com.tangem.operations.pins.CheckUserCodesResponse import com.tangem.tap.common.redux.NotificationAction import com.tangem.tap.common.redux.global.FiatCurrencyName import com.tangem.tap.domain.termsOfUse.CardTou -import com.tangem.tap.network.coinmarketcap.FiatCurrency import com.tangem.wallet.R import org.rekotlin.Action @@ -20,6 +21,7 @@ sealed class DetailsAction : Action { val cardTou: CardTou, val fiatCurrencyName: FiatCurrencyName, val fiatCurrencies: List? = null, + val tangemTechService: TangemTechService, ) : DetailsAction() object ShowDisclaimer : DetailsAction() @@ -46,7 +48,7 @@ sealed class DetailsAction : Action { object CreateBackup : DetailsAction() sealed class AppCurrencyAction : DetailsAction() { - data class SetCurrencies(val currencies: List) : AppCurrencyAction() + data class SetCurrencies(val currencies: List) : AppCurrencyAction() object ChooseAppCurrency : AppCurrencyAction() object Cancel : AppCurrencyAction() data class SelectAppCurrency(val fiatCurrencyName: FiatCurrencyName) : AppCurrencyAction() diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index bc7e76fa5e..03a3298d90 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -4,6 +4,7 @@ import com.tangem.common.CompletionResult import com.tangem.common.card.FirmwareVersion import com.tangem.common.core.TangemSdkError import com.tangem.common.services.Result +import com.tangem.network.api.tangemTech.Coins import com.tangem.operations.pins.CheckUserCodesResponse import com.tangem.tap.* import com.tangem.tap.common.analytics.Analytics @@ -19,7 +20,6 @@ import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletM import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.wallet.models.hasSendableAmountsOrPendingTransactions import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.network.coinmarketcap.CoinMarketCapService import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -33,7 +33,7 @@ class DetailsMiddleware { { next -> { action -> when (action) { - is DetailsAction.PrepareScreen -> prepareData() + is DetailsAction.PrepareScreen -> prepareData(action) is DetailsAction.ResetToFactory -> eraseWalletMiddleware.handle(action) is DetailsAction.AppCurrencyAction -> appCurrencyMiddleware.handle(action) is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action) @@ -68,23 +68,26 @@ class DetailsMiddleware { } } - private fun prepareData() { + private fun prepareData(action: DetailsAction.PrepareScreen) { + val fiatCurrenciesPrefStorage = preferencesStorage.fiatCurrenciesPrefStorage + val storedFiatCurrencies = fiatCurrenciesPrefStorage.restore() + if (storedFiatCurrencies.isNotEmpty()) { + store.dispatch(DetailsAction.AppCurrencyAction.SetCurrencies(storedFiatCurrencies)) + } + scope.launch { - val loadedCurrencies = preferencesStorage.getFiatCurrencies() - if (loadedCurrencies.isNullOrEmpty()) { - val response = CoinMarketCapService().getFiatCurrencies() - withContext(Dispatchers.Main) { - when (response) { - is Result.Success -> { - preferencesStorage.saveFiatCurrencies(response.data) - store.dispatch(DetailsAction.AppCurrencyAction.SetCurrencies(response.data)) - } + val tangemTechService = action.tangemTechService + when (val result = tangemTechService.coins.currencies()) { + is Result.Success -> { + val fiatCurrencies = result.data.currencies.filter { + it.type == Coins.CurrenciesResponse.CurrencyType.Fiat.type + } + if (fiatCurrencies.isNotEmpty() && fiatCurrencies.toSet() != storedFiatCurrencies.toSet()) { + fiatCurrenciesPrefStorage.save(fiatCurrencies) + dispatchOnMain(DetailsAction.AppCurrencyAction.SetCurrencies(fiatCurrencies)) } } - } else { - withContext(Dispatchers.Main) { - store.dispatch(DetailsAction.AppCurrencyAction.SetCurrencies(loadedCurrencies)) - } + is Result.Failure -> {} } } } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index 848a5f4a8e..796c7c171f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -43,13 +43,15 @@ private fun handlePrepareScreen( action: DetailsAction.PrepareScreen, state: DetailsState, ): DetailsState { - val backupIsActive = action.scanResponse.card.backupStatus?.isActive ?: false return DetailsState( scanResponse = action.scanResponse, wallets = action.wallets, cardInfo = action.scanResponse.card.toCardInfo(), - appCurrencyState = AppCurrencyState(action.fiatCurrencyName), + appCurrencyState = state.appCurrencyState.copy( + fiatCurrencyName = action.fiatCurrencyName, + showAppCurrencyDialog = false, + ), cardTermsOfUseUrl = action.cardTou.getUrl(action.scanResponse.card), createBackupAllowed = action.scanResponse.card.backupStatus == Card.BackupStatus.NoBackup, ) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt index 48d8ded881..bcf176341b 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt @@ -3,11 +3,11 @@ package com.tangem.tap.features.details.redux import android.net.Uri import com.tangem.blockchain.common.Wallet import com.tangem.domain.common.ScanResponse +import com.tangem.network.api.tangemTech.Coins import com.tangem.tap.common.entities.Button import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY import com.tangem.tap.common.redux.global.FiatCurrencyName import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState -import com.tangem.tap.network.coinmarketcap.FiatCurrency import com.tangem.tap.store import org.rekotlin.StateType import java.util.* @@ -51,8 +51,9 @@ data class SecurityScreenState( ) enum class SecurityOption { LongTap, PassCode, AccessCode } + data class AppCurrencyState( val fiatCurrencyName: FiatCurrencyName = DEFAULT_FIAT_CURRENCY, val showAppCurrencyDialog: Boolean = false, - val fiatCurrencies: List? = null, + val fiatCurrencies: List? = null, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/CurrencySelectionDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/CurrencySelectionDialog.kt index 47720dfbc4..d55e321dd6 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/CurrencySelectionDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/CurrencySelectionDialog.kt @@ -2,10 +2,10 @@ package com.tangem.tap.features.details.ui import android.content.Context import androidx.appcompat.app.AlertDialog +import com.tangem.network.api.tangemTech.Coins import com.tangem.tap.common.extensions.toFormattedString import com.tangem.tap.common.redux.global.FiatCurrencyName import com.tangem.tap.features.details.redux.DetailsAction -import com.tangem.tap.network.coinmarketcap.FiatCurrency import com.tangem.tap.store import com.tangem.wallet.R @@ -13,11 +13,11 @@ class CurrencySelectionDialog { var dialog: AlertDialog? = null - fun show(currencies: List, currentAppCurrency: FiatCurrencyName, context: Context) { + fun show(currencies: List, currentAppCurrency: FiatCurrencyName, context: Context) { if (dialog == null) { val currenciesToShow = currencies.map { it.toFormattedString() }.toTypedArray() - var currentSelection = currencies.indexOfFirst { it.symbol == currentAppCurrency } + var currentSelection = currencies.indexOfFirst { it.code == currentAppCurrency } dialog = AlertDialog.Builder(context) .setTitle(context.getString(R.string.details_row_title_currency)) @@ -26,7 +26,7 @@ class CurrencySelectionDialog { } .setPositiveButton(context.getString(R.string.common_done)) { _, _ -> val selectedCurrency = currencies[currentSelection] - store.dispatch(DetailsAction.AppCurrencyAction.SelectAppCurrency(selectedCurrency.symbol)) + store.dispatch(DetailsAction.AppCurrencyAction.SelectAppCurrency(selectedCurrency.code)) } .setOnDismissListener { store.dispatch(DetailsAction.AppCurrencyAction.Cancel) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 54fcb9870a..f06904cec2 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -166,7 +166,8 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber) { + val json: String = converter.toJson(currencies) + return preferences.edit().putString(FIAT_CURRENCIES_KEY, json).apply() + } + + fun restore(): List { + val json = preferences.getString(FIAT_CURRENCIES_KEY, "") + val type = converter.typedList(Coins.CurrenciesResponse.Currency::class.java) + if (json.isNullOrBlank()) return emptyList() + + return converter.fromJson(json, type) ?: emptyList() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt index 12d5eaa695..7bee7c8c6f 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -4,13 +4,9 @@ import android.app.Application import android.content.Context import android.content.SharedPreferences import androidx.core.content.edit -import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.Moshi -import com.squareup.moshi.Types -import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import com.tangem.common.json.MoshiJsonConverter import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY import com.tangem.tap.common.redux.global.FiatCurrencyName -import com.tangem.tap.network.coinmarketcap.FiatCurrency import java.util.* @@ -20,39 +16,24 @@ class PreferencesStorage(applicationContext: Application) { val appRatingLaunchObserver: AppRatingLaunchObserver val usedCardsPrefStorage: UsedCardsPrefStorage + val fiatCurrenciesPrefStorage: FiatCurrenciesPrefStorage init { incrementLaunchCounter() appRatingLaunchObserver = AppRatingLaunchObserver(preferences, getCountOfLaunches()) - usedCardsPrefStorage = UsedCardsPrefStorage(preferences) + usedCardsPrefStorage = UsedCardsPrefStorage(preferences, MoshiJsonConverter.INSTANCE) usedCardsPrefStorage.migrate() - } - - private val fiatCurrenciesAdapter: JsonAdapter> by lazy { - val moshi = Moshi.Builder() - .add(KotlinJsonAdapterFactory()) - .build() - val type = Types.newParameterizedType(List::class.java, FiatCurrency::class.java) - moshi.adapter(type) + fiatCurrenciesPrefStorage = FiatCurrenciesPrefStorage(preferences, MoshiJsonConverter.INSTANCE) + fiatCurrenciesPrefStorage.migrate() } fun getAppCurrency(): FiatCurrencyName { return preferences.getString(APP_CURRENCY_KEY, DEFAULT_FIAT_CURRENCY) - ?: DEFAULT_FIAT_CURRENCY + ?: DEFAULT_FIAT_CURRENCY } fun saveAppCurrency(fiatCurrencyName: FiatCurrencyName) { - return preferences.edit().putString(APP_CURRENCY_KEY, fiatCurrencyName).apply() - } - - fun getFiatCurrencies(): List? { - val json = preferences.getString(FIAT_CURRENCIES_KEY, "") - return if (json.isNullOrBlank()) null else fiatCurrenciesAdapter.fromJson(json) as List - } - - fun saveFiatCurrencies(currencies: List) { - val json: String = fiatCurrenciesAdapter.toJson(currencies) - return preferences.edit().putString(FIAT_CURRENCIES_KEY, json).apply() + preferences.edit { putString(APP_CURRENCY_KEY, fiatCurrencyName) } } fun getCountOfLaunches(): Int = preferences.getInt(APP_LAUNCH_COUNT_KEY, 1) @@ -63,7 +44,7 @@ class PreferencesStorage(applicationContext: Application) { } fun saveDisclaimerAccepted() { - preferences.edit().putBoolean(DISCLAIMER_ACCEPTED_KEY, true).apply() + preferences.edit { putBoolean(DISCLAIMER_ACCEPTED_KEY, true) } } fun wasDisclaimerAccepted(): Boolean { @@ -71,7 +52,7 @@ class PreferencesStorage(applicationContext: Application) { } fun saveTwinsOnboardingShown() { - preferences.edit().putBoolean(TWINS_ONBOARDING_SHOWN_KEY, true).apply() + preferences.edit { putBoolean(TWINS_ONBOARDING_SHOWN_KEY, true) } } fun wasTwinsOnboardingShown(): Boolean { @@ -86,7 +67,6 @@ class PreferencesStorage(applicationContext: Application) { companion object { private const val PREFERENCES_NAME = "tapPrefs" private const val APP_CURRENCY_KEY = "appCurrency" - private const val FIAT_CURRENCIES_KEY = "fiatCurrencies" private const val DISCLAIMER_ACCEPTED_KEY = "disclaimerAccepted" private const val TWINS_ONBOARDING_SHOWN_KEY = "twinsOnboardingShown" private const val APP_LAUNCH_COUNT_KEY = "launchCount" diff --git a/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt b/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt index 9d8d877f32..5d7f241fb8 100644 --- a/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt @@ -11,10 +11,9 @@ import timber.log.Timber */ class UsedCardsPrefStorage( private val preferences: SharedPreferences, + private val jsonConverter: MoshiJsonConverter ) { - private val jsonConverter = MoshiJsonConverter.INSTANCE - internal fun migrate() { val scannedIds = preferences.getString(SCANNED_CARDS_IDS_KEY, null) ?: return @@ -27,7 +26,7 @@ class UsedCardsPrefStorage( fun scanned(cardId: String) { val restoredList = restore() val foundItem = findCardInfo(cardId, restoredList)?.copy(isScanned = true) - ?: UsedCardInfo(cardId, true) + ?: UsedCardInfo(cardId, true) save(foundItem, restoredList) } @@ -39,7 +38,7 @@ class UsedCardsPrefStorage( fun activationStarted(cardId: String) { val restoredList = restore() val foundItem = findCardInfo(cardId, restoredList)?.copy(isActivationStarted = true) - ?: UsedCardInfo(cardId, isActivationStarted = true) + ?: UsedCardInfo(cardId, isActivationStarted = true) save(foundItem, restoredList) } @@ -55,7 +54,7 @@ class UsedCardsPrefStorage( fun activationFinished(cardId: String) { val restoredList = restore() val foundItem = findCardInfo(cardId, restoredList)?.copy(isActivationStarted = false) - ?: UsedCardInfo(cardId, isActivationStarted = false) + ?: UsedCardInfo(cardId, isActivationStarted = false) save(foundItem, restoredList) } diff --git a/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalState.kt b/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalState.kt index c51219beab..5bf1264638 100644 --- a/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalState.kt +++ b/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalState.kt @@ -8,7 +8,8 @@ import com.tangem.network.api.tangemTech.TangemTechService [REDACTED_AUTHOR] */ data class DomainGlobalState( - // there is a part of mirrors from the GlobalState + // there is a part of mirrors from the GlobalState. + // It updates on GlobalAction.SaveScanNoteResponse -> DomainGlobalAction.SaveScanNoteResponse(scanResponse) val scanResponse: ScanResponse? = null, // val networkServices: NetworkServices = NetworkServices(), diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt b/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt index 534074aa0c..02e15c4672 100644 --- a/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt +++ b/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt @@ -9,12 +9,7 @@ interface HttpResponse sealed interface TangemTechResponse : HttpResponse sealed class Coins : TangemTechResponse { - data class PricesResponse(val prices: List) : Coins() { - data class Price( - val name: String, - val price: BigDecimal, - ) - } + data class PricesResponse(val prices: Map) : Coins() data class CheckAddressResponse(val imageHost: String?, val tokens: List, val total: Int) : Coins() { data class Token( @@ -51,11 +46,15 @@ sealed class Coins : TangemTechResponse { data class CurrenciesResponse(val currencies: List) { data class Currency( val id: String, - val code: String, + val code: String, // this is an uppercase id val name: String, val rateBTC: String, - val unit: String, + val unit: String, // $, €, ₽ val type: String, ) + + enum class CurrencyType(val type: String) { + Fiat("fiat"), Crypto("crypto") + } } } \ No newline at end of file diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt index 2917063e30..622f2a299a 100644 --- a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt +++ b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt @@ -11,7 +11,7 @@ interface TangemTechApi { @GET("coins/prices") suspend fun coinsPrices( @Query("currency") currency: String, - @Query("ids") ids: List, + @Query("ids") ids: String, ): Coins.PricesResponse @GET("coins/check-address") diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt index ac969a6868..d6c5f5b66a 100644 --- a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt +++ b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt @@ -34,7 +34,8 @@ class TangemTechService { private fun createApi(): TangemTechApi { val retrofit = createRetrofitInstance( baseUrl = baseUrl, - interceptors = headerInterceptors.toList() + interceptors = headerInterceptors.toList(), +// logEnabled = true, ) return retrofit.create(TangemTechApi::class.java).apply { techRoutes.forEach { it.setApi(this) } @@ -62,7 +63,9 @@ class CoinsRoute : TangemTechRoute { currency: String, ids: List ): Result = withContext(Dispatchers.IO) { - performRequest { api.coinsPrices(currency, ids) } + performRequest { + api.coinsPrices(currency.lowercase(), ids.joinToString(",")) + } } suspend fun checkAddress(