From 8261e72fec50d9ab804948b58c1936c571a9258d Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 8 Dec 2022 16:58:37 +0300 Subject: [PATCH 001/145] Updated on 2026-08-14 --- .../java/com/tangem/tap/TapApplication.kt | 2 +- .../tap/common/extensions/Navigation.kt | 3 +- core/datasource/build.gradle.kts | 2 + .../tangem/datasource/api/common}/Adapters.kt | 2 +- .../api/tangemTech/TangemTechService.kt | 3 +- .../com/tangem/datasource/di/NetworkModule.kt | 10 ++- features/swap/data/build.gradle.kts | 2 + .../tangem/feature/swap/SwapRepositoryImpl.kt | 13 ++-- .../swap/converters/ApproveConverter.kt | 2 +- .../swap/converters/QuotesConverter.kt | 5 +- .../feature/swap/converters/SwapConverter.kt | 28 +++++--- .../swap/converters/TokensConverter.kt | 15 ++++- .../tangem/feature/swap/di/SwapDataModule.kt | 44 +++++++++++++ .../feature/swap/domain/SwapInteractor.kt | 16 +++++ .../feature/swap/domain/SwapInteractorImpl.kt | 65 +++++++++++++++++++ .../feature/swap/domain/SwapRepository.kt | 11 ++-- .../swap/domain/models/SwapDataHolder.kt | 8 +++ .../swap/domain/models/SwapResultModel.kt | 14 ++++ .../domain/models/{ => data}/ApproveModel.kt | 2 +- .../swap/domain/models/{ => data}/Currency.kt | 2 +- .../domain/models/{ => data}/QuoteModel.kt | 2 +- .../swap/domain/models/data/SwapDataModel.kt | 17 +++++ .../TransactionModel.kt} | 15 +---- features/swap/presentation/build.gradle.kts | 1 + .../feature/swap/presentation/SwapFragment.kt | 33 ++++++++++ 25 files changed, 269 insertions(+), 48 deletions(-) rename {app/src/main/java/com/tangem/tap/common/moshi => core/datasource/src/main/java/com/tangem/datasource/api/common}/Adapters.kt (87%) create mode 100644 features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataHolder.kt create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapResultModel.kt rename features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/{ => data}/ApproveModel.kt (88%) rename features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/{ => data}/Currency.kt (93%) rename features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/{ => data}/QuoteModel.kt (69%) create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapDataModel.kt rename features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/{SwapTransactionModel.kt => data/TransactionModel.kt} (57%) create mode 100644 features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index d17e83bd33..b3bc0a111a 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -11,6 +11,7 @@ import com.tangem.blockchain.common.BlockchainSdkConfig import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder import com.tangem.common.json.MoshiJsonConverter +import com.tangem.datasource.api.common.BigDecimalAdapter import com.tangem.datasource.api.common.MoshiConverter import com.tangem.domain.DomainLayer import com.tangem.domain.common.LogConfig @@ -29,7 +30,6 @@ import com.tangem.tap.common.feedback.AdditionalFeedbackInfo import com.tangem.tap.common.feedback.FeedbackManager import com.tangem.tap.common.images.createCoilImageLoader import com.tangem.tap.common.log.TangemLogCollector -import com.tangem.tap.common.moshi.BigDecimalAdapter import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.appReducer import com.tangem.tap.common.redux.global.GlobalAction diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index 3abbbac6d1..1d78d19e09 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -7,6 +7,7 @@ import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentManager import com.tangem.common.extensions.VoidCallback import com.tangem.feature.referral.ReferralFragment +import com.tangem.feature.swap.presentation.SwapFragment import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.FragmentShareTransition import com.tangem.tap.features.details.ui.appsettings.AppSettingsFragment @@ -105,7 +106,7 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.OnboardingOther -> OnboardingOtherCardsFragment() AppScreen.Wallet -> WalletFragment() AppScreen.Send -> SendFragment() - AppScreen.Details -> DetailsFragment() + AppScreen.Details -> SwapFragment()//DetailsFragment() AppScreen.DetailsSecurity -> SecurityModeFragment() AppScreen.CardSettings -> CardSettingsFragment() AppScreen.AppSettings -> AppSettingsFragment() diff --git a/core/datasource/build.gradle.kts b/core/datasource/build.gradle.kts index 082885c1d7..67563bfbb8 100644 --- a/core/datasource/build.gradle.kts +++ b/core/datasource/build.gradle.kts @@ -63,6 +63,8 @@ dependencies { implementation(Library.okHttp) implementation(Library.okHttpLogging) + kapt("com.squareup.moshi:moshi-kotlin-codegen:1.14.0") + /** Time */ implementation(Library.jodatime) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/moshi/Adapters.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/Adapters.kt similarity index 87% rename from app/src/main/java/com/tangem/tap/common/moshi/Adapters.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/common/Adapters.kt index c0ec09ea9c..568b042179 100644 --- a/app/src/main/java/com/tangem/tap/common/moshi/Adapters.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/Adapters.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.common.moshi +package com.tangem.datasource.api.common import com.squareup.moshi.FromJson import com.squareup.moshi.ToJson diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt index 50540a226e..00638342fc 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt @@ -15,7 +15,8 @@ class TangemTechService( private val logIsEnabled: Boolean = false, ) { - private val headerInterceptors = mutableListOf( + private val + headerInterceptors = mutableListOf( CacheControlHttpInterceptor(cacheMaxAge), ) diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index d662a98721..c53b453f1b 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -3,6 +3,7 @@ package com.tangem.datasource.di import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import com.tangem.datasource.api.AuthHeaderInterceptor +import com.tangem.datasource.api.common.BigDecimalAdapter import com.tangem.datasource.api.oneinch.OneInchApi import com.tangem.datasource.api.referral.ReferralApi import com.tangem.datasource.api.tangemTech.TangemTechApi @@ -27,7 +28,12 @@ class NetworkModule { fun provideTangemTechApi(okHttpClient: OkHttpClient): TangemTechApi { return Retrofit.Builder() .addConverterFactory( - MoshiConverterFactory.create(), + MoshiConverterFactory.create( + Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .add(BigDecimalAdapter()) + .build(), + ), ) .baseUrl(DEV_TANGEM_TECH_BASE_URL) .client(okHttpClient) @@ -80,6 +86,6 @@ class NetworkModule { private companion object { const val PROD_TANGEM_TECH_BASE_URL = "https://api.tangem-tech.com/v1/" const val DEV_TANGEM_TECH_BASE_URL = "https://devapi.tangem-tech.com/v1/" - const val ONE_INCH_ETHER_BASE_URL = "" + const val ONE_INCH_ETHER_BASE_URL = "https://api.1inch.io/swagger/ethereum-json/" } } \ No newline at end of file diff --git a/features/swap/data/build.gradle.kts b/features/swap/data/build.gradle.kts index 298948e089..fff5731bd3 100644 --- a/features/swap/data/build.gradle.kts +++ b/features/swap/data/build.gradle.kts @@ -31,6 +31,8 @@ dependencies { implementation(project(":core:utils")) implementation(project(":features:swap:domain")) + kapt("com.squareup.moshi:moshi-kotlin-codegen:1.14.0") + /** DI */ implementation(Library.hilt) kapt(Library.hiltKapt) diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt index 2095876a1b..5424e1fd3d 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt @@ -7,9 +7,10 @@ import com.tangem.feature.swap.converters.QuotesConverter import com.tangem.feature.swap.converters.SwapConverter import com.tangem.feature.swap.converters.TokensConverter import com.tangem.feature.swap.domain.SwapRepository -import com.tangem.feature.swap.domain.models.ApproveModel -import com.tangem.feature.swap.domain.models.Currency -import com.tangem.feature.swap.domain.models.QuoteModel +import com.tangem.feature.swap.domain.models.data.ApproveModel +import com.tangem.feature.swap.domain.models.data.Currency +import com.tangem.feature.swap.domain.models.data.QuoteModel +import com.tangem.feature.swap.domain.models.data.SwapDataModel import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext import javax.inject.Inject @@ -64,16 +65,16 @@ class SwapRepositoryImpl @Inject constructor( fromTokenAddress: String, toTokenAddress: String, amount: String, - fromAddress: String, + fromWalletAddress: String, slippage: Int, - ) { + ): SwapDataModel { return withContext(coroutineDispatcher.io) { swapConverter.convert( oneInchApi.swap( fromTokenAddress = fromTokenAddress, toTokenAddress = toTokenAddress, amount = amount, - fromAddress = fromAddress, + fromAddress = fromWalletAddress, slippage = slippage, ), ) diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ApproveConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ApproveConverter.kt index 4d3574869d..c5d5d0e600 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ApproveConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ApproveConverter.kt @@ -1,7 +1,7 @@ package com.tangem.feature.swap.converters import com.tangem.datasource.api.oneinch.models.ApproveCalldataResponse -import com.tangem.feature.swap.domain.models.ApproveModel +import com.tangem.feature.swap.domain.models.data.ApproveModel import com.tangem.utils.converter.Converter import javax.inject.Inject diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt index 52723c6596..058a6a6159 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt @@ -1,10 +1,11 @@ package com.tangem.feature.swap.converters import com.tangem.datasource.api.oneinch.models.QuoteResponse -import com.tangem.feature.swap.domain.models.QuoteModel +import com.tangem.feature.swap.domain.models.data.QuoteModel import com.tangem.utils.converter.Converter +import javax.inject.Inject -class QuotesConverter : Converter { +class QuotesConverter @Inject constructor(): Converter { override fun convert(value: QuoteResponse): QuoteModel { return QuoteModel( diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt index 244ae4f2a0..87dec263da 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt @@ -1,24 +1,32 @@ package com.tangem.feature.swap.converters import com.tangem.datasource.api.oneinch.models.SwapResponse -import com.tangem.feature.swap.domain.models.SwapTransactionModel +import com.tangem.datasource.api.oneinch.models.TransactionDto +import com.tangem.feature.swap.domain.models.data.SwapDataModel +import com.tangem.feature.swap.domain.models.data.TransactionModel import com.tangem.utils.converter.Converter import javax.inject.Inject -class SwapConverter @Inject constructor() : Converter { +class SwapConverter @Inject constructor() : Converter { - override fun convert(value: SwapResponse): SwapTransactionModel { - return SwapTransactionModel( + override fun convert(value: SwapResponse): SwapDataModel { + return SwapDataModel( fromTokenAddress = value.fromToken.address, toTokenAddress = value.toToken.address, toTokenAmount = value.fromTokenAmount, fromTokenAmount = value.toTokenAmount, - fromWalletAddress = value.transaction.fromAddress, - toWalletAddress = value.transaction.toAddress, - data = value.transaction.data, - value = value.transaction.value, - gasPrice = value.transaction.gasPrice, - gas = value.transaction.gas, + transaction = convertTransaction(value.transaction), + ) + } + + private fun convertTransaction(transactionDto: TransactionDto): TransactionModel { + return TransactionModel( + fromWalletAddress = transactionDto.fromAddress, + toWalletAddress = transactionDto.toAddress, + data = transactionDto.data, + value = transactionDto.value, + gasPrice = transactionDto.gasPrice, + gas = transactionDto.gas, ) } } \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/TokensConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/TokensConverter.kt index c5414d666c..11596db8bc 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/TokensConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/TokensConverter.kt @@ -1,7 +1,7 @@ package com.tangem.feature.swap.converters import com.tangem.datasource.api.tangemTech.CoinsResponse -import com.tangem.feature.swap.domain.models.Currency +import com.tangem.feature.swap.domain.models.data.Currency import com.tangem.utils.converter.Converter import javax.inject.Inject @@ -17,7 +17,7 @@ class TokensConverter @Inject constructor() : Converter + + suspend fun getTokenBalance(): String + + suspend fun findBestQuote( + fromTokenAddress: String, + toTokenAddress: String, + amount: String, + ): QuoteModel + + suspend fun onSwap(): SwapResultModel } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt new file mode 100644 index 0000000000..b057ac50f8 --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -0,0 +1,65 @@ +package com.tangem.feature.swap.domain + +import com.tangem.feature.swap.domain.models.SwapDataHolder +import com.tangem.feature.swap.domain.models.SwapResultModel +import com.tangem.feature.swap.domain.models.data.Currency +import com.tangem.feature.swap.domain.models.data.QuoteModel +import com.tangem.feature.swap.domain.models.data.TransactionModel + +//todo add @Inject after merge referral and add HiltCore +class SwapInteractorImpl( + private val repository: SwapRepository, +) : SwapInteractor { + + private var lastDataForSwap: SwapDataHolder? = null + + override suspend fun getTokensToSwap(networkId: String): List { + return repository.getExchangeableTokens(networkId) + } + + override suspend fun getTokenBalance(): String { + TODO("Not yet implemented") + } + + override suspend fun findBestQuote(fromTokenAddress: String, toTokenAddress: String, amount: String): QuoteModel { + repository.findBestQuote(fromTokenAddress, toTokenAddress, amount).let { + lastDataForSwap = SwapDataHolder(it, amount) + return it + } + } + + override suspend fun onSwap(): SwapResultModel { + val dataForSwap = lastDataForSwap + if (dataForSwap != null) { + val swapData = repository.prepareSwapTransaction( + fromTokenAddress = dataForSwap.quoteModel.fromTokenAmount, + toTokenAddress = dataForSwap.quoteModel.toTokenAmount, + amount = dataForSwap.amount, + slippage = DEFAULT_SLIPPAGE, + fromWalletAddress = getWalletAddress(), + ) + signTransactionData(swapData.transaction) //todo implement + return SwapResultModel.SwapSuccess( + dataForSwap.quoteModel.fromTokenAmount, + dataForSwap.quoteModel.toTokenAmount, + ) + } + return SwapResultModel.SwapError( + 0, + "", + ) + } + + //todo implement after merge referral + private fun getWalletAddress(): String { + return "" + } + + //todo implement after merge referral + private fun signTransactionData(transaction: TransactionModel) { + } + + companion object { + private const val DEFAULT_SLIPPAGE = 1 + } +} \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt index 963f87bfdb..cb2a2bf07a 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt @@ -1,8 +1,9 @@ package com.tangem.feature.swap.domain -import com.tangem.feature.swap.domain.models.ApproveModel -import com.tangem.feature.swap.domain.models.Currency -import com.tangem.feature.swap.domain.models.QuoteModel +import com.tangem.feature.swap.domain.models.data.ApproveModel +import com.tangem.feature.swap.domain.models.data.Currency +import com.tangem.feature.swap.domain.models.data.QuoteModel +import com.tangem.feature.swap.domain.models.data.SwapDataModel interface SwapRepository { @@ -46,7 +47,7 @@ interface SwapRepository { fromTokenAddress: String, toTokenAddress: String, amount: String, - fromAddress: String, + fromWalletAddress: String, slippage: Int, - ) + ): SwapDataModel } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataHolder.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataHolder.kt new file mode 100644 index 0000000000..6216c6d4d5 --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataHolder.kt @@ -0,0 +1,8 @@ +package com.tangem.feature.swap.domain.models + +import com.tangem.feature.swap.domain.models.data.QuoteModel + +data class SwapDataHolder( + val quoteModel: QuoteModel, + val amount: String, +) \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapResultModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapResultModel.kt new file mode 100644 index 0000000000..014fb40127 --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapResultModel.kt @@ -0,0 +1,14 @@ +package com.tangem.feature.swap.domain.models + +sealed interface SwapResultModel { + + data class SwapSuccess( + val fromTokenAmount: String, + val toTokenAmount: String, + ) : SwapResultModel + + data class SwapError( + val errorCode: Int, + val errorMessage: String, + ) : SwapResultModel +} \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ApproveModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/ApproveModel.kt similarity index 88% rename from features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ApproveModel.kt rename to features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/ApproveModel.kt index 2fce5634f9..a553a8d4bf 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ApproveModel.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/ApproveModel.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.swap.domain.models +package com.tangem.feature.swap.domain.models.data /** * Approve model diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/Currency.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/Currency.kt similarity index 93% rename from features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/Currency.kt rename to features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/Currency.kt index 05fe0cad3b..cc69440af7 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/Currency.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/Currency.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.swap.domain.models +package com.tangem.feature.swap.domain.models.data sealed class Currency( open val id: String, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/QuoteModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/QuoteModel.kt similarity index 69% rename from features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/QuoteModel.kt rename to features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/QuoteModel.kt index e71378fd22..4402e296b1 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/QuoteModel.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/QuoteModel.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.swap.domain.models +package com.tangem.feature.swap.domain.models.data data class QuoteModel( val fromTokenAmount: String, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapDataModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapDataModel.kt new file mode 100644 index 0000000000..d7b70ca338 --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapDataModel.kt @@ -0,0 +1,17 @@ +package com.tangem.feature.swap.domain.models.data + +/** + * Swap transaction model + * + * @property fromTokenAddress token from which want to convert + * @property toTokenAddress token to want to convert + * @property toTokenAmount amount "target" token + * @property fromTokenAmount amount "initial" token + */ +data class SwapDataModel( + val fromTokenAddress: String, + val toTokenAddress: String, + val toTokenAmount: String, + val fromTokenAmount: String, + val transaction: TransactionModel +) \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapTransactionModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/TransactionModel.kt similarity index 57% rename from features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapTransactionModel.kt rename to features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/TransactionModel.kt index f36d74eb61..978c7e5ec7 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapTransactionModel.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/TransactionModel.kt @@ -1,25 +1,16 @@ -package com.tangem.feature.swap.domain.models +package com.tangem.feature.swap.domain.models.data /** - * Swap transaction model + * Transaction model * - * @property fromTokenAddress token from which want to convert - * @property toTokenAddress token to want to convert - * @property toTokenAmount amount "target" token - * @property fromTokenAmount amount "initial" token * @property fromWalletAddress wallet from address (transactions will be sent from this address) * @property toWalletAddress transactions will be sent to our(1inch) contract address * @property data The encoded data to call the approve method on the swapped token contract * @property value Native token value in WEI (for approve is always 0) * @property gasPrice maximum amount of gas for a swap default: 11500000; max: 11500000 * @property gas estimated amount of the gas limit, increase this value by 25% - * @constructor Create empty Swap transaction model */ -data class SwapTransactionModel( - val fromTokenAddress: String, - val toTokenAddress: String, - val toTokenAmount: String, - val fromTokenAmount: String, +data class TransactionModel( val fromWalletAddress: String, val toWalletAddress: String, val data: String, diff --git a/features/swap/presentation/build.gradle.kts b/features/swap/presentation/build.gradle.kts index 8eac586d41..034079a92e 100644 --- a/features/swap/presentation/build.gradle.kts +++ b/features/swap/presentation/build.gradle.kts @@ -49,6 +49,7 @@ dependencies { /** Domain */ implementation(project(":features:swap:domain")) + implementation(project(":features:swap:data"))//todo remove, only for test /** Other libraries */ implementation(Library.composeShimmer) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt new file mode 100644 index 0000000000..6e5a23dd82 --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt @@ -0,0 +1,33 @@ +package com.tangem.feature.swap.presentation + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.tangem.feature.swap.domain.SwapInteractorImpl +import com.tangem.feature.swap.domain.SwapRepository +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import javax.inject.Inject + +@AndroidEntryPoint +class SwapFragment : Fragment() { + + @Inject + lateinit var swapRepository: SwapRepository + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + //test + val swapInteractor = SwapInteractorImpl(swapRepository) + + CoroutineScope(Dispatchers.IO).launch { + Log.d("test", swapInteractor.getTokensToSwap("ethereum").toString()) + } + + return super.onCreateView(inflater, container, savedInstanceState) + } +} \ No newline at end of file From 361ac9508b9185ca93b6732d396b21dd6ddd9a3f Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 12 Dec 2022 16:26:33 +0300 Subject: [PATCH 002/145] Updated on 2026-08-14 --- .../tap/common/extensions/Navigation.kt | 5 +- .../tangem/tap/proxy/UserWalletManagerImpl.kt | 4 +- core/datasource/build.gradle.kts | 2 +- .../datasource/api/oneinch/OneInchApi.kt | 2 + .../api/tangemTech/TangemTechService.kt | 3 +- .../com/tangem/datasource/di/NetworkModule.kt | 52 +++++++++-------- .../referral/data/ReferralRepositoryImpl.kt | 3 +- .../referral/domain/ReferralInteractorImpl.kt | 2 +- features/swap/data/build.gradle.kts | 2 - .../tangem/feature/swap/SwapRepositoryImpl.kt | 2 +- features/swap/domain/build.gradle.kts | 12 ++++ .../feature/swap/domain/SwapInteractor.kt | 4 +- .../feature/swap/domain/SwapInteractorImpl.kt | 58 ++++++++++++------- .../swap/domain/cache/SwapDataCache.kt | 15 +++++ .../swap/domain/cache/SwapDataCacheImpl.kt | 39 +++++++++++++ .../swap/domain/di/SwapDomainModule.kt | 30 ++++++++++ .../swap/domain/models/SwapDataHolder.kt | 5 +- .../swap/domain/models/data/QuoteModel.kt | 3 +- .../feature/swap/presentation/SwapFragment.kt | 30 +++++----- .../feature/swap/viewmodels/SwapViewModel.kt | 15 +++++ .../tangem/lib/crypto/UserWalletManager.kt | 4 +- 21 files changed, 210 insertions(+), 82 deletions(-) create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt create mode 100644 features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index 1d78d19e09..36db926f06 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -7,7 +7,6 @@ import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentManager import com.tangem.common.extensions.VoidCallback import com.tangem.feature.referral.ReferralFragment -import com.tangem.feature.swap.presentation.SwapFragment import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.FragmentShareTransition import com.tangem.tap.features.details.ui.appsettings.AppSettingsFragment @@ -89,7 +88,7 @@ fun FragmentActivity.getPreviousScreen(): AppScreen? { fun FragmentActivity.addOnBackPressedDispatcher( isEnabled: Boolean = true, - onBackPressed: VoidCallback + onBackPressed: VoidCallback, ): OnBackPressedCallback = (object : OnBackPressedCallback(isEnabled) { override fun handleOnBackPressed() { onBackPressed() @@ -106,7 +105,7 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.OnboardingOther -> OnboardingOtherCardsFragment() AppScreen.Wallet -> WalletFragment() AppScreen.Send -> SendFragment() - AppScreen.Details -> SwapFragment()//DetailsFragment() + AppScreen.Details -> DetailsFragment() AppScreen.DetailsSecurity -> SecurityModeFragment() AppScreen.CardSettings -> CardSettingsFragment() AppScreen.AppSettings -> AppSettingsFragment() diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt index 8856cdc80d..218bf19ae5 100644 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -84,8 +84,8 @@ class UserWalletManagerImpl( mainStore.dispatch(action) } - override fun getWalletAddress(currency: Currency): String { - val blockchain = Blockchain.fromNetworkId(currency.networkId) + override fun getWalletAddress(networkId: String): String { + val blockchain = Blockchain.fromNetworkId(networkId) val card = appStateHolder.getActualCard() if (blockchain != null && card != null) { val blockchainNetwork = BlockchainNetwork(blockchain, card) diff --git a/core/datasource/build.gradle.kts b/core/datasource/build.gradle.kts index 67563bfbb8..1359125527 100644 --- a/core/datasource/build.gradle.kts +++ b/core/datasource/build.gradle.kts @@ -63,7 +63,7 @@ dependencies { implementation(Library.okHttp) implementation(Library.okHttpLogging) - kapt("com.squareup.moshi:moshi-kotlin-codegen:1.14.0") + // kapt("com.squareup.moshi:moshi-kotlin-codegen:1.14.0") /** Time */ implementation(Library.jodatime) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt index 774d001e04..e230166425 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt @@ -114,6 +114,7 @@ interface OneInchApi { * * @return [QuoteResponse] */ + @GET("quote") suspend fun quote( @Query("fromTokenAddress") fromTokenAddress: String, @Query("toTokenAddress") toTokenAddress: String, @@ -174,6 +175,7 @@ interface OneInchApi { * * @return [SwapResponse] */ + @GET("swap") suspend fun swap( @Query("fromTokenAddress") fromTokenAddress: String, @Query("toTokenAddress") toTokenAddress: String, diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt index 00638342fc..50540a226e 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt @@ -15,8 +15,7 @@ class TangemTechService( private val logIsEnabled: Boolean = false, ) { - private val - headerInterceptors = mutableListOf( + private val headerInterceptors = mutableListOf( CacheControlHttpInterceptor(cacheMaxAge), ) diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index c53b453f1b..94977976b0 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -15,6 +15,7 @@ import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Converter import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory import javax.inject.Singleton @@ -25,16 +26,12 @@ class NetworkModule { @Provides @Singleton - fun provideTangemTechApi(okHttpClient: OkHttpClient): TangemTechApi { + fun provideTangemTechApi( + okHttpClient: OkHttpClient, + converter: Converter.Factory, + ): TangemTechApi { return Retrofit.Builder() - .addConverterFactory( - MoshiConverterFactory.create( - Moshi.Builder() - .add(KotlinJsonAdapterFactory()) - .add(BigDecimalAdapter()) - .build(), - ), - ) + .addConverterFactory(converter) .baseUrl(DEV_TANGEM_TECH_BASE_URL) .client(okHttpClient) .build() @@ -44,28 +41,26 @@ class NetworkModule { @Provides @Singleton @OneInchEthereum - fun provideOneInchEthereumApi(): OneInchApi { + fun provideOneInchEthereumApi(converter: Converter.Factory): OneInchApi { return Retrofit.Builder() - .addConverterFactory( - MoshiConverterFactory.create(), - ) + .addConverterFactory(converter) .baseUrl(ONE_INCH_ETHER_BASE_URL) - .client(OkHttpClient()) + .client( + OkHttpClient.Builder() + .addInterceptor( + HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY), + ) + .build(), + ) .build() .create(OneInchApi::class.java) } @Provides @Singleton - fun provideReferralApi(okHttpClient: OkHttpClient): ReferralApi { + fun provideReferralApi(okHttpClient: OkHttpClient, converter: Converter.Factory): ReferralApi { return Retrofit.Builder() - .addConverterFactory( - MoshiConverterFactory.create( - Moshi.Builder() - .addLast(KotlinJsonAdapterFactory()) - .build(), - ), - ) + .addConverterFactory(converter) .baseUrl(DEV_TANGEM_TECH_BASE_URL) .client(okHttpClient) .build() @@ -83,9 +78,20 @@ class NetworkModule { .build() } + @Provides + @Singleton + fun provideMoshiConverter(): Converter.Factory { + return MoshiConverterFactory.create( + Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .add(BigDecimalAdapter()) + .build(), + ) + } + private companion object { const val PROD_TANGEM_TECH_BASE_URL = "https://api.tangem-tech.com/v1/" const val DEV_TANGEM_TECH_BASE_URL = "https://devapi.tangem-tech.com/v1/" - const val ONE_INCH_ETHER_BASE_URL = "https://api.1inch.io/swagger/ethereum-json/" + const val ONE_INCH_ETHER_BASE_URL = "https://api.1inch.io/v5.0/1/" } } \ No newline at end of file diff --git a/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt b/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt index 789d5d3bc8..bf2387f070 100644 --- a/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt +++ b/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt @@ -7,8 +7,9 @@ import com.tangem.feature.referral.domain.ReferralRepository import com.tangem.feature.referral.domain.models.ReferralData import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext +import javax.inject.Inject -internal class ReferralRepositoryImpl( +internal class ReferralRepositoryImpl @Inject constructor( private val referralApi: ReferralApi, private val referralConverter: ReferralConverter, private val coroutineDispatcher: CoroutineDispatcherProvider, diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt index bf17471c70..c7943b3cb6 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt @@ -26,7 +26,7 @@ internal class ReferralInteractorImpl( if (tokensForReferral.isNotEmpty()) { val currency = tokensConverter.convert(tokensForReferral.first()) deriveOrAddTokens(currency) - val publicAddress = userWalletManager.getWalletAddress(currency) + val publicAddress = userWalletManager.getWalletAddress(currency.networkId) return repository.startReferral( walletId = userWalletManager.getWalletId(), networkId = currency.networkId, diff --git a/features/swap/data/build.gradle.kts b/features/swap/data/build.gradle.kts index fff5731bd3..298948e089 100644 --- a/features/swap/data/build.gradle.kts +++ b/features/swap/data/build.gradle.kts @@ -31,8 +31,6 @@ dependencies { implementation(project(":core:utils")) implementation(project(":features:swap:domain")) - kapt("com.squareup.moshi:moshi-kotlin-codegen:1.14.0") - /** DI */ implementation(Library.hilt) kapt(Library.hiltKapt) diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt index 5424e1fd3d..7ea4007b3f 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt @@ -15,7 +15,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext import javax.inject.Inject -class SwapRepositoryImpl @Inject constructor( +internal class SwapRepositoryImpl @Inject constructor( private val tangemTechApi: TangemTechApi, private val oneInchApi: OneInchApi, private val tokensConverter: TokensConverter, diff --git a/features/swap/domain/build.gradle.kts b/features/swap/domain/build.gradle.kts index e8259cee84..0d5065fe21 100644 --- a/features/swap/domain/build.gradle.kts +++ b/features/swap/domain/build.gradle.kts @@ -1,6 +1,18 @@ plugins { id("java-library") id("org.jetbrains.kotlin.jvm") + kotlin("kapt") +} + +dependencies { + + /** Libs */ + implementation(project(":libs:crypto")) + implementation(project(":core:utils")) + + /** DI */ + implementation(Library.hiltCore) + kapt(Library.hiltKapt) } java { diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt index 5e2a5ea1aa..f75c0842ad 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt @@ -1,14 +1,14 @@ package com.tangem.feature.swap.domain +import com.tangem.feature.swap.domain.models.SwapResultModel import com.tangem.feature.swap.domain.models.data.Currency import com.tangem.feature.swap.domain.models.data.QuoteModel -import com.tangem.feature.swap.domain.models.SwapResultModel interface SwapInteractor { suspend fun getTokensToSwap(networkId: String): List - suspend fun getTokenBalance(): String + suspend fun getTokenBalance(tokenId: String): String suspend fun findBestQuote( fromTokenAddress: String, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index b057ac50f8..87e05aa527 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -1,47 +1,62 @@ package com.tangem.feature.swap.domain -import com.tangem.feature.swap.domain.models.SwapDataHolder +import com.tangem.feature.swap.domain.cache.SwapDataCache import com.tangem.feature.swap.domain.models.SwapResultModel import com.tangem.feature.swap.domain.models.data.Currency import com.tangem.feature.swap.domain.models.data.QuoteModel import com.tangem.feature.swap.domain.models.data.TransactionModel +import com.tangem.lib.crypto.UserWalletManager +import javax.inject.Inject -//todo add @Inject after merge referral and add HiltCore -class SwapInteractorImpl( +internal class SwapInteractorImpl @Inject constructor( + private val userWalletManager: UserWalletManager, private val repository: SwapRepository, + private val cache: SwapDataCache, ) : SwapInteractor { - private var lastDataForSwap: SwapDataHolder? = null - override suspend fun getTokensToSwap(networkId: String): List { - return repository.getExchangeableTokens(networkId) + val availableTokens = cache.getAvailableTokens(networkId) + return availableTokens.ifEmpty { + val tokens = repository.getExchangeableTokens(networkId) + cache.cacheAvailableToSwapTokens(networkId, tokens) + cache.cacheNetworkId(networkId) + tokens + } } - override suspend fun getTokenBalance(): String { + override suspend fun getTokenBalance(tokenId: String): String { TODO("Not yet implemented") } override suspend fun findBestQuote(fromTokenAddress: String, toTokenAddress: String, amount: String): QuoteModel { - repository.findBestQuote(fromTokenAddress, toTokenAddress, amount).let { - lastDataForSwap = SwapDataHolder(it, amount) - return it + val networkId = cache.getNetworkId() + val isAllowedToSpend = if (networkId.isNullOrEmpty()) { + false + } else { + repository.checkTokensSpendAllowance(fromTokenAddress, userWalletManager.getWalletAddress(networkId)) != "0" + } + repository.findBestQuote(fromTokenAddress, toTokenAddress, amount).let { quotes -> + cache.cacheSwapParams(quotes.copy(isAllowedToSpend = isAllowedToSpend), amount) + return quotes } } override suspend fun onSwap(): SwapResultModel { - val dataForSwap = lastDataForSwap - if (dataForSwap != null) { + val quoteModel = cache.getLastQuote() + val amountToSwap = cache.getAmountToSwap() + val networkId = cache.getNetworkId() + if (quoteModel != null && amountToSwap != null && networkId != null) { val swapData = repository.prepareSwapTransaction( - fromTokenAddress = dataForSwap.quoteModel.fromTokenAmount, - toTokenAddress = dataForSwap.quoteModel.toTokenAmount, - amount = dataForSwap.amount, + fromTokenAddress = quoteModel.fromTokenAmount, + toTokenAddress = quoteModel.toTokenAmount, + amount = amountToSwap, slippage = DEFAULT_SLIPPAGE, - fromWalletAddress = getWalletAddress(), + fromWalletAddress = getWalletAddress(networkId), ) signTransactionData(swapData.transaction) //todo implement return SwapResultModel.SwapSuccess( - dataForSwap.quoteModel.fromTokenAmount, - dataForSwap.quoteModel.toTokenAmount, + quoteModel.fromTokenAmount, + quoteModel.toTokenAmount, ) } return SwapResultModel.SwapError( @@ -50,9 +65,8 @@ class SwapInteractorImpl( ) } - //todo implement after merge referral - private fun getWalletAddress(): String { - return "" + private fun getWalletAddress(networkId: String): String { + return userWalletManager.getWalletAddress(networkId) } //todo implement after merge referral @@ -60,6 +74,6 @@ class SwapInteractorImpl( } companion object { - private const val DEFAULT_SLIPPAGE = 1 + private const val DEFAULT_SLIPPAGE = 2 } } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt new file mode 100644 index 0000000000..1f6d2373ef --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt @@ -0,0 +1,15 @@ +package com.tangem.feature.swap.domain.cache + +import com.tangem.feature.swap.domain.models.data.Currency +import com.tangem.feature.swap.domain.models.data.QuoteModel + +interface SwapDataCache { + + fun cacheNetworkId(networkId: String) + fun cacheSwapParams(quoteModel: QuoteModel, amount: String) + fun cacheAvailableToSwapTokens(networkId: String, tokens: List) + fun getAvailableTokens(networkId: String): List + fun getLastQuote(): QuoteModel? + fun getAmountToSwap(): String? + fun getNetworkId(): String? +} \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt new file mode 100644 index 0000000000..f4fd1cd174 --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt @@ -0,0 +1,39 @@ +package com.tangem.feature.swap.domain.cache + +import com.tangem.feature.swap.domain.models.SwapDataHolder +import com.tangem.feature.swap.domain.models.data.Currency +import com.tangem.feature.swap.domain.models.data.QuoteModel + +class SwapDataCacheImpl : SwapDataCache { + + private var lastDataForSwap: SwapDataHolder = SwapDataHolder() + private val availableTokensForNetwork: MutableMap> = mutableMapOf() + + override fun cacheSwapParams(quoteModel: QuoteModel, amount: String) { + lastDataForSwap = lastDataForSwap.copy(quoteModel = quoteModel, amountToSwap = amount) + } + + override fun cacheAvailableToSwapTokens(networkId: String, tokens: List) { + availableTokensForNetwork[networkId] = tokens + } + + override fun cacheNetworkId(networkId: String) { + lastDataForSwap = lastDataForSwap.copy(networkId = networkId) + } + + override fun getNetworkId(): String? { + return lastDataForSwap.networkId + } + + override fun getAvailableTokens(networkId: String): List { + return availableTokensForNetwork.getOrElse(networkId) { emptyList() } + } + + override fun getLastQuote(): QuoteModel? { + return lastDataForSwap.quoteModel + } + + override fun getAmountToSwap(): String? { + return lastDataForSwap.amountToSwap + } +} \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt new file mode 100644 index 0000000000..ef7b8e44c8 --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -0,0 +1,30 @@ +package com.tangem.feature.swap.domain.di + +import com.tangem.feature.swap.domain.SwapInteractor +import com.tangem.feature.swap.domain.SwapInteractorImpl +import com.tangem.feature.swap.domain.SwapRepository +import com.tangem.feature.swap.domain.cache.SwapDataCacheImpl +import com.tangem.lib.crypto.UserWalletManager +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class SwapDomainModule { + + @Provides + @Singleton + fun provideSwapInteractor( + referralRepository: SwapRepository, + userWalletManager: UserWalletManager, + ): SwapInteractor { + return SwapInteractorImpl( + userWalletManager = userWalletManager, + repository = referralRepository, + cache = SwapDataCacheImpl(), + ) + } +} \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataHolder.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataHolder.kt index 6216c6d4d5..60d62274de 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataHolder.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataHolder.kt @@ -3,6 +3,7 @@ package com.tangem.feature.swap.domain.models import com.tangem.feature.swap.domain.models.data.QuoteModel data class SwapDataHolder( - val quoteModel: QuoteModel, - val amount: String, + val quoteModel: QuoteModel? = null, + val amountToSwap: String? = null, + val networkId: String? = null, ) \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/QuoteModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/QuoteModel.kt index 4402e296b1..9a80b7c936 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/QuoteModel.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/QuoteModel.kt @@ -3,5 +3,6 @@ package com.tangem.feature.swap.domain.models.data data class QuoteModel( val fromTokenAmount: String, val toTokenAmount: String, - val estimatedGas: Int + val estimatedGas: Int, + val isAllowedToSpend: Boolean = false, ) \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt index 6e5a23dd82..cc841afcb7 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt @@ -1,33 +1,29 @@ package com.tangem.feature.swap.presentation import android.os.Bundle -import android.util.Log +import android.transition.TransitionInflater import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import androidx.core.view.WindowCompat import androidx.fragment.app.Fragment -import com.tangem.feature.swap.domain.SwapInteractorImpl -import com.tangem.feature.swap.domain.SwapRepository +import androidx.fragment.app.viewModels +import com.tangem.feature.swap.viewmodels.SwapViewModel import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import javax.inject.Inject @AndroidEntryPoint class SwapFragment : Fragment() { - @Inject - lateinit var swapRepository: SwapRepository + private val viewModel by viewModels() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - //test - val swapInteractor = SwapInteractorImpl(swapRepository) - - CoroutineScope(Dispatchers.IO).launch { - Log.d("test", swapInteractor.getTokensToSwap("ethereum").toString()) + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) } + viewModel + return ComposeView(inflater.context).apply { + setContent { + //todo add compose screen here + } } - - return super.onCreateView(inflater, container, savedInstanceState) } } \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt new file mode 100644 index 0000000000..84f79dad22 --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -0,0 +1,15 @@ +package com.tangem.feature.swap.viewmodels + +import androidx.lifecycle.ViewModel +import com.tangem.feature.swap.domain.SwapInteractor +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +internal class SwapViewModel @Inject constructor( + private val referralInteractor: SwapInteractor, + private val dispatchers: CoroutineDispatcherProvider, +) : ViewModel() { + +} \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt index 6df9887170..ef60119263 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt @@ -34,7 +34,7 @@ interface UserWalletManager { /** * Returns wallet public address for token * - * @param currency for which find address + * @param networkId for currency */ - fun getWalletAddress(currency: Currency): String + fun getWalletAddress(networkId: String): String } \ No newline at end of file From c8d5d8b15be1725521dd1eec87491fde90393bad Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 12 Dec 2022 16:56:24 +0300 Subject: [PATCH 003/145] Updated on 2026-08-14 --- features/swap/data/build.gradle.kts | 14 ++++++++++++++ features/swap/presentation/build.gradle.kts | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/features/swap/data/build.gradle.kts b/features/swap/data/build.gradle.kts index 298948e089..0d610522bf 100644 --- a/features/swap/data/build.gradle.kts +++ b/features/swap/data/build.gradle.kts @@ -22,6 +22,20 @@ android { targetCompatibility = JavaVersion.VERSION_1_8 isCoreLibraryDesugaringEnabled = false } + + buildTypes { + create("debug_beta") { + initWith(getByName("release")) + BuildConfigFieldFactory( + fields = listOf( + Field.Environment("release"), + Field.TestActionEnabled(true), + Field.LogEnabled(true), + ), + builder = ::buildConfigField, + ).create() + } + } } dependencies { diff --git a/features/swap/presentation/build.gradle.kts b/features/swap/presentation/build.gradle.kts index 8eac586d41..82e54a8b08 100644 --- a/features/swap/presentation/build.gradle.kts +++ b/features/swap/presentation/build.gradle.kts @@ -23,6 +23,20 @@ android { isCoreLibraryDesugaringEnabled = false } + buildTypes { + create("debug_beta") { + initWith(getByName("release")) + BuildConfigFieldFactory( + fields = listOf( + Field.Environment("release"), + Field.TestActionEnabled(true), + Field.LogEnabled(true), + ), + builder = ::buildConfigField, + ).create() + } + } + buildFeatures { compose = true } From e646fe85977dde0abf9c1ef056dffb5b1cb5f70a Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 13 Dec 2022 12:28:17 +0300 Subject: [PATCH 004/145] Updated on 2026-08-14 --- .../wallet/ui/dialogs/ScanFailsDialog.kt | 2 +- .../{strings-app.xml => strings.xml} | 228 +++++++++++++++- .../src/main/res/values-de/strings_final.xml | 232 ---------------- ..._plurals_final.xml => strings_plurals.xml} | 0 .../{strings-app.xml => strings.xml} | 228 +++++++++++++++- .../src/main/res/values-fr/strings_final.xml | 232 ---------------- ..._plurals_final.xml => strings_plurals.xml} | 0 .../src/main/res/values-it/strings-app.xml | 233 ---------------- .../src/main/res/values-it/strings_final.xml | 251 +++++++++++++++++- .../src/main/res/values-ru/strings-app.xml | 233 ---------------- .../src/main/res/values-ru/strings_final.xml | 243 ++++++++++++++++- .../values/{strings-app.xml => strings.xml} | 230 +++++++++++++++- .../res/src/main/res/values/strings_final.xml | 232 ---------------- ..._plurals_final.xml => strings_plurals.xml} | 0 14 files changed, 1147 insertions(+), 1197 deletions(-) rename core/res/src/main/res/values-de/{strings-app.xml => strings.xml} (52%) delete mode 100644 core/res/src/main/res/values-de/strings_final.xml rename core/res/src/main/res/values-de/{strings_plurals_final.xml => strings_plurals.xml} (100%) rename core/res/src/main/res/values-fr/{strings-app.xml => strings.xml} (52%) delete mode 100644 core/res/src/main/res/values-fr/strings_final.xml rename core/res/src/main/res/values-fr/{strings_plurals_final.xml => strings_plurals.xml} (100%) delete mode 100644 core/res/src/main/res/values-it/strings-app.xml delete mode 100644 core/res/src/main/res/values-ru/strings-app.xml rename core/res/src/main/res/values/{strings-app.xml => strings.xml} (51%) delete mode 100644 core/res/src/main/res/values/strings_final.xml rename core/res/src/main/res/values/{strings_plurals_final.xml => strings_plurals.xml} (100%) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt index d3bc64fa17..cef955c28e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt @@ -24,7 +24,7 @@ class ScanFailsDialog { Analytics.send(IntroductionProcess.ButtonRequestSupport()) store.dispatch(GlobalAction.SendEmail(ScanFailsEmail())) } - setNeutralButton(R.string.alert_troubleshooting_scan_card_ok) { _, _ -> } + setNeutralButton(R.string.common_cancel) { _, _ -> } setOnDismissListener { store.dispatchDialogHide() } }.create() } diff --git a/core/res/src/main/res/values-de/strings-app.xml b/core/res/src/main/res/values-de/strings.xml similarity index 52% rename from core/res/src/main/res/values-de/strings-app.xml rename to core/res/src/main/res/values-de/strings.xml index 9b09fb053e..6058ee6608 100644 --- a/core/res/src/main/res/values-de/strings-app.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -1,5 +1,223 @@ - + + Tangem Wallet + 3 cards + 2 cards + Shipping + Free + I have a promo code… + Total + Other payment methods + Buy now + Meet\nTangem + Buy + Store + Send + Pay + Exchange + Lend + Borrow + Revolutionary Hardware Wallet + Store your crypto assets secure while keeping private keys contained in your card + Ultra Secure Backup + Up to + 3 physical cards + to one wallet + Thousands of Currencies + A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card + DeFi Compatible + Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services + The Wallet for Everyone + Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. + Order + Search tokens + You are currently running in Demo mode. All funds are not real. + This feature is disabled in Demo mode + Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. + Available networks + %s network not found. Please, add it first and try again. + Attention + Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. + Tokens in Solana network are not supported by this card due to firmware limitation. + Contract address copied! + Contract address + Required field + Please select the network + Decimal number must be a valid integer, no higher than %d + Contract address is invalid + Derivation path is invalid + Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. + This token/network has already been added to your list + Decimals + Network + Not selected + E.g. USD Coin + Name + E.g. USDC + Token symbol + BIP44 coin type + Default + The server is not available, please try again later + Total balance + The amount does not include some of your funds + Tokens + Manage tokens + No rate + Network is unreachable + Hide token + Hide %s + Hide + You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. + Unable to hide %s + The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. + Your wallet has not been backed up + To protect your assets, we advise you to carry out this procedure + Erneut versuchen + Chat + %s network + I understand + Yes + No + Russian bank cards are not accepted at the moment + Do you have a bank card of another country or a UnionPay card? + This network is not supported. Please select another network. + Card Settings + Security Mode + Change Access Code + Access code will be changed on this card only + Continue + Tangem Bot + Get your card ready! + Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. + Scan Card + App Settings + Keep the wallet in the app + Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. + Save Access Code + Biometric authentication will be requested instead of the access code for interactions with your card. + Removing the saved card deletes all the saved wallets and their access codes. + This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + WalletConnect + Connect to Dapps + This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + I understand that after performing this action, I will no longer have access to the current wallet + Reset the Card + Reset to Factory Settings + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Select network + Scan your card + To access all the networks you need to scan the card + Connection with this Dapp cannot be established due to its technical implementation. + Welcome back! + Use %s or scan a card to access the app + Log in with %s + Karte scannen + Access the app + Log into the app and check your balance without scanning the card + Access code + Note that making a transaction with your funds will still require your card + My Wallets + Multi-currency + Single-currency + Add new wallet + Rename Wallet + Wallet name + Unlock all with %s + Invalid Tag. It won\'t be added to the transaction. + Invalid Memo. It won\'t be added to the transaction. + Tag + Memo + Attention + Tap the card with the visa logo + No funds for activation + Please contact support + Four identical digits isn\'t safe + Such a PIN can be brute-forced easily + Pin code + Connect + KYC + Verify your identity + Set PIN code + Register + Verify via Utorg + Refresh + Connect your card + Verify your identity + KYC is in progress + Connect your card to the decentralized payment system + To start using your card you have to pass the KYC process + Please wait until the verification is completed. You\'ll be notified via email. Usually it takes up to 1 hour. You can close the app and come back later. + PIN Code + Set PIN code for your SaltPay card + Chat with support + Please hold the card until the operation complete + To start the backup process you have to add Tangem card as your backup + No backup card + Backup card ready + Finalize the backup process by creating an access code + Prepare the SaltPay card + Tap the SaltPay card + Tap the Tangem card + Support + Claim %s + To get started, simply claim wxDAI to your wallet + Claim + Congratulations! Your first payment crypto card has been activated + Claiming + It will take a few seconds + Something went wrong + Please check your email for further instructions + Do you want to exit the activation process? + In this case, you will need to start from the beginning. + You have used a card from another wallet. Tap the card associated with this wallet + Referral program + Refer your friends to Tangem + You + Will get + for each wallet bought by your friend on your %s network address%s + Your friend + Will get a + %s discount + when buying a card on tangem.com + Your friends bought + Your personal code + Participate + terms and conditions + By tapping this button you accept + You\'ve accepted + of the referral program + Internal error: wallet manager not found + Bilanz: %s + Share + Copy + Erfolg + Give Permission + To continue you need to allow 1inch smart contracts to use your %s + Amount %s + Your Wallet + Spender + Approve + Swap of %s to + Swap + Insufficient funds + Give Permission + Permit and Swap + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Reason: %s. Please try again later. + Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Personal code copied! + Buy Tangem Wallet with discount!\n%s + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. + Ungültige Adresse + This wallet has already been saved, you can add another one + Entfernen + Privacy policy + Enable biometric authorization + It looks like you have biometric authentication disabled, it is necessary to save wallets + Enable + %d selected + biometric authentication + biometrics Tangem Änderungen speichern Warnung @@ -97,7 +315,6 @@ Can\'t send a transaction Reason: %s Are you having difficulty scanning your card? - I\'m okay Request support Send feedback Really cool! @@ -217,7 +434,7 @@ Tangem feedback Feedback Tell us what functions you are missing, and we will try to help you. - Please tell us what card do you have? + Please tell us what card do you have Please tell us more about your issue. Every small detail can help. Hi support team, The following information is optional. You can erase it if you don\'t want to share it. @@ -227,6 +444,11 @@ Fehler OK Failed to establish WalletConnect session. Please, try again later. + Would you like to use biometrics? + Biometrics will be requested instead of the access code for interactions with your wallet + Allow to use biometrics + Enable biometric authentication + Go to settings to enable biometric authentication in the Tangem App %s diff --git a/core/res/src/main/res/values-de/strings_final.xml b/core/res/src/main/res/values-de/strings_final.xml deleted file mode 100644 index bb7027a32f..0000000000 --- a/core/res/src/main/res/values-de/strings_final.xml +++ /dev/null @@ -1,232 +0,0 @@ - - - Tangem Wallet - 3 cards - 2 cards - Shipping - Free - I have a promo code… - Total - Other payment methods - Buy now - Meet\nTangem - Buy - Store - Send - Pay - Exchange - Lend - Borrow - Revolutionary Hardware Wallet - Store your crypto assets secure while keeping private keys contained in your card - Ultra Secure Backup - Up to - 3 physical cards - to one wallet - Thousands of Currencies - A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card - DeFi Compatible - Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services - The Wallet for Everyone - Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. - Order - Search tokens - You are currently running in Demo mode. All funds are not real. - This feature is disabled in Demo mode - Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. - Available networks - %s network not found. Please, add it first and try again. - Attention - Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. - Tokens in Solana network are not supported by this card due to firmware limitation. - Contract address copied! - Contract address - Required field - Please select the network - Decimal number must be a valid integer, no higher than %d - Contract address is invalid - Derivation path is invalid - Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. - This token/network has already been added to your list - Decimals - Network - Not selected - E.g. USD Coin - Name - E.g. USDC - Token symbol - BIP44 coin type - Default - The server is not available, please try again later - Total balance - The amount does not include some of your funds - Tokens - Manage tokens - No rate - Network is unreachable - Hide token - Hide %s - Hide - You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. - Unable to hide %s - The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. - Your wallet has not been backed up - To protect your assets, we advise you to carry out this procedure - Erneut versuchen - Chat - %s network - I understand - Yes - No - Russian bank cards are not accepted at the moment - Do you have a bank card of another country or a UnionPay card? - This network is not supported. Please select another network. - Card Settings - Security Mode - Change access code - Access code will be changed on this card only - Continue - Tangem Bot - Get your card ready! - Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. - Scan Card - App Settings - Keep the wallet in the app - Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. - Save Access Code - Biometric authentication will be requested instead of the access code for interactions with your card. - Removing the saved card deletes all the saved wallets and their access codes. - This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. - WalletConnect - Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - I understand that after performing this action, I will no longer have access to the current wallet - Reset the card - Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - Select network - Scan your card - To access all the networks you need to scan the card - Connection with this Dapp cannot be established due to its technical implementation. - Welcome back! - Use %s or scan a card to access the app - Log in with %s - Karte scannen - Save your Wallet - Would you like to use %s? - Would you like to use biometrics? - Access the app - Log into the app and check your balance without scanning the card - Access code - %s will be requested instead of the access code for interactions with your wallet - Biometrics will be requested instead of the access code for interactions with your wallet - Note that making a transaction with your funds will still require your card - Allow to use %s - Allow to use biometrics - New feature - My Wallets - Multi-currency - Single-currency - Add new wallet - Rename Wallet - Wallet name - Unlock all with %s - Invalid Tag. It won\'t be added to the transaction. - Invalid Memo. It won\'t be added to the transaction. - Tag - Memo - Attention - Tap the card with the visa logo - No funds for activation - Please contact support - Four identical digits isn\'t safe - Such a PIN can be brute-forced easily - Pin code - Connect - KYC - Verify your identity - Set PIN code - Register - Verify via Utorg - Refresh - Connect your card - Verify your identity - KYC is in progress - Connect your card to the decentralized payment system - To start using your card you have to pass the KYC process - Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. - PIN Code - Set PIN code for your SaltPay card - Chat with support - Please hold the card until the operation complete - To start the backup process you have to add Tangem card as your backup - No backup card - Backup card ready - Finalize the backup process by creating an access code - Prepare the SaltPay card - Tap the SaltPay card - Tap the Tangem card - Support - Claim %s - To get started, simply claim wxDAI to your wallet - Claim - Congratulations! Your first payment crypto card has been activated - Claiming - It will take a few seconds - Something went wrong - Please check you email for further instructions - Do you want to exit the activation process? - In this case, you will need to start from the beginning. - You have used a card from another wallet. Tap the card associated with this wallet - Referral program - Refer your friends to Tangem - You - Will get - for each wallet bought by your friend on your %s network address%s - Your friend - Will get a - %s discount - when buying a card on tangem.com - Your friends bought - Your personal code - Participate - terms and conditions - By tapping this button you accept - You\'ve accepted - of the referral program - Internal error: wallet manager not found - Bilanz: %s - Share - Copy - Erfolg - Give Permission - To continue you need to allow 1inch smart contracts to use your %s - Amount %s - Your Wallet - Spender - Approve - Swap of %s to - Swap - Insufficient funds - Give Permission - Permit and Swap - Failed to load the information about the referral program. Please try again later. - Failed to load the information about the referral program. Reason: %s. Please try again later. - Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. - Personal code copied! - Buy Tangem Wallet with discount!\n%s - %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. - Ungültige Adresse - This wallet has already been saved, you can add another one - Entfernen - Privacy policy - Enable biometric authorization - It looks like you have biometric authentication disabled, it is necessary to save wallets - Enable - Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. - %d selected - biometric authentication - biometrics - Enable biometric authentication - Go to settings to enable biometric authentication in the Tangem App - diff --git a/core/res/src/main/res/values-de/strings_plurals_final.xml b/core/res/src/main/res/values-de/strings_plurals.xml similarity index 100% rename from core/res/src/main/res/values-de/strings_plurals_final.xml rename to core/res/src/main/res/values-de/strings_plurals.xml diff --git a/core/res/src/main/res/values-fr/strings-app.xml b/core/res/src/main/res/values-fr/strings.xml similarity index 52% rename from core/res/src/main/res/values-fr/strings-app.xml rename to core/res/src/main/res/values-fr/strings.xml index 8d7d037a69..5df2ade184 100644 --- a/core/res/src/main/res/values-fr/strings-app.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -1,5 +1,223 @@ - + + Tangem Wallet + 3 cards + 2 cards + Shipping + Free + I have a promo code… + Total + Other payment methods + Buy now + Meet\nTangem + Buy + Store + Send + Pay + Exchange + Lend + Borrow + Revolutionary Hardware Wallet + Store your crypto assets secure while keeping private keys contained in your card + Ultra Secure Backup + Up to + 3 physical cards + to one wallet + Thousands of Currencies + A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card + DeFi Compatible + Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services + The Wallet for Everyone + Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. + Order + Search tokens + You are currently running in Demo mode. All funds are not real. + This feature is disabled in Demo mode + Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. + Available networks + %s network not found. Please, add it first and try again. + Attention + Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. + Tokens in Solana network are not supported by this card due to firmware limitation. + Contract address copied! + Contract address + Required field + Please select the network + Decimal number must be a valid integer, no higher than %d + Contract address is invalid + Derivation path is invalid + Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. + This token/network has already been added to your list + Decimals + Network + Not selected + E.g. USD Coin + Name + E.g. USDC + Token symbol + BIP44 coin type + Default + The server is not available, please try again later + Total balance + The amount does not include some of your funds + Tokens + Manage tokens + No rate + Network is unreachable + Hide token + Hide %s + Hide + You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. + Unable to hide %s + The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. + Your wallet has not been backed up + To protect your assets, we advise you to carry out this procedure + Réessayer + Chat + %s network + I understand + Yes + No + Russian bank cards are not accepted at the moment + Do you have a bank card of another country or a UnionPay card? + This network is not supported. Please select another network. + Card Settings + Security Mode + Change Access Code + Access code will be changed on this card only + Continue + Tangem Bot + Get your card ready! + Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. + Scan Card + App Settings + Keep the wallet in the app + Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. + Save Access Code + Biometric authentication will be requested instead of the access code for interactions with your card. + Removing the saved card deletes all the saved wallets and their access codes. + This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + WalletConnect + Connect to Dapps + This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + I understand that after performing this action, I will no longer have access to the current wallet + Reset the Card + Reset to Factory Settings + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Select network + Scan your card + To access all the networks you need to scan the card + Connection with this Dapp cannot be established due to its technical implementation. + Welcome back! + Use %s or scan a card to access the app + Log in with %s + Scannez la carte + Access the app + Log into the app and check your balance without scanning the card + Access code + Note that making a transaction with your funds will still require your card + My Wallets + Multi-currency + Single-currency + Add new wallet + Rename Wallet + Wallet name + Unlock all with %s + Invalid Tag. It won\'t be added to the transaction. + Invalid Memo. It won\'t be added to the transaction. + Tag + Memo + Attention + Tap the card with the visa logo + No funds for activation + Please contact support + Four identical digits isn\'t safe + Such a PIN can be brute-forced easily + Pin code + Connect + KYC + Verify your identity + Set PIN code + Register + Verify via Utorg + Refresh + Connect your card + Verify your identity + KYC is in progress + Connect your card to the decentralized payment system + To start using your card you have to pass the KYC process + Please wait until the verification is completed. You\'ll be notified via email. Usually it takes up to 1 hour. You can close the app and come back later. + PIN Code + Set PIN code for your SaltPay card + Chat with support + Please hold the card until the operation complete + To start the backup process you have to add Tangem card as your backup + No backup card + Backup card ready + Finalize the backup process by creating an access code + Prepare the SaltPay card + Tap the SaltPay card + Tap the Tangem card + Support + Claim %s + To get started, simply claim wxDAI to your wallet + Claim + Congratulations! Your first payment crypto card has been activated + Claiming + It will take a few seconds + Something went wrong + Please check your email for further instructions + Do you want to exit the activation process? + In this case, you will need to start from the beginning. + You have used a card from another wallet. Tap the card associated with this wallet + Referral program + Refer your friends to Tangem + You + Will get + for each wallet bought by your friend on your %s network address%s + Your friend + Will get a + %s discount + when buying a card on tangem.com + Your friends bought + Your personal code + Participate + terms and conditions + By tapping this button you accept + You\'ve accepted + of the referral program + Internal error: wallet manager not found + Solde : %s + Share + Copy + Avec succès + Give Permission + To continue you need to allow 1inch smart contracts to use your %s + Amount %s + Your Wallet + Spender + Approve + Swap of %s to + Swap + Insufficient funds + Give Permission + Permit and Swap + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Reason: %s. Please try again later. + Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Personal code copied! + Buy Tangem Wallet with discount!\n%s + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. + Adresse incorrecte + This wallet has already been saved, you can add another one + Supprimer + Privacy policy + Enable biometric authorization + It looks like you have biometric authentication disabled, it is necessary to save wallets + Enable + %d selected + biometric authentication + biometrics Tangem Sauvegarder les modifications Alerte @@ -97,7 +315,6 @@ Can\'t send a transaction Reason: %s Are you having difficulty scanning your card? - I\'m okay Request support Send feedback Really cool! @@ -217,7 +434,7 @@ Tangem feedback Feedback Tell us what functions you are missing, and we will try to help you. - Please tell us what card do you have? + Please tell us what card do you have Please tell us more about your issue. Every small detail can help. Hi support team, The following information is optional. You can erase it if you don\'t want to share it. @@ -227,6 +444,11 @@ Erreur OK Failed to establish WalletConnect session. Please, try again later. + Would you like to use biometrics? + Biometrics will be requested instead of the access code for interactions with your wallet + Allow to use biometrics + Enable biometric authentication + Go to settings to enable biometric authentication in the Tangem App %s diff --git a/core/res/src/main/res/values-fr/strings_final.xml b/core/res/src/main/res/values-fr/strings_final.xml deleted file mode 100644 index bacd616a66..0000000000 --- a/core/res/src/main/res/values-fr/strings_final.xml +++ /dev/null @@ -1,232 +0,0 @@ - - - Tangem Wallet - 3 cards - 2 cards - Shipping - Free - I have a promo code… - Total - Other payment methods - Buy now - Meet\nTangem - Buy - Store - Send - Pay - Exchange - Lend - Borrow - Revolutionary Hardware Wallet - Store your crypto assets secure while keeping private keys contained in your card - Ultra Secure Backup - Up to - 3 physical cards - to one wallet - Thousands of Currencies - A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card - DeFi Compatible - Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services - The Wallet for Everyone - Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. - Order - Search tokens - You are currently running in Demo mode. All funds are not real. - This feature is disabled in Demo mode - Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. - Available networks - %s network not found. Please, add it first and try again. - Attention - Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. - Tokens in Solana network are not supported by this card due to firmware limitation. - Contract address copied! - Contract address - Required field - Please select the network - Decimal number must be a valid integer, no higher than %d - Contract address is invalid - Derivation path is invalid - Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. - This token/network has already been added to your list - Decimals - Network - Not selected - E.g. USD Coin - Name - E.g. USDC - Token symbol - BIP44 coin type - Default - The server is not available, please try again later - Total balance - The amount does not include some of your funds - Tokens - Manage tokens - No rate - Network is unreachable - Hide token - Hide %s - Hide - You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. - Unable to hide %s - The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. - Your wallet has not been backed up - To protect your assets, we advise you to carry out this procedure - Réessayer - Chat - %s network - I understand - Yes - No - Russian bank cards are not accepted at the moment - Do you have a bank card of another country or a UnionPay card? - This network is not supported. Please select another network. - Card Settings - Security Mode - Change access code - Access code will be changed on this card only - Continue - Tangem Bot - Get your card ready! - Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. - Scan Card - App Settings - Keep the wallet in the app - Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. - Save Access Code - Biometric authentication will be requested instead of the access code for interactions with your card. - Removing the saved card deletes all the saved wallets and their access codes. - This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. - WalletConnect - Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - I understand that after performing this action, I will no longer have access to the current wallet - Reset the card - Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - Select network - Scan your card - To access all the networks you need to scan the card - Connection with this Dapp cannot be established due to its technical implementation. - Welcome back! - Use %s or scan a card to access the app - Log in with %s - Scannez la carte - Save your Wallet - Would you like to use %s? - Would you like to use biometrics? - Access the app - Log into the app and check your balance without scanning the card - Access code - %s will be requested instead of the access code for interactions with your wallet - Biometrics will be requested instead of the access code for interactions with your wallet - Note that making a transaction with your funds will still require your card - Allow to use %s - Allow to use biometrics - New feature - My Wallets - Multi-currency - Single-currency - Add new wallet - Rename Wallet - Wallet name - Unlock all with %s - Invalid Tag. It won\'t be added to the transaction. - Invalid Memo. It won\'t be added to the transaction. - Tag - Memo - Attention - Tap the card with the visa logo - No funds for activation - Please contact support - Four identical digits isn\'t safe - Such a PIN can be brute-forced easily - Pin code - Connect - KYC - Verify your identity - Set PIN code - Register - Verify via Utorg - Refresh - Connect your card - Verify your identity - KYC is in progress - Connect your card to the decentralized payment system - To start using your card you have to pass the KYC process - Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. - PIN Code - Set PIN code for your SaltPay card - Chat with support - Please hold the card until the operation complete - To start the backup process you have to add Tangem card as your backup - No backup card - Backup card ready - Finalize the backup process by creating an access code - Prepare the SaltPay card - Tap the SaltPay card - Tap the Tangem card - Support - Claim %s - To get started, simply claim wxDAI to your wallet - Claim - Congratulations! Your first payment crypto card has been activated - Claiming - It will take a few seconds - Something went wrong - Please check you email for further instructions - Do you want to exit the activation process? - In this case, you will need to start from the beginning. - You have used a card from another wallet. Tap the card associated with this wallet - Referral program - Refer your friends to Tangem - You - Will get - for each wallet bought by your friend on your %s network address%s - Your friend - Will get a - %s discount - when buying a card on tangem.com - Your friends bought - Your personal code - Participate - terms and conditions - By tapping this button you accept - You\'ve accepted - of the referral program - Internal error: wallet manager not found - Solde : %s - Share - Copy - Avec succès - Give Permission - To continue you need to allow 1inch smart contracts to use your %s - Amount %s - Your Wallet - Spender - Approve - Swap of %s to - Swap - Insufficient funds - Give Permission - Permit and Swap - Failed to load the information about the referral program. Please try again later. - Failed to load the information about the referral program. Reason: %s. Please try again later. - Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. - Personal code copied! - Buy Tangem Wallet with discount!\n%s - %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. - Adresse incorrecte - This wallet has already been saved, you can add another one - Supprimer - Privacy policy - Enable biometric authorization - It looks like you have biometric authentication disabled, it is necessary to save wallets - Enable - Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. - %d selected - biometric authentication - biometrics - Enable biometric authentication - Go to settings to enable biometric authentication in the Tangem App - diff --git a/core/res/src/main/res/values-fr/strings_plurals_final.xml b/core/res/src/main/res/values-fr/strings_plurals.xml similarity index 100% rename from core/res/src/main/res/values-fr/strings_plurals_final.xml rename to core/res/src/main/res/values-fr/strings_plurals.xml diff --git a/core/res/src/main/res/values-it/strings-app.xml b/core/res/src/main/res/values-it/strings-app.xml deleted file mode 100644 index c5e3134366..0000000000 --- a/core/res/src/main/res/values-it/strings-app.xml +++ /dev/null @@ -1,233 +0,0 @@ - - - Tangem - Mantieni le modifiche - Avviso - Fatto - Annulla - Accetta - Non hai fornito l\'accesso alla tua videocamera, modifica le tue impostazioni sulla privacy - Invia - Crea portafoglio - Tangem - Saldo verificato - Il saldo sta per essere caricato… - Conto non creato - Carta vuota - Crea un portafoglio per iniziare ad utilizzare la tua carta Tangem - Questa carta non è supportata - La tua carta Tangem è stata creata per funzionare con un\'altra applicazione. Leggere il nome e le istruzioni sulla carta e installare l\'applicazione corretta - Transazione in corso… - Riceviu - da %s - Inviau - su %s - Cerca indirizzo - Crea PayString - Crea PayString - Nome PayString - $payid.tangem.com - Il tuo PayString – informazioni per te uniche, come il tuo numero di telefono, indirizzo email o ABN. - Errore di risposta durante la creazione del PayString - Questo PayString esiste già. Scegline un altro. - Indirizzo o PayString - Indirizzo - Invia - Costi della rete - Importo - Commissione - Totale - Importo totale - Insufficiente - Normale - Prioritario - Includi commissione - Sarà inviato %s - Sarà inviato %s e %s - La transazione è stata firmata con successo e inviata al nodo blockchain. Il saldo del portafoglio verrà aggiornato dopo un po\' di tempo - Requisiti - ID carta - Emittente - Firmato - Hash %s - Valuta dell\'applicazione - Mantenimento della carta - Questo meccanismo protegge dagli avvicinamenti senza contatto sulla carta. Attiva un ritardo tra la ricezione e l\'esecuzione di un comando. Dopo la prima transazione firmata, questo telefono verrà associato alla carta e le transazioni verranno firmate immediatamente. - Password - Dovrai inserire una password prima di eseguire qualsiasi comando che modifichi lo stato della carta. - Codice di accesso - Prima di scansionare la carta sarà necessario inserire il codice di accesso corretto - Questa carta è già stata ricaricata e ha firmato transazioni in passato. Valuta la possibilità di prelevare immediatamente tutti i fondi se hai ricevuto questa carta da una fonte inaffidabile. - Questa carta non è progettata per funzionare con Tangem - La carta che hai scansionato è una carta di sviluppo. Non utilizzarla come strumento di pagamento. - Avvicina per firmare - To create wallet, connect your phone and the card exactly as it shown above - Avvicina per modificare il codice di accesso - Avvicina per modificare la password - Termini del servizio - Nessuna connessione a Internet - PayString non supportato dalla blockchain - PayString non registrato - Richiesta PayString fallita - L\'indirizzo corrisponde all\'indirizzo del tuo portafoglio - L\'indirizzo è stato copiato con successo - Inserire prima il PayString desiderato - Avvicina per scansionare - Avvicina la carta - Scan card failed. Try again - Buy - Sell - Exchange - Create twin wallet - Generate wallet keys on both cards to start using your Twins - One wallet. Two cards. - This one that you are holding in your hands and the other one with number %s.\n\nBoth cards can be used to extract funds from this wallet. - Card terms of use - Tangem Twin - This action is irreversible. You will not have access to the old wallet. - Scan the #%s twin card - Preparing card - Creating wallet - Scan the card #%s - You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d - Start - Add - Search - Send Feedback - Can\'t send a transaction - Reason: %s - Are you having difficulty scanning your card? - I\'m okay - Request support - Send feedback - Really cool! - Can be better - One question - How do you like Tangem? - Add custom token - Please wait for %s transaction to complete to be able to send funds - Authenticity check failed - This card might be a production sample or counterfeit - Important security information \u26A0 - This card has signed transactions in the past - Learn more - There are only %s signatures available on this card. You must withdraw all of your funds. - Disconnect - Sign - Sign and send - Reject - This is a Testnet card. Don\'t accept it as a payment. This card must only be used for testing and development purposes. - Do you want to buy or sell crypto? - Oops, the current version of the application is not ready to work with this card, please check for updates. - Activating card - How does it work? - Receive crypto - Create a wallet - Let\'s generate all the keys on your card and create a secure wallet - Create wallet - Top up your wallet - To get started, simply top up the wallet with any amount - To get started, simply top up the wallet with more than %s %s - Buy crypto - Show the wallet\'s address - Success! - Your card is activated and ready to be used - Balance - Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. - Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. - If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. - The twinning process is partly complete. You can\'t exit it now. - Ok, ho capito! - Getting started - Backup now - Skip for later - Finalize the backup process - Add a backup card - Scan the card #%d - Scan primary card - No backup cards - Prepare your card - One backup card added - Two backup cards added - Backup card #%d - To start the backup process add up to two backup cards. - Prepare the primary card - You can add one more card or finalize the backup process - Max number of cards added. Finalize the backup process. - You\'ve added one backup card. When backup process is finished you can\'t add more backup cards. If you have one more card, add it to backup. Do you like to continue the backup process? - Create Access Code - Re-enter your Access Code - Protect - Personalize - Restore - You have to set up a single access code to protect all your wallets. - You can set an individual access code on each card later. - The access code can be restored with a linked card, don\'t keep all cards in one place. - Choose any word, phrase, or number you want as your access code. - Access code must be at least 4 characters long - Entered access code didn\'t match the initial access code - Create - Submit - Creating a backup - The backup process is partly complete. You can\'t exit it now. - Backup wallet - Identical cards - Access code - Access code restore - You can backup your keys up to two other blank Tangem Wallet cards. - All the backup cards can be used as full-functional with the identical keys. - You will be able to set an access code to protect your wallets. - Access code can be restored with one of backup cards. - Your wallet card is configured and ready for use. - Link More Cards - You have an interrupted backup. Do you want to resume? - Yes, resume - Discard - This is an irreversible action - If you will discard the backup now, then you will have to reset the cards to factory settings to start over again - Resume backup - Discard - Scan the primary card - Continue to my wallet - Sending %s - Prepare the backup card with number %s - Prepare the primary card with number %s - Solana network charges a rent of %s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %s to use it for free. - This card is not a bearer note. We can\'t currently match the signature count on the card with the information on the blockchain. This is normal but in rare cases can mean a previous holder is holding back an offline signature, which is a security concern.\nDo not accept this card as physical payment from someone you don\'t trust.\nIt\'s perfectly safe in all other respects.\nTangem is the only hardware wallet to offer signature count protection. - Request to create transaction for %s\n%s\n\nAmount: %s\nFee: %s\nTotal: %s\nBalance: %s - Request to start a session for\n%s\n\nNETWORK: %s\n\nURL: %s - WalletConnect Sessions - Ooops. No Sessions. - No opened WalletConnect sessions - Open session - Can\'t send transaction. Not enough funds. - Requesting to sign a message.\n\n%s - Clipboard contain WalletConnect code. Use copied value or scan QR-code - Paste from clipboard - Scan new code - This card can\'t be used to establish WalletConnect session - The operation couldn\'t be completed. \n\nYou have already established a WalletConnect session with this parameters. - Failed to establish WalletConnect session: timeout error. Please, try again later. - Dapp %s, requesting to\nsign BNB transaction.\n\n%s - Transaction details:\nFrom: %s\nTo: %s\nAmount: %s - Trade order for %s\nPrice: %s\nAmount to receive: %s\nAmount to pay: %s - My suggestions - Can\'t scan a card - Can\'t send a transaction - Tangem feedback - Feedback - Tell us what functions you are missing, and we will try to help you. - Please tell us what card do you have? - Please tell us more about your issue. Every small detail can help. - Hi support team, - The following information is optional. You can erase it if you don\'t want to share it. - ≈ %s (inc. commissione: %s) - Primary Card - Blockchain is unreachable. Try later - Errore - OK - Failed to establish WalletConnect session. Please, try again later. - - - %s - \ No newline at end of file diff --git a/core/res/src/main/res/values-it/strings_final.xml b/core/res/src/main/res/values-it/strings_final.xml index 34a7c383db..15531dbf86 100644 --- a/core/res/src/main/res/values-it/strings_final.xml +++ b/core/res/src/main/res/values-it/strings_final.xml @@ -83,7 +83,7 @@ This network is not supported. Please select another network. Card Settings Security Mode - Change access code + Change Access Code Access code will be changed on this card only Continue Tangem Bot @@ -101,7 +101,7 @@ Connect to Dapps This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. I understand that after performing this action, I will no longer have access to the current wallet - Reset the card + Reset the Card Reset to Factory Settings Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. Select network @@ -112,18 +112,10 @@ Use %s or scan a card to access the app Log in with %s Scansiona carta - Save your Wallet - Would you like to use %s? - Would you like to use biometrics? Access the app Log into the app and check your balance without scanning the card Access code - %s will be requested instead of the access code for interactions with your wallet - Biometrics will be requested instead of the access code for interactions with your wallet Note that making a transaction with your funds will still require your card - Allow to use %s - Allow to use biometrics - New feature My Wallets Multi-currency Single-currency @@ -154,7 +146,7 @@ KYC is in progress Connect your card to the decentralized payment system To start using your card you have to pass the KYC process - Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. + Please wait until the verification is completed. You\'ll be notified via email. Usually it takes up to 1 hour. You can close the app and come back later. PIN Code Set PIN code for your SaltPay card Chat with support @@ -174,7 +166,7 @@ Claiming It will take a few seconds Something went wrong - Please check you email for further instructions + Please check your email for further instructions Do you want to exit the activation process? In this case, you will need to start from the beginning. You have used a card from another wallet. Tap the card associated with this wallet @@ -223,10 +215,241 @@ Enable biometric authorization It looks like you have biometric authentication disabled, it is necessary to save wallets Enable - Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. %d selected biometric authentication biometrics + Tangem + Mantieni le modifiche + Avviso + Fatto + Annulla + Accetta + Non hai fornito l\'accesso alla tua videocamera, modifica le tue impostazioni sulla privacy + Invia + Crea portafoglio + Tangem + Saldo verificato + Il saldo sta per essere caricato… + Conto non creato + Carta vuota + Crea un portafoglio per iniziare ad utilizzare la tua carta Tangem + Questa carta non è supportata + La tua carta Tangem è stata creata per funzionare con un\'altra applicazione. Leggere il nome e le istruzioni sulla carta e installare l\'applicazione corretta + Transazione in corso… + Riceviu + da %s + Inviau + su %s + Cerca indirizzo + Crea PayString + Crea PayString + Nome PayString + $payid.tangem.com + Il tuo PayString – informazioni per te uniche, come il tuo numero di telefono, indirizzo email o ABN. + Errore di risposta durante la creazione del PayString + Questo PayString esiste già. Scegline un altro. + Indirizzo o PayString + Indirizzo + Invia + Costi della rete + Importo + Commissione + Totale + Importo totale + Insufficiente + Normale + Prioritario + Includi commissione + Sarà inviato %s + Sarà inviato %s e %s + La transazione è stata firmata con successo e inviata al nodo blockchain. Il saldo del portafoglio verrà aggiornato dopo un po\' di tempo + Requisiti + ID carta + Emittente + Firmato + Hash %s + Valuta dell\'applicazione + Mantenimento della carta + Questo meccanismo protegge dagli avvicinamenti senza contatto sulla carta. Attiva un ritardo tra la ricezione e l\'esecuzione di un comando. Dopo la prima transazione firmata, questo telefono verrà associato alla carta e le transazioni verranno firmate immediatamente. + Password + Dovrai inserire una password prima di eseguire qualsiasi comando che modifichi lo stato della carta. + Codice di accesso + Prima di scansionare la carta sarà necessario inserire il codice di accesso corretto + Questa carta è già stata ricaricata e ha firmato transazioni in passato. Valuta la possibilità di prelevare immediatamente tutti i fondi se hai ricevuto questa carta da una fonte inaffidabile. + Questa carta non è progettata per funzionare con Tangem + La carta che hai scansionato è una carta di sviluppo. Non utilizzarla come strumento di pagamento. + Avvicina per firmare + To create wallet, connect your phone and the card exactly as it shown above + Avvicina per modificare il codice di accesso + Avvicina per modificare la password + Termini del servizio + Nessuna connessione a Internet + PayString non supportato dalla blockchain + PayString non registrato + Richiesta PayString fallita + L\'indirizzo corrisponde all\'indirizzo del tuo portafoglio + L\'indirizzo è stato copiato con successo + Inserire prima il PayString desiderato + Avvicina per scansionare + Avvicina la carta + Scan card failed. Try again + Buy + Sell + Exchange + Create twin wallet + Generate wallet keys on both cards to start using your Twins + One wallet. Two cards. + This one that you are holding in your hands and the other one with number %s.\n\nBoth cards can be used to extract funds from this wallet. + Card terms of use + Tangem Twin + This action is irreversible. You will not have access to the old wallet. + Scan the #%s twin card + Preparing card + Creating wallet + Scan the card #%s + You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d + Start + Add + Search + Send Feedback + Can\'t send a transaction + Reason: %s + Are you having difficulty scanning your card? + Request support + Send feedback + Really cool! + Can be better + One question + How do you like Tangem? + Add custom token + Please wait for %s transaction to complete to be able to send funds + Authenticity check failed + This card might be a production sample or counterfeit + Important security information \u26A0 + This card has signed transactions in the past + Learn more + There are only %s signatures available on this card. You must withdraw all of your funds. + Disconnect + Sign + Sign and send + Reject + This is a Testnet card. Don\'t accept it as a payment. This card must only be used for testing and development purposes. + Do you want to buy or sell crypto? + Oops, the current version of the application is not ready to work with this card, please check for updates. + Activating card + How does it work? + Receive crypto + Create a wallet + Let\'s generate all the keys on your card and create a secure wallet + Create wallet + Top up your wallet + To get started, simply top up the wallet with any amount + To get started, simply top up the wallet with more than %s %s + Buy crypto + Show the wallet\'s address + Success! + Your card is activated and ready to be used + Balance + Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. + Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. + If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. + The twinning process is partly complete. You can\'t exit it now. + Ok, ho capito! + Getting started + Backup now + Skip for later + Finalize the backup process + Add a backup card + Scan the card #%d + Scan primary card + No backup cards + Prepare your card + One backup card added + Two backup cards added + Backup card #%d + To start the backup process add up to two backup cards. + Prepare the primary card + You can add one more card or finalize the backup process + Max number of cards added. Finalize the backup process. + You\'ve added one backup card. When backup process is finished you can\'t add more backup cards. If you have one more card, add it to backup. Do you like to continue the backup process? + Create Access Code + Re-enter your Access Code + Protect + Personalize + Restore + You have to set up a single access code to protect all your wallets. + You can set an individual access code on each card later. + The access code can be restored with a linked card, don\'t keep all cards in one place. + Choose any word, phrase, or number you want as your access code. + Access code must be at least 4 characters long + Entered access code didn\'t match the initial access code + Create + Submit + Creating a backup + The backup process is partly complete. You can\'t exit it now. + Backup wallet + Identical cards + Access code + Access code restore + You can backup your keys up to two other blank Tangem Wallet cards. + All the backup cards can be used as full-functional with the identical keys. + You will be able to set an access code to protect your wallets. + Access code can be restored with one of backup cards. + Your wallet card is configured and ready for use. + Link More Cards + You have an interrupted backup. Do you want to resume? + Yes, resume + Discard + This is an irreversible action + If you will discard the backup now, then you will have to reset the cards to factory settings to start over again + Resume backup + Discard + Scan the primary card + Continue to my wallet + Sending %s + Prepare the backup card with number %s + Prepare the primary card with number %s + Solana network charges a rent of %s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %s to use it for free. + This card is not a bearer note. We can\'t currently match the signature count on the card with the information on the blockchain. This is normal but in rare cases can mean a previous holder is holding back an offline signature, which is a security concern.\nDo not accept this card as physical payment from someone you don\'t trust.\nIt\'s perfectly safe in all other respects.\nTangem is the only hardware wallet to offer signature count protection. + Request to create transaction for %s\n%s\n\nAmount: %s\nFee: %s\nTotal: %s\nBalance: %s + Request to start a session for\n%s\n\nNETWORK: %s\n\nURL: %s + WalletConnect Sessions + Ooops. No Sessions. + No opened WalletConnect sessions + Open session + Can\'t send transaction. Not enough funds. + Requesting to sign a message.\n\n%s + Clipboard contain WalletConnect code. Use copied value or scan QR-code + Paste from clipboard + Scan new code + This card can\'t be used to establish WalletConnect session + The operation couldn\'t be completed. \n\nYou have already established a WalletConnect session with this parameters. + Failed to establish WalletConnect session: timeout error. Please, try again later. + Dapp %s, requesting to\nsign BNB transaction.\n\n%s + Transaction details:\nFrom: %s\nTo: %s\nAmount: %s + Trade order for %s\nPrice: %s\nAmount to receive: %s\nAmount to pay: %s + My suggestions + Can\'t scan a card + Can\'t send a transaction + Tangem feedback + Feedback + Tell us what functions you are missing, and we will try to help you. + Please tell us what card do you have + Please tell us more about your issue. Every small detail can help. + Hi support team, + The following information is optional. You can erase it if you don\'t want to share it. + ≈ %s (inc. commissione: %s) + Primary Card + Blockchain is unreachable. Try later + Errore + OK + Failed to establish WalletConnect session. Please, try again later. + Would you like to use biometrics? + Biometrics will be requested instead of the access code for interactions with your wallet + Allow to use biometrics Enable biometric authentication Go to settings to enable biometric authentication in the Tangem App - + + + %s + \ No newline at end of file diff --git a/core/res/src/main/res/values-ru/strings-app.xml b/core/res/src/main/res/values-ru/strings-app.xml deleted file mode 100644 index 34b9be72db..0000000000 --- a/core/res/src/main/res/values-ru/strings-app.xml +++ /dev/null @@ -1,233 +0,0 @@ - - - Tangem - Сохранить изменения - Предупреждение - Готово - Отмена - Принять - Вы не предоставили доступ к камере, пожалуйста, измените настройки конфиденциальности. - Отправить - Создать кошелек - Tangem - Подтвержденный баланс - Баланс загружается… - Аккаунт не создан - Карта пуста - Создайте кошелек, чтобы начать пользоваться картой Tangem - Эта карта не поддерживается - Ваша карта Tangem предназначена для работы с другим приложением. Пожалуйста, ознакомьтесь с названием и инструкциями на вашей карте и установите правильное приложение. - Транзакция подтверждается… - Получение - от %s - Отправка - к %s - Проверить адрес - Создать PayString - Создать PayString - PayString имя - $payid.tangem.com - Ваша PayString — это уникальная для вас информация, такая как номер телефона, электронная почта или ABN - Ошибка при создании PayString - Такой PayString уже существует. Попробуйте другой. - Адрес или PayString - Адрес - Отправить - Сетевая комиссия - Сумма - Комиссия - Всего - Максимальная сумма - Низкая - Нормальная - Приоритетная - Включая комиссию - %s будет отправлено - %s и %s будет отправлено - Транзакция успешно подписана и отправлена в блокчейн. Баланс будет обновлен через некоторое время - Подробности - Номер карты - Эмитент - Подписано - %s хэшей - Валюта приложения - Задержка сканирования - Этот механизм защищает карту от бесконтактных атак. Между сканированием карты и выполнением команды будет добавлена задержка. - Пароль - Перед выполнением любой команды, влекущей за собой изменение состояния карты, вам необходимо будет ввести пароль. - Код доступа - Перед сканированием карты вам нужно будет ввести правильный код доступа. - Эта карта ранее пополнялась и подписывала транзакции. Выведите средства как можно быстрее, если вы получили эту карту из ненадежного источника. Если это ваша карта, то не о чем беспокоиться. - Эта карта не предназначена для работы с этим приложением - Карта, которую вы отсканировали, является картой разработчика. Не принимайте её в качестве оплаты. - Нажмите, чтобы подписать - Чтобы создать кошелек, соедините телефон и карту в точности, как показано выше. - Чтобы изменить код доступа, соедините телефон и карту в точности, как показано выше. - Чтобы изменить пароль, соедините телефон и карту в точности, как показано выше. - Условия использования - Нет соединения с интернетом - PayString не поддерживается блокчейном - PayString не зарегистрирован - Не удалось выполнить запрос PayString - Адрес совпадает с адресом кошелька - Адрес скопирован в буфер обмена - Сначала введите желаемую PayString - Приложите, чтобы отсканировать - Приложите карту - Ошибка сканирования карты. Попробуй снова. - Купить - Продать - Обменять - Создать кошелек - Сгенерируйте ключи кошелька на обеих картах, чтобы начать использовать свои Twin карты - Один кошелек. Две карты. - Это карта, которую вы держите в руках. У парной карты номер %s.\n\nОбе карты можно использовать для вывода средств из этого кошелька. - Условия использования карты - Tangem Twin - Это действие необратимо. У вас не будет доступа к старому кошельку. - Отсканируйте twin-карту #%s - Подготовка карты - Создание кошелька - Сканировать карту #%s - Вы отсканировали ту же карту. Для создания twin-кошелька вам необходимо отсканировать карту с номером %d - Начать - Добавить - Искать - Отправить отзыв - Не могу отправить транзакцию - Причина: %s - У вас возникли трудности со сканированием карты? - Отмена - Обратиться в поддержку - Отправить отзыв - Очень круто! - Можно лучше - Один вопрос - Как вам Tangem? - Добавить токен - Пожалуйста, дождитесь завершения транзакции %s, чтобы иметь возможность отправить средства - Проверка подлинности не удалась - Эта карта может быть производственным образцом или подделкой - Важная информация о безопасности \u26A0 - Эта карта подписывала транзакции в прошлом - Узнать больше - На этой карте осталось только %s подписей. Вы должны вывести все свои средства. - Отключить - Подписать - Подписать и отправить - Отклонить - Это тестовая карта. Не принимайте её в качестве оплаты. Эта карта должна использоваться только в целях тестирования и разработки. - Вы хотите купить или продать криптовалюту? - К сожалению, текущая версия приложения не готова к работе с этой картой, проверьте наличие обновлений - Активация карты - Как это работает? - Получить криптовалюту - Создать кошелек - Давайте сгенерируем все ключи на вашей карте и создадим безопасный кошелек - Создать кошелек - Пополните свой кошелек - Пополните кошелек на любую сумму, чтобы начать пользоваться картой - Пополните кошелек более чем на %s %s, чтобы начать пользоваться картой - Купить криптовалюту - Показать адрес кошелька - Успешно! - Ваша карта активирована и готова к использованию - Баланс - Отправляйте только %s (%s) на этот адрес. Иначе это может привести к утрате средств. - Отправляйте только %s (%s) из сети %s на этот адрес. Иначе это может привести к утрате средств. - Если процесc создания кошелька каким-либо образом прервется, вам придется начинать сначала. - Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас. - Понятно! - Подготовка - Создать резервную копию - Пропустить - Завершите процесс резервного копирования - Добавить резервную карту - Сканировать карту #%d - Сканировать основную карту - Нет резервных карт - Подготовьте свою карту - Добавлена одна резервная карта - Добавлены две резервные карты - Резервная карта #%d - Чтобы начать процесс резервного копирования, добавьте одну или две резервные карты. - Подготовьте основную карту - Вы можете добавить еще одну карту или завершить процесс резервного копирования - Добавлено максимальное количество карт. Завершите процесс резервного копирования. - Вы добавили одну резервную карту. После того, как процесс будет завершен, Вы больше не сможете добавить карт. Если у Вас есть еще одна карта, добавьте ее в резервную копию. Хотите продолжить? - Создайте код доступа - Повторно введите код доступа - Защита - Персонализация - Восстановление - Вам необходимо установить единый код доступа для защиты всех ваших карт. - Позже вы сможете установить индивидуальный код доступа для каждой карты. - Код доступа можно восстановить с помощью привязанной карты. Не храните все карты в одном месте. - Выберите любое слово, фразу или число в качестве кода доступа. - Код доступа должен состоять не менее чем из 4 символов. - Введенные коды доступа не совпадают - Создать - Отправить - Резервная копия - Процесс резервного копирования почти завершен. Вы не можете выйти из него сейчас. - Резервная копия карты - Идентичные карты - Код доступа - Восстановление кода доступа - Вы можете сделать резервную копию своих ключей на одной или двух других пустых картах Wallet. - Все резервные карты являются полнофункциональными и содержат одинаковые ключи. - Вы сможете установить код доступа для защиты своих кошельков. - Код доступа можно восстановить с помощью одной из резервных карт. - Ваша карта настроена и готова к использованию. - Добавить еще карты - Вы не закончили резервное копирование. Хотите продолжить? - Да, возобновить - Отказаться - Это необратимое действие - Если сейчас отказаться, то придётся сбрасывать карты до заводских настроек, чтобы начать заново - Возобновить резервное копирование - Отказаться - Сканировать основную карту - Перейти к моему кошельку - Отправка %s - Подготовьте резервную карту с номером %s - Подготовьте основную карту с номером %s - Сеть Solana взимает арендную плату в размере %s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %s, чтобы не платить арендную плату. - Эта карта не является платежным средством. В настоящее время мы не можем сопоставить количество подписей на карте с информацией в блокчейне. Это нормально, но в редких случаях может означать, что предыдущий владелец удерживает подписанную транзакцию от публикации, что является поводом для беспокойства.\nНе принимайте эту карту в качестве физического платежа от кого-то, кому вы не доверяете.\nВо всех остальных отношениях эта карта совершенно безопасна.\nTangem — единственный аппаратный кошелек, предлагающий защиту методом подсчета подписей. - Запрос на создание транзакции для %s\n%s\n\nСумма: %s\nКомиссия: %s\nВсего: %s\nБаланс: %s - Запрос на открытие сессии для\n%s\n\nСЕТЬ: %s\n\nURL: %s - Сессии WalletConnect - Упс. Нет сессий. - Нет открытых сессий WalletConnect - Открытая сессия - Невозможно отправить транзакцию. Недостаточно средств. - Запрос на подпись сообщения.\n\n%s - Буфер обмена содержит код WalletConnect. Использовать скопированное значение или отсканировать QR-код - Вставить из буфера обмена - Сканировать новый код - Эту карту нельзя использовать с WalletConnect. - Операция не может быть завершена. \n\nВы уже установили сеанс WalletConnect с этими параметрами. - Не удалось установить сессию WalletConnect за отведённое время. Пожалуйста, повторите попытку позже. - Dapp %s, запрос на\nподпись транзакции с BNB.\n\n%s - Детали транзакции:\nОт: %s\nК: %s\nСумма: %s - Торговый ордер на %s\nЦена: %s\nСумма к получению: %s\nСумма к оплате: %s - Мои предложения - Не могу отсканировать карту - Не могу отправить транзакцию - Обращение в поддержку Tangem - Обращение в поддержку - Расскажите, каких функций вам не хватает, и мы постараемся вам помочь. - Скажите, пожалуйста, какая у вас карта? - Пожалуйста, расскажите нам больше о вашей проблеме. Каждая маленькая деталь может помочь. - Привет, команда поддержки, - Информация ниже не является обязательной. Вы можете стереть её, если хотите. - ≈ %s (вкл. комиссию: %s) - Основная карта - Блокчейн недоступен. Попробуй позже. - Ошибка - Ок - Не удалось установить сессию WalletConnect. Пожалуйста, повторите попытку позже. - - - %s - \ No newline at end of file diff --git a/core/res/src/main/res/values-ru/strings_final.xml b/core/res/src/main/res/values-ru/strings_final.xml index 14e74e4d88..39eeea4029 100644 --- a/core/res/src/main/res/values-ru/strings_final.xml +++ b/core/res/src/main/res/values-ru/strings_final.xml @@ -112,18 +112,10 @@ Используйте %s или код доступа для входа в приложение Войти с %s Сканировать карту - Сохраните ваш кошелек - Вы хотите использовать %s? - Вы хотите использовать биометрию? Доступ в приложение Войдите в приложение и следите за своим балансом без сканирования карты Код доступа - Для операций с вашим кошельком будет запрашиваться %s вместо кода доступа карты - Для операций с вашим кошельком будет запрашиваться биометрия вместо кода доступа карты Обратите внимание, что для совершения транзакции с вашими средствами по-прежнему потребуется ваша карта - Использовать %s - Использовать биометрию - Новый функционал Мои кошельки Мультивалютные Одновалютные @@ -223,10 +215,241 @@ Включите биометрическую аутентификацию Похоже, что у вас отключена биометрическая аутентификация, она необходима для сохранения кошельков Включить - Функция сохранения кошелька позволяет вам использовать свой кошелек с биометрической аутентификацией, не прикладывая карту к телефону для получения доступа. %d выбрано биометрическую аутентификацию биометрией + Tangem + Сохранить изменения + Предупреждение + Готово + Отмена + Принять + Вы не предоставили доступ к камере, пожалуйста, измените настройки конфиденциальности. + Отправить + Создать кошелек + Tangem + Подтвержденный баланс + Баланс загружается… + Аккаунт не создан + Карта пуста + Создайте кошелек, чтобы начать пользоваться картой Tangem + Эта карта не поддерживается + Ваша карта Tangem предназначена для работы с другим приложением. Пожалуйста, ознакомьтесь с названием и инструкциями на вашей карте и установите правильное приложение. + Транзакция подтверждается… + Получение + от %s + Отправка + к %s + Проверить адрес + Создать PayString + Создать PayString + PayString имя + $payid.tangem.com + Ваша PayString — это уникальная для вас информация, такая как номер телефона, электронная почта или ABN + Ошибка при создании PayString + Такой PayString уже существует. Попробуйте другой. + Адрес или PayString + Адрес + Отправить + Сетевая комиссия + Сумма + Комиссия + Всего + Максимальная сумма + Низкая + Нормальная + Приоритетная + Включая комиссию + %s будет отправлено + %s и %s будет отправлено + Транзакция успешно подписана и отправлена в блокчейн. Баланс будет обновлен через некоторое время + Подробности + Номер карты + Эмитент + Подписано + %s хэшей + Валюта приложения + Задержка сканирования + Этот механизм защищает карту от бесконтактных атак. Между сканированием карты и выполнением команды будет добавлена задержка. + Пароль + Перед выполнением любой команды, влекущей за собой изменение состояния карты, вам необходимо будет ввести пароль. + Код доступа + Перед сканированием карты вам нужно будет ввести правильный код доступа. + Эта карта ранее пополнялась и подписывала транзакции. Выведите средства как можно быстрее, если вы получили эту карту из ненадежного источника. Если это ваша карта, то не о чем беспокоиться. + Эта карта не предназначена для работы с этим приложением + Карта, которую вы отсканировали, является картой разработчика. Не принимайте её в качестве оплаты. + Нажмите, чтобы подписать + Чтобы создать кошелек, соедините телефон и карту в точности, как показано выше. + Чтобы изменить код доступа, соедините телефон и карту в точности, как показано выше. + Чтобы изменить пароль, соедините телефон и карту в точности, как показано выше. + Условия использования + Нет соединения с интернетом + PayString не поддерживается блокчейном + PayString не зарегистрирован + Не удалось выполнить запрос PayString + Адрес совпадает с адресом кошелька + Адрес скопирован в буфер обмена + Сначала введите желаемую PayString + Приложите, чтобы отсканировать + Приложите карту + Ошибка сканирования карты. Попробуй снова. + Купить + Продать + Обменять + Создать кошелек + Сгенерируйте ключи кошелька на обеих картах, чтобы начать использовать свои Twin карты + Один кошелек. Две карты. + Это карта, которую вы держите в руках. У парной карты номер %s.\n\nОбе карты можно использовать для вывода средств из этого кошелька. + Условия использования карты + Tangem Twin + Это действие необратимо. У вас не будет доступа к старому кошельку. + Отсканируйте twin-карту #%s + Подготовка карты + Создание кошелька + Сканировать карту #%s + Вы отсканировали ту же карту. Для создания twin-кошелька вам необходимо отсканировать карту с номером %d + Начать + Добавить + Искать + Отправить отзыв + Не могу отправить транзакцию + Причина: %s + У вас возникли трудности со сканированием карты? + Обратиться в поддержку + Отправить отзыв + Очень круто! + Можно лучше + Один вопрос + Как вам Tangem? + Добавить токен + Пожалуйста, дождитесь завершения транзакции %s, чтобы иметь возможность отправить средства + Проверка подлинности не удалась + Эта карта может быть производственным образцом или подделкой + Важная информация о безопасности \u26A0 + Эта карта подписывала транзакции в прошлом + Узнать больше + На этой карте осталось только %s подписей. Вы должны вывести все свои средства. + Отключить + Подписать + Подписать и отправить + Отклонить + Это тестовая карта. Не принимайте её в качестве оплаты. Эта карта должна использоваться только в целях тестирования и разработки. + Вы хотите купить или продать криптовалюту? + К сожалению, текущая версия приложения не готова к работе с этой картой, проверьте наличие обновлений + Активация карты + Как это работает? + Получить криптовалюту + Создать кошелек + Давайте сгенерируем все ключи на вашей карте и создадим безопасный кошелек + Создать кошелек + Пополните свой кошелек + Пополните кошелек на любую сумму, чтобы начать пользоваться картой + Пополните кошелек более чем на %s %s, чтобы начать пользоваться картой + Купить криптовалюту + Показать адрес кошелька + Успешно! + Ваша карта активирована и готова к использованию + Баланс + Отправляйте только %s (%s) на этот адрес. Иначе это может привести к утрате средств. + Отправляйте только %s (%s) из сети %s на этот адрес. Иначе это может привести к утрате средств. + Если процесc создания кошелька каким-либо образом прервется, вам придется начинать сначала. + Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас. + Понятно! + Подготовка + Создать резервную копию + Пропустить + Завершите процесс резервного копирования + Добавить резервную карту + Сканировать карту #%d + Сканировать основную карту + Нет резервных карт + Подготовьте свою карту + Добавлена одна резервная карта + Добавлены две резервные карты + Резервная карта #%d + Чтобы начать процесс резервного копирования, добавьте одну или две резервные карты. + Подготовьте основную карту + Вы можете добавить еще одну карту или завершить процесс резервного копирования + Добавлено максимальное количество карт. Завершите процесс резервного копирования. + Вы добавили одну резервную карту. После того, как процесс будет завершен, Вы больше не сможете добавить карт. Если у Вас есть еще одна карта, добавьте ее в резервную копию. Хотите продолжить? + Создайте код доступа + Повторно введите код доступа + Защита + Персонализация + Восстановление + Вам необходимо установить единый код доступа для защиты всех ваших карт. + Позже вы сможете установить индивидуальный код доступа для каждой карты. + Код доступа можно восстановить с помощью привязанной карты. Не храните все карты в одном месте. + Выберите любое слово, фразу или число в качестве кода доступа. + Код доступа должен состоять не менее чем из 4 символов. + Введенные коды доступа не совпадают + Создать + Отправить + Резервная копия + Процесс резервного копирования почти завершен. Вы не можете выйти из него сейчас. + Резервная копия карты + Идентичные карты + Код доступа + Восстановление кода доступа + Вы можете сделать резервную копию своих ключей на одной или двух других пустых картах Wallet. + Все резервные карты являются полнофункциональными и содержат одинаковые ключи. + Вы сможете установить код доступа для защиты своих кошельков. + Код доступа можно восстановить с помощью одной из резервных карт. + Ваша карта настроена и готова к использованию. + Добавить еще карты + Вы не закончили резервное копирование. Хотите продолжить? + Да, возобновить + Отказаться + Это необратимое действие + Если сейчас отказаться, то придётся сбрасывать карты до заводских настроек, чтобы начать заново + Возобновить резервное копирование + Отказаться + Сканировать основную карту + Перейти к моему кошельку + Отправка %s + Подготовьте резервную карту с номером %s + Подготовьте основную карту с номером %s + Сеть Solana взимает арендную плату в размере %s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %s, чтобы не платить арендную плату. + Эта карта не является платежным средством. В настоящее время мы не можем сопоставить количество подписей на карте с информацией в блокчейне. Это нормально, но в редких случаях может означать, что предыдущий владелец удерживает подписанную транзакцию от публикации, что является поводом для беспокойства.\nНе принимайте эту карту в качестве физического платежа от кого-то, кому вы не доверяете.\nВо всех остальных отношениях эта карта совершенно безопасна.\nTangem — единственный аппаратный кошелек, предлагающий защиту методом подсчета подписей. + Запрос на создание транзакции для %s\n%s\n\nСумма: %s\nКомиссия: %s\nВсего: %s\nБаланс: %s + Запрос на открытие сессии для\n%s\n\nСЕТЬ: %s\n\nURL: %s + Сессии WalletConnect + Упс. Нет сессий. + Нет открытых сессий WalletConnect + Открытая сессия + Невозможно отправить транзакцию. Недостаточно средств. + Запрос на подпись сообщения.\n\n%s + Буфер обмена содержит код WalletConnect. Использовать скопированное значение или отсканировать QR-код + Вставить из буфера обмена + Сканировать новый код + Эту карту нельзя использовать с WalletConnect. + Операция не может быть завершена. \n\nВы уже установили сеанс WalletConnect с этими параметрами. + Не удалось установить сессию WalletConnect за отведённое время. Пожалуйста, повторите попытку позже. + Dapp %s, запрос на\nподпись транзакции с BNB.\n\n%s + Детали транзакции:\nОт: %s\nК: %s\nСумма: %s + Торговый ордер на %s\nЦена: %s\nСумма к получению: %s\nСумма к оплате: %s + Мои предложения + Не могу отсканировать карту + Не могу отправить транзакцию + Обращение в поддержку Tangem + Обращение в поддержку + Расскажите, каких функций вам не хватает, и мы постараемся вам помочь. + Скажите, пожалуйста, какая у вас карта? + Пожалуйста, расскажите нам больше о вашей проблеме. Каждая маленькая деталь может помочь. + Привет, команда поддержки, + Информация ниже не является обязательной. Вы можете стереть её, если хотите. + ≈ %s (вкл. комиссию: %s) + Основная карта + Блокчейн недоступен. Попробуй позже. + Ошибка + Ок + Не удалось установить сессию WalletConnect. Пожалуйста, повторите попытку позже. + Вы хотите использовать биометрию? + Для операций с вашим кошельком будет запрашиваться биометрия вместо кода доступа карты + Использовать биометрию Включите биометрическую аутентификацию Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem App - + + + %s + \ No newline at end of file diff --git a/core/res/src/main/res/values/strings-app.xml b/core/res/src/main/res/values/strings.xml similarity index 51% rename from core/res/src/main/res/values/strings-app.xml rename to core/res/src/main/res/values/strings.xml index 4d8976d15d..44b4597b4e 100644 --- a/core/res/src/main/res/values/strings-app.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1,5 +1,223 @@ - + + Tangem Wallet + 3 cards + 2 cards + Shipping + Free + I have a promo code… + Total + Other payment methods + Buy now + Meet\nTangem + Buy + Store + Send + Pay + Exchange + Lend + Borrow + Revolutionary Hardware Wallet + Store your crypto assets secure while keeping private keys contained in your card + Ultra Secure Backup + Up to + 3 physical cards + to one wallet + Thousands of Currencies + A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card + DeFi Compatible + Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services + The Wallet for Everyone + Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. + Order + Search tokens + You are currently running in Demo mode. All funds are not real. + This feature is disabled in Demo mode + Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. + Available networks + %s network not found. Please, add it first and try again. + Attention + Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. + Tokens in Solana network are not supported by this card due to firmware limitation. + Contract address copied! + Contract address + Required field + Please select the network + Decimal number must be a valid integer, no higher than %d + Contract address is invalid + Derivation path is invalid + Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. + This token/network has already been added to your list + Decimals + Network + Not selected + E.g. USD Coin + Name + E.g. USDC + Token symbol + BIP44 coin type + Default + The server is not available, please try again later + Total balance + The amount does not include some of your funds + Tokens + Manage tokens + No rate + Network is unreachable + Hide token + Hide %s + Hide + You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. + Unable to hide %s + The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. + Your wallet has not been backed up + To protect your assets, we advise you to carry out this procedure + Retry + Chat + %s network + I understand + Yes + No + Russian bank cards are not accepted at the moment + Do you have a bank card of another country or a UnionPay card? + This network is not supported. Please select another network. + Card Settings + Security Mode + Change Access Code + Access code will be changed on this card only + Continue + Tangem Bot + Get your card ready! + Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. + Scan Card + App Settings + Keep the wallet in the app + Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. + Save Access Code + Biometric authentication will be requested instead of the access code for interactions with your card. + Removing the saved card deletes all the saved wallets and their access codes. + This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + WalletConnect + Connect to Dapps + This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + I understand that after performing this action, I will no longer have access to the current wallet + Reset the Card + Reset to Factory Settings + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Select network + Scan your card + To access all the networks you need to scan the card + Connection with this Dapp cannot be established due to its technical implementation. + Welcome back! + Use %s or scan a card to access the app + Log in with %s + Scan card + Access the app + Log into the app and check your balance without scanning the card + Access code + Note that making a transaction with your funds will still require your card + My Wallets + Multi-currency + Single-currency + Add new wallet + Rename Wallet + Wallet name + Unlock all with %s + Invalid Tag. It won\'t be added to the transaction. + Invalid Memo. It won\'t be added to the transaction. + Tag + Memo + Attention + Tap the card with the visa logo + No funds for activation + Please contact support + Four identical digits isn\'t safe + Such a PIN can be brute-forced easily + Pin code + Connect + KYC + Verify your identity + Set PIN code + Register + Verify via Utorg + Refresh + Connect your card + Verify your identity + KYC is in progress + Connect your card to the decentralized payment system + To start using your card you have to pass the KYC process + Please wait until the verification is completed. You\'ll be notified via email. Usually it takes up to 1 hour. You can close the app and come back later. + PIN Code + Set PIN code for your SaltPay card + Chat with support + Please hold the card until the operation complete + To start the backup process you have to add Tangem card as your backup + No backup card + Backup card ready + Finalize the backup process by creating an access code + Prepare the SaltPay card + Tap the SaltPay card + Tap the Tangem card + Support + Claim %s + To get started, simply claim wxDAI to your wallet + Claim + Congratulations! Your first payment crypto card has been activated! + Claiming + It will take a few seconds + Something went wrong + Please check your email for further instructions + Do you want to exit the activation process? + In this case, you will need to start from the beginning. + You have used a card from another wallet. Tap the card associated with this wallet + Referral program + Refer your friends to Tangem + You + Will get + for each wallet bought by your friend on your %s network address%s + Your friend + Will get a + %s discount + when buying a card on tangem.com + Your friends bought + Your personal code + Participate + terms and conditions + By tapping this button you accept + You\'ve accepted + of the referral program + Internal error: wallet manager not found + Balance: %s + Share + Copy + Success + Give Permission + To continue you need to allow 1inch smart contracts to use your %s + Amount %s + Your Wallet + Spender + Approve + Swap of %s to + Swap + Insufficient funds + Give Permission + Permit and Swap + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Reason: %s. Please try again later. + Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Personal code copied! + Buy Tangem Wallet with discount!\n%s + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. + Invalid address + This wallet has already been saved, you can add another one + Delete + Privacy policy + Enable biometric authorization + It looks like you have biometric authentication disabled, it is necessary to save wallets + Enable + %d selected + biometric authentication + biometrics Tangem Save changes Warning @@ -54,7 +272,7 @@ Long Tap This mechanism protects against proximity attacks on a card. It will enforce a delay between reception and execution of a command. Passcode - Before executing any command entailing a change of the card state you will have to enter the passcode. + Before executing any command entailing a change of the card state, you will have to enter the passcode. Access code You will have to submit the correct access code before scanning the card This card has been already topped up and signed transactions in the past. Consider immediate withdrawal of all funds if you have received this card from an untrusted source. If it\'s your card, there is nothing to worry about. @@ -97,7 +315,6 @@ Can\'t send a transaction Reason: %s Are you having difficulty scanning your card? - I\'m okay Request support Send feedback Really cool! @@ -217,7 +434,7 @@ Tangem feedback Feedback Tell us what functions you are missing, and we will try to help you. - Please tell us what card do you have? + Please tell us what card do you have Please tell us more about your issue. Every small detail can help. Hi support team, The following information is optional. You can erase it if you don\'t want to share it. @@ -227,6 +444,11 @@ Error OK Failed to establish WalletConnect session. Please, try again later. + Would you like to use biometrics? + Biometrics will be requested instead of the access code for interactions with your wallet + Allow to use biometrics + Enable biometric authentication + Go to settings to enable biometric authentication in the Tangem App %s diff --git a/core/res/src/main/res/values/strings_final.xml b/core/res/src/main/res/values/strings_final.xml deleted file mode 100644 index d41e3ca81f..0000000000 --- a/core/res/src/main/res/values/strings_final.xml +++ /dev/null @@ -1,232 +0,0 @@ - - - Tangem Wallet - 3 cards - 2 cards - Shipping - Free - I have a promo code… - Total - Other payment methods - Buy now - Meet\nTangem - Buy - Store - Send - Pay - Exchange - Lend - Borrow - Revolutionary Hardware Wallet - Store your crypto assets secure while keeping private keys contained in your card - Ultra Secure Backup - Up to - 3 physical cards - to one wallet - Thousands of Currencies - A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card - DeFi Compatible - Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services - The Wallet for Everyone - Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. - Order - Search tokens - You are currently running in Demo mode. All funds are not real. - This feature is disabled in Demo mode - Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. - Available networks - %s network not found. Please, add it first and try again. - Attention - Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. - Tokens in Solana network are not supported by this card due to firmware limitation. - Contract address copied! - Contract address - Required field - Please select the network - Decimal number must be a valid integer, no higher than %d - Contract address is invalid - Derivation path is invalid - Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. - This token/network has already been added to your list - Decimals - Network - Not selected - E.g. USD Coin - Name - E.g. USDC - Token symbol - BIP44 coin type - Default - The server is not available, please try again later - Total balance - The amount does not include some of your funds - Tokens - Manage tokens - No rate - Network is unreachable - Hide token - Hide %s - Hide - You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. - Unable to hide %s - The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. - Your wallet has not been backed up - To protect your assets, we advise you to carry out this procedure - Retry - Chat - %s network - I understand - Yes - No - Russian bank cards are not accepted at the moment - Do you have a bank card of another country or a UnionPay card? - This network is not supported. Please select another network. - Card Settings - Security Mode - Change Access Code - Access code will be changed on this card only - Continue - Tangem Bot - Get your card ready! - Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. - Scan Card - App Settings - Keep the wallet in the app - Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. - Save Access Code - Biometric authentication will be requested instead of the access code for interactions with your card. - Removing the saved card deletes all the saved wallets and their access codes. - This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. - WalletConnect - Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - I understand that after performing this action, I will no longer have access to the current wallet - Reset the Card - Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - Select network - Scan your card - To access all the networks you need to scan the card - Connection with this Dapp cannot be established due to its technical implementation. - Welcome back! - Use %s or scan a card to access the app - Log in with %s - Scan card - Save your wallet - Would you like to use %s? - Would you like to use biometrics? - Access the app - Log into the app and check your balance without scanning the card - Access code - %s will be requested instead of the access code for interactions with your wallet - Biometrics will be requested instead of the access code for interactions with your wallet - Note that making a transaction with your funds will still require your card - Allow to use %s - Allow to use biometrics - New feature - My Wallets - Multi-currency - Single-currency - Add new wallet - Rename Wallet - Wallet name - Unlock all with %s - Invalid Tag. It won\'t be added to the transaction. - Invalid Memo. It won\'t be added to the transaction. - Tag - Memo - Attention - Tap the card with the visa logo - No funds for activation - Please contact support - Four identical digits isn\'t safe - Such a PIN can be brute-forced easily - Pin code - Connect - KYC - Verify your identity - Set PIN code - Register - Verify via Utorg - Refresh - Connect your card - Verify your identity - KYC is in progress - Connect your card to the decentralized payment system - To start using your card you have to pass the KYC process - Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. - PIN Code - Set PIN code for your SaltPay card - Chat with support - Please hold the card until the operation complete - To start the backup process you have to add Tangem card as your backup - No backup card - Backup card ready - Finalize the backup process by creating an access code - Prepare the SaltPay card - Tap the SaltPay card - Tap the Tangem card - Support - Claim %s - To get started, simply claim wxDAI to your wallet - Claim - Congratulations! Your first payment crypto card has been activated - Claiming - It will take a few seconds - Something went wrong - Please check your email for further instructions - Do you want to exit the activation process? - In this case, you will need to start from the beginning. - You have used a card from another wallet. Tap the card associated with this wallet - Referral program - Refer your friends to Tangem - You - Will get - for each wallet bought by your friend on your %s network address%s - Your friend - Will get a - %s discount - when buying a card on tangem.com - Your friends bought - Your personal code - Participate - terms and conditions - By tapping this button you accept - You\'ve accepted - of the referral program - Internal error: wallet manager not found - Balance: %s - Share - Copy - Success - Give Permission - To continue you need to allow 1inch smart contracts to use your %s - Amount %s - Your Wallet - Spender - Approve - Swap of %s to - Swap - Insufficient funds - Give Permission - Permit and Swap - Failed to load the information about the referral program. Please try again later. - Failed to load the information about the referral program. Reason: %s. Please try again later. - Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. - Personal code copied! - Buy Tangem Wallet with discount!\n%s - %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. - Invalid address - This wallet has already been saved, you can add another one - Delete - Privacy policy - Enable biometric authorization - It looks like you have biometric authentication disabled, it is necessary to save wallets - Enable - Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. - %d selected - biometric authentication - biometrics - Enable biometric authentication - Go to settings to enable biometric authentication in the Tangem App - diff --git a/core/res/src/main/res/values/strings_plurals_final.xml b/core/res/src/main/res/values/strings_plurals.xml similarity index 100% rename from core/res/src/main/res/values/strings_plurals_final.xml rename to core/res/src/main/res/values/strings_plurals.xml From b87f415e2994aa2fa28b4284aa16248b360b82f2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 12 Dec 2022 21:16:53 +0300 Subject: [PATCH 005/145] Updated on 2026-08-14 --- .../datasource/api/oneinch/BaseResponse.kt | 8 +++++ .../datasource/api/oneinch/OneInchApi.kt | 5 +-- .../api/oneinch/OneInchErrorsHandler.kt | 34 +++++++++++++++++++ .../com/tangem/datasource/di/NetworkModule.kt | 31 +++++++++-------- features/swap/data/build.gradle.kts | 3 ++ .../tangem/feature/swap/SwapRepositoryImpl.kt | 16 ++++++--- .../swap/converters/QuotesConverter.kt | 27 +++++++++++---- .../feature/swap/converters/SwapConverter.kt | 31 ++++++++++++----- .../tangem/feature/swap/di/SwapDataModule.kt | 3 ++ .../feature/swap/domain/SwapInteractorImpl.kt | 25 +++++++++----- .../feature/swap/domain/SwapRepository.kt | 5 +-- .../domain/models/AggregatedSwapDataModel.kt | 15 ++++++++ .../swap/domain/models/data/DataError.kt | 18 ++++++++++ 13 files changed, 175 insertions(+), 46 deletions(-) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/oneinch/BaseResponse.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchErrorsHandler.kt create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/AggregatedSwapDataModel.kt create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/DataError.kt diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/BaseResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/BaseResponse.kt new file mode 100644 index 0000000000..866c26d858 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/BaseResponse.kt @@ -0,0 +1,8 @@ +package com.tangem.datasource.api.oneinch + +import com.tangem.datasource.api.oneinch.models.SwapErrorDto + +data class BaseOneInchResponse( + val body: T?, + val errorDto: SwapErrorDto?, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt index e230166425..613d5d1000 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt @@ -8,6 +8,7 @@ import com.tangem.datasource.api.oneinch.models.QuoteResponse import com.tangem.datasource.api.oneinch.models.StatusResponse import com.tangem.datasource.api.oneinch.models.SwapResponse import com.tangem.datasource.api.oneinch.models.TokensResponse +import retrofit2.Response import retrofit2.http.GET import retrofit2.http.Query @@ -127,7 +128,7 @@ interface OneInchApi { @Query("mainRouteParts") mainRouteParts: String? = null, @Query("parts") parts: String? = null, @Query("gasPrice") gasPrice: String? = null, - ): QuoteResponse + ): Response /** * Generate data for calling the 1inch router for exchange @@ -197,6 +198,6 @@ interface OneInchApi { @Query("complexityLevel") complexityLevel: String? = null, @Query("gasLimit") gasLimit: String? = null, @Query("gasPrice") gasPrice: String? = null, - ): SwapResponse + ): Response //endregion Swap } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchErrorsHandler.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchErrorsHandler.kt new file mode 100644 index 0000000000..72e9cd5342 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchErrorsHandler.kt @@ -0,0 +1,34 @@ +package com.tangem.datasource.api.oneinch + +import com.squareup.moshi.Moshi +import com.tangem.datasource.api.oneinch.models.SwapErrorDto +import retrofit2.HttpException +import retrofit2.Response +import javax.inject.Inject + +class OneInchErrorsHandler @Inject constructor(private val moshi: Moshi) { + + fun handleOneInchResponse(response: Response): BaseOneInchResponse { + val body = response.body() + return if (response.isSuccessful) { + return BaseOneInchResponse(body, null) + } else { + when (response.code()) { + HTTP_CODE_400 -> { + if (body != null) { + BaseOneInchResponse(null, moshi.adapter(SwapErrorDto::class.java).fromJson(body.toString())) + } else { + throw HttpException(response) + } + } + else -> { + throw HttpException(response) + } + } + } + } + + companion object { + private const val HTTP_CODE_400 = 400 + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index 94977976b0..a79330cfcf 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -15,7 +15,6 @@ import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor -import retrofit2.Converter import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory import javax.inject.Singleton @@ -28,10 +27,12 @@ class NetworkModule { @Singleton fun provideTangemTechApi( okHttpClient: OkHttpClient, - converter: Converter.Factory, + moshi: Moshi, ): TangemTechApi { return Retrofit.Builder() - .addConverterFactory(converter) + .addConverterFactory( + MoshiConverterFactory.create(moshi), + ) .baseUrl(DEV_TANGEM_TECH_BASE_URL) .client(okHttpClient) .build() @@ -41,9 +42,11 @@ class NetworkModule { @Provides @Singleton @OneInchEthereum - fun provideOneInchEthereumApi(converter: Converter.Factory): OneInchApi { + fun provideOneInchEthereumApi(moshi: Moshi): OneInchApi { return Retrofit.Builder() - .addConverterFactory(converter) + .addConverterFactory( + MoshiConverterFactory.create(moshi), + ) .baseUrl(ONE_INCH_ETHER_BASE_URL) .client( OkHttpClient.Builder() @@ -58,9 +61,11 @@ class NetworkModule { @Provides @Singleton - fun provideReferralApi(okHttpClient: OkHttpClient, converter: Converter.Factory): ReferralApi { + fun provideReferralApi(okHttpClient: OkHttpClient, moshi: Moshi): ReferralApi { return Retrofit.Builder() - .addConverterFactory(converter) + .addConverterFactory( + MoshiConverterFactory.create(moshi), + ) .baseUrl(DEV_TANGEM_TECH_BASE_URL) .client(okHttpClient) .build() @@ -80,13 +85,11 @@ class NetworkModule { @Provides @Singleton - fun provideMoshiConverter(): Converter.Factory { - return MoshiConverterFactory.create( - Moshi.Builder() - .add(KotlinJsonAdapterFactory()) - .add(BigDecimalAdapter()) - .build(), - ) + fun provideMoshi(): Moshi { + return Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .add(BigDecimalAdapter()) + .build() } private companion object { diff --git a/features/swap/data/build.gradle.kts b/features/swap/data/build.gradle.kts index 0d610522bf..9f2c7c0c4d 100644 --- a/features/swap/data/build.gradle.kts +++ b/features/swap/data/build.gradle.kts @@ -45,6 +45,9 @@ dependencies { implementation(project(":core:utils")) implementation(project(":features:swap:domain")) + /** Network */ + implementation(Library.retrofit) + /** DI */ implementation(Library.hilt) kapt(Library.hiltKapt) diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt index 7ea4007b3f..ca376c5d99 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt @@ -1,12 +1,14 @@ package com.tangem.feature.swap import com.tangem.datasource.api.oneinch.OneInchApi +import com.tangem.datasource.api.oneinch.OneInchErrorsHandler import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.feature.swap.converters.ApproveConverter import com.tangem.feature.swap.converters.QuotesConverter import com.tangem.feature.swap.converters.SwapConverter import com.tangem.feature.swap.converters.TokensConverter import com.tangem.feature.swap.domain.SwapRepository +import com.tangem.feature.swap.domain.models.AggregatedSwapDataModel import com.tangem.feature.swap.domain.models.data.ApproveModel import com.tangem.feature.swap.domain.models.data.Currency import com.tangem.feature.swap.domain.models.data.QuoteModel @@ -22,6 +24,7 @@ internal class SwapRepositoryImpl @Inject constructor( private val quotesConverter: QuotesConverter, private val swapConverter: SwapConverter, private val approveConverter: ApproveConverter, + private val oneInchErrorsHandler: OneInchErrorsHandler, private val coroutineDispatcher: CoroutineDispatcherProvider, ) : SwapRepository { @@ -31,15 +34,17 @@ internal class SwapRepositoryImpl @Inject constructor( } } - override suspend fun findBestQuote(fromTokenAddress: String, toTokenAddress: String, amount: String): QuoteModel { + override suspend fun findBestQuote(fromTokenAddress: String, toTokenAddress: String, amount: String): + AggregatedSwapDataModel { return withContext(coroutineDispatcher.io) { - quotesConverter.convert( + val quoteResponse = oneInchErrorsHandler.handleOneInchResponse( oneInchApi.quote( fromTokenAddress = fromTokenAddress, toTokenAddress = toTokenAddress, amount = amount, ), ) + quotesConverter.convert(quoteResponse) } } @@ -67,9 +72,9 @@ internal class SwapRepositoryImpl @Inject constructor( amount: String, fromWalletAddress: String, slippage: Int, - ): SwapDataModel { + ): AggregatedSwapDataModel { return withContext(coroutineDispatcher.io) { - swapConverter.convert( + val swapResponse = oneInchErrorsHandler.handleOneInchResponse( oneInchApi.swap( fromTokenAddress = fromTokenAddress, toTokenAddress = toTokenAddress, @@ -78,6 +83,9 @@ internal class SwapRepositoryImpl @Inject constructor( slippage = slippage, ), ) + swapConverter.convert( + swapResponse, + ) } } } \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt index 058a6a6159..fe37564101 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt @@ -1,17 +1,30 @@ package com.tangem.feature.swap.converters +import com.tangem.datasource.api.oneinch.BaseOneInchResponse import com.tangem.datasource.api.oneinch.models.QuoteResponse +import com.tangem.feature.swap.domain.models.AggregatedSwapDataModel import com.tangem.feature.swap.domain.models.data.QuoteModel +import com.tangem.feature.swap.domain.models.data.mapErrors import com.tangem.utils.converter.Converter import javax.inject.Inject -class QuotesConverter @Inject constructor(): Converter { +class QuotesConverter @Inject constructor() : Converter, AggregatedSwapDataModel> { - override fun convert(value: QuoteResponse): QuoteModel { - return QuoteModel( - fromTokenAmount = value.fromTokenAmount, - toTokenAmount = value.toTokenAmount, - estimatedGas = value.estimatedGas, - ) + override fun convert(value: BaseOneInchResponse): AggregatedSwapDataModel { + val body = value.body + return if (body != null) { + AggregatedSwapDataModel( + QuoteModel( + fromTokenAmount = body.fromTokenAmount, + toTokenAmount = body.toTokenAmount, + estimatedGas = body.estimatedGas, + ), + ) + } else { + AggregatedSwapDataModel( + null, + mapErrors(value.errorDto?.description), + ) + } } } \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt index 87dec263da..1c2fb0e2c2 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt @@ -1,22 +1,35 @@ package com.tangem.feature.swap.converters +import com.tangem.datasource.api.oneinch.BaseOneInchResponse import com.tangem.datasource.api.oneinch.models.SwapResponse import com.tangem.datasource.api.oneinch.models.TransactionDto +import com.tangem.feature.swap.domain.models.AggregatedSwapDataModel import com.tangem.feature.swap.domain.models.data.SwapDataModel import com.tangem.feature.swap.domain.models.data.TransactionModel +import com.tangem.feature.swap.domain.models.data.mapErrors import com.tangem.utils.converter.Converter import javax.inject.Inject -class SwapConverter @Inject constructor() : Converter { +class SwapConverter @Inject constructor() : Converter, AggregatedSwapDataModel> { - override fun convert(value: SwapResponse): SwapDataModel { - return SwapDataModel( - fromTokenAddress = value.fromToken.address, - toTokenAddress = value.toToken.address, - toTokenAmount = value.fromTokenAmount, - fromTokenAmount = value.toTokenAmount, - transaction = convertTransaction(value.transaction), - ) + override fun convert(value: BaseOneInchResponse): AggregatedSwapDataModel { + val body = value.body + return if (body != null) { + AggregatedSwapDataModel( + SwapDataModel( + fromTokenAddress = body.fromToken.address, + toTokenAddress = body.toToken.address, + toTokenAmount = body.fromTokenAmount, + fromTokenAmount = body.toTokenAmount, + transaction = convertTransaction(body.transaction), + ), + ) + } else { + AggregatedSwapDataModel( + null, + mapErrors(value.errorDto?.description), + ) + } } private fun convertTransaction(transactionDto: TransactionDto): TransactionModel { diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt index 5e4ed69628..0fdb016f7b 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt @@ -1,6 +1,7 @@ package com.tangem.feature.swap.di import com.tangem.datasource.api.oneinch.OneInchApi +import com.tangem.datasource.api.oneinch.OneInchErrorsHandler import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.di.qualifiers.OneInchEthereum import com.tangem.feature.swap.SwapRepositoryImpl @@ -29,6 +30,7 @@ class SwapDataModule { quotesConverter: QuotesConverter, swapConverter: SwapConverter, approveConverter: ApproveConverter, + oneInchErrorsHandler: OneInchErrorsHandler, coroutineDispatcher: CoroutineDispatcherProvider, ): SwapRepository { return SwapRepositoryImpl( @@ -38,6 +40,7 @@ class SwapDataModule { quotesConverter = quotesConverter, swapConverter = swapConverter, approveConverter = approveConverter, + oneInchErrorsHandler = oneInchErrorsHandler, coroutineDispatcher = coroutineDispatcher, ) } diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 87e05aa527..942a6d256d 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -36,8 +36,13 @@ internal class SwapInteractorImpl @Inject constructor( repository.checkTokensSpendAllowance(fromTokenAddress, userWalletManager.getWalletAddress(networkId)) != "0" } repository.findBestQuote(fromTokenAddress, toTokenAddress, amount).let { quotes -> - cache.cacheSwapParams(quotes.copy(isAllowedToSpend = isAllowedToSpend), amount) - return quotes + val quoteDataModel = quotes.dataModel + if (quoteDataModel != null) { + cache.cacheSwapParams(quoteDataModel.copy(isAllowedToSpend = isAllowedToSpend), amount) + return quoteDataModel + } else { + error("") //todo handle error in domain layer(task [REDACTED_TASK_KEY]) + } } } @@ -52,12 +57,16 @@ internal class SwapInteractorImpl @Inject constructor( amount = amountToSwap, slippage = DEFAULT_SLIPPAGE, fromWalletAddress = getWalletAddress(networkId), - ) - signTransactionData(swapData.transaction) //todo implement - return SwapResultModel.SwapSuccess( - quoteModel.fromTokenAmount, - quoteModel.toTokenAmount, - ) + ).dataModel + if (swapData != null) { + signTransactionData(swapData.transaction) //todo implement + return SwapResultModel.SwapSuccess( + quoteModel.fromTokenAmount, + quoteModel.toTokenAmount, + ) + } else { + error("") //todo handle error in domain layer(task [REDACTED_TASK_KEY]) + } } return SwapResultModel.SwapError( 0, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt index cb2a2bf07a..4c8254c809 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt @@ -1,5 +1,6 @@ package com.tangem.feature.swap.domain +import com.tangem.feature.swap.domain.models.AggregatedSwapDataModel import com.tangem.feature.swap.domain.models.data.ApproveModel import com.tangem.feature.swap.domain.models.data.Currency import com.tangem.feature.swap.domain.models.data.QuoteModel @@ -13,7 +14,7 @@ interface SwapRepository { fromTokenAddress: String, toTokenAddress: String, amount: String, - ): QuoteModel + ): AggregatedSwapDataModel /** * Returns address of 1inch router that must be trusted @@ -49,5 +50,5 @@ interface SwapRepository { amount: String, fromWalletAddress: String, slippage: Int, - ): SwapDataModel + ): AggregatedSwapDataModel } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/AggregatedSwapDataModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/AggregatedSwapDataModel.kt new file mode 100644 index 0000000000..2554398d3c --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/AggregatedSwapDataModel.kt @@ -0,0 +1,15 @@ +package com.tangem.feature.swap.domain.models + +import com.tangem.feature.swap.domain.models.data.DataError + +/** + * Model that aggregate data model from repository return with error [DataError] if it exists + * + * @param T model type + * @property dataModel + * @property error possible from repository [DataError] + */ +data class AggregatedSwapDataModel( + val dataModel: T?, + val error: DataError = DataError.NO_ERROR, +) \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/DataError.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/DataError.kt new file mode 100644 index 0000000000..6498ac07e0 --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/DataError.kt @@ -0,0 +1,18 @@ +package com.tangem.feature.swap.domain.models.data + +enum class DataError { + NO_ERROR, + UNKNOWN_ERROR, + INSUFFICIENT_LIQUIDITY +} + +fun mapErrors(error: String?): DataError { + return if (error == null) { + DataError.UNKNOWN_ERROR + } else when (error) { + INSUFFICIENT_LIQUIDITY_ERROR -> DataError.INSUFFICIENT_LIQUIDITY + else -> DataError.UNKNOWN_ERROR + } +} + +private const val INSUFFICIENT_LIQUIDITY_ERROR = "insufficient liquidity" \ No newline at end of file From f8fd6013dc12ced842af6edcb63500cb7ee48673 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 14 Dec 2022 09:53:59 +0300 Subject: [PATCH 006/145] Updated on 2026-08-14 --- .../disclaimer/ui/DisclaimerFragment.kt | 7 +- .../disclaimer/ui/DisclaimerWebViewClient.kt | 100 ++++++++++++++++++ 2 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerWebViewClient.kt diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt index 633ef10d6a..93d93a8a2e 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt @@ -4,7 +4,6 @@ import android.os.Bundle import android.view.View import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding -import com.tangem.tap.common.extensions.configureSettings import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.BaseFragment @@ -24,7 +23,11 @@ class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubs super.onViewCreated(view, savedInstanceState) addBackPressHandler(handler = this) binding.toolbar.setNavigationOnClickListener { handleOnBackPressed() } - binding.webView.configureSettings() + binding.webView.apply { + settings.allowFileAccess = false + settings.javaScriptEnabled = false + webViewClient = DisclaimerWebViewClient + } } override fun onStart() { diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerWebViewClient.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerWebViewClient.kt new file mode 100644 index 0000000000..c2c03a42cc --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerWebViewClient.kt @@ -0,0 +1,100 @@ +package com.tangem.tap.features.disclaimer.ui + +import android.webkit.WebResourceError +import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse +import android.webkit.WebView +import android.webkit.WebViewClient + +object DisclaimerWebViewClient : WebViewClient() { + + private const val HTML_MIME_TYPE = "text/html" + private const val UTF8_ENCODING = "utf-8" + + override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) { + super.onReceivedError(view, request, error) + view?.loadLocalContent() + } + + override fun onReceivedHttpError( + view: WebView?, + request: WebResourceRequest?, + errorResponse: WebResourceResponse?, + ) { + super.onReceivedHttpError(view, request, errorResponse) + view?.loadLocalContent() + } + + private fun WebView?.loadLocalContent() { + this?.loadDataWithBaseURL(null, TERMS_OF_SERVICE_HTML, HTML_MIME_TYPE, UTF8_ENCODING, null) + } +} + +private const val TERMS_OF_SERVICE_HTML = + """ + + + + + + Legal Disclaimer + + + +

Legal Disclaimer

+

    1. Tangem application (Software)

+

    The Software is intended for usage only with Tangem hardware wallets (Cards) via NFC interface. The + Software DOES NOT:

+

    a) Generate, store, transmit, or have access to private (secret) cryptographic keys to + blockchain + wallets holding digital assets, including crypto-currency.

+

    b) Generate, store, transmit, or have access to secret keys, passwords, passphrases, recovery phrases + that can be used to restore or to copy private (secret) keys to blockchain wallets holding digital assets, including + crypto-currency.

+

    c) Provide exchange, trading, investment services on behalf of Tangem AG.

+

    2. Risks related to the use of Software

+

    Tangem will not be responsible for any losses, damages or claims arising from events falling within the + scope of the following five categories:

+

    a) Mistakes made by the user of any cryptocurrency-related software or service, e.g., forgotten + passwords, payments sent to wrong addresses, and accidental deletion of blockchain wallets on Cards.

+

    b) Problems of Software and/or any blockchain- or cryptocurrency- related software or service, e.g., + corrupted files, incorrectly constructed transactions, unsafe cryptographic libraries, malware.

+

    c) Technical failures in the hardware of the user, including Cards, of any cryptocurrency-related + software or service, e.g., data loss due to a faulty or damaged storage device.

+

    d) Security problems experienced by the user of any cryptocurrency-related software or + service, e.g., + unauthorized access to users' wallets and/or accounts.

+

    e) Actions or inactions of third parties and/or events experienced by third parties, e.g., bankruptcy + of service providers, information security attacks on service providers, and fraud conducted by third parties.

+

    3. Trading and Investment risks

+

    There is considerable exposure to risk in any crypto-currency or other digital asset exchange + transaction. Any transaction involving currencies involves risks including, but not limited to, the potential for + changing economic conditions that may substantially affect the price or liquidity of a currency. Investments in + crypto-currency exchange speculation may also be susceptible to sharp rises and falls as the relevant market values + fluctuate. It is for this reason that when speculating in such markets it is advisable to use only risk capital.

+

    4. Electronic Trading Risks

+

    Before you engage in transactions using an electronic system, you should carefully review the rules and + regulations of the exchanges offering the system and/or listing the instruments you intend to trade. Online trading + has inherent risk due to system response and access times that may vary due to market conditions, system + performance, and other factors. You should understand these and additional risks before trading.

+

    5. Compliance with tax obligations        

+

    The users of the Software are solely responsible to determinate what, if any, taxes apply to their + crypto-currency transactions. The owners of, or contributors to, the Software are NOT responsible for determining + the taxes that apply to crypto-currency transactions.        

+

    6. No warranties        

+

    The Software is provided on an "as is" basis without any warranties of any kind regarding the + Software and/or any content, data, materials and/or services provided on the Software.       +  

+

    7. Limitation of liability

+

    Unless otherwise required by law, in no event shall the owners of, or contributors to, the Software be + liable for any damages of any kind, including, but not limited to, loss of use, loss of profits, or loss of data + arising out of or in any way connected with the use of the Software. In no way are the owners of, or contributors + to, the Software responsible for the actions, decisions, or other behavior taken or not taken by you in reliance + upon the Software.

+

    8. Last amendment

+

    This disclaimer was amended for the last time on October 1st, 2020.

+ + + """ \ No newline at end of file From bee55441a6d2520894bc64d22460877497126fec Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 14 Dec 2022 21:28:45 +0300 Subject: [PATCH 007/145] Updated on 2026-08-14 --- .../com/tangem/tap/LockUserWalletsTimer.kt | 26 +++++++++---------- .../redux/navigation/NavigationMiddleware.kt | 20 +++++++------- .../redux/navigation/NavigationReducer.kt | 2 ++ .../tap/persistence/PreferencesStorage.kt | 14 ++++++++++ 4 files changed, 39 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt index 0bfb76a8ac..426f49ca25 100644 --- a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt +++ b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt @@ -16,7 +16,7 @@ import kotlin.time.Duration internal class LockUserWalletsTimer( owner: LifecycleOwner, - private val duration: Duration = with(Duration) { 5.minutes }, + private val duration: Duration = with(Duration) { 10.minutes }, ) : LifecycleOwner by owner, DefaultLifecycleObserver { @@ -25,8 +25,6 @@ internal class LockUserWalletsTimer( field?.cancel() field = value } - private var isStopped = false - private var openWelcomeScreenWhenResumed = false init { lifecycle.addObserver(this) @@ -35,22 +33,22 @@ internal class LockUserWalletsTimer( override fun onResume(owner: LifecycleOwner) { Timber.d( """ - Owner resumed - |- Was stopped: $isStopped - |- Need to open welcome screen: $openWelcomeScreenWhenResumed - """.trimIndent(), + Owner resumed + |- Was stopped: ${preferencesStorage.wasApplicationStopped} + |- Need to open welcome screen: ${preferencesStorage.shouldOpenWelcomeScreenOnResume} + """.trimIndent(), ) - isStopped = false + preferencesStorage.wasApplicationStopped = false start() - if (openWelcomeScreenWhenResumed) { + if (preferencesStorage.shouldOpenWelcomeScreenOnResume) { store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) - openWelcomeScreenWhenResumed = false + preferencesStorage.shouldOpenWelcomeScreenOnResume = false } } override fun onStop(owner: LifecycleOwner) { Timber.d("Owner stopped") - isStopped = true + preferencesStorage.wasApplicationStopped = true } override fun onDestroy(owner: LifecycleOwner) { @@ -103,13 +101,13 @@ internal class LockUserWalletsTimer( Timber.d( """ Finished - |- App is stopped: $isStopped + |- App is stopped: ${preferencesStorage.wasApplicationStopped} |- Millis passed: ${currentTime - startTime} """.trimIndent(), ) userWalletsListManager.lock() - if (isStopped) { - openWelcomeScreenWhenResumed = true + if (preferencesStorage.wasApplicationStopped) { + preferencesStorage.shouldOpenWelcomeScreenOnResume = true } else { store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) } diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt index c8457793e5..871ef1ea5c 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt @@ -28,15 +28,17 @@ val navigationMiddleware: Middleware = { _, state -> ) } is NavigationAction.PopBackTo -> { - when (val screen = action.screen) { - AppScreen.Home, - AppScreen.Welcome, - -> { - navState?.activity?.get()?.popBackTo(screen, inclusive = true) - store.dispatchOnMain(NavigationAction.NavigateTo(screen)) - } - else -> { - navState?.activity?.get()?.popBackTo(screen, action.inclusive) + if (navState?.backStack?.lastOrNull() != action.screen) { + when (val screen = action.screen) { + AppScreen.Home, + AppScreen.Welcome, + -> { + navState?.activity?.get()?.popBackTo(screen, inclusive = true) + store.dispatchOnMain(NavigationAction.NavigateTo(screen)) + } + else -> { + navState?.activity?.get()?.popBackTo(screen, action.inclusive) + } } } } diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt index 7d63cb95ee..b6f7522586 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt @@ -20,6 +20,8 @@ private fun internalReduce(action: Action, state: AppState): NavigationState { navState.copy(backStack = navState.backStack + navigationAction.screen) } is NavigationAction.PopBackTo -> { + if (navState.backStack.lastOrNull() == navigationAction.screen) return navState + val screen = navigationAction.screen ?: navState.activity?.get()?.getPreviousScreen() val index = navState.backStack.lastIndexOf(screen) + 1 state.navigationState.copy(backStack = navState.backStack.subList(0, index)) 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 eaf21b8d83..d419aafffc 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -51,6 +51,18 @@ class PreferencesStorage(applicationContext: Application) { putBoolean(SAVE_ACCESS_CODES_KEY, value) } + var wasApplicationStopped: Boolean + get() = preferences.getBoolean(APPLICATION_STOPPED_KEY, false) + set(value) = preferences.edit { + putBoolean(APPLICATION_STOPPED_KEY, value) + } + + var shouldOpenWelcomeScreenOnResume: Boolean + get() = preferences.getBoolean(OPEN_WELCOME_ON_RESUME_KEY, false) + set(value) = preferences.edit { + putBoolean(OPEN_WELCOME_ON_RESUME_KEY, value) + } + fun getCountOfLaunches(): Int = preferences.getInt(APP_LAUNCH_COUNT_KEY, 1) fun saveTwinsOnboardingShown() { @@ -74,6 +86,8 @@ class PreferencesStorage(applicationContext: Application) { private const val SAVE_WALLET_DIALOG_SHOWN_KEY = "saveUserWalletShown" private const val SAVE_USER_WALLETS_KEY = "saveUserWallets" private const val SAVE_ACCESS_CODES_KEY = "saveAccessCodes" + private const val APPLICATION_STOPPED_KEY = "applicationStopped" + private const val OPEN_WELCOME_ON_RESUME_KEY = "openWelcomeOnResume" } } From 78fab3a9dec721db49ae2feb556dd5d1e3ebb2da Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 15 Dec 2022 10:40:02 +0300 Subject: [PATCH 008/145] Updated on 2026-08-14 --- .../redux/reducers/MultiWalletReducer.kt | 93 +++++++++---------- .../{strings_final.xml => strings.xml} | 0 .../{strings_final.xml => strings.xml} | 0 3 files changed, 46 insertions(+), 47 deletions(-) rename core/res/src/main/res/values-it/{strings_final.xml => strings.xml} (100%) rename core/res/src/main/res/values-ru/{strings_final.xml => strings.xml} (100%) 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 3b174c915f..6885dbc11d 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 @@ -1,28 +1,42 @@ package com.tangem.tap.features.wallet.redux.reducers +import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.WalletManager -import com.tangem.common.extensions.guard +import com.tangem.tap.common.extensions.dispatchToastNotification import com.tangem.tap.common.extensions.toFiatString import com.tangem.tap.common.extensions.toFormattedCurrencyString +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.tokens.models.BlockchainNetwork -import com.tangem.tap.features.wallet.models.* -import com.tangem.tap.features.wallet.redux.* +import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.features.wallet.models.WalletRent +import com.tangem.tap.features.wallet.models.filterByToken +import com.tangem.tap.features.wallet.models.getPendingTransactions +import com.tangem.tap.features.wallet.models.removeUnknownTransactions +import com.tangem.tap.features.wallet.redux.ProgressState +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.features.wallet.redux.WalletData +import com.tangem.tap.features.wallet.redux.WalletMainButton +import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN +import com.tangem.tap.features.wallet.redux.WalletStore import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.features.wallet.ui.TokenData import com.tangem.tap.store +import com.tangem.tap.userWalletsListManager +import com.tangem.wallet.R import java.math.BigDecimal class MultiWalletReducer { fun reduce(action: WalletAction.MultiWallet, state: WalletState): WalletState { return when (action) { is WalletAction.MultiWallet.AddBlockchains -> { - val wallets: List = action.blockchains.mapNotNull { blockchain -> + val wallets: List = action.blockchains.map { blockchain -> val walletManager = action.walletManagers.firstOrNull { it.wallet.blockchain == blockchain.blockchain && (it.wallet.publicKey.derivationPath?.rawPath == blockchain.derivationPath) @@ -61,10 +75,7 @@ class MultiWalletReducer { } else { state.selectedCurrency } - state.copy( - wallets = wallets, - selectedCurrency = selectedCurrency - ) + state.copy(wallets = wallets, selectedCurrency = selectedCurrency) } is WalletAction.MultiWallet.AddBlockchain -> { val walletManager = action.walletManager ?: state.getWalletManager(action.blockchain) @@ -97,16 +108,22 @@ class MultiWalletReducer { newState } } - is WalletAction.MultiWallet.AddTokens -> { - addTokens(action.tokens, action.blockchain, state) - } - is WalletAction.MultiWallet.AddToken -> { - addTokens(listOf(action.token), action.blockchain, state) - } + is WalletAction.MultiWallet.AddTokens -> addTokens(action.tokens, action.blockchain, state) + is WalletAction.MultiWallet.AddToken -> addTokens(listOf(action.token), action.blockchain, state) is WalletAction.MultiWallet.TokenLoaded -> { val currency = Currency.fromBlockchainNetwork(action.blockchain, action.token) - val walletManager = state.getWalletManager(currency).guard { - throw NullPointerException("MultiWallet.TokenLoaded: WalletManager must be not NULL") + val walletManager = state.getWalletManager(currency) + if (walletManager == null) { + if (userWalletsListManager.hasSavedUserWallets) { + store.dispatch(NavigationAction.PopBackTo(screen = AppScreen.Welcome)) + } else { + store.dispatch(NavigationAction.PopBackTo(screen = AppScreen.Home)) + } + FirebaseCrashlytics.getInstance().recordException( + IllegalStateException("MultiWallet.TokenLoaded: walletManager is null"), + ) + store.dispatchToastNotification(R.string.internal_error_wallet_manager_not_found) + return state } val wallet = walletManager.wallet val pendingTransactions = wallet.getPendingTransactions() @@ -143,55 +160,37 @@ class MultiWalletReducer { ) state.updateWalletData(newTokenWalletData) } - is WalletAction.MultiWallet.SetIsMultiwalletAllowed -> - state.copy(isMultiwalletAllowed = action.isMultiwalletAllowed) - - is WalletAction.MultiWallet.SelectWallet -> - state.copy(selectedCurrency = action.walletData?.currency) + is WalletAction.MultiWallet.SetIsMultiwalletAllowed -> state.copy(isMultiwalletAllowed = action.isMultiwalletAllowed) + is WalletAction.MultiWallet.SelectWallet -> state.copy(selectedCurrency = action.walletData?.currency) is WalletAction.MultiWallet.TryToRemoveWallet -> state - is WalletAction.MultiWallet.RemoveWallet -> { - state.removeWallet(state.getWalletData(action.currency)) - } + is WalletAction.MultiWallet.RemoveWallet -> state.removeWallet(state.getWalletData(action.currency)) is WalletAction.MultiWallet.RemoveWallets -> { var updatedState = state action.currencies.forEach { updatedState = updatedState.removeWallet(state.getWalletData(it)) } updatedState } - is WalletAction.MultiWallet.SetPrimaryBlockchain -> - state.copy(primaryBlockchain = action.blockchain) - - is WalletAction.MultiWallet.SetPrimaryToken -> - state.copy(primaryToken = action.token) + is WalletAction.MultiWallet.SetPrimaryBlockchain -> state.copy(primaryBlockchain = action.blockchain) + is WalletAction.MultiWallet.SetPrimaryToken -> state.copy(primaryToken = action.token) is WalletAction.MultiWallet.SaveCurrencies -> state - is WalletAction.MultiWallet.ShowWalletBackupWarning -> state.copy( - showBackupWarning = action.show, - ) - is WalletAction.MultiWallet.AddMissingDerivations -> state.copy( - missingDerivations = action.blockchains, - ) + is WalletAction.MultiWallet.ShowWalletBackupWarning -> state.copy(showBackupWarning = action.show) + is WalletAction.MultiWallet.AddMissingDerivations -> state.copy(missingDerivations = action.blockchains) is WalletAction.MultiWallet.BackupWallet -> state - is WalletAction.MultiWallet.ScanToGetDerivations -> state.copy( - state = ProgressState.Loading, - ) + is WalletAction.MultiWallet.ScanToGetDerivations -> state.copy(state = ProgressState.Loading) } } private fun findWalletRent(walletStore: WalletStore?): WalletRent? { - return walletStore?.walletsData?.firstOrNull { - it.walletRent != null - }?.walletRent + return walletStore?.walletsData?.firstOrNull { it.walletRent != null }?.walletRent } private fun getExistentialDeposit(walletManager: WalletManager?): String? { return (walletManager as? ExistentialDepositProvider)?.getExistentialDeposit()?.toPlainString() } -} -private fun addTokens( - tokens: List, blockchain: BlockchainNetwork, state: WalletState -): WalletState { - val wallets = tokens.mapNotNull { token -> token.toWallet(state, blockchain) } - return state.updateWalletsData(wallets) + private fun addTokens(tokens: List, blockchain: BlockchainNetwork, state: WalletState): WalletState { + val wallets = tokens.mapNotNull { token -> token.toWallet(state, blockchain) } + return state.updateWalletsData(wallets) + } } fun Token.toWallet(state: WalletState, blockchain: BlockchainNetwork): WalletData? { diff --git a/core/res/src/main/res/values-it/strings_final.xml b/core/res/src/main/res/values-it/strings.xml similarity index 100% rename from core/res/src/main/res/values-it/strings_final.xml rename to core/res/src/main/res/values-it/strings.xml diff --git a/core/res/src/main/res/values-ru/strings_final.xml b/core/res/src/main/res/values-ru/strings.xml similarity index 100% rename from core/res/src/main/res/values-ru/strings_final.xml rename to core/res/src/main/res/values-ru/strings.xml From 25b396f08f956363735ea635facd79a9511af191 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 12 Dec 2022 17:09:41 +0400 Subject: [PATCH 009/145] Updated on 2026-08-14 --- .../ui/common/DetailsComposeElements.kt | 4 +- .../ui/walletconnect/WalletConnectScreen.kt | 2 +- .../ui/components/SaveWalletScreenContent.kt | 2 +- .../WallectSelectorScreenContent.kt | 4 +- .../ui/components/WelcomeScreenContent.kt | 2 +- app/src/main/res/drawable/ic_close.xml | 5 - app/src/main/res/drawable/ic_more.xml | 5 - app/src/main/res/layout/fragment_topup.xml | 2 +- ...layout_onboarding_saltpay_connect_card.xml | 2 +- .../main/res/layout/layout_pseudo_toolbar.xml | 2 +- .../res/layout/layout_warning_card_action.xml | 2 +- app/src/main/res/menu/menu_wallet.xml | 2 +- core/ui/build.gradle.kts | 1 + .../com/tangem/core/ui/components/Buttons.kt | 20 +- .../core/ui/components/ButtonsDeprecated.kt | 46 ++- .../com/tangem/core/ui/components/Cards.kt | 317 +++++++++++++++--- .../com/tangem/core/ui/components/Spacers.kt | 15 + .../tangem/core/ui/components/TextFields.kt | 2 +- .../appbar/AppBarWithAdditionalButtons.kt | 17 +- .../components/appbar/AppBarWithBackButton.kt | 9 +- .../com/tangem/core/ui/res/TangemDimens.kt | 11 + .../{ic_alert.xml => ic_alert_24.xml} | 0 .../drawable/{ic_back.xml => ic_back_24.xml} | 0 core/ui/src/main/res/drawable/ic_check_40.xml | 10 + .../src/main/res/drawable/ic_chevron_24.xml | 4 +- .../main/res/drawable/ic_chevron_right_24.xml | 10 + core/ui/src/main/res/drawable/ic_clock_24.xml | 10 + .../{ic_close.xml => ic_close_24.xml} | 0 .../drawable/{ic_copy.xml => ic_copy_24.xml} | 0 .../{ic_discount.xml => ic_discount_28.xml} | 0 .../res/drawable/ic_exchange_vertical_24.xml | 10 + .../ui/src/main/res/drawable/ic_locked_24.xml | 10 + ...e_vertical.xml => ic_more_vertical_24.xml} | 0 .../drawable/{ic_plus.xml => ic_plus_24.xml} | 0 .../src/main/res/drawable/ic_refresh_24.xml | 10 + .../drawable/{ic_scan.xml => ic_scan_24.xml} | 0 .../{ic_share.xml => ic_share_24.xml} | 0 core/ui/src/main/res/drawable/ic_tangem.xml | 15 - .../ui/src/main/res/drawable/ic_tangem_24.xml | 16 + .../{ic_tether.xml => ic_tether_28.xml} | 0 .../main/res/drawable/img_attention_20.xml | 16 + .../res/drawable/img_polygon_round_16.xml | 13 + .../referral/ui/NonParticipateBottomBlock.kt | 3 +- .../referral/ui/ParticipateBottomBlock.kt | 4 +- .../feature/referral/ui/ReferralScreen.kt | 8 +- 45 files changed, 497 insertions(+), 114 deletions(-) delete mode 100644 app/src/main/res/drawable/ic_close.xml delete mode 100644 app/src/main/res/drawable/ic_more.xml rename core/ui/src/main/res/drawable/{ic_alert.xml => ic_alert_24.xml} (100%) rename core/ui/src/main/res/drawable/{ic_back.xml => ic_back_24.xml} (100%) create mode 100644 core/ui/src/main/res/drawable/ic_check_40.xml rename app/src/main/res/drawable/ic_plus.xml => core/ui/src/main/res/drawable/ic_chevron_24.xml (61%) create mode 100644 core/ui/src/main/res/drawable/ic_chevron_right_24.xml create mode 100644 core/ui/src/main/res/drawable/ic_clock_24.xml rename core/ui/src/main/res/drawable/{ic_close.xml => ic_close_24.xml} (100%) rename core/ui/src/main/res/drawable/{ic_copy.xml => ic_copy_24.xml} (100%) rename core/ui/src/main/res/drawable/{ic_discount.xml => ic_discount_28.xml} (100%) create mode 100644 core/ui/src/main/res/drawable/ic_exchange_vertical_24.xml create mode 100644 core/ui/src/main/res/drawable/ic_locked_24.xml rename core/ui/src/main/res/drawable/{ic_more_vertical.xml => ic_more_vertical_24.xml} (100%) rename core/ui/src/main/res/drawable/{ic_plus.xml => ic_plus_24.xml} (100%) create mode 100644 core/ui/src/main/res/drawable/ic_refresh_24.xml rename core/ui/src/main/res/drawable/{ic_scan.xml => ic_scan_24.xml} (100%) rename core/ui/src/main/res/drawable/{ic_share.xml => ic_share_24.xml} (100%) delete mode 100644 core/ui/src/main/res/drawable/ic_tangem.xml create mode 100644 core/ui/src/main/res/drawable/ic_tangem_24.xml rename core/ui/src/main/res/drawable/{ic_tether.xml => ic_tether_28.xml} (100%) create mode 100644 core/ui/src/main/res/drawable/img_attention_20.xml create mode 100644 core/ui/src/main/res/drawable/img_polygon_round_16.xml diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt index dd6f290ebe..1517c58124 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt @@ -102,7 +102,7 @@ fun EmptyTopBarWithNavigation( { IconButton(onClick = onBackClick) { Icon( - painter = painterResource(id = R.drawable.ic_back), + painter = painterResource(id = R.drawable.ic_back_24), contentDescription = null, tint = TangemTheme.colors.icon.primary1, ) @@ -136,6 +136,6 @@ fun DetailsMainButton( ) { Text(text = title) Spacer(modifier = modifier.size(8.dp)) - Icon(painter = painterResource(id = R.drawable.ic_tangem), contentDescription = "") + Icon(painter = painterResource(id = R.drawable.ic_tangem_24), contentDescription = "") } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt index 499f31ddcf..5b1f0c2ce6 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt @@ -81,7 +81,7 @@ private fun AddSessionFab( modifier = modifier, ) { Icon( - painter = painterResource(id = R.drawable.ic_plus), + painter = painterResource(id = R.drawable.ic_plus_24), contentDescription = "", ) } diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt index 40b84a07d4..e04a1de7f7 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt @@ -92,7 +92,7 @@ private fun Header( ) { Icon( modifier = Modifier.size(TangemTheme.dimens.size24), - painter = painterResource(id = R.drawable.ic_close), + painter = painterResource(id = R.drawable.ic_close_24), tint = TangemTheme.colors.icon.secondary, contentDescription = stringResource(id = R.string.common_cancel), ) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt index 6c57a43f00..136f786705 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt @@ -201,7 +201,7 @@ private fun Footer( modifier = Modifier.fillMaxWidth(), text = stringResource(R.string.user_wallet_list_add_button), showProgress = showAddCardProgress, - icon = painterResource(id = R.drawable.ic_tangem), + icon = painterResource(id = R.drawable.ic_tangem_24), onClick = onAddCardClick, ) } @@ -230,7 +230,7 @@ private fun EditWalletsBar( ) { Icon( modifier = Modifier.size(TangemTheme.dimens.size24), - painter = painterResource(id = R.drawable.ic_close), + painter = painterResource(id = R.drawable.ic_close_24), tint = TangemTheme.colors.icon.primary1, contentDescription = "Unselect wallets", ) diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt index c0a780226b..709c151963 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt @@ -85,7 +85,7 @@ internal fun WelcomeScreenContent( .fillMaxWidth(), text = stringResource(R.string.welcome_unlock_card), showProgress = showScanCardProgress, - icon = painterResource(id = R.drawable.ic_tangem), + icon = painterResource(id = R.drawable.ic_tangem_24), onClick = onScanCardClick, ) SpacerH16() diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml deleted file mode 100644 index d276f8584c..0000000000 --- a/app/src/main/res/drawable/ic_close.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_more.xml b/app/src/main/res/drawable/ic_more.xml deleted file mode 100644 index 2ea52e3dbc..0000000000 --- a/app/src/main/res/drawable/ic_more.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/layout/fragment_topup.xml b/app/src/main/res/layout/fragment_topup.xml index 01182c0c4c..366c9faa7c 100644 --- a/app/src/main/res/layout/fragment_topup.xml +++ b/app/src/main/res/layout/fragment_topup.xml @@ -10,7 +10,7 @@ android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_constraintTop_toTopOf="parent" - app:navigationIcon="@drawable/ic_close" + app:navigationIcon="@drawable/ic_close_24" app:title="@string/wallet_button_buy" /> + android:src="@drawable/ic_close_24" /> diff --git a/app/src/main/res/layout/layout_warning_card_action.xml b/app/src/main/res/layout/layout_warning_card_action.xml index 9a0aafc0d5..e85e77d3e5 100644 --- a/app/src/main/res/layout/layout_warning_card_action.xml +++ b/app/src/main/res/layout/layout_warning_card_action.xml @@ -22,7 +22,7 @@ android:tint="@android:color/white" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@+id/warning_content_container" - app:srcCompat="@drawable/ic_close" /> + app:srcCompat="@drawable/ic_close_24" /> diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 2ab555ec1d..28792f7aa2 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -55,6 +55,7 @@ dependencies { /** Other libraries */ implementation(Library.accompanistSystemUiController) implementation(Library.materialComponent) + implementation(Library.composeShimmer) implementation(project(":core:res")) } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt index 80ca296491..57ea5a799d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt @@ -429,14 +429,14 @@ private fun PrimaryButtonSample( PrimaryButtonIconRight( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", - icon = painterResource(id = R.drawable.ic_tangem), + icon = painterResource(id = R.drawable.ic_tangem_24), onClick = { /* no-op */ }, ) Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) PrimaryButtonIconLeft( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", - icon = painterResource(id = R.drawable.ic_tangem), + icon = painterResource(id = R.drawable.ic_tangem_24), onClick = { /* no-op */ }, ) Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) @@ -450,7 +450,7 @@ private fun PrimaryButtonSample( PrimaryButtonIconRight( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", - icon = painterResource(id = R.drawable.ic_tangem), + icon = painterResource(id = R.drawable.ic_tangem_24), enabled = false, onClick = { /* no-op */ }, ) @@ -458,7 +458,7 @@ private fun PrimaryButtonSample( PrimaryButtonIconLeft( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", - icon = painterResource(id = R.drawable.ic_tangem), + icon = painterResource(id = R.drawable.ic_tangem_24), enabled = false, onClick = { /* no-op */ }, ) @@ -505,14 +505,14 @@ private fun SecondaryButtonSample( SecondaryButtonIconRight( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", - icon = painterResource(id = R.drawable.ic_tangem), + icon = painterResource(id = R.drawable.ic_tangem_24), onClick = { /* no-op */ }, ) Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) SecondaryButtonIconLeft( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", - icon = painterResource(id = R.drawable.ic_tangem), + icon = painterResource(id = R.drawable.ic_tangem_24), onClick = { /* no-op */ }, ) Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) @@ -526,7 +526,7 @@ private fun SecondaryButtonSample( SecondaryButtonIconRight( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", - icon = painterResource(id = R.drawable.ic_tangem), + icon = painterResource(id = R.drawable.ic_tangem_24), enabled = false, onClick = { /* no-op */ }, ) @@ -534,7 +534,7 @@ private fun SecondaryButtonSample( SecondaryButtonIconLeft( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", - icon = painterResource(id = R.drawable.ic_tangem), + icon = painterResource(id = R.drawable.ic_tangem_24), enabled = false, onClick = { /* no-op */ }, ) @@ -572,7 +572,7 @@ private fun TextButtonSample( Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) TextButtonIconLeft( text = "Enabled", - icon = painterResource(id = R.drawable.ic_plus), + icon = painterResource(id = R.drawable.ic_plus_24), onClick = { /* no-op */ }, ) Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) @@ -584,7 +584,7 @@ private fun TextButtonSample( Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) TextButtonIconLeft( text = "Enabled", - icon = painterResource(id = R.drawable.ic_plus), + icon = painterResource(id = R.drawable.ic_plus_24), enabled = false, onClick = { /* no-op */ }, ) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/ButtonsDeprecated.kt b/core/ui/src/main/java/com/tangem/core/ui/components/ButtonsDeprecated.kt index 7fec4a465d..26fe7edb44 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/ButtonsDeprecated.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/ButtonsDeprecated.kt @@ -28,7 +28,12 @@ import com.tangem.core.ui.res.textColor @Composable fun Preview_PrimaryStartIconButton_Enabled_InLightTheme() { TangemTheme(isDark = false) { - PrimaryStartIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) + PrimaryStartIconButton( + text = "Manage tokens", + iconResId = R.drawable.ic_tangem_24, + enabled = true, + onClicked = {}, + ) } } @@ -36,7 +41,12 @@ fun Preview_PrimaryStartIconButton_Enabled_InLightTheme() { @Composable fun Preview_PrimaryStartIconButton_Enabled_InDarkTheme() { TangemTheme(isDark = true) { - PrimaryStartIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) + PrimaryStartIconButton( + text = "Manage tokens", + iconResId = R.drawable.ic_tangem_24, + enabled = true, + onClicked = {}, + ) } } @@ -46,7 +56,7 @@ fun Preview_PrimaryStartIconButton_Disabled_InLightTheme() { TangemTheme(isDark = false) { PrimaryStartIconButton( text = "Manage tokens", - iconResId = R.drawable.ic_tangem, + iconResId = R.drawable.ic_tangem_24, enabled = false, onClicked = {}, ) @@ -59,7 +69,7 @@ fun Preview_PrimaryStartIconButton_Disabled_InDarkTheme() { TangemTheme(isDark = true) { PrimaryStartIconButton( text = "Manage tokens", - iconResId = R.drawable.ic_tangem, + iconResId = R.drawable.ic_tangem_24, enabled = false, onClicked = {}, ) @@ -70,7 +80,12 @@ fun Preview_PrimaryStartIconButton_Disabled_InDarkTheme() { @Composable fun Preview_PrimaryEndIconButton_Enabled_InLightTheme() { TangemTheme(isDark = false) { - PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) + PrimaryEndIconButton( + text = "Manage tokens", + iconResId = R.drawable.ic_tangem_24, + enabled = true, + onClicked = {}, + ) } } @@ -78,7 +93,12 @@ fun Preview_PrimaryEndIconButton_Enabled_InLightTheme() { @Composable fun Preview_PrimaryEndIconButton_Enabled_InDarkTheme() { TangemTheme(isDark = true) { - PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) + PrimaryEndIconButton( + text = "Manage tokens", + iconResId = R.drawable.ic_tangem_24, + enabled = true, + onClicked = {}, + ) } } @@ -86,7 +106,12 @@ fun Preview_PrimaryEndIconButton_Enabled_InDarkTheme() { @Composable fun Preview_PrimaryEndIconButton_Disabled_InLightTheme() { TangemTheme(isDark = false) { - PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = false, onClicked = {}) + PrimaryEndIconButton( + text = "Manage tokens", + iconResId = R.drawable.ic_tangem_24, + enabled = false, + onClicked = {}, + ) } } @@ -94,7 +119,12 @@ fun Preview_PrimaryEndIconButton_Disabled_InLightTheme() { @Composable fun Preview_PrimaryEndIconButton_Disabled_InDarkTheme() { TangemTheme(isDark = true) { - PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = false, onClicked = {}) + PrimaryEndIconButton( + text = "Manage tokens", + iconResId = R.drawable.ic_tangem_24, + enabled = false, + onClicked = {}, + ) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt index 90d5ee1ce4..2a8e167e49 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt @@ -1,79 +1,320 @@ package com.tangem.core.ui.components +import androidx.compose.foundation.Image +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.MaterialTheme +import androidx.compose.material.Divider +import androidx.compose.material.Icon import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TextColorType -import com.tangem.core.ui.res.textColor - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Composable -fun Preview_SimpleInfoCard_InLightTheme() { - TangemTheme(isDark = false) { - SmallInfoCard(startText = "Balance", endText = "0.4405434 BTC") - } -} - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Composable -fun Preview_SimpleInfoCard_InDarkTheme() { - TangemTheme(isDark = true) { - SmallInfoCard(startText = "Balance", endText = "0.4405434 BTC") - } -} +import com.valentinilk.shimmer.shimmer /** * Small card with text information attached to the edges * * @param startText text information attached to the left edge * @param endText text information attached to the right edge + * @param isLoading if true, shimmer is shown instead of [startText] and [endText] * * @see Figma component */ @Composable -fun SmallInfoCard(startText: String, endText: String) { +fun SmallInfoCard(startText: String, endText: String, isLoading: Boolean = false) { Surface( shape = RoundedCornerShape(TangemTheme.dimens.radius12), - color = MaterialTheme.colors.secondary, + color = TangemTheme.colors.background.primary, elevation = TangemTheme.dimens.elevation2, ) { - Row( + CardInfoBox(startText = startText, endText = endText, isLoading = isLoading) + } +} + +/** + * [SmallInfoCard] with warning information shown underneath it. + * + * @param startText text information attached to the left edge + * @param endText text information attached to the right edge + * @param warningText text of the warning + * + * @see Figma component + */ +@Composable +fun SmallInfoCardWithWarning( + startText: String, + endText: String, + warningText: String, +) { + Surface( + shape = RoundedCornerShape(TangemTheme.dimens.size12), + color = TangemTheme.colors.background.primary, + elevation = TangemTheme.dimens.elevation2, + ) { + Column( modifier = Modifier - .fillMaxWidth() - .height(TangemTheme.dimens.size48) - .padding( - horizontal = TangemTheme.dimens.spacing16, - vertical = TangemTheme.dimens.spacing12, - ), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + .fillMaxWidth(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + + CardInfoBox(startText = startText, endText = endText) + + Divider( + color = TangemTheme.colors.stroke.primary, + thickness = TangemTheme.dimens.size0_5, + modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing12), + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding( + top = TangemTheme.dimens.spacing14, + bottom = TangemTheme.dimens.spacing16, + start = TangemTheme.dimens.spacing16, + end = TangemTheme.dimens.spacing16, + ), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = Modifier + .background( + color = TangemTheme.colors.background.secondary, + shape = CircleShape, + ) + .height(TangemTheme.dimens.size40) + .width(TangemTheme.dimens.size40), + contentAlignment = Alignment.Center, + ) { + Image( + painter = painterResource(id = R.drawable.img_attention_20), + contentDescription = null, + ) + } + SpacerW12() + Text( + text = warningText, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.body2, + ) + } + } + } +} + +/** + * Small card with an icon to the left and title with description shown to the right of it. + * Additional context can be shown to the right of it. + * + * @param title shown to the right of icon + * @param description shown to the right of icon + * @param icon icon to the left + * @param additionalContent shown at the right edge of the card + * + * @see Figma component + */ +@Composable +fun CardWithIcon( + title: String, + description: String, + icon: @Composable () -> Unit, + additionalContent: @Composable () -> Unit = {}, +) { + Surface( + shape = RoundedCornerShape(TangemTheme.dimens.radius12), + color = TangemTheme.colors.background.primary, + elevation = TangemTheme.dimens.elevation2, + ) { + IconWithTitleAndDescription( + title = title, + description = description, + icon = icon, + additionalContent = additionalContent, + ) + } +} + +/** + * A widget with an icon to the left and title with description shown to the right of it. + * Additional context can be shown to the right of it. + * + * @param title shown to the right of icon + * @param description shown to the right of icon + * @param icon icon to the left + * @param additionalContent shown at the right edge of the card + * + * @see Figma component + */ +@Composable +fun IconWithTitleAndDescription( + title: String, + description: String, + icon: @Composable () -> Unit, + additionalContent: @Composable () -> Unit = {}, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier + .wrapContentHeight() + .fillMaxWidth() + .padding( + horizontal = TangemTheme.dimens.spacing16, + vertical = TangemTheme.dimens.spacing12, + ), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically, + ) { + + Box( + modifier = Modifier + .background( + color = TangemTheme.colors.background.secondary, + shape = CircleShape, + ) + .height(TangemTheme.dimens.size40) + .width(TangemTheme.dimens.size40), + contentAlignment = Alignment.Center, + ) { + icon() + } + + SpacerW12() + + Column( + modifier = Modifier + .weight(weight = 1f, fill = true), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.Start, ) { Text( - text = startText, - color = MaterialTheme.colors.textColor(type = TextColorType.TERTIARY), - maxLines = 1, - style = MaterialTheme.typography.subtitle2 + text = title, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.subtitle1, ) + SpacerH4() + Text( + text = description, + color = TangemTheme.colors.text.secondary, + style = TangemTheme.typography.body2, + ) + } + + additionalContent() + } +} + +// region elements + +@Composable +private fun CardInfoBox(startText: String, endText: String, isLoading: Boolean = false) { + Row( + modifier = Modifier + .fillMaxWidth() + .height(TangemTheme.dimens.size48) + .padding( + horizontal = TangemTheme.dimens.spacing16, + vertical = TangemTheme.dimens.spacing12, + ), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = startText, + color = TangemTheme.colors.text.tertiary, + maxLines = 1, + style = TangemTheme.typography.subtitle2, + ) + + if (isLoading) { + Box( + modifier = Modifier + .shimmer() + .size( + width = TangemTheme.dimens.size80, + height = TangemTheme.dimens.size20, + ), + ) + } else { Text( text = endText, - color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1), + color = TangemTheme.colors.text.primary1, maxLines = 1, - style = MaterialTheme.typography.body2 + style = TangemTheme.typography.body2, ) } } -} \ No newline at end of file +} + +// endregion elements + +// region Preview + +@Composable +private fun CardsPreview() { + Column(modifier = Modifier.fillMaxWidth()) { + SmallInfoCard(startText = "Balance", endText = "0.4405434 BTC") + + SpacerH32() + + SmallInfoCardWithWarning( + startText = "Balance", + endText = "0.4405434 BTC", + warningText = + "Not enough funds for fee on your Polygon wallet to create a transaction. Top up your Polygon wallet first.", + ) + + SpacerH32() + + CardWithIcon( + title = "Permit is valid until", + description = "26:30", + icon = { + Icon( + painterResource(id = R.drawable.ic_clock_24), + contentDescription = null, + tint = TangemTheme.colors.icon.primary1, + ) + }, + ) + } +} + +@Preview(showBackground = true) +@Composable +fun Preview_Cards_InLightTheme() { + TangemTheme(isDark = false) { + CardsPreview() + } +} + +@Preview(showBackground = true) +@Composable +fun Preview_InfoCardWithWarning_InDarkTheme() { + TangemTheme(isDark = true) { + CardsPreview() + } +} + +// endregion Preview \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt index fcc5dc2d24..93813f92e5 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt @@ -46,6 +46,11 @@ fun SpacerH8(modifier: Modifier = Modifier) { SpacerH(8.dp, modifier) } +@Composable +fun SpacerH10(modifier: Modifier = Modifier) { + SpacerH(10.dp, modifier) +} + @Composable fun SpacerH12(modifier: Modifier = Modifier) { SpacerH(12.dp, modifier) @@ -71,6 +76,11 @@ fun SpacerH32(modifier: Modifier = Modifier) { SpacerH(32.dp, modifier) } +@Composable +fun SpacerH50(modifier: Modifier = Modifier) { + SpacerH(50.dp, modifier) +} + // TODO: Refactor with context receivers @Composable fun ColumnScope.SpacerHMax(modifier: Modifier = Modifier) { @@ -105,6 +115,11 @@ fun SpacerW8(modifier: Modifier = Modifier) { SpacerW(8.dp, modifier) } +@Composable +fun SpacerW12(modifier: Modifier = Modifier) { + SpacerW(12.dp, modifier) +} + @Composable fun SpacerW16(modifier: Modifier = Modifier) { SpacerW(16.dp, modifier) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt b/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt index f14d87c904..03af4693a4 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt @@ -140,7 +140,7 @@ private fun TangemTextField( }, trailingIcon = { val iconRes by rememberUpdatedState( - newValue = if (isError) R.drawable.ic_alert else R.drawable.ic_close, + newValue = if (isError) R.drawable.ic_alert_24 else R.drawable.ic_close_24, ) IconButton( modifier = Modifier.size(32.dp), diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt index 6416a8343f..d51a90d999 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt @@ -12,7 +12,6 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R @@ -83,11 +82,11 @@ fun Preview_AppBarWithAdditionalButtons_InLightTheme() { AppBarWithAdditionalButtons( text = "Tangem", startButton = AdditionalButton( - iconRes = R.drawable.ic_scan, + iconRes = R.drawable.ic_scan_24, onIconClicked = {}, ), endButton = AdditionalButton( - iconRes = R.drawable.ic_more_vertical, + iconRes = R.drawable.ic_more_vertical_24, onIconClicked = {}, ), ) @@ -101,11 +100,11 @@ fun Preview_AppBarWithAdditionalButtons_InDarkTheme() { AppBarWithAdditionalButtons( text = "Tangem", startButton = AdditionalButton( - iconRes = R.drawable.ic_scan, + iconRes = R.drawable.ic_scan_24, onIconClicked = {}, ), endButton = AdditionalButton( - iconRes = R.drawable.ic_more_vertical, + iconRes = R.drawable.ic_more_vertical_24, onIconClicked = {}, ), ) @@ -119,7 +118,7 @@ fun Preview_AppBarWithOnlyStartButtons_InLightTheme() { AppBarWithAdditionalButtons( text = "Tangem", startButton = AdditionalButton( - iconRes = R.drawable.ic_scan, + iconRes = R.drawable.ic_scan_24, onIconClicked = {}, ), ) @@ -133,7 +132,7 @@ fun Preview_AppBarWithOnlyStartButtons_InDarkTheme() { AppBarWithAdditionalButtons( text = "Tangem", startButton = AdditionalButton( - iconRes = R.drawable.ic_scan, + iconRes = R.drawable.ic_scan_24, onIconClicked = {}, ), ) @@ -147,7 +146,7 @@ fun Preview_AppBarWithOnlyEndButtons_InLightTheme() { AppBarWithAdditionalButtons( text = "Tangem", endButton = AdditionalButton( - iconRes = R.drawable.ic_more_vertical, + iconRes = R.drawable.ic_more_vertical_24, onIconClicked = {}, ), ) @@ -161,7 +160,7 @@ fun Preview_AppBarWithOnlyEndButtons_InDarkTheme() { AppBarWithAdditionalButtons( text = "Tangem", endButton = AdditionalButton( - iconRes = R.drawable.ic_more_vertical, + iconRes = R.drawable.ic_more_vertical_24, onIconClicked = {}, ), ) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt index 3f4c5efa9e..863de765e1 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt @@ -13,6 +13,7 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview @@ -29,7 +30,11 @@ import com.tangem.core.ui.res.TangemTheme * >Figma component */ @Composable -fun AppBarWithBackButton(text: String? = null, onBackClick: () -> Unit) { +fun AppBarWithBackButton( + text: String? = null, + navigationIcon: Painter? = null, + onBackClick: () -> Unit, +) { Row( modifier = Modifier .background(MaterialTheme.colors.primary) @@ -39,7 +44,7 @@ fun AppBarWithBackButton(text: String? = null, onBackClick: () -> Unit) { verticalAlignment = Alignment.CenterVertically, ) { Icon( - painter = painterResource(R.drawable.ic_back), + painter = navigationIcon ?: painterResource(R.drawable.ic_back_24), contentDescription = null, modifier = Modifier .size(size = dimensionResource(R.dimen.size24)) diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt index 38321bcba1..061e1f81c6 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt @@ -29,8 +29,10 @@ data class TangemDimens internal constructor( // endregion Radius // region Size val size0: Dp = 0.dp, + val size0_5: Dp = 0.5.dp, val size4: Dp = 4.dp, val size8: Dp = 8.dp, + val size11: Dp = 11.dp, val size12: Dp = 12.dp, val size16: Dp = 16.dp, val size18: Dp = 18.dp, @@ -38,16 +40,23 @@ data class TangemDimens internal constructor( val size24: Dp = 24.dp, val size28: Dp = 28.dp, val size32: Dp = 32.dp, + val size36: Dp = 36.dp, val size40: Dp = 40.dp, val size42: Dp = 42.dp, val size44: Dp = 44.dp, + val size46: Dp = 46.dp, val size48: Dp = 48.dp, + val size50: Dp = 50.dp, val size56: Dp = 56.dp, val size62: Dp = 62.dp, + val size80: Dp = 80.dp, val size84: Dp = 84.dp, val size86: Dp = 86.dp, + val size93: Dp = 93.dp, val size96: Dp = 96.dp, val size102: Dp = 102.dp, + val size116: Dp = 116.dp, + val size142: Dp = 142.dp, val size164: Dp = 164.dp, val size200: Dp = 200.dp, // endregion Size @@ -61,11 +70,13 @@ data class TangemDimens internal constructor( val spacing16: Dp = 16.dp, val spacing20: Dp = 20.dp, val spacing24: Dp = 24.dp, + val spacing28: Dp = 28.dp, val spacing32: Dp = 32.dp, val spacing34: Dp = 34.dp, val spacing44: Dp = 44.dp, val spacing50: Dp = 50.dp, val spacing54: Dp = 54.dp, val spacing56: Dp = 56.dp, + val spacing92: Dp = 92.dp, // endregion Spacing ) \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_alert.xml b/core/ui/src/main/res/drawable/ic_alert_24.xml similarity index 100% rename from core/ui/src/main/res/drawable/ic_alert.xml rename to core/ui/src/main/res/drawable/ic_alert_24.xml diff --git a/core/ui/src/main/res/drawable/ic_back.xml b/core/ui/src/main/res/drawable/ic_back_24.xml similarity index 100% rename from core/ui/src/main/res/drawable/ic_back.xml rename to core/ui/src/main/res/drawable/ic_back_24.xml diff --git a/core/ui/src/main/res/drawable/ic_check_40.xml b/core/ui/src/main/res/drawable/ic_check_40.xml new file mode 100644 index 0000000000..00f79c9226 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_check_40.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_plus.xml b/core/ui/src/main/res/drawable/ic_chevron_24.xml similarity index 61% rename from app/src/main/res/drawable/ic_plus.xml rename to core/ui/src/main/res/drawable/ic_chevron_24.xml index e8b4a25be1..19898141ba 100644 --- a/app/src/main/res/drawable/ic_plus.xml +++ b/core/ui/src/main/res/drawable/ic_chevron_24.xml @@ -1,5 +1,7 @@ - + diff --git a/core/ui/src/main/res/drawable/ic_chevron_right_24.xml b/core/ui/src/main/res/drawable/ic_chevron_right_24.xml new file mode 100644 index 0000000000..1bb30766a3 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_chevron_right_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/ui/src/main/res/drawable/ic_clock_24.xml b/core/ui/src/main/res/drawable/ic_clock_24.xml new file mode 100644 index 0000000000..f2dc1c7357 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_clock_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/ui/src/main/res/drawable/ic_close.xml b/core/ui/src/main/res/drawable/ic_close_24.xml similarity index 100% rename from core/ui/src/main/res/drawable/ic_close.xml rename to core/ui/src/main/res/drawable/ic_close_24.xml diff --git a/core/ui/src/main/res/drawable/ic_copy.xml b/core/ui/src/main/res/drawable/ic_copy_24.xml similarity index 100% rename from core/ui/src/main/res/drawable/ic_copy.xml rename to core/ui/src/main/res/drawable/ic_copy_24.xml diff --git a/core/ui/src/main/res/drawable/ic_discount.xml b/core/ui/src/main/res/drawable/ic_discount_28.xml similarity index 100% rename from core/ui/src/main/res/drawable/ic_discount.xml rename to core/ui/src/main/res/drawable/ic_discount_28.xml diff --git a/core/ui/src/main/res/drawable/ic_exchange_vertical_24.xml b/core/ui/src/main/res/drawable/ic_exchange_vertical_24.xml new file mode 100644 index 0000000000..3ba7904b83 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_exchange_vertical_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/ui/src/main/res/drawable/ic_locked_24.xml b/core/ui/src/main/res/drawable/ic_locked_24.xml new file mode 100644 index 0000000000..565e084d7c --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_locked_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/ui/src/main/res/drawable/ic_more_vertical.xml b/core/ui/src/main/res/drawable/ic_more_vertical_24.xml similarity index 100% rename from core/ui/src/main/res/drawable/ic_more_vertical.xml rename to core/ui/src/main/res/drawable/ic_more_vertical_24.xml diff --git a/core/ui/src/main/res/drawable/ic_plus.xml b/core/ui/src/main/res/drawable/ic_plus_24.xml similarity index 100% rename from core/ui/src/main/res/drawable/ic_plus.xml rename to core/ui/src/main/res/drawable/ic_plus_24.xml diff --git a/core/ui/src/main/res/drawable/ic_refresh_24.xml b/core/ui/src/main/res/drawable/ic_refresh_24.xml new file mode 100644 index 0000000000..c859856a15 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_refresh_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/ui/src/main/res/drawable/ic_scan.xml b/core/ui/src/main/res/drawable/ic_scan_24.xml similarity index 100% rename from core/ui/src/main/res/drawable/ic_scan.xml rename to core/ui/src/main/res/drawable/ic_scan_24.xml diff --git a/core/ui/src/main/res/drawable/ic_share.xml b/core/ui/src/main/res/drawable/ic_share_24.xml similarity index 100% rename from core/ui/src/main/res/drawable/ic_share.xml rename to core/ui/src/main/res/drawable/ic_share_24.xml diff --git a/core/ui/src/main/res/drawable/ic_tangem.xml b/core/ui/src/main/res/drawable/ic_tangem.xml deleted file mode 100644 index 93f8741777..0000000000 --- a/core/ui/src/main/res/drawable/ic_tangem.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - diff --git a/core/ui/src/main/res/drawable/ic_tangem_24.xml b/core/ui/src/main/res/drawable/ic_tangem_24.xml new file mode 100644 index 0000000000..04c194fb62 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_tangem_24.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/core/ui/src/main/res/drawable/ic_tether.xml b/core/ui/src/main/res/drawable/ic_tether_28.xml similarity index 100% rename from core/ui/src/main/res/drawable/ic_tether.xml rename to core/ui/src/main/res/drawable/ic_tether_28.xml diff --git a/core/ui/src/main/res/drawable/img_attention_20.xml b/core/ui/src/main/res/drawable/img_attention_20.xml new file mode 100644 index 0000000000..6c88899401 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_attention_20.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/core/ui/src/main/res/drawable/img_polygon_round_16.xml b/core/ui/src/main/res/drawable/img_polygon_round_16.xml new file mode 100644 index 0000000000..9a8e109166 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_polygon_round_16.xml @@ -0,0 +1,13 @@ + + + + diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt index 29e86058e8..03644d0342 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.PrimaryEndIconButton @@ -23,7 +22,7 @@ internal fun NonParticipateBottomBlock(onAgreementClicked: () -> Unit, onPartici PrimaryEndIconButton( modifier = Modifier.padding(all = TangemTheme.dimens.spacing16), text = stringResource(id = R.string.referral_button_participate), - iconResId = R.drawable.ic_tangem, + iconResId = R.drawable.ic_tangem_24, onClicked = onParticipateClicked, ) } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt index 0027b80634..deb95ccdb4 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt @@ -108,7 +108,7 @@ private fun AdditionalButtons(code: String, shareLink: String, showCopySnackbar: PrimaryStartIconButton( modifier = Modifier.weight(1f), text = stringResource(id = R.string.common_copy), - iconResId = R.drawable.ic_copy, + iconResId = R.drawable.ic_copy_24, onClicked = { hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) clipboardManager.setText(AnnotatedString(code)) @@ -120,7 +120,7 @@ private fun AdditionalButtons(code: String, shareLink: String, showCopySnackbar: PrimaryStartIconButton( modifier = Modifier.weight(1f), text = stringResource(id = R.string.common_share), - iconResId = R.drawable.ic_share, + iconResId = R.drawable.ic_share_24, onClicked = { hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) context.shareText(context.getString(R.string.referral_share_link, shareLink)) diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt index 39d5296a12..09d9bb6db4 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt @@ -207,9 +207,9 @@ private fun ReferralInfo( ) } is ReferralInfoState.Loading -> { - LoadingCondition(iconResId = R.drawable.ic_tether) + LoadingCondition(iconResId = R.drawable.ic_tether_28) VerticalSpacer(spaceResId = R.dimen.spacing32) - LoadingCondition(iconResId = R.drawable.ic_discount) + LoadingCondition(iconResId = R.drawable.ic_discount_28) } } } @@ -223,7 +223,7 @@ private fun Conditions(state: ReferralInfoContentState) { @Composable private fun ConditionForYou(state: ReferralInfoContentState) { - Condition(iconResId = R.drawable.ic_tether) { + Condition(iconResId = R.drawable.ic_tether_28) { when (state) { is ReferralInfoState.ParticipantContent -> InfoForYou( award = state.award, @@ -240,7 +240,7 @@ private fun ConditionForYou(state: ReferralInfoContentState) { @Composable private fun ConditionForYourFriend(discount: String) { - Condition(iconResId = R.drawable.ic_discount) { + Condition(iconResId = R.drawable.ic_discount_28) { InfoForYourFriend(discount = discount) } } From 78f93a849147c375bfc064e3dc234f28457caa21 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 15 Dec 2022 12:45:36 +0300 Subject: [PATCH 010/145] Updated on 2026-08-14 --- .../datasource/api/oneinch/BaseResponse.kt | 8 --- .../datasource/api/oneinch/OneInchApi.kt | 2 +- .../api/oneinch/OneInchErrorsHandler.kt | 18 +++--- .../oneinch/errors/OneIncResponseException.kt | 5 ++ .../tangem/feature/swap/SwapRepositoryImpl.kt | 64 ++++++++++++------- .../swap/converters/QuotesConverter.kt | 29 +++------ .../feature/swap/converters/SwapConverter.kt | 31 +++------ .../feature/swap/domain/SwapInteractor.kt | 9 +-- .../feature/swap/domain/SwapInteractorImpl.kt | 48 ++++++++------ .../feature/swap/domain/SwapRepository.kt | 8 +-- .../swap/domain/cache/SwapDataCache.kt | 2 +- .../swap/domain/cache/SwapDataCacheImpl.kt | 2 +- .../swap/domain/models/SwapDataHolder.kt | 5 +- .../swap/domain/models/SwapResultModel.kt | 14 ---- .../swap/domain/models/data/DataError.kt | 11 ++-- .../swap/domain/models/data/QuoteModel.kt | 8 --- .../swap/domain/models/data/SwapState.kt | 20 ++++++ .../feature/swap/viewmodels/SwapViewModel.kt | 28 ++++++++ 18 files changed, 169 insertions(+), 143 deletions(-) delete mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/oneinch/BaseResponse.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/oneinch/errors/OneIncResponseException.kt delete mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapResultModel.kt delete mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/QuoteModel.kt create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapState.kt diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/BaseResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/BaseResponse.kt deleted file mode 100644 index 866c26d858..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/BaseResponse.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.datasource.api.oneinch - -import com.tangem.datasource.api.oneinch.models.SwapErrorDto - -data class BaseOneInchResponse( - val body: T?, - val errorDto: SwapErrorDto?, -) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt index 613d5d1000..651e726ad3 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt @@ -58,7 +58,7 @@ interface OneInchApi { suspend fun approveAllowance( @Query("tokenAddress") tokenAddress: String, @Query("walletAddress") walletAddress: String, - ): AllowanceResponse + ): Response //endregion Approve //region Info diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchErrorsHandler.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchErrorsHandler.kt index 72e9cd5342..792c9866d7 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchErrorsHandler.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchErrorsHandler.kt @@ -1,25 +1,25 @@ package com.tangem.datasource.api.oneinch import com.squareup.moshi.Moshi +import com.tangem.datasource.api.oneinch.errors.OneIncResponseException import com.tangem.datasource.api.oneinch.models.SwapErrorDto import retrofit2.HttpException import retrofit2.Response import javax.inject.Inject -class OneInchErrorsHandler @Inject constructor(private val moshi: Moshi) { +class OneInchErrorsHandler @Inject constructor(moshi: Moshi) { - fun handleOneInchResponse(response: Response): BaseOneInchResponse { - val body = response.body() + private val errorMoshiAdapter = moshi.adapter(SwapErrorDto::class.java) + + @Throws(OneIncResponseException::class) + fun handleOneInchResponse(response: Response): T { return if (response.isSuccessful) { - return BaseOneInchResponse(body, null) + response.body() ?: error("response body is null") } else { when (response.code()) { HTTP_CODE_400 -> { - if (body != null) { - BaseOneInchResponse(null, moshi.adapter(SwapErrorDto::class.java).fromJson(body.toString())) - } else { - throw HttpException(response) - } + val swapErrorDto = errorMoshiAdapter.fromJson(response.errorBody()?.string() ?: "") + throw OneIncResponseException(swapErrorDto ?: throw HttpException(response)) } else -> { throw HttpException(response) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/errors/OneIncResponseException.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/errors/OneIncResponseException.kt new file mode 100644 index 0000000000..94c94f6e32 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/errors/OneIncResponseException.kt @@ -0,0 +1,5 @@ +package com.tangem.datasource.api.oneinch.errors + +import com.tangem.datasource.api.oneinch.models.SwapErrorDto + +class OneIncResponseException(val data: SwapErrorDto) : Exception() \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt index ca376c5d99..d37105e46e 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt @@ -2,6 +2,7 @@ package com.tangem.feature.swap import com.tangem.datasource.api.oneinch.OneInchApi import com.tangem.datasource.api.oneinch.OneInchErrorsHandler +import com.tangem.datasource.api.oneinch.errors.OneIncResponseException import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.feature.swap.converters.ApproveConverter import com.tangem.feature.swap.converters.QuotesConverter @@ -11,8 +12,9 @@ import com.tangem.feature.swap.domain.SwapRepository import com.tangem.feature.swap.domain.models.AggregatedSwapDataModel import com.tangem.feature.swap.domain.models.data.ApproveModel import com.tangem.feature.swap.domain.models.data.Currency -import com.tangem.feature.swap.domain.models.data.QuoteModel import com.tangem.feature.swap.domain.models.data.SwapDataModel +import com.tangem.feature.swap.domain.models.data.SwapState.QuoteModel +import com.tangem.feature.swap.domain.models.data.mapErrors import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext import javax.inject.Inject @@ -37,14 +39,18 @@ internal class SwapRepositoryImpl @Inject constructor( override suspend fun findBestQuote(fromTokenAddress: String, toTokenAddress: String, amount: String): AggregatedSwapDataModel { return withContext(coroutineDispatcher.io) { - val quoteResponse = oneInchErrorsHandler.handleOneInchResponse( - oneInchApi.quote( - fromTokenAddress = fromTokenAddress, - toTokenAddress = toTokenAddress, - amount = amount, - ), - ) - quotesConverter.convert(quoteResponse) + try { + val response = oneInchErrorsHandler.handleOneInchResponse( + oneInchApi.quote( + fromTokenAddress = fromTokenAddress, + toTokenAddress = toTokenAddress, + amount = amount, + ), + ) + AggregatedSwapDataModel(dataModel = quotesConverter.convert(response)) + } catch (ex: OneIncResponseException) { + AggregatedSwapDataModel(null, mapErrors(ex.data.description)) + } } } @@ -54,15 +60,23 @@ internal class SwapRepositoryImpl @Inject constructor( } } - override suspend fun dataToApprove(tokenAddress: String, amount: String): ApproveModel { + override suspend fun dataToApprove(tokenAddress: String, amount: String?): ApproveModel { return withContext(coroutineDispatcher.io) { approveConverter.convert(oneInchApi.approveTransaction(tokenAddress, amount)) } } - override suspend fun checkTokensSpendAllowance(tokenAddress: String, walletAddress: String): String { + override suspend fun checkTokensSpendAllowance(tokenAddress: String, walletAddress: String): + AggregatedSwapDataModel { return withContext(coroutineDispatcher.io) { - oneInchApi.approveAllowance(tokenAddress, walletAddress).allowance + try { + val response = oneInchErrorsHandler.handleOneInchResponse( + oneInchApi.approveAllowance(tokenAddress, walletAddress) + ) + AggregatedSwapDataModel(response.allowance) + } catch (ex: OneIncResponseException) { + AggregatedSwapDataModel(null, mapErrors(ex.data.description)) + } } } @@ -74,18 +88,20 @@ internal class SwapRepositoryImpl @Inject constructor( slippage: Int, ): AggregatedSwapDataModel { return withContext(coroutineDispatcher.io) { - val swapResponse = oneInchErrorsHandler.handleOneInchResponse( - oneInchApi.swap( - fromTokenAddress = fromTokenAddress, - toTokenAddress = toTokenAddress, - amount = amount, - fromAddress = fromWalletAddress, - slippage = slippage, - ), - ) - swapConverter.convert( - swapResponse, - ) + try { + val swapResponse = oneInchErrorsHandler.handleOneInchResponse( + oneInchApi.swap( + fromTokenAddress = fromTokenAddress, + toTokenAddress = toTokenAddress, + amount = amount, + fromAddress = fromWalletAddress, + slippage = slippage, + ), + ) + AggregatedSwapDataModel(swapConverter.convert(swapResponse)) + } catch (ex: OneIncResponseException) { + AggregatedSwapDataModel(null, mapErrors(ex.data.description)) + } } } } \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt index fe37564101..d6566ef6e3 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt @@ -1,30 +1,17 @@ package com.tangem.feature.swap.converters -import com.tangem.datasource.api.oneinch.BaseOneInchResponse import com.tangem.datasource.api.oneinch.models.QuoteResponse -import com.tangem.feature.swap.domain.models.AggregatedSwapDataModel -import com.tangem.feature.swap.domain.models.data.QuoteModel -import com.tangem.feature.swap.domain.models.data.mapErrors +import com.tangem.feature.swap.domain.models.data.SwapState.QuoteModel import com.tangem.utils.converter.Converter import javax.inject.Inject -class QuotesConverter @Inject constructor() : Converter, AggregatedSwapDataModel> { +class QuotesConverter @Inject constructor() : Converter { - override fun convert(value: BaseOneInchResponse): AggregatedSwapDataModel { - val body = value.body - return if (body != null) { - AggregatedSwapDataModel( - QuoteModel( - fromTokenAmount = body.fromTokenAmount, - toTokenAmount = body.toTokenAmount, - estimatedGas = body.estimatedGas, - ), - ) - } else { - AggregatedSwapDataModel( - null, - mapErrors(value.errorDto?.description), - ) - } + override fun convert(value: QuoteResponse): QuoteModel { + return QuoteModel( + fromTokenAmount = value.fromTokenAmount, + toTokenAmount = value.toTokenAmount, + estimatedGas = value.estimatedGas, + ) } } \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt index 1c2fb0e2c2..87dec263da 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt @@ -1,35 +1,22 @@ package com.tangem.feature.swap.converters -import com.tangem.datasource.api.oneinch.BaseOneInchResponse import com.tangem.datasource.api.oneinch.models.SwapResponse import com.tangem.datasource.api.oneinch.models.TransactionDto -import com.tangem.feature.swap.domain.models.AggregatedSwapDataModel import com.tangem.feature.swap.domain.models.data.SwapDataModel import com.tangem.feature.swap.domain.models.data.TransactionModel -import com.tangem.feature.swap.domain.models.data.mapErrors import com.tangem.utils.converter.Converter import javax.inject.Inject -class SwapConverter @Inject constructor() : Converter, AggregatedSwapDataModel> { +class SwapConverter @Inject constructor() : Converter { - override fun convert(value: BaseOneInchResponse): AggregatedSwapDataModel { - val body = value.body - return if (body != null) { - AggregatedSwapDataModel( - SwapDataModel( - fromTokenAddress = body.fromToken.address, - toTokenAddress = body.toToken.address, - toTokenAmount = body.fromTokenAmount, - fromTokenAmount = body.toTokenAmount, - transaction = convertTransaction(body.transaction), - ), - ) - } else { - AggregatedSwapDataModel( - null, - mapErrors(value.errorDto?.description), - ) - } + override fun convert(value: SwapResponse): SwapDataModel { + return SwapDataModel( + fromTokenAddress = value.fromToken.address, + toTokenAddress = value.toToken.address, + toTokenAmount = value.fromTokenAmount, + fromTokenAmount = value.toTokenAmount, + transaction = convertTransaction(value.transaction), + ) } private fun convertTransaction(transactionDto: TransactionDto): TransactionModel { diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt index f75c0842ad..be45612480 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt @@ -1,8 +1,7 @@ package com.tangem.feature.swap.domain -import com.tangem.feature.swap.domain.models.SwapResultModel import com.tangem.feature.swap.domain.models.data.Currency -import com.tangem.feature.swap.domain.models.data.QuoteModel +import com.tangem.feature.swap.domain.models.data.SwapState interface SwapInteractor { @@ -10,11 +9,13 @@ interface SwapInteractor { suspend fun getTokenBalance(tokenId: String): String + suspend fun givePermissionToSwap(tokenAddress: String) + suspend fun findBestQuote( fromTokenAddress: String, toTokenAddress: String, amount: String, - ): QuoteModel + ): SwapState - suspend fun onSwap(): SwapResultModel + suspend fun onSwap(): SwapState } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 942a6d256d..3a989bb4c6 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -1,9 +1,9 @@ package com.tangem.feature.swap.domain import com.tangem.feature.swap.domain.cache.SwapDataCache -import com.tangem.feature.swap.domain.models.SwapResultModel import com.tangem.feature.swap.domain.models.data.Currency -import com.tangem.feature.swap.domain.models.data.QuoteModel +import com.tangem.feature.swap.domain.models.data.DataError +import com.tangem.feature.swap.domain.models.data.SwapState import com.tangem.feature.swap.domain.models.data.TransactionModel import com.tangem.lib.crypto.UserWalletManager import javax.inject.Inject @@ -28,12 +28,20 @@ internal class SwapInteractorImpl @Inject constructor( TODO("Not yet implemented") } - override suspend fun findBestQuote(fromTokenAddress: String, toTokenAddress: String, amount: String): QuoteModel { + override suspend fun givePermissionToSwap(tokenAddress: String) { + val oneInchAddress = repository.addressForTrust() + val transactionData = repository.dataToApprove(tokenAddress) + //todo sign transaction (next tasks) + } + + override suspend fun findBestQuote(fromTokenAddress: String, toTokenAddress: String, amount: String): SwapState { val networkId = cache.getNetworkId() val isAllowedToSpend = if (networkId.isNullOrEmpty()) { false } else { - repository.checkTokensSpendAllowance(fromTokenAddress, userWalletManager.getWalletAddress(networkId)) != "0" + val allowance = + repository.checkTokensSpendAllowance(fromTokenAddress, userWalletManager.getWalletAddress(networkId)) + allowance.error == DataError.NO_ERROR && allowance.dataModel != "0" } repository.findBestQuote(fromTokenAddress, toTokenAddress, amount).let { quotes -> val quoteDataModel = quotes.dataModel @@ -41,37 +49,37 @@ internal class SwapInteractorImpl @Inject constructor( cache.cacheSwapParams(quoteDataModel.copy(isAllowedToSpend = isAllowedToSpend), amount) return quoteDataModel } else { - error("") //todo handle error in domain layer(task [REDACTED_TASK_KEY]) + return SwapState.SwapError(quotes.error) } } } - override suspend fun onSwap(): SwapResultModel { + override suspend fun onSwap(): SwapState { val quoteModel = cache.getLastQuote() val amountToSwap = cache.getAmountToSwap() val networkId = cache.getNetworkId() if (quoteModel != null && amountToSwap != null && networkId != null) { - val swapData = repository.prepareSwapTransaction( + repository.prepareSwapTransaction( fromTokenAddress = quoteModel.fromTokenAmount, toTokenAddress = quoteModel.toTokenAmount, amount = amountToSwap, slippage = DEFAULT_SLIPPAGE, fromWalletAddress = getWalletAddress(networkId), - ).dataModel - if (swapData != null) { - signTransactionData(swapData.transaction) //todo implement - return SwapResultModel.SwapSuccess( - quoteModel.fromTokenAmount, - quoteModel.toTokenAmount, - ) - } else { - error("") //todo handle error in domain layer(task [REDACTED_TASK_KEY]) + ).let { + val swapData = it.dataModel + if (swapData != null) { + signTransactionData(swapData.transaction) //todo implement + return SwapState.SwapSuccess( + quoteModel.fromTokenAmount, + quoteModel.toTokenAmount, + ) + } else { + return SwapState.SwapError(it.error) + } } + } else { + throw IllegalStateException("cache is empty, call 'findBestQuote' first") } - return SwapResultModel.SwapError( - 0, - "", - ) } private fun getWalletAddress(networkId: String): String { diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt index 4c8254c809..01f534a296 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt @@ -3,8 +3,8 @@ package com.tangem.feature.swap.domain import com.tangem.feature.swap.domain.models.AggregatedSwapDataModel import com.tangem.feature.swap.domain.models.data.ApproveModel import com.tangem.feature.swap.domain.models.data.Currency -import com.tangem.feature.swap.domain.models.data.QuoteModel import com.tangem.feature.swap.domain.models.data.SwapDataModel +import com.tangem.feature.swap.domain.models.data.SwapState interface SwapRepository { @@ -14,7 +14,7 @@ interface SwapRepository { fromTokenAddress: String, toTokenAddress: String, amount: String, - ): AggregatedSwapDataModel + ): AggregatedSwapDataModel /** * Returns address of 1inch router that must be trusted @@ -31,7 +31,7 @@ interface SwapRepository { */ suspend fun dataToApprove( tokenAddress: String, - amount: String, + amount: String? = null, ): ApproveModel /** @@ -42,7 +42,7 @@ interface SwapRepository { * * @return amount of tokens allowed to spend */ - suspend fun checkTokensSpendAllowance(tokenAddress: String, walletAddress: String): String + suspend fun checkTokensSpendAllowance(tokenAddress: String, walletAddress: String): AggregatedSwapDataModel suspend fun prepareSwapTransaction( fromTokenAddress: String, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt index 1f6d2373ef..13cdd8e17d 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt @@ -1,7 +1,7 @@ package com.tangem.feature.swap.domain.cache import com.tangem.feature.swap.domain.models.data.Currency -import com.tangem.feature.swap.domain.models.data.QuoteModel +import com.tangem.feature.swap.domain.models.data.SwapState.QuoteModel interface SwapDataCache { diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt index f4fd1cd174..7aab37de03 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt @@ -2,7 +2,7 @@ package com.tangem.feature.swap.domain.cache import com.tangem.feature.swap.domain.models.SwapDataHolder import com.tangem.feature.swap.domain.models.data.Currency -import com.tangem.feature.swap.domain.models.data.QuoteModel +import com.tangem.feature.swap.domain.models.data.SwapState.QuoteModel class SwapDataCacheImpl : SwapDataCache { diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataHolder.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataHolder.kt index 60d62274de..4d50aa2da8 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataHolder.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataHolder.kt @@ -1,9 +1,10 @@ package com.tangem.feature.swap.domain.models -import com.tangem.feature.swap.domain.models.data.QuoteModel +import com.tangem.feature.swap.domain.models.data.SwapState.QuoteModel data class SwapDataHolder( - val quoteModel: QuoteModel? = null, + val quoteModel: + QuoteModel? = null, val amountToSwap: String? = null, val networkId: String? = null, ) \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapResultModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapResultModel.kt deleted file mode 100644 index 014fb40127..0000000000 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapResultModel.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.tangem.feature.swap.domain.models - -sealed interface SwapResultModel { - - data class SwapSuccess( - val fromTokenAmount: String, - val toTokenAmount: String, - ) : SwapResultModel - - data class SwapError( - val errorCode: Int, - val errorMessage: String, - ) : SwapResultModel -} \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/DataError.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/DataError.kt index 6498ac07e0..60e80e094e 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/DataError.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/DataError.kt @@ -3,16 +3,19 @@ package com.tangem.feature.swap.domain.models.data enum class DataError { NO_ERROR, UNKNOWN_ERROR, - INSUFFICIENT_LIQUIDITY + INSUFFICIENT_LIQUIDITY, + CANNOT_SYNC } fun mapErrors(error: String?): DataError { return if (error == null) { DataError.UNKNOWN_ERROR - } else when (error) { - INSUFFICIENT_LIQUIDITY_ERROR -> DataError.INSUFFICIENT_LIQUIDITY + } else when { + error == INSUFFICIENT_LIQUIDITY_ERROR -> DataError.INSUFFICIENT_LIQUIDITY + error.startsWith(CANNOT_SYNC_ERROR) -> DataError.CANNOT_SYNC else -> DataError.UNKNOWN_ERROR } } -private const val INSUFFICIENT_LIQUIDITY_ERROR = "insufficient liquidity" \ No newline at end of file +private const val INSUFFICIENT_LIQUIDITY_ERROR = "insufficient liquidity" +private const val CANNOT_SYNC_ERROR = "cannot sync" \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/QuoteModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/QuoteModel.kt deleted file mode 100644 index 9a80b7c936..0000000000 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/QuoteModel.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.feature.swap.domain.models.data - -data class QuoteModel( - val fromTokenAmount: String, - val toTokenAmount: String, - val estimatedGas: Int, - val isAllowedToSpend: Boolean = false, -) \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapState.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapState.kt new file mode 100644 index 0000000000..7c269ea158 --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapState.kt @@ -0,0 +1,20 @@ +package com.tangem.feature.swap.domain.models.data + +sealed interface SwapState { + + data class QuoteModel( + val fromTokenAmount: String, + val toTokenAmount: String, + val estimatedGas: Int, + val isAllowedToSpend: Boolean = false, + ) : SwapState + + data class SwapSuccess( + val fromTokenAmount: String, + val toTokenAmount: String, + ) : SwapState + + data class SwapError( + val errorType: DataError, + ) : SwapState +} \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index 84f79dad22..fd8a40070f 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -1,9 +1,13 @@ package com.tangem.feature.swap.viewmodels +import android.util.Log import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.tangem.feature.swap.domain.SwapInteractor +import com.tangem.feature.swap.domain.models.data.Currency import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel @@ -12,4 +16,28 @@ internal class SwapViewModel @Inject constructor( private val dispatchers: CoroutineDispatcherProvider, ) : ViewModel() { + // region temp for test + init { + viewModelScope.launch { + runCatching { + val tokens = referralInteractor.getTokensToSwap("binance-smart-chain") + val token = tokens[1] + val token2 = tokens[2] + if (token is Currency.NonNativeToken && token2 is Currency.NonNativeToken) { + findBestQuotes(token.contractAddress, token2.contractAddress) + } + }.onFailure { + Log.e("SwapViewModel", it.message ?: it.cause.toString()) + } + } + } + + private suspend fun findBestQuotes(fromTokenAddress: String, toTokenAddress: String) { + referralInteractor.findBestQuote( + fromTokenAddress, + toTokenAddress, + "1", + ) + } + // endregion temp for test } \ No newline at end of file From 795877a53de05a4ad46914b710af9be5762d3490 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 15 Dec 2022 14:16:22 +0400 Subject: [PATCH 011/145] Updated on 2026-08-14 --- .../com/tangem/core/ui/components/Cards.kt | 28 +++++++++++++++---- .../components/appbar/AppBarWithBackButton.kt | 6 ++-- core/ui/src/main/res/drawable/ic_check_24.xml | 10 +++++++ core/ui/src/main/res/drawable/ic_check_40.xml | 10 ------- .../res/drawable/ic_exchange_vertical_24.xml | 2 +- .../src/main/res/drawable/img_polygon_22.xml | 13 +++++++++ .../res/drawable/img_polygon_round_16.xml | 13 --------- 7 files changed, 50 insertions(+), 32 deletions(-) create mode 100644 core/ui/src/main/res/drawable/ic_check_24.xml delete mode 100644 core/ui/src/main/res/drawable/ic_check_40.xml create mode 100644 core/ui/src/main/res/drawable/img_polygon_22.xml delete mode 100644 core/ui/src/main/res/drawable/img_polygon_round_16.xml diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt index 2a8e167e49..8eb31f6669 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt @@ -38,13 +38,23 @@ import com.valentinilk.shimmer.shimmer * >Figma component */ @Composable -fun SmallInfoCard(startText: String, endText: String, isLoading: Boolean = false) { +fun SmallInfoCard( + modifier: Modifier = Modifier, + startText: String, + endText: String, + isLoading: Boolean = false, +) { Surface( shape = RoundedCornerShape(TangemTheme.dimens.radius12), color = TangemTheme.colors.background.primary, elevation = TangemTheme.dimens.elevation2, ) { - CardInfoBox(startText = startText, endText = endText, isLoading = isLoading) + CardInfoBox( + modifier = modifier, + startText = startText, + endText = endText, + isLoading = isLoading, + ) } } @@ -60,6 +70,7 @@ fun SmallInfoCard(startText: String, endText: String, isLoading: Boolean = false */ @Composable fun SmallInfoCardWithWarning( + modifier: Modifier = Modifier, startText: String, endText: String, warningText: String, @@ -70,7 +81,7 @@ fun SmallInfoCardWithWarning( elevation = TangemTheme.dimens.elevation2, ) { Column( - modifier = Modifier + modifier = modifier .fillMaxWidth(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, @@ -136,6 +147,7 @@ fun SmallInfoCardWithWarning( */ @Composable fun CardWithIcon( + modifier: Modifier = Modifier, title: String, description: String, icon: @Composable () -> Unit, @@ -147,6 +159,7 @@ fun CardWithIcon( elevation = TangemTheme.dimens.elevation2, ) { IconWithTitleAndDescription( + modifier = modifier, title = title, description = description, icon = icon, @@ -228,9 +241,14 @@ fun IconWithTitleAndDescription( // region elements @Composable -private fun CardInfoBox(startText: String, endText: String, isLoading: Boolean = false) { +private fun CardInfoBox( + modifier: Modifier = Modifier, + startText: String, + endText: String, + isLoading: Boolean = false, +) { Row( - modifier = Modifier + modifier = modifier .fillMaxWidth() .height(TangemTheme.dimens.size48) .padding( diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt index 863de765e1..a5ecdac8f2 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.appbar +import androidx.annotation.DrawableRes import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -13,7 +14,6 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview @@ -32,7 +32,7 @@ import com.tangem.core.ui.res.TangemTheme @Composable fun AppBarWithBackButton( text: String? = null, - navigationIcon: Painter? = null, + @DrawableRes iconRes: Int? = null, onBackClick: () -> Unit, ) { Row( @@ -44,7 +44,7 @@ fun AppBarWithBackButton( verticalAlignment = Alignment.CenterVertically, ) { Icon( - painter = navigationIcon ?: painterResource(R.drawable.ic_back_24), + painter = painterResource(iconRes ?: R.drawable.ic_back_24), contentDescription = null, modifier = Modifier .size(size = dimensionResource(R.dimen.size24)) diff --git a/core/ui/src/main/res/drawable/ic_check_24.xml b/core/ui/src/main/res/drawable/ic_check_24.xml new file mode 100644 index 0000000000..d3e69cbd22 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_check_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/ui/src/main/res/drawable/ic_check_40.xml b/core/ui/src/main/res/drawable/ic_check_40.xml deleted file mode 100644 index 00f79c9226..0000000000 --- a/core/ui/src/main/res/drawable/ic_check_40.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/core/ui/src/main/res/drawable/ic_exchange_vertical_24.xml b/core/ui/src/main/res/drawable/ic_exchange_vertical_24.xml index 3ba7904b83..ddc06b2448 100644 --- a/core/ui/src/main/res/drawable/ic_exchange_vertical_24.xml +++ b/core/ui/src/main/res/drawable/ic_exchange_vertical_24.xml @@ -5,6 +5,6 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/core/ui/src/main/res/drawable/img_polygon_22.xml b/core/ui/src/main/res/drawable/img_polygon_22.xml new file mode 100644 index 0000000000..efb2fff8c1 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_polygon_22.xml @@ -0,0 +1,13 @@ + + + + diff --git a/core/ui/src/main/res/drawable/img_polygon_round_16.xml b/core/ui/src/main/res/drawable/img_polygon_round_16.xml deleted file mode 100644 index 9a8e109166..0000000000 --- a/core/ui/src/main/res/drawable/img_polygon_round_16.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - From 7306fb756e9af37094f242aa5c5ae6d817b58fea Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 15 Dec 2022 15:45:57 +0400 Subject: [PATCH 012/145] Updated on 2026-08-14 --- .../com/tangem/core/ui/components/Warnings.kt | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt new file mode 100644 index 0000000000..447cdc661d --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt @@ -0,0 +1,191 @@ +package com.tangem.core.ui.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Icon +import androidx.compose.material.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.R +import com.tangem.core.ui.res.TangemTheme + +/** + * A card with a warning icon to the left and title with description shown to the right of it. + * + * @param title title of the warning in bold + * @param description description of the warning + * + * @see Figma component + */ +@Composable +fun WarningCard( + modifier: Modifier = Modifier, + title: String, + description: String, +) { + WarningCardSurface( + content = { + WarningBody( + title = title, + description = description, + modifier = modifier, + ) + }, + ) +} + +/** + * [WarningCard], but clickable (with an 'greater then' icon to the left) + * + * @param title title of the warning in bold + * @param description description of the warning + * @param onClick action to be performed when warning is clicked + * + * @see Figma component + */ +@Composable +fun ClickableWarningCard( + modifier: Modifier = Modifier, + title: String, + description: String, + onClick: () -> Unit, +) { + WarningCardSurface( + content = { + WarningBody(modifier = modifier, title = title, description = description) { + SpacerW12() + Icon( + painter = painterResource(id = R.drawable.ic_chevron_right_24), + tint = TangemTheme.colors.icon.informative, + contentDescription = null, + ) + } + }, + onClick = onClick, + ) +} + +/** + * [WarningCard], but clickable and with a 'refresh' icon at the right edge + * + * @param title title of the warning in bold + * @param description description of the warning + * @param onClick action to be performed when warning is clicked + * + * @see Figma component + */ +@Composable +fun RefreshableWaringCard( + modifier: Modifier = Modifier, + title: String, + description: String, + onClick: () -> Unit, +) { + WarningCardSurface( + content = { + WarningBody(modifier = modifier, title = title, description = description) { + SpacerW12() + Icon( + painter = painterResource(id = R.drawable.ic_refresh_24), + tint = TangemTheme.colors.icon.primary1, + contentDescription = null, + ) + } + }, + onClick = onClick, + ) +} + +// region elements + +@Composable +private fun WarningBody( + modifier: Modifier = Modifier, + title: String, + description: String, + additionalContent: @Composable () -> Unit = {}, +) { + IconWithTitleAndDescription( + modifier = modifier, + title = title, + description = description, + additionalContent = additionalContent, + icon = { + Image( + painter = painterResource(id = R.drawable.img_attention_20), + contentDescription = null, + ) + }, + ) +} + +@Composable +private fun WarningCardSurface( + modifier: Modifier = Modifier, + onClick: (() -> Unit)? = null, + content: @Composable () -> Unit, +) { + Surface( + shape = RoundedCornerShape(TangemTheme.dimens.size12), + color = TangemTheme.colors.background.primary, + elevation = TangemTheme.dimens.elevation2, + modifier = modifier.clickable( + enabled = onClick != null, + onClick = onClick ?: {}, + ), + ) { + content() + } +} + +// endregion elements + +// region Preview + +@Composable +fun WarningsPreview() { + Column(modifier = Modifier.fillMaxWidth()) { + WarningCard( + title = "Exchange rate has expired", + description = "To access all the networks, you need to scan the card.", + ) + SpacerH32() + ClickableWarningCard( + title = "Exchange rate has expired", + description = "To access all the networks, you need to scan the card.", + onClick = {}, + ) + SpacerH32() + RefreshableWaringCard( + title = "Exchange rate has expired", + description = "To access all the networks, you need to scan the card.", + onClick = {}, + ) + } +} + +@Preview(showBackground = true) +@Composable +fun Preview_Warning_InLightTheme() { + TangemTheme(isDark = false) { + WarningsPreview() + } +} + +@Preview(showBackground = true) +@Composable +fun Preview_Warning_InDarkTheme() { + TangemTheme(isDark = true) { + WarningsPreview() + } +} + +// endregion Preview \ No newline at end of file From e43b5ebdfbbf0092815700cc72d64b105c02dad3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 15 Dec 2022 16:00:27 +0400 Subject: [PATCH 013/145] Updated on 2026-08-14 --- .../main/java/com/tangem/core/ui/components/Warnings.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt index 447cdc661d..86a1085ebf 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt @@ -30,11 +30,11 @@ fun WarningCard( description: String, ) { WarningCardSurface( + modifier = modifier, content = { WarningBody( title = title, description = description, - modifier = modifier, ) }, ) @@ -58,8 +58,9 @@ fun ClickableWarningCard( onClick: () -> Unit, ) { WarningCardSurface( + modifier = modifier, content = { - WarningBody(modifier = modifier, title = title, description = description) { + WarningBody(title = title, description = description) { SpacerW12() Icon( painter = painterResource(id = R.drawable.ic_chevron_right_24), @@ -90,8 +91,9 @@ fun RefreshableWaringCard( onClick: () -> Unit, ) { WarningCardSurface( + modifier = modifier, content = { - WarningBody(modifier = modifier, title = title, description = description) { + WarningBody(title = title, description = description) { SpacerW12() Icon( painter = painterResource(id = R.drawable.ic_refresh_24), From 129a52d97fd373e137b61445038ee618bcf2e9e1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 15 Dec 2022 15:45:57 +0400 Subject: [PATCH 014/145] Updated on 2026-08-14 --- .../com/tangem/core/ui/components/Warnings.kt | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt new file mode 100644 index 0000000000..86a1085ebf --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt @@ -0,0 +1,193 @@ +package com.tangem.core.ui.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Icon +import androidx.compose.material.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.R +import com.tangem.core.ui.res.TangemTheme + +/** + * A card with a warning icon to the left and title with description shown to the right of it. + * + * @param title title of the warning in bold + * @param description description of the warning + * + * @see Figma component + */ +@Composable +fun WarningCard( + modifier: Modifier = Modifier, + title: String, + description: String, +) { + WarningCardSurface( + modifier = modifier, + content = { + WarningBody( + title = title, + description = description, + ) + }, + ) +} + +/** + * [WarningCard], but clickable (with an 'greater then' icon to the left) + * + * @param title title of the warning in bold + * @param description description of the warning + * @param onClick action to be performed when warning is clicked + * + * @see Figma component + */ +@Composable +fun ClickableWarningCard( + modifier: Modifier = Modifier, + title: String, + description: String, + onClick: () -> Unit, +) { + WarningCardSurface( + modifier = modifier, + content = { + WarningBody(title = title, description = description) { + SpacerW12() + Icon( + painter = painterResource(id = R.drawable.ic_chevron_right_24), + tint = TangemTheme.colors.icon.informative, + contentDescription = null, + ) + } + }, + onClick = onClick, + ) +} + +/** + * [WarningCard], but clickable and with a 'refresh' icon at the right edge + * + * @param title title of the warning in bold + * @param description description of the warning + * @param onClick action to be performed when warning is clicked + * + * @see Figma component + */ +@Composable +fun RefreshableWaringCard( + modifier: Modifier = Modifier, + title: String, + description: String, + onClick: () -> Unit, +) { + WarningCardSurface( + modifier = modifier, + content = { + WarningBody(title = title, description = description) { + SpacerW12() + Icon( + painter = painterResource(id = R.drawable.ic_refresh_24), + tint = TangemTheme.colors.icon.primary1, + contentDescription = null, + ) + } + }, + onClick = onClick, + ) +} + +// region elements + +@Composable +private fun WarningBody( + modifier: Modifier = Modifier, + title: String, + description: String, + additionalContent: @Composable () -> Unit = {}, +) { + IconWithTitleAndDescription( + modifier = modifier, + title = title, + description = description, + additionalContent = additionalContent, + icon = { + Image( + painter = painterResource(id = R.drawable.img_attention_20), + contentDescription = null, + ) + }, + ) +} + +@Composable +private fun WarningCardSurface( + modifier: Modifier = Modifier, + onClick: (() -> Unit)? = null, + content: @Composable () -> Unit, +) { + Surface( + shape = RoundedCornerShape(TangemTheme.dimens.size12), + color = TangemTheme.colors.background.primary, + elevation = TangemTheme.dimens.elevation2, + modifier = modifier.clickable( + enabled = onClick != null, + onClick = onClick ?: {}, + ), + ) { + content() + } +} + +// endregion elements + +// region Preview + +@Composable +fun WarningsPreview() { + Column(modifier = Modifier.fillMaxWidth()) { + WarningCard( + title = "Exchange rate has expired", + description = "To access all the networks, you need to scan the card.", + ) + SpacerH32() + ClickableWarningCard( + title = "Exchange rate has expired", + description = "To access all the networks, you need to scan the card.", + onClick = {}, + ) + SpacerH32() + RefreshableWaringCard( + title = "Exchange rate has expired", + description = "To access all the networks, you need to scan the card.", + onClick = {}, + ) + } +} + +@Preview(showBackground = true) +@Composable +fun Preview_Warning_InLightTheme() { + TangemTheme(isDark = false) { + WarningsPreview() + } +} + +@Preview(showBackground = true) +@Composable +fun Preview_Warning_InDarkTheme() { + TangemTheme(isDark = true) { + WarningsPreview() + } +} + +// endregion Preview \ No newline at end of file From 6a6d7665d02aae959d6ca7e4dd2b9cdcc418e9b3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 19 Dec 2022 11:40:08 +0400 Subject: [PATCH 015/145] Updated on 2026-08-14 --- .../core/ui/components/SuccessScreen.kt | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/SuccessScreen.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/SuccessScreen.kt b/core/ui/src/main/java/com/tangem/core/ui/components/SuccessScreen.kt new file mode 100644 index 0000000000..c0f603db45 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/SuccessScreen.kt @@ -0,0 +1,140 @@ +package com.tangem.core.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.R +import com.tangem.core.ui.res.TangemTheme + +/** + * Screen for showing success + * + * @param successMessage + * @param onButtonClick action on clicking "Done" button + * + * @see Figma component + */ +@Composable +fun SuccessScreenContent( + modifier: Modifier = Modifier, + successMessage: String, + onButtonClick: () -> Unit, +) { + Column( + modifier = modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .background(TangemTheme.colors.background.secondary) + .padding( + horizontal = TangemTheme.dimens.spacing16, + vertical = TangemTheme.dimens.spacing32, + ), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + SpacerHHalf() + SuccessImage() + SpacerH50() + Text( + text = stringResource(id = R.string.common_success), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + SpacerH12() + Text( + text = successMessage, + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + SpacerHHalf() + PrimaryButton( + text = stringResource(id = R.string.common_done), + modifier = Modifier + .fillMaxWidth(), + onClick = { onButtonClick() }, + ) + } +} + +@Composable +fun SuccessImage() { + Box( + modifier = Modifier + .background( + color = TangemTheme.colors.icon.accent.copy(alpha = 0.2f), + shape = CircleShape, + ), + contentAlignment = Alignment.Center, + ) { + Box( + modifier = Modifier + .padding(TangemTheme.dimens.spacing24) + .background( + color = TangemTheme.colors.icon.accent, + shape = CircleShape, + ) + .height(TangemTheme.dimens.size93) + .width(TangemTheme.dimens.size93), + contentAlignment = Alignment.Center, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_check_24), + contentDescription = null, + tint = TangemTheme.colors.icon.primary2, + modifier = Modifier.size(TangemTheme.dimens.size40), + ) + } + } +} + +// region preview + +@Composable +private fun SuccessScreenPreview() { + SuccessScreenContent( + successMessage = "Swap of 1 000 DAI to 1 131,46 MATIC", + onButtonClick = {}, + ) +} + +@Preview(showBackground = true) +@Composable +fun Preview_SuccessScreenContent_InLightTheme() { + TangemTheme(isDark = false) { + SuccessScreenPreview() + } +} + +@Preview(showBackground = true) +@Composable +fun Preview_SuccessScreenContent_InDarkTheme() { + TangemTheme(isDark = true) { + SuccessScreenPreview() + } +} + +// endregion preview \ No newline at end of file From c77f979e06fd0b37b107e2aa6ef0769fd84ba803 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 19 Dec 2022 19:06:52 +0300 Subject: [PATCH 016/145] Updated on 2026-08-14 --- .../send/redux/middlewares/SendMiddleware.kt | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 22d6c9138d..b093418152 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -5,18 +5,26 @@ import com.tangem.blockchain.blockchains.binance.BinanceTransactionExtras import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder -import com.tangem.blockchain.common.* +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.BlockchainSdkError +import com.tangem.blockchain.common.TransactionError +import com.tangem.blockchain.common.TransactionSender +import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.extensions.SimpleResult import com.tangem.common.core.TangemSdkError import com.tangem.common.services.Result import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.extensions.withMainContext -import com.tangem.tap.* +import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Token -import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.extensions.dispatchDialogShow +import com.tangem.tap.common.extensions.dispatchErrorNotification +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.safeUpdate +import com.tangem.tap.common.extensions.stripZeroPlainString import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction @@ -28,13 +36,24 @@ import com.tangem.tap.domain.extensions.minimalAmount import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.demo.DemoTransactionSender import com.tangem.tap.features.demo.isDemoCard -import com.tangem.tap.features.send.redux.* +import com.tangem.tap.features.send.redux.AddressPayIdActionUi +import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction +import com.tangem.tap.features.send.redux.AmountAction +import com.tangem.tap.features.send.redux.AmountActionUi import com.tangem.tap.features.send.redux.FeeAction.RequestFee +import com.tangem.tap.features.send.redux.PrepareSendScreen +import com.tangem.tap.features.send.redux.SendAction +import com.tangem.tap.features.send.redux.SendActionUi import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.tap.features.send.redux.states.ExternalTransactionData import com.tangem.tap.features.send.redux.states.MainCurrencyType import com.tangem.tap.features.send.redux.states.TransactionExtrasState import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.tangemSdk +import com.tangem.tap.userWalletsListManager +import com.tangem.tap.walletCurrenciesManager import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -163,7 +182,7 @@ private fun sendTransaction( return@launch } - val isLinkedTerminal = tangemSdk.config.linkedTerminal + val linkedTerminalState = tangemSdk.config.linkedTerminal if (card.isStart2Coin) { tangemSdk.config.linkedTerminal = false } @@ -191,6 +210,7 @@ private fun sendTransaction( FirebaseCrashlytics.getInstance().recordException(ex) delay(DELAY_SDK_DIALOG_CLOSE) withMainContext { + tangemSdk.config.linkedTerminal = linkedTerminalState dispatch(SendAction.ChangeSendButtonState(ButtonState.ENABLED)) store.dispatchErrorNotification(TapError.CustomError(ex.localizedMessage ?: "Unknown error")) } @@ -199,7 +219,7 @@ private fun sendTransaction( withMainContext { dispatch(SendAction.ChangeSendButtonState(ButtonState.ENABLED)) - tangemSdk.config.linkedTerminal = isLinkedTerminal + tangemSdk.config.linkedTerminal = linkedTerminalState val currencyType = AnalyticsParam.CurrencyType.Amount(amountToSend) when (sendResult) { From 651531a53245ee0bfdbf594f3a34b6e684656072 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 20 Dec 2022 11:37:56 +0300 Subject: [PATCH 017/145] Updated on 2026-08-14 --- .../cardsettings/CardSettingsScreenState.kt | 19 ++--- .../ui/cardsettings/CardSettingsViewModel.kt | 13 ++- .../details/ui/resetcard/ResetCardScreen.kt | 79 ++++++++----------- .../ui/resetcard/ResetCardScreenState.kt | 3 + .../ui/resetcard/ResetCardViewModel.kt | 6 ++ core/res/src/main/res/values-de/strings.xml | 7 +- core/res/src/main/res/values-fr/strings.xml | 7 +- core/res/src/main/res/values-it/strings.xml | 7 +- core/res/src/main/res/values-ru/strings.xml | 11 +-- core/res/src/main/res/values/strings.xml | 11 +-- 10 files changed, 88 insertions(+), 75 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt index e7354df5c5..927780750e 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt @@ -15,18 +15,18 @@ data class CardSettingsScreenState( ) sealed class CardInfo( - val titleRes: TextReference, val subtitle: TextReference, val clickable: Boolean = false, + val titleRes: TextReference, + val subtitle: TextReference, + val clickable: Boolean = false, ) { class CardId(subtitle: String) : CardInfo( titleRes = TextReference.Res(R.string.details_row_title_cid), - subtitle = TextReference - .Str(subtitle), + subtitle = TextReference.Str(subtitle), ) class Issuer(subtitle: String) : CardInfo( titleRes = TextReference.Res(R.string.details_row_title_issuer), - subtitle = TextReference - .Str(subtitle), + subtitle = TextReference.Str(subtitle), ) class SignedHashes(hashes: String) : CardInfo( @@ -34,10 +34,7 @@ sealed class CardInfo( subtitle = TextReference.Res(R.string.details_row_subtitle_signed_hashes_format, hashes), ) - class SecurityMode( - securityOption: SecurityOption, - clickable: Boolean, - ) : CardInfo( + class SecurityMode(securityOption: SecurityOption, clickable: Boolean) : CardInfo( titleRes = TextReference.Res(R.string.card_settings_security_mode), subtitle = TextReference.Res(securityOption.toTitleRes()), clickable = clickable, @@ -49,9 +46,9 @@ sealed class CardInfo( clickable = true, ) - object ResetToFactorySettings : CardInfo( + class ResetToFactorySettings(subtitle: TextReference.Res) : CardInfo( titleRes = TextReference.Res(R.string.card_settings_reset_card_to_factory), - subtitle = TextReference.Res(R.string.card_settings_reset_card_to_factory_footer), + subtitle = subtitle, clickable = true, ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt index 44f5cdb8d2..ce9da7f8b0 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt @@ -8,6 +8,7 @@ import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.details.redux.CardSettingsState import com.tangem.tap.features.details.redux.DetailsAction +import com.tangem.wallet.R import org.rekotlin.Store class CardSettingsViewModel(private val store: Store) { @@ -46,7 +47,17 @@ class CardSettingsViewModel(private val store: Store) { cardDetails.add(CardInfo.ChangeAccessCode) } if (state.resetCardAllowed) { - cardDetails.add(CardInfo.ResetToFactorySettings) + cardDetails.add( + CardInfo.ResetToFactorySettings( + subtitle = TextReference.Res( + if (state.card.backupStatus?.isActive == true) { + R.string.reset_card_with_backup_to_factory_message + } else { + R.string.reset_card_without_backup_to_factory_message + }, + ), + ), + ) } CardSettingsScreenState( diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt index 85d53a13eb..944e4c715f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt @@ -32,77 +32,64 @@ import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold import com.tangem.wallet.R @Composable -fun ResetCardScreen( - state: ResetCardScreenState, - onBackPressed: () -> Unit, - modifier: Modifier = Modifier, -) { - SettingsScreensScaffold( - content = { ResetCardView(state = state, modifier = modifier) }, - onBackClick = onBackPressed, - backgroundColor = Color.Transparent, - ) +fun ResetCardScreen(state: ResetCardScreenState, onBackPressed: () -> Unit) { + SettingsScreensScaffold( + content = { ResetCardView(state = state) }, + onBackClick = onBackPressed, + backgroundColor = Color.Transparent, + ) } @Composable -fun ResetCardView( - state: ResetCardScreenState, - modifier: Modifier = Modifier, -) { +fun ResetCardView(state: ResetCardScreenState) { Column( - modifier = modifier + modifier = Modifier .fillMaxSize() .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.SpaceBetween, ) { - Box( - modifier = modifier, - ) { + Box { Image( painter = painterResource(id = R.drawable.ic_reset_background), - contentDescription = "", - modifier = modifier.offset(y = (-82).dp), + contentDescription = null, + modifier = Modifier.offset(y = (-82).dp), ) ScreenTitle(titleRes = R.string.card_settings_reset_card_to_factory) } - Spacer( - modifier = modifier.weight(1f), - ) + Spacer(modifier = Modifier.weight(1f)) Column( - modifier = modifier - .offset(y = (-32).dp), + modifier = Modifier.offset(y = (-32).dp), verticalArrangement = Arrangement.Bottom, ) { Text( text = stringResource(id = R.string.common_attention), - modifier = modifier.padding(start = 20.dp, end = 20.dp), + modifier = Modifier.padding(start = 20.dp, end = 20.dp), style = TangemTypography.headline3, color = colorResource(id = R.color.text_primary_1), ) - Spacer(modifier = modifier.size(24.dp)) + Spacer(modifier = Modifier.size(24.dp)) Text( - text = stringResource(id = R.string.reset_card_to_factory_message), - modifier = modifier - .padding(start = 20.dp, end = 20.dp), + text = stringResource(id = state.descriptionResId), + modifier = Modifier.padding(start = 20.dp, end = 20.dp), style = TangemTypography.body1, color = colorResource(id = R.color.text_secondary), ) - Spacer(modifier = modifier.size(28.dp)) + Spacer(modifier = Modifier.size(28.dp)) Row( - modifier = modifier + modifier = Modifier .fillMaxWidth() .clickable( - onClick = { state.onAcceptWarningToggleClick(!state.accepted) }, - ) + onClick = { state.onAcceptWarningToggleClick(!state.accepted) }, + ) .padding(top = 16.dp, bottom = 16.dp), ) { IconToggleButton( checked = state.accepted, onCheckedChange = state.onAcceptWarningToggleClick, - modifier = modifier.padding(start = 20.dp, end = 20.dp), + modifier = Modifier.padding(start = 20.dp, end = 20.dp), ) { Icon( painter = painterResource( @@ -124,15 +111,13 @@ fun ResetCardView( text = stringResource(id = R.string.reset_card_to_factory_warning_message), style = TangemTypography.body2, color = colorResource(id = R.color.text_secondary), - modifier = modifier - .padding(end = 20.dp), + modifier = Modifier.padding(end = 20.dp), ) } - Spacer(modifier = modifier.size(16.dp)) + Spacer(modifier = Modifier.size(16.dp)) Box( - modifier = modifier - .padding(start = 16.dp, end = 16.dp, bottom = 32.dp), + modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 32.dp), ) { DetailsMainButton( title = stringResource(id = R.string.reset_card_to_factory_button_title), @@ -146,8 +131,14 @@ fun ResetCardView( @Composable @Preview -fun ResetCardScreenPreview( - -) { - ResetCardScreen(state = ResetCardScreenState(onAcceptWarningToggleClick = {}, accepted = true) {}, {}) +fun ResetCardScreenPreview() { + ResetCardScreen( + state = ResetCardScreenState( + descriptionResId = R.string.reset_card_without_backup_to_factory_message, + accepted = false, + onAcceptWarningToggleClick = {}, + onResetButtonClick = {}, + ), + onBackPressed = {}, + ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt index f658eac463..54693e7557 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt @@ -1,6 +1,9 @@ package com.tangem.tap.features.details.ui.resetcard +import androidx.annotation.StringRes + data class ResetCardScreenState( + @StringRes val descriptionResId: Int, val accepted: Boolean = false, val onAcceptWarningToggleClick: (Boolean) -> Unit, val onResetButtonClick: () -> Unit, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt index 15f891dba3..1606631d68 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt @@ -3,12 +3,18 @@ package com.tangem.tap.features.details.ui.resetcard import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.details.redux.CardSettingsState import com.tangem.tap.features.details.redux.DetailsAction +import com.tangem.wallet.R import org.rekotlin.Store class ResetCardViewModel(private val store: Store) { fun updateState(state: CardSettingsState?): ResetCardScreenState { return ResetCardScreenState( + descriptionResId = if (state?.card?.backupStatus?.isActive == true) { + R.string.reset_card_with_backup_to_factory_message + } else { + R.string.reset_card_without_backup_to_factory_message + }, accepted = state?.resetConfirmed ?: false, onAcceptWarningToggleClick = { store.dispatch(DetailsAction.ResetToFactory.Confirm(it)) }, onResetButtonClick = { store.dispatch(DetailsAction.ResetToFactory.Proceed) }, diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 6058ee6608..2ed6276780 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -99,11 +99,11 @@ This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. WalletConnect Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet or use the card to recover the access code. I understand that after performing this action, I will no longer have access to the current wallet Reset the Card Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. Select network Scan your card To access all the networks you need to scan the card @@ -279,7 +279,7 @@ Diese Karte ist für die Zusammenarbeit mit Tangem nicht geeignet Die von Ihnen gescannte Karte ist eine Entwicklungskarte. Akzeptieren Sie sie nicht als Zahlungsmittel. Tippen um zu signieren - To create wallet, connect your phone and the card exactly as it shown above + To create the wallet tap the card as shown above and do not remove until the end of the operation Tippen Sie um den Zugangscode zu ändern Tippen Sie um den Passcode zu ändern Nutzungsbedingungen @@ -449,6 +449,7 @@ Allow to use biometrics Enable biometric authentication Go to settings to enable biometric authentication in the Tangem App + Scan the card %s diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 5df2ade184..5c7572747e 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -99,11 +99,11 @@ This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. WalletConnect Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet or use the card to recover the access code. I understand that after performing this action, I will no longer have access to the current wallet Reset the Card Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. Select network Scan your card To access all the networks you need to scan the card @@ -279,7 +279,7 @@ Cette carte n\'est pas conçue pour fonctionner avec Tangem La carte que vous avez scannée est une carte de développement. Ne l\'acceptez pas comme paiement. Touchez pour signer - To create wallet, connect your phone and the card exactly as it shown above + To create the wallet tap the card as shown above and do not remove until the end of the operation Touchez, pour modifier le code d\'accès Touchez, pour modifier le mot de passe Conditions d\'utilisation @@ -449,6 +449,7 @@ Allow to use biometrics Enable biometric authentication Go to settings to enable biometric authentication in the Tangem App + Scan the card %s diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml index 15531dbf86..2b7d521568 100644 --- a/core/res/src/main/res/values-it/strings.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -99,11 +99,11 @@ This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. WalletConnect Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet or use the card to recover the access code. I understand that after performing this action, I will no longer have access to the current wallet Reset the Card Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. Select network Scan your card To access all the networks you need to scan the card @@ -279,7 +279,7 @@ Questa carta non è progettata per funzionare con Tangem La carta che hai scansionato è una carta di sviluppo. Non utilizzarla come strumento di pagamento. Avvicina per firmare - To create wallet, connect your phone and the card exactly as it shown above + To create the wallet tap the card as shown above and do not remove until the end of the operation Avvicina per modificare il codice di accesso Avvicina per modificare la password Termini del servizio @@ -449,6 +449,7 @@ Allow to use biometrics Enable biometric authentication Go to settings to enable biometric authentication in the Tangem App + Scan the card %s diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 39eeea4029..95b3fc88e0 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -99,11 +99,11 @@ Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком. WalletConnect Подключение к Dapps - Это действие приведет к полному удалению кошелька на этой карте. Кошелек невозможно будет восстановить или использовать данную карту для восстановления кода доступа + Сброс к заводским настройкам приведет к полному удалению кошелька на этой карте, а также отвязыванию карты из приложения. Кошелек невозможно будет восстановить. + Сброс к заводским настройкам приведет к полному удалению кошелька на этой карте, а также отвязыванию карты из приложения. Кошелек невозможно будет восстановить или использовать данную карту для восстановления кода доступа. Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку Сбросить карту Сброс к заводским настройкам - Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать эту карту для восстановления кода доступа. Выберите сеть Отсканируйте карту Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту @@ -279,9 +279,9 @@ Эта карта не предназначена для работы с этим приложением Карта, которую вы отсканировали, является картой разработчика. Не принимайте её в качестве оплаты. Нажмите, чтобы подписать - Чтобы создать кошелек, соедините телефон и карту в точности, как показано выше. - Чтобы изменить код доступа, соедините телефон и карту в точности, как показано выше. - Чтобы изменить пароль, соедините телефон и карту в точности, как показано выше. + Чтобы создать кошелек, приложите карту как показано выше и не убирайте до окончания операции + Чтобы изменить код доступа, приложите карту как показано выше и не убирайте до окончания операции + Чтобы изменить пароль, приложите карту как показано выше и не убирайте до окончания операции Условия использования Нет соединения с интернетом PayString не поддерживается блокчейном @@ -449,6 +449,7 @@ Использовать биометрию Включите биометрическую аутентификацию Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem App + Отсканируйте карту %s diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 44b4597b4e..6bfdbb64e0 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -99,11 +99,11 @@ This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. WalletConnect Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet or use the card to recover the access code. I understand that after performing this action, I will no longer have access to the current wallet Reset the Card Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. Select network Scan your card To access all the networks you need to scan the card @@ -279,9 +279,9 @@ This card is not designed to work with this app The card you scanned is a development card. Don\'t accept it as a payment. Tap to sign - To create wallet, connect your phone and the card exactly as it shown above - To change the access code, connect your phone and the card exactly as it shown above - To change the passcode, connect your phone and the card exactly as it shown above + To create the wallet tap the card as shown above and do not remove until the end of the operation + To change the access code tap the card as shown above and do not remove until the end of the operation + To change the passcode tap the card as shown above and do not remove until the end of the operation Terms of Service No internet connection PayString unsupported by blockchain @@ -449,6 +449,7 @@ Allow to use biometrics Enable biometric authentication Go to settings to enable biometric authentication in the Tangem App + Scan the card %s From 1c5519a70200c609caed790e8767a1c8ef190883 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 22 Dec 2022 16:25:58 +0300 Subject: [PATCH 018/145] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 1 + .../com/tangem/tap/proxy/AppStateHolder.kt | 5 +- .../tap/proxy/TransactionManagerImpl.kt | 133 ++++++++++++++++++ .../com/tangem/tap/proxy/di/ProxyModule.kt | 8 ++ .../com/tangem/datasource/di/NetworkModule.kt | 23 --- .../tangem/datasource/di/OneInchApiFactory.kt | 16 +++ .../tangem/datasource/di/OneInchApisModule.kt | 53 +++++++ .../di/qualifiers/OneInchApiQualifiers.kt | 15 -- .../domain/converter/TokensConverter.kt | 1 + .../tangem/feature/swap/SwapRepositoryImpl.kt | 30 ++-- .../tangem/feature/swap/di/SwapDataModule.kt | 7 +- .../feature/swap/domain/SwapInteractorImpl.kt | 35 ++++- .../feature/swap/domain/SwapRepository.kt | 11 +- .../swap/domain/di/SwapDomainModule.kt | 7 +- .../tangem/lib/crypto/TransactionManager.kt | 17 +++ .../com/tangem/lib/crypto/models/Currency.kt | 20 ++- .../tangem/lib/crypto/models/NativeToken.kt | 8 -- .../lib/crypto/models/NonNativeToken.kt | 10 -- .../models/transactions/SendTxResult.kt | 10 ++ 19 files changed, 327 insertions(+), 83 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/di/OneInchApiFactory.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/di/OneInchApisModule.kt delete mode 100644 core/datasource/src/main/java/com/tangem/datasource/di/qualifiers/OneInchApiQualifiers.kt create mode 100644 libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt delete mode 100644 libs/crypto/src/main/java/com/tangem/lib/crypto/models/NativeToken.kt delete mode 100644 libs/crypto/src/main/java/com/tangem/lib/crypto/models/NonNativeToken.kt create mode 100644 libs/crypto/src/main/java/com/tangem/lib/crypto/models/transactions/SendTxResult.kt diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 5ef7c985a6..9007fc7694 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -79,6 +79,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac tangemSdk = TangemSdk.init(this, TangemSdkManager.config) tangemSdkManager = TangemSdkManager(tangemSdk, this) appStateHolder.tangemSdkManager = tangemSdkManager + appStateHolder.tangemSdk = tangemSdk backupService = BackupService.init(tangemSdk, this) userWalletsListManager = UserWalletsListManager.provideBiometricImplementation( context = applicationContext, diff --git a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt index 11cfdee61f..4bc04e994e 100644 --- a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt @@ -1,5 +1,6 @@ package com.tangem.tap.proxy +import com.tangem.TangemSdk import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.redux.AppState @@ -7,18 +8,20 @@ import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.tokens.UserTokensRepository import com.tangem.tap.features.wallet.redux.WalletState import org.rekotlin.Store +import javax.inject.Inject /** * Holds objects from old modules, that missing in DI graph * Object sets manually to use in new modules and [AppStateHolder] proxies its to DI */ -class AppStateHolder { +class AppStateHolder @Inject constructor(){ var scanResponse: ScanResponse? = null var walletState: WalletState? = null var userTokensRepository: UserTokensRepository? = null var mainStore: Store? = null var tangemSdkManager: TangemSdkManager? = null + var tangemSdk: TangemSdk? = null fun getActualCard(): CardDTO? { return scanResponse?.card diff --git a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt new file mode 100644 index 0000000000..9cbeec23f9 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -0,0 +1,133 @@ +package com.tangem.tap.proxy + +import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.tangem.Message +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.BlockchainSdkError +import com.tangem.blockchain.common.Token +import com.tangem.blockchain.common.TransactionSender +import com.tangem.blockchain.common.TransactionSigner +import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.extensions.SimpleResult +import com.tangem.common.core.TangemSdkError +import com.tangem.domain.common.extensions.fromNetworkId +import com.tangem.lib.crypto.TransactionManager +import com.tangem.lib.crypto.models.Currency +import com.tangem.lib.crypto.models.NativeToken +import com.tangem.lib.crypto.models.NonNativeToken +import com.tangem.lib.crypto.models.transactions.SendTxResult +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.domain.TangemSigner +import com.tangem.tap.domain.tokens.models.BlockchainNetwork +import com.tangem.tap.store +import com.tangem.tap.tangemSdk +import java.math.BigDecimal + +class TransactionManagerImpl( + private val appStateHolder: AppStateHolder, +) : TransactionManager { + + @Throws(IllegalStateException::class) + override suspend fun sendTransaction( + networkId: String, + amountToSend: BigDecimal, + currencyToSend: Currency, + feeAmount: BigDecimal, + destinationAddress: String, + dataToSign: String, + ): SendTxResult { + val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + val walletManager = getActualWalletManager(blockchain) + val amount = when (currencyToSend) { + is NativeToken -> { + Amount(value = amountToSend, blockchain = blockchain) + } + is NonNativeToken -> { + Amount( + convertNonNativeToken(currencyToSend), + amountToSend, + ) + } + } + val txData = walletManager.createTransaction( + amount = amount, + fee = Amount(value = feeAmount, blockchain = blockchain), + destination = destinationAddress, + ).copy(hash = dataToSign) + + val signer = transactionSigner(walletManager) + + val sendResult = try { + (walletManager as TransactionSender).send(txData, signer) + } catch (ex: Exception) { + FirebaseCrashlytics.getInstance().recordException(ex) + return SendTxResult.UnknownError(ex) + } + return handleSendResult(sendResult) + } + + private fun handleSendResult(result: SimpleResult): SendTxResult { + when (result) { + is SimpleResult.Success -> { + return SendTxResult.Success + } + is SimpleResult.Failure -> { + val error = (result.error as? BlockchainSdkError) ?: return SendTxResult.UnknownError() + when (error) { + is BlockchainSdkError.WrappedTangemError -> { + val tangemSdkError = (error.tangemError as? TangemSdkError) + ?: return SendTxResult.UnknownError() + if (tangemSdkError is TangemSdkError.UserCancelled) return SendTxResult.UserCancelledError + return SendTxResult.TangemSdkError(tangemSdkError.code, tangemSdkError.cause) + } + else -> { + return SendTxResult.TangemSdkError(error.code, error.cause) + } + } + } + } + } + + private fun transactionSigner(walletManager: WalletManager): TransactionSigner { + val actualCard = requireNotNull(appStateHolder.getActualCard()) { "no card found" } + return TangemSigner( + card = actualCard, + tangemSdk = tangemSdk, + initialMessage = Message("test transaction title", "test description"), + ) { signResponse -> + store.dispatch( + GlobalAction.UpdateWalletSignedHashes( + walletSignedHashes = signResponse.totalSignedHashes, + walletPublicKey = walletManager.wallet.publicKey.seedKey, + remainingSignatures = signResponse.remainingSignatures, + ), + ) + } + } + + private fun getActualWalletManager(blockchain: Blockchain): WalletManager { + val card = appStateHolder.getActualCard() + if (card != null) { + val blockchainNetwork = BlockchainNetwork(blockchain, card) + val walletManager = appStateHolder.walletState?.getWalletManager(blockchainNetwork) + if (walletManager != null) { + return walletManager + } else { + error("no wallet manager found") + } + } else { + error("card not found") + } + } + + private fun convertNonNativeToken(token: NonNativeToken): Token { + return Token( + name = token.name, + symbol = token.symbol, + contractAddress = token.contractAddress, + decimals = token.decimalCount, + id = token.id, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt index 07d3fb1e8d..1a65b1ddd0 100644 --- a/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt +++ b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt @@ -2,9 +2,11 @@ package com.tangem.tap.proxy.di import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.lib.crypto.DerivationManager +import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.UserWalletManager import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.proxy.DerivationManagerImpl +import com.tangem.tap.proxy.TransactionManagerImpl import com.tangem.tap.proxy.UserWalletManagerImpl import dagger.Module import dagger.Provides @@ -31,6 +33,12 @@ class ProxyModule { ) } + @Provides + @Singleton + fun provideTransactionManager(appStateHolder: AppStateHolder): TransactionManager { + return TransactionManagerImpl(appStateHolder) + } + @Provides @Singleton fun provideDerivationManager(appStateHolder: AppStateHolder): DerivationManager { diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index a79330cfcf..51dabf7253 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -4,10 +4,8 @@ import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import com.tangem.datasource.api.AuthHeaderInterceptor import com.tangem.datasource.api.common.BigDecimalAdapter -import com.tangem.datasource.api.oneinch.OneInchApi import com.tangem.datasource.api.referral.ReferralApi import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.datasource.di.qualifiers.OneInchEthereum import com.tangem.lib.auth.AuthProvider import dagger.Module import dagger.Provides @@ -39,26 +37,6 @@ class NetworkModule { .create(TangemTechApi::class.java) } - @Provides - @Singleton - @OneInchEthereum - fun provideOneInchEthereumApi(moshi: Moshi): OneInchApi { - return Retrofit.Builder() - .addConverterFactory( - MoshiConverterFactory.create(moshi), - ) - .baseUrl(ONE_INCH_ETHER_BASE_URL) - .client( - OkHttpClient.Builder() - .addInterceptor( - HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY), - ) - .build(), - ) - .build() - .create(OneInchApi::class.java) - } - @Provides @Singleton fun provideReferralApi(okHttpClient: OkHttpClient, moshi: Moshi): ReferralApi { @@ -95,6 +73,5 @@ class NetworkModule { private companion object { const val PROD_TANGEM_TECH_BASE_URL = "https://api.tangem-tech.com/v1/" const val DEV_TANGEM_TECH_BASE_URL = "https://devapi.tangem-tech.com/v1/" - const val ONE_INCH_ETHER_BASE_URL = "https://api.1inch.io/v5.0/1/" } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/OneInchApiFactory.kt b/core/datasource/src/main/java/com/tangem/datasource/di/OneInchApiFactory.kt new file mode 100644 index 0000000000..e9493488d1 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/di/OneInchApiFactory.kt @@ -0,0 +1,16 @@ +package com.tangem.datasource.di + +import com.tangem.datasource.api.oneinch.OneInchApi + +class OneInchApiFactory { + + private val oneInchApiMap = mutableMapOf() + + fun putApi(networkId: String, api: OneInchApi) { + oneInchApiMap[networkId] = api + } + + fun getApi(networkId: String): OneInchApi { + return oneInchApiMap[networkId] ?: error("no api found for networkId $networkId") + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/OneInchApisModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/OneInchApisModule.kt new file mode 100644 index 0000000000..e23d2849c2 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/di/OneInchApisModule.kt @@ -0,0 +1,53 @@ +package com.tangem.datasource.di + +import com.squareup.moshi.Moshi +import com.tangem.datasource.api.oneinch.OneInchApi +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class OneInchApisModule { + + @Provides + @Singleton + fun provideOneInchApiFactory(moshi: Moshi): OneInchApiFactory { + val apiFactory = OneInchApiFactory() + apiFactory.putApi(ETH_NETWORK, createOneInchApiWithUrl(ONE_INCH_BASE_URL + ONE_INCH_ETH_PATH, moshi)) + apiFactory.putApi(BSC_NETWORK, createOneInchApiWithUrl(ONE_INCH_BASE_URL + ONE_INCH_BSC_PATH, moshi)) + return apiFactory + } + + private fun createOneInchApiWithUrl(url: String, moshi: Moshi): OneInchApi { + return Retrofit.Builder() + .addConverterFactory( + MoshiConverterFactory.create(moshi), + ) + .baseUrl(url) + .client( + OkHttpClient.Builder() + .addInterceptor( + HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY), + ) + .build(), + ) + .build() + .create(OneInchApi::class.java) + } + + companion object { + private const val ONE_INCH_BASE_URL = "https://api.1inch.io/v5.0/" + private const val ONE_INCH_ETH_PATH = "1/" + private const val ONE_INCH_BSC_PATH = "56/" + + private const val ETH_NETWORK = "ethereum" + private const val BSC_NETWORK = "binance-smart-chain" + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/qualifiers/OneInchApiQualifiers.kt b/core/datasource/src/main/java/com/tangem/datasource/di/qualifiers/OneInchApiQualifiers.kt deleted file mode 100644 index f312541fde..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/di/qualifiers/OneInchApiQualifiers.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.datasource.di.qualifiers - -import javax.inject.Qualifier - -@Qualifier -@Retention(AnnotationRetention.BINARY) -annotation class OneInchEthereum - -@Qualifier -@Retention(AnnotationRetention.BINARY) -annotation class OneInchBinance - -@Qualifier -@Retention(AnnotationRetention.BINARY) -annotation class OneInchOptimism \ No newline at end of file diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt index 2f756567f1..8abe25e73c 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt @@ -5,6 +5,7 @@ import com.tangem.lib.crypto.models.Currency import com.tangem.lib.crypto.models.NativeToken import com.tangem.lib.crypto.models.NonNativeToken import com.tangem.utils.converter.Converter + import javax.inject.Inject class TokensConverter @Inject constructor() : Converter { diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt index d37105e46e..c7d8cd3d5c 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt @@ -4,6 +4,7 @@ import com.tangem.datasource.api.oneinch.OneInchApi import com.tangem.datasource.api.oneinch.OneInchErrorsHandler import com.tangem.datasource.api.oneinch.errors.OneIncResponseException import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.di.OneInchApiFactory import com.tangem.feature.swap.converters.ApproveConverter import com.tangem.feature.swap.converters.QuotesConverter import com.tangem.feature.swap.converters.SwapConverter @@ -21,7 +22,7 @@ import javax.inject.Inject internal class SwapRepositoryImpl @Inject constructor( private val tangemTechApi: TangemTechApi, - private val oneInchApi: OneInchApi, + private val oneInchApiFactory: OneInchApiFactory, private val tokensConverter: TokensConverter, private val quotesConverter: QuotesConverter, private val swapConverter: SwapConverter, @@ -36,12 +37,16 @@ internal class SwapRepositoryImpl @Inject constructor( } } - override suspend fun findBestQuote(fromTokenAddress: String, toTokenAddress: String, amount: String): + override suspend fun findBestQuote( + networkId: String, fromTokenAddress: String, toTokenAddress: String, + amount: + String, + ): AggregatedSwapDataModel { return withContext(coroutineDispatcher.io) { try { val response = oneInchErrorsHandler.handleOneInchResponse( - oneInchApi.quote( + getOneInchApi(networkId).quote( fromTokenAddress = fromTokenAddress, toTokenAddress = toTokenAddress, amount = amount, @@ -54,24 +59,24 @@ internal class SwapRepositoryImpl @Inject constructor( } } - override suspend fun addressForTrust(): String { + override suspend fun addressForTrust(networkId: String): String { return withContext(coroutineDispatcher.io) { - oneInchApi.approveSpender().address + getOneInchApi(networkId).approveSpender().address } } - override suspend fun dataToApprove(tokenAddress: String, amount: String?): ApproveModel { + override suspend fun dataToApprove(networkId: String, tokenAddress: String, amount: String?): ApproveModel { return withContext(coroutineDispatcher.io) { - approveConverter.convert(oneInchApi.approveTransaction(tokenAddress, amount)) + approveConverter.convert(getOneInchApi(networkId).approveTransaction(tokenAddress, amount)) } } - override suspend fun checkTokensSpendAllowance(tokenAddress: String, walletAddress: String): + override suspend fun checkTokensSpendAllowance(networkId: String, tokenAddress: String, walletAddress: String): AggregatedSwapDataModel { return withContext(coroutineDispatcher.io) { try { val response = oneInchErrorsHandler.handleOneInchResponse( - oneInchApi.approveAllowance(tokenAddress, walletAddress) + getOneInchApi(networkId).approveAllowance(tokenAddress, walletAddress), ) AggregatedSwapDataModel(response.allowance) } catch (ex: OneIncResponseException) { @@ -81,6 +86,7 @@ internal class SwapRepositoryImpl @Inject constructor( } override suspend fun prepareSwapTransaction( + networkId: String, fromTokenAddress: String, toTokenAddress: String, amount: String, @@ -90,7 +96,7 @@ internal class SwapRepositoryImpl @Inject constructor( return withContext(coroutineDispatcher.io) { try { val swapResponse = oneInchErrorsHandler.handleOneInchResponse( - oneInchApi.swap( + getOneInchApi(networkId).swap( fromTokenAddress = fromTokenAddress, toTokenAddress = toTokenAddress, amount = amount, @@ -104,4 +110,8 @@ internal class SwapRepositoryImpl @Inject constructor( } } } + + private fun getOneInchApi(networkId: String): OneInchApi { + return oneInchApiFactory.getApi(networkId) + } } \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt index 0fdb016f7b..bb35020942 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt @@ -1,9 +1,8 @@ package com.tangem.feature.swap.di -import com.tangem.datasource.api.oneinch.OneInchApi import com.tangem.datasource.api.oneinch.OneInchErrorsHandler import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.datasource.di.qualifiers.OneInchEthereum +import com.tangem.datasource.di.OneInchApiFactory import com.tangem.feature.swap.SwapRepositoryImpl import com.tangem.feature.swap.converters.ApproveConverter import com.tangem.feature.swap.converters.QuotesConverter @@ -25,7 +24,7 @@ class SwapDataModule { @Singleton fun provideSwapRepository( tangemTechApi: TangemTechApi, - @OneInchEthereum oneInchApi: OneInchApi, + oneInchApiFactory: OneInchApiFactory, tokensConverter: TokensConverter, quotesConverter: QuotesConverter, swapConverter: SwapConverter, @@ -35,7 +34,7 @@ class SwapDataModule { ): SwapRepository { return SwapRepositoryImpl( tangemTechApi = tangemTechApi, - oneInchApi = oneInchApi, + oneInchApiFactory = oneInchApiFactory, tokensConverter = tokensConverter, quotesConverter = quotesConverter, swapConverter = swapConverter, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 3a989bb4c6..43862840fb 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -5,10 +5,13 @@ import com.tangem.feature.swap.domain.models.data.Currency import com.tangem.feature.swap.domain.models.data.DataError import com.tangem.feature.swap.domain.models.data.SwapState import com.tangem.feature.swap.domain.models.data.TransactionModel +import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.UserWalletManager +import java.math.BigDecimal import javax.inject.Inject internal class SwapInteractorImpl @Inject constructor( + private val transactionManager: TransactionManager, private val userWalletManager: UserWalletManager, private val repository: SwapRepository, private val cache: SwapDataCache, @@ -29,21 +32,40 @@ internal class SwapInteractorImpl @Inject constructor( } override suspend fun givePermissionToSwap(tokenAddress: String) { - val oneInchAddress = repository.addressForTrust() - val transactionData = repository.dataToApprove(tokenAddress) - //todo sign transaction (next tasks) + cache.getNetworkId()?.let { networkId -> + val oneInchAddress = repository.addressForTrust(networkId) + val transactionData = repository.dataToApprove(networkId, tokenAddress) + //todo for test + // transactionManager.sendTransaction( + // networkId = networkId, + // amountToSend = BigDecimal.ZERO, + // currencyToSend = , + // feeAmount = BigDecimal.ZERO, + // destinationAddress = oneInchAddress, + // dataToSign = transactionData.data + // ) + } } override suspend fun findBestQuote(fromTokenAddress: String, toTokenAddress: String, amount: String): SwapState { val networkId = cache.getNetworkId() val isAllowedToSpend = if (networkId.isNullOrEmpty()) { - false + error("no networkId found, please call getTokensToSwap first") } else { val allowance = - repository.checkTokensSpendAllowance(fromTokenAddress, userWalletManager.getWalletAddress(networkId)) + repository.checkTokensSpendAllowance( + networkId = networkId, + tokenAddress = fromTokenAddress, + walletAddress = userWalletManager.getWalletAddress(networkId), + ) allowance.error == DataError.NO_ERROR && allowance.dataModel != "0" } - repository.findBestQuote(fromTokenAddress, toTokenAddress, amount).let { quotes -> + repository.findBestQuote( + networkId = networkId, + fromTokenAddress = fromTokenAddress, + toTokenAddress = toTokenAddress, + amount = amount, + ).let { quotes -> val quoteDataModel = quotes.dataModel if (quoteDataModel != null) { cache.cacheSwapParams(quoteDataModel.copy(isAllowedToSpend = isAllowedToSpend), amount) @@ -60,6 +82,7 @@ internal class SwapInteractorImpl @Inject constructor( val networkId = cache.getNetworkId() if (quoteModel != null && amountToSwap != null && networkId != null) { repository.prepareSwapTransaction( + networkId = networkId, fromTokenAddress = quoteModel.fromTokenAmount, toTokenAddress = quoteModel.toTokenAmount, amount = amountToSwap, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt index 01f534a296..55a973b7e9 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt @@ -11,6 +11,7 @@ interface SwapRepository { suspend fun getExchangeableTokens(networkId: String): List suspend fun findBestQuote( + networkId: String, fromTokenAddress: String, toTokenAddress: String, amount: String, @@ -21,7 +22,7 @@ interface SwapRepository { * * @return address */ - suspend fun addressForTrust(): String + suspend fun addressForTrust(networkId: String): String /** * Generate "data" for calling contract in order to allow 1inch spend funds @@ -30,6 +31,7 @@ interface SwapRepository { * @param amount number of tokens is allowed. By default infinite */ suspend fun dataToApprove( + networkId: String, tokenAddress: String, amount: String? = null, ): ApproveModel @@ -42,9 +44,14 @@ interface SwapRepository { * * @return amount of tokens allowed to spend */ - suspend fun checkTokensSpendAllowance(tokenAddress: String, walletAddress: String): AggregatedSwapDataModel + suspend fun checkTokensSpendAllowance( + networkId: String, + tokenAddress: String, + walletAddress: String, + ): AggregatedSwapDataModel suspend fun prepareSwapTransaction( + networkId: String, fromTokenAddress: String, toTokenAddress: String, amount: String, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt index ef7b8e44c8..eb5d59171e 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -4,6 +4,7 @@ import com.tangem.feature.swap.domain.SwapInteractor import com.tangem.feature.swap.domain.SwapInteractorImpl import com.tangem.feature.swap.domain.SwapRepository import com.tangem.feature.swap.domain.cache.SwapDataCacheImpl +import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.UserWalletManager import dagger.Module import dagger.Provides @@ -18,12 +19,14 @@ class SwapDomainModule { @Provides @Singleton fun provideSwapInteractor( - referralRepository: SwapRepository, + swapRepository: SwapRepository, userWalletManager: UserWalletManager, + transactionManager: TransactionManager, ): SwapInteractor { return SwapInteractorImpl( + transactionManager = transactionManager, userWalletManager = userWalletManager, - repository = referralRepository, + repository = swapRepository, cache = SwapDataCacheImpl(), ) } diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt new file mode 100644 index 0000000000..93a7ab6914 --- /dev/null +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt @@ -0,0 +1,17 @@ +package com.tangem.lib.crypto + +import com.tangem.lib.crypto.models.Currency +import com.tangem.lib.crypto.models.transactions.SendTxResult +import java.math.BigDecimal + +interface TransactionManager { + + suspend fun sendTransaction( + networkId: String, + amountToSend: BigDecimal, + currencyToSend: Currency, + feeAmount: BigDecimal, + destinationAddress: String, + dataToSign: String, + ): SendTxResult +} \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Currency.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Currency.kt index 82afac8e25..cd49b303ef 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Currency.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Currency.kt @@ -4,9 +4,25 @@ package com.tangem.lib.crypto.models * Currency data class that can be native blockchain Token * or custom Token (used in this lib to replace and divide logic Currency from app module) */ -abstract class Currency( +sealed class Currency( open val id: String, open val name: String, open val symbol: String, open val networkId: String, -) \ No newline at end of file +) + +data class NativeToken( + override val id: String, + override val name: String, + override val symbol: String, + override val networkId: String, +) : Currency(id, name, symbol, networkId) + +class NonNativeToken( + override val id: String, + override val name: String, + override val symbol: String, + override val networkId: String, + val contractAddress: String, + val decimalCount: Int, +) : Currency(id, name, symbol, networkId) \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/NativeToken.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/NativeToken.kt deleted file mode 100644 index f8ab7464d6..0000000000 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/NativeToken.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.lib.crypto.models - -data class NativeToken( - override val id: String, - override val name: String, - override val symbol: String, - override val networkId: String, -) : Currency(id, name, symbol, networkId) \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/NonNativeToken.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/NonNativeToken.kt deleted file mode 100644 index a2d812cd1b..0000000000 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/NonNativeToken.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.tangem.lib.crypto.models - -class NonNativeToken( - override val id: String, - override val name: String, - override val symbol: String, - override val networkId: String, - val contractAddress: String, - val decimalCount: Int, -) : Currency(id, name, symbol, networkId) \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/transactions/SendTxResult.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/transactions/SendTxResult.kt new file mode 100644 index 0000000000..25655c4119 --- /dev/null +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/transactions/SendTxResult.kt @@ -0,0 +1,10 @@ +package com.tangem.lib.crypto.models.transactions + +sealed interface SendTxResult { + + object Success : SendTxResult + object UserCancelledError : SendTxResult + data class TangemSdkError(val code: Int, val cause: Throwable?) : SendTxResult + data class BlockchainSdkError(val code: Int, val cause: Throwable?) : SendTxResult + data class UnknownError(val ex: Exception? = null) : SendTxResult +} \ No newline at end of file From 64185e7482a6750ef908cefe914f048624b89ef2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 22 Dec 2022 18:25:14 +0300 Subject: [PATCH 019/145] Updated on 2026-08-14 --- .../features/details/redux/DetailsReducer.kt | 10 ++- .../features/details/redux/DetailsState.kt | 2 + .../ui/cardsettings/CardSettingsScreen.kt | 20 +++-- .../cardsettings/CardSettingsScreenState.kt | 6 +- .../ui/cardsettings/CardSettingsViewModel.kt | 13 +--- .../ui/common/DetailsComposeElements.kt | 5 +- .../details/ui/resetcard/ResetCardScreen.kt | 76 ++++++++++++------- .../ui/resetcard/ResetCardScreenState.kt | 4 +- .../ui/resetcard/ResetCardViewModel.kt | 13 ++-- .../tap/features/details/ui/utils/Mappers.kt | 13 ++++ ...ackground.xml => ill_reset_background.xml} | 0 11 files changed, 104 insertions(+), 58 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/details/ui/utils/Mappers.kt rename app/src/main/res/drawable/{ic_reset_background.xml => ill_reset_background.xml} (100%) 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 f00f0b46b4..d040111cdf 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 @@ -4,6 +4,7 @@ import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.TapWorkarounds.isTangemNote +import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.domain.common.isTangemTwin import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.extensions.isWalletDataSupported @@ -193,5 +194,12 @@ private fun CardDTO.toCardInfo(): CardInfo { val cardId = this.cardId.chunked(4).joinToString(separator = " ") val issuer = this.issuer.name val signedHashes = this.signedHashesCount() - return CardInfo(cardId, issuer, signedHashes) + + return CardInfo( + cardId = cardId, + issuer = issuer, + signedHashes = signedHashes, + isTwin = isTangemTwins, + hasBackup = backupStatus?.isActive == true, + ) } \ No newline at end of file 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 3cb211b7c5..9551208c5c 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 @@ -40,6 +40,8 @@ data class CardInfo( val cardId: String, val issuer: String, val signedHashes: Int, + val isTwin: Boolean, + val hasBackup: Boolean, ) data class CardSettingsState( diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt index 31941ed1ee..6158d9bf38 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt @@ -156,18 +156,26 @@ fun CardSettings( ) .padding(start = 20.dp, end = 20.dp, bottom = paddingBottom, top = paddingTop), ) { - val titleColor = if (it.clickable) R.color.text_primary_1 else R.color.text_tertiary - val subtitleColor = if (it.clickable) R.color.text_secondary else R.color.text_tertiary + val titleColor = if (it.clickable) { + TangemTheme.colors.text.primary1 + } else { + TangemTheme.colors.text.tertiary + } + val subtitleColor = if (it.clickable) { + TangemTheme.colors.text.secondary + } else { + TangemTheme.colors.text.tertiary + } Text( text = it.titleRes.resolveReference(), - color = colorResource(id = titleColor), - style = TangemTypography.subtitle1, + color = titleColor, + style = TangemTheme.typography.subtitle1, ) Spacer(modifier = modifier.size(4.dp)) Text( text = it.subtitle.resolveReference(), - color = colorResource(id = subtitleColor), - style = TangemTypography.body2, + color = subtitleColor, + style = TangemTheme.typography.body2, ) } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt index 927780750e..5ae42a55a9 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt @@ -6,7 +6,9 @@ import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.res.stringResource import com.tangem.tap.features.details.redux.SecurityOption import com.tangem.tap.features.details.ui.securitymode.toTitleRes +import com.tangem.tap.features.details.ui.utils.toResetCardDescriptionText import com.tangem.wallet.R +import com.tangem.tap.features.details.redux.CardInfo as ReduxCardInfo data class CardSettingsScreenState( val cardDetails: List? = null, @@ -46,9 +48,9 @@ sealed class CardInfo( clickable = true, ) - class ResetToFactorySettings(subtitle: TextReference.Res) : CardInfo( + class ResetToFactorySettings(cardInfo: ReduxCardInfo) : CardInfo( titleRes = TextReference.Res(R.string.card_settings_reset_card_to_factory), - subtitle = subtitle, + subtitle = cardInfo.toResetCardDescriptionText(), clickable = true, ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt index ce9da7f8b0..fd0ad8a3db 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt @@ -8,7 +8,6 @@ import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.details.redux.CardSettingsState import com.tangem.tap.features.details.redux.DetailsAction -import com.tangem.wallet.R import org.rekotlin.Store class CardSettingsViewModel(private val store: Store) { @@ -47,17 +46,7 @@ class CardSettingsViewModel(private val store: Store) { cardDetails.add(CardInfo.ChangeAccessCode) } if (state.resetCardAllowed) { - cardDetails.add( - CardInfo.ResetToFactorySettings( - subtitle = TextReference.Res( - if (state.card.backupStatus?.isActive == true) { - R.string.reset_card_with_backup_to_factory_message - } else { - R.string.reset_card_without_backup_to_factory_message - }, - ), - ), - ) + cardDetails.add(CardInfo.ResetToFactorySettings(state.cardInfo)) } CardSettingsScreenState( diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt index 1517c58124..e6f431cfc4 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt @@ -26,7 +26,6 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.tangem.core.ui.res.TangemTheme -import com.tangem.tap.common.compose.TangemTypography import com.tangem.wallet.R @Composable @@ -84,8 +83,8 @@ fun ScreenTitle( Text( text = stringResource(id = titleRes), modifier = modifier.padding(start = 20.dp, end = 20.dp), - style = TangemTypography.headline1, - color = colorResource(id = R.color.text_primary_1), + style = TangemTheme.typography.h1, + color = TangemTheme.colors.text.primary1, ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt index 944e4c715f..05a69049a9 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.details.ui.resetcard import androidx.compose.foundation.Image +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -19,13 +20,13 @@ import androidx.compose.material.IconToggleButton import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.tangem.tap.common.compose.TangemTypography +import com.tangem.core.ui.res.TangemTheme +import com.tangem.tap.features.details.ui.cardsettings.TextReference +import com.tangem.tap.features.details.ui.cardsettings.resolveReference import com.tangem.tap.features.details.ui.common.DetailsMainButton import com.tangem.tap.features.details.ui.common.ScreenTitle import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold @@ -36,7 +37,6 @@ fun ResetCardScreen(state: ResetCardScreenState, onBackPressed: () -> Unit) { SettingsScreensScaffold( content = { ResetCardView(state = state) }, onBackClick = onBackPressed, - backgroundColor = Color.Transparent, ) } @@ -50,7 +50,7 @@ fun ResetCardView(state: ResetCardScreenState) { ) { Box { Image( - painter = painterResource(id = R.drawable.ic_reset_background), + painter = painterResource(id = R.drawable.ill_reset_background), contentDescription = null, modifier = Modifier.offset(y = (-82).dp), ) @@ -64,17 +64,17 @@ fun ResetCardView(state: ResetCardScreenState) { Text( text = stringResource(id = R.string.common_attention), modifier = Modifier.padding(start = 20.dp, end = 20.dp), - style = TangemTypography.headline3, - color = colorResource(id = R.color.text_primary_1), + style = TangemTheme.typography.h3, + color = TangemTheme.colors.text.primary1, ) Spacer(modifier = Modifier.size(24.dp)) Text( - text = stringResource(id = state.descriptionResId), + text = state.descriptionText.resolveReference(), modifier = Modifier.padding(start = 20.dp, end = 20.dp), - style = TangemTypography.body1, - color = colorResource(id = R.color.text_secondary), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.secondary, ) Spacer(modifier = Modifier.size(28.dp)) @@ -101,16 +101,16 @@ fun ResetCardView(state: ResetCardScreenState) { ), contentDescription = null, tint = if (state.accepted) { - colorResource(id = R.color.icon_accent) + TangemTheme.colors.icon.accent } else { - colorResource(id = R.color.icon_secondary) + TangemTheme.colors.icon.secondary }, ) } Text( text = stringResource(id = R.string.reset_card_to_factory_warning_message), - style = TangemTypography.body2, - color = colorResource(id = R.color.text_secondary), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, modifier = Modifier.padding(end = 20.dp), ) } @@ -129,16 +129,40 @@ fun ResetCardView(state: ResetCardScreenState) { } } +// region Preview @Composable -@Preview -fun ResetCardScreenPreview() { - ResetCardScreen( - state = ResetCardScreenState( - descriptionResId = R.string.reset_card_without_backup_to_factory_message, - accepted = false, - onAcceptWarningToggleClick = {}, - onResetButtonClick = {}, - ), - onBackPressed = {}, - ) -} \ No newline at end of file +private fun ResetCardScreenSample( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .background(TangemTheme.colors.background.secondary), + ) { + ResetCardScreen( + state = ResetCardScreenState( + accepted = true, + descriptionText = TextReference.Res(R.string.reset_card_with_backup_to_factory_message), + onAcceptWarningToggleClick = {}, + onResetButtonClick = {}, + ), + onBackPressed = {}, + ) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun ResetCardScreenPreview_Light() { + TangemTheme { + ResetCardScreenSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun ResetCardScreenPreview_Dark() { + TangemTheme(isDark = true) { + ResetCardScreenSample() + } +} +// endregion Preview \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt index 54693e7557..fee3e41a5b 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt @@ -1,10 +1,10 @@ package com.tangem.tap.features.details.ui.resetcard -import androidx.annotation.StringRes +import com.tangem.tap.features.details.ui.cardsettings.TextReference data class ResetCardScreenState( - @StringRes val descriptionResId: Int, val accepted: Boolean = false, + val descriptionText: TextReference, val onAcceptWarningToggleClick: (Boolean) -> Unit, val onResetButtonClick: () -> Unit, ) { diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt index 1606631d68..9ba899bc7d 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt @@ -3,19 +3,20 @@ package com.tangem.tap.features.details.ui.resetcard import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.details.redux.CardSettingsState import com.tangem.tap.features.details.redux.DetailsAction -import com.tangem.wallet.R +import com.tangem.tap.features.details.ui.cardsettings.TextReference +import com.tangem.tap.features.details.ui.utils.toResetCardDescriptionText import org.rekotlin.Store class ResetCardViewModel(private val store: Store) { fun updateState(state: CardSettingsState?): ResetCardScreenState { + val descriptionText = state?.cardInfo + ?.toResetCardDescriptionText() + ?: TextReference.Str(value = "") + return ResetCardScreenState( - descriptionResId = if (state?.card?.backupStatus?.isActive == true) { - R.string.reset_card_with_backup_to_factory_message - } else { - R.string.reset_card_without_backup_to_factory_message - }, accepted = state?.resetConfirmed ?: false, + descriptionText = descriptionText, onAcceptWarningToggleClick = { store.dispatch(DetailsAction.ResetToFactory.Confirm(it)) }, onResetButtonClick = { store.dispatch(DetailsAction.ResetToFactory.Proceed) }, ) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/utils/Mappers.kt b/app/src/main/java/com/tangem/tap/features/details/ui/utils/Mappers.kt new file mode 100644 index 0000000000..459e5ba49a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/utils/Mappers.kt @@ -0,0 +1,13 @@ +package com.tangem.tap.features.details.ui.utils + +import com.tangem.tap.features.details.redux.CardInfo +import com.tangem.tap.features.details.ui.cardsettings.TextReference +import com.tangem.wallet.R + +internal fun CardInfo.toResetCardDescriptionText(): TextReference { + return if (!this.hasBackup || this.isTwin) { + TextReference.Res(R.string.reset_card_without_backup_to_factory_message) + } else { + TextReference.Res(R.string.reset_card_with_backup_to_factory_message) + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_reset_background.xml b/app/src/main/res/drawable/ill_reset_background.xml similarity index 100% rename from app/src/main/res/drawable/ic_reset_background.xml rename to app/src/main/res/drawable/ill_reset_background.xml From 05da18fa9c625366d2acc709890bd6a62ae3c639 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 19 Dec 2022 21:32:53 +0400 Subject: [PATCH 020/145] Updated on 2026-08-14 --- .../tap/common/extensions/Blockchain.kt | 33 -- .../currencyItem/CurrencyItemHeader.kt | 6 +- .../ui/compose/currencyItem/NetworkItem.kt | 4 +- .../wallet/ui/images/CurrencyIconRequest.kt | 6 +- .../main/res/drawable/ic_arbitrum_round.xml | 13 - .../main/res/drawable/ic_avalanche_round.xml | 6 - .../main/res/drawable/ic_binance_round.xml | 6 - .../res/drawable/ic_bitcoin_cash_round.xml | 6 - .../main/res/drawable/ic_bitcoin_round.xml | 6 - app/src/main/res/drawable/ic_bsc_round.xml | 9 - .../main/res/drawable/ic_cardano_round.xml | 6 - .../main/res/drawable/ic_dogecoin_round.xml | 6 - app/src/main/res/drawable/ic_eth_round.xml | 14 - app/src/main/res/drawable/ic_ethc_round.xml | 9 - app/src/main/res/drawable/ic_fantom_round.xml | 6 - app/src/main/res/drawable/ic_gnosis_round.xml | 8 - app/src/main/res/drawable/ic_kusama_round.xml | 6 - .../main/res/drawable/ic_litecoin_round.xml | 9 - app/src/main/res/drawable/ic_optimism.xml | 16 - .../main/res/drawable/ic_polkadot_round.xml | 11 - .../main/res/drawable/ic_polygon_round.xml | 6 - app/src/main/res/drawable/ic_rsk_round.xml | 6 - app/src/main/res/drawable/ic_solana_round.xml | 18 - .../main/res/drawable/ic_stellar_round.xml | 12 - app/src/main/res/drawable/ic_tezos_round.xml | 6 - app/src/main/res/drawable/ic_tron_round.xml | 9 - app/src/main/res/drawable/ic_xrp_round.xml | 6 - core/res/src/main/res/values-de/strings.xml | 2 + core/res/src/main/res/values-fr/strings.xml | 2 + core/res/src/main/res/values-it/strings.xml | 2 + core/res/src/main/res/values-ru/strings.xml | 2 + core/res/src/main/res/values/strings.xml | 2 + .../com/tangem/core/ui/components/Cards.kt | 7 +- .../ui/components/CurrencyPlaceholderIcon.kt | 59 +++ .../core/ui/components/ResizableText.kt | 69 ++++ .../core/ui/components/SuccessScreen.kt | 2 +- .../tangem/core/ui/extensions/Blockchain.kt | 37 ++ .../com/tangem/core/ui/res/TangemDimens.kt | 2 + .../src/main/res/drawable/img_arbitrum_22.xml | 26 ++ .../main/res/drawable/img_avalanche_22.xml | 14 + .../src/main/res/drawable/img_binance_22.xml | 14 + core/ui/src/main/res/drawable/img_bsc_22.xml | 22 + core/ui/src/main/res/drawable/img_btc_22.xml | 13 + .../src/main/res/drawable/img_btc_cash_22.xml | 13 + .../src/main/res/drawable/img_cardano_22.xml | 13 + core/ui/src/main/res/drawable/img_dash_22.xml | 16 + .../src/main/res/drawable/img_dogecoin_22.xml | 14 + core/ui/src/main/res/drawable/img_eth_22.xml | 28 ++ .../main/res/drawable/img_eth_classic_22.xml | 22 + .../src/main/res/drawable/img_eth_fair_22.xml | 0 .../src/main/res/drawable/img_eth_pow_22.xml | 0 .../src/main/res/drawable/img_fantom_22.xml | 14 + .../src/main/res/drawable/img_gnosis_22.xml | 22 + .../src/main/res/drawable/img_kusama_22.xml | 13 + .../src/main/res/drawable/img_litecoin_22.xml | 13 + .../src/main/res/drawable/img_optimism_22.xml | 14 + .../src/main/res/drawable/img_polkadot_22.xml | 14 + core/ui/src/main/res/drawable/img_rsk_22.xml | 13 + .../src/main/res/drawable/img_solana_22.xml | 40 ++ .../src/main/res/drawable/img_stellar_22.xml | 13 + .../ui/src/main/res/drawable/img_tezos_22.xml | 13 + core/ui/src/main/res/drawable/img_tron_22.xml | 13 + core/ui/src/main/res/drawable/img_xrp_22.xml | 13 + features/swap/presentation/build.gradle.kts | 1 + .../swap/models/SwapPermissionStateHolder.kt | 22 + .../swap/models/SwapSelectTokenStateHolder.kt | 7 + .../feature/swap/models/SwapStateHolder.kt | 79 ++++ .../swap/models/SwapSuccessStateHolder.kt | 5 + .../feature/swap/presentation/SwapFragment.kt | 25 +- .../tangem/feature/swap/router/SwapRouter.kt | 31 ++ .../ui/SwapPermissionBottomSheetContent.kt | 219 ++++++++++ .../com/tangem/feature/swap/ui/SwapScreen.kt | 66 +++ .../feature/swap/ui/SwapScreenContent.kt | 226 +++++++++++ .../feature/swap/ui/SwapSuccessScreen.kt | 31 ++ .../tangem/feature/swap/ui/TransactionCard.kt | 378 ++++++++++++++++++ .../feature/swap/viewmodels/SwapViewModel.kt | 81 ++++ 76 files changed, 1748 insertions(+), 248 deletions(-) delete mode 100644 app/src/main/res/drawable/ic_arbitrum_round.xml delete mode 100644 app/src/main/res/drawable/ic_avalanche_round.xml delete mode 100644 app/src/main/res/drawable/ic_binance_round.xml delete mode 100644 app/src/main/res/drawable/ic_bitcoin_cash_round.xml delete mode 100644 app/src/main/res/drawable/ic_bitcoin_round.xml delete mode 100644 app/src/main/res/drawable/ic_bsc_round.xml delete mode 100644 app/src/main/res/drawable/ic_cardano_round.xml delete mode 100644 app/src/main/res/drawable/ic_dogecoin_round.xml delete mode 100644 app/src/main/res/drawable/ic_eth_round.xml delete mode 100644 app/src/main/res/drawable/ic_ethc_round.xml delete mode 100644 app/src/main/res/drawable/ic_fantom_round.xml delete mode 100644 app/src/main/res/drawable/ic_gnosis_round.xml delete mode 100644 app/src/main/res/drawable/ic_kusama_round.xml delete mode 100644 app/src/main/res/drawable/ic_litecoin_round.xml delete mode 100644 app/src/main/res/drawable/ic_optimism.xml delete mode 100644 app/src/main/res/drawable/ic_polkadot_round.xml delete mode 100644 app/src/main/res/drawable/ic_polygon_round.xml delete mode 100644 app/src/main/res/drawable/ic_rsk_round.xml delete mode 100644 app/src/main/res/drawable/ic_solana_round.xml delete mode 100644 app/src/main/res/drawable/ic_stellar_round.xml delete mode 100644 app/src/main/res/drawable/ic_tezos_round.xml delete mode 100644 app/src/main/res/drawable/ic_tron_round.xml delete mode 100644 app/src/main/res/drawable/ic_xrp_round.xml create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/CurrencyPlaceholderIcon.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/ResizableText.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/extensions/Blockchain.kt create mode 100644 core/ui/src/main/res/drawable/img_arbitrum_22.xml create mode 100644 core/ui/src/main/res/drawable/img_avalanche_22.xml create mode 100644 core/ui/src/main/res/drawable/img_binance_22.xml create mode 100644 core/ui/src/main/res/drawable/img_bsc_22.xml create mode 100644 core/ui/src/main/res/drawable/img_btc_22.xml create mode 100644 core/ui/src/main/res/drawable/img_btc_cash_22.xml create mode 100644 core/ui/src/main/res/drawable/img_cardano_22.xml create mode 100644 core/ui/src/main/res/drawable/img_dash_22.xml create mode 100644 core/ui/src/main/res/drawable/img_dogecoin_22.xml create mode 100644 core/ui/src/main/res/drawable/img_eth_22.xml create mode 100644 core/ui/src/main/res/drawable/img_eth_classic_22.xml rename app/src/main/res/drawable/ic_ethereumfair_round.xml => core/ui/src/main/res/drawable/img_eth_fair_22.xml (100%) rename app/src/main/res/drawable/ic_ethereumpow_round.xml => core/ui/src/main/res/drawable/img_eth_pow_22.xml (100%) create mode 100644 core/ui/src/main/res/drawable/img_fantom_22.xml create mode 100644 core/ui/src/main/res/drawable/img_gnosis_22.xml create mode 100644 core/ui/src/main/res/drawable/img_kusama_22.xml create mode 100644 core/ui/src/main/res/drawable/img_litecoin_22.xml create mode 100644 core/ui/src/main/res/drawable/img_optimism_22.xml create mode 100644 core/ui/src/main/res/drawable/img_polkadot_22.xml create mode 100644 core/ui/src/main/res/drawable/img_rsk_22.xml create mode 100644 core/ui/src/main/res/drawable/img_solana_22.xml create mode 100644 core/ui/src/main/res/drawable/img_stellar_22.xml create mode 100644 core/ui/src/main/res/drawable/img_tezos_22.xml create mode 100644 core/ui/src/main/res/drawable/img_tron_22.xml create mode 100644 core/ui/src/main/res/drawable/img_xrp_22.xml create mode 100644 features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapPermissionStateHolder.kt create mode 100644 features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSelectTokenStateHolder.kt create mode 100644 features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt create mode 100644 features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSuccessStateHolder.kt create mode 100644 features/swap/presentation/src/main/java/com/tangem/feature/swap/router/SwapRouter.kt create mode 100644 features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheetContent.kt create mode 100644 features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt create mode 100644 features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt create mode 100644 features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt create mode 100644 features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt 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 e53e1effbe..5e10f11f1d 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 @@ -5,39 +5,6 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.remove import com.tangem.wallet.R -@DrawableRes -fun Blockchain.getRoundIconRes(): Int { - return when (this) { - Blockchain.Arbitrum, Blockchain.ArbitrumTestnet -> R.drawable.ic_arbitrum_round - Blockchain.Ducatus -> R.drawable.ic_ducatus - Blockchain.Bitcoin, Blockchain.BitcoinTestnet,-> R.drawable.ic_bitcoin_round - Blockchain.BitcoinCash -> R.drawable.ic_bitcoin_cash_round - Blockchain.Litecoin -> R.drawable.ic_litecoin_round - Blockchain.Ethereum, Blockchain.EthereumTestnet, -> R.drawable.ic_eth_round - Blockchain.EthereumClassic, Blockchain.EthereumClassicTestnet -> R.drawable.ic_eth_round - Blockchain.RSK -> R.drawable.ic_rsk_round - Blockchain.Cardano, Blockchain.CardanoShelley -> R.drawable.ic_cardano_round - Blockchain.Tezos -> R.drawable.ic_tezos_round - Blockchain.XRP -> R.drawable.ic_xrp_round - Blockchain.Stellar -> R.drawable.ic_stellar_round - Blockchain.Avalanche, Blockchain.AvalancheTestnet -> R.drawable.ic_avalanche_round - Blockchain.Polygon, Blockchain.PolygonTestnet -> R.drawable.ic_polygon_round - 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 - Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.ic_tron_round - Blockchain.Gnosis -> R.drawable.ic_gnosis_round - Blockchain.EthereumPow, Blockchain.EthereumPowTestnet -> R.drawable.ic_ethereumpow_round - Blockchain.EthereumFair -> R.drawable.ic_ethereumfair_round - Blockchain.Polkadot, Blockchain.PolkadotTestnet -> R.drawable.ic_polkadot_round - Blockchain.Kusama -> R.drawable.ic_kusama_round - Blockchain.Optimism, Blockchain.OptimismTestnet -> R.drawable.ic_optimism - Blockchain.Dash -> R.drawable.ic_dash - else -> R.drawable.ic_tangem_logo - } -} - @DrawableRes fun Blockchain.getGreyedOutIconRes(): Int { return when (this) { diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemHeader.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemHeader.kt index b029341439..32c6c7b329 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemHeader.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemHeader.kt @@ -31,9 +31,9 @@ import androidx.compose.ui.unit.sp import coil.compose.SubcomposeAsyncImage import coil.request.ImageRequest import com.tangem.blockchain.common.Blockchain +import com.tangem.core.ui.extensions.getActiveIconRes import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.tap.common.extensions.getGreyedOutIconRes -import com.tangem.tap.common.extensions.getRoundIconRes import com.tangem.tap.domain.tokens.Currency import com.tangem.tap.features.tokens.redux.TokenWithBlockchain import com.tangem.tap.features.tokens.ui.compose.CurrencyPlaceholderIcon @@ -142,7 +142,7 @@ private fun NetworksRow( } else { val isAdded = addedTokens.map { it.token.contractAddress }.contains(contract.address) val iconResId = if (isAdded) { - contract.blockchain.getRoundIconRes() + getActiveIconRes(contract.blockchain.id) } else { contract.blockchain.getGreyedOutIconRes() } @@ -173,7 +173,7 @@ private fun BlockchainNetworkItem( addedBlockchains: List, ) { val added = addedBlockchains.contains(blockchain) - val icon = if (added) blockchain?.getRoundIconRes() else blockchain?.getGreyedOutIconRes() + val icon = if (added) blockchain?.let { getActiveIconRes(it.id) } else blockchain?.getGreyedOutIconRes() if (icon != null) { Box( Modifier diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/NetworkItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/NetworkItem.kt index 2928ace0d8..494cbc8553 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/NetworkItem.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/NetworkItem.kt @@ -31,10 +31,10 @@ import androidx.compose.ui.unit.sp import coil.compose.SubcomposeAsyncImage import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token +import com.tangem.core.ui.extensions.getActiveIconRes import com.tangem.tap.common.extensions.fullNameWithoutTestnet import com.tangem.tap.common.extensions.getGreyedOutIconRes import com.tangem.tap.common.extensions.getNetworkName -import com.tangem.tap.common.extensions.getRoundIconRes import com.tangem.tap.domain.tokens.Contract import com.tangem.tap.domain.tokens.Currency import com.tangem.tap.features.tokens.redux.ContractAddress @@ -85,7 +85,7 @@ fun NetworkItem( .align(Alignment.CenterVertically), ) { SubcomposeAsyncImage( - model = if (added) blockchain.getRoundIconRes() else blockchain.getGreyedOutIconRes(), + model = if (added) getActiveIconRes(blockchain.id) else blockchain.getGreyedOutIconRes(), contentDescription = blockchain.fullName, loading = { CurrencyPlaceholderIcon(blockchain.id) }, error = { CurrencyPlaceholderIcon(blockchain.id) }, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt index 729bfe3fcc..b02511017c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt @@ -8,9 +8,9 @@ import coil.request.ImageRequest import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.IconsUtil import com.tangem.blockchain.common.Token +import com.tangem.core.ui.extensions.getActiveIconRes import com.tangem.domain.common.extensions.toNetworkId import com.tangem.tap.common.extensions.getColor -import com.tangem.tap.common.extensions.getRoundIconRes import com.tangem.tap.common.extensions.getTextColor import com.tangem.tap.domain.extensions.getCustomIconUrl import com.tangem.tap.domain.tokens.getIconUrl @@ -85,8 +85,8 @@ class CurrencyIconRequest( crossinline onError: (Blockchain) -> Unit = {}, ) { currencyImageView.loadIcon( - data = if (getLocalImage) blockchain.getRoundIconRes() else getIconUrl(blockchain.toNetworkId()), - placeholderRes = blockchain.getRoundIconRes(), + data = if (getLocalImage) getActiveIconRes(blockchain.id) else getIconUrl(blockchain.toNetworkId()), + placeholderRes = getActiveIconRes(blockchain.id), onStart = { onStart(blockchain) }, onSuccess = { onSuccess(blockchain) }, onError = { onError(blockchain) }, diff --git a/app/src/main/res/drawable/ic_arbitrum_round.xml b/app/src/main/res/drawable/ic_arbitrum_round.xml deleted file mode 100644 index 632ea33dae..0000000000 --- a/app/src/main/res/drawable/ic_arbitrum_round.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_avalanche_round.xml b/app/src/main/res/drawable/ic_avalanche_round.xml deleted file mode 100644 index ac2c5623a1..0000000000 --- a/app/src/main/res/drawable/ic_avalanche_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_binance_round.xml b/app/src/main/res/drawable/ic_binance_round.xml deleted file mode 100644 index 6b399342e0..0000000000 --- a/app/src/main/res/drawable/ic_binance_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_bitcoin_cash_round.xml b/app/src/main/res/drawable/ic_bitcoin_cash_round.xml deleted file mode 100644 index 704b9c7c96..0000000000 --- a/app/src/main/res/drawable/ic_bitcoin_cash_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_bitcoin_round.xml b/app/src/main/res/drawable/ic_bitcoin_round.xml deleted file mode 100644 index ab0c448c23..0000000000 --- a/app/src/main/res/drawable/ic_bitcoin_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_bsc_round.xml b/app/src/main/res/drawable/ic_bsc_round.xml deleted file mode 100644 index cca406ef7b..0000000000 --- a/app/src/main/res/drawable/ic_bsc_round.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/ic_cardano_round.xml b/app/src/main/res/drawable/ic_cardano_round.xml deleted file mode 100644 index 6764bbb9e8..0000000000 --- a/app/src/main/res/drawable/ic_cardano_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_dogecoin_round.xml b/app/src/main/res/drawable/ic_dogecoin_round.xml deleted file mode 100644 index 8a89a70dcf..0000000000 --- a/app/src/main/res/drawable/ic_dogecoin_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_eth_round.xml b/app/src/main/res/drawable/ic_eth_round.xml deleted file mode 100644 index b62152ede8..0000000000 --- a/app/src/main/res/drawable/ic_eth_round.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_ethc_round.xml b/app/src/main/res/drawable/ic_ethc_round.xml deleted file mode 100644 index 31f3dbacd8..0000000000 --- a/app/src/main/res/drawable/ic_ethc_round.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/ic_fantom_round.xml b/app/src/main/res/drawable/ic_fantom_round.xml deleted file mode 100644 index e970acee86..0000000000 --- a/app/src/main/res/drawable/ic_fantom_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_gnosis_round.xml b/app/src/main/res/drawable/ic_gnosis_round.xml deleted file mode 100644 index 8734b7177b..0000000000 --- a/app/src/main/res/drawable/ic_gnosis_round.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/ic_kusama_round.xml b/app/src/main/res/drawable/ic_kusama_round.xml deleted file mode 100644 index f620ae3130..0000000000 --- a/app/src/main/res/drawable/ic_kusama_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_litecoin_round.xml b/app/src/main/res/drawable/ic_litecoin_round.xml deleted file mode 100644 index 018f6f35bc..0000000000 --- a/app/src/main/res/drawable/ic_litecoin_round.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/ic_optimism.xml b/app/src/main/res/drawable/ic_optimism.xml deleted file mode 100644 index fb8ce845a1..0000000000 --- a/app/src/main/res/drawable/ic_optimism.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/ic_polkadot_round.xml b/app/src/main/res/drawable/ic_polkadot_round.xml deleted file mode 100644 index 346f130d82..0000000000 --- a/app/src/main/res/drawable/ic_polkadot_round.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_polygon_round.xml b/app/src/main/res/drawable/ic_polygon_round.xml deleted file mode 100644 index d8e57f8f97..0000000000 --- a/app/src/main/res/drawable/ic_polygon_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_rsk_round.xml b/app/src/main/res/drawable/ic_rsk_round.xml deleted file mode 100644 index f4a6e92bde..0000000000 --- a/app/src/main/res/drawable/ic_rsk_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_solana_round.xml b/app/src/main/res/drawable/ic_solana_round.xml deleted file mode 100644 index 49659278ae..0000000000 --- a/app/src/main/res/drawable/ic_solana_round.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_stellar_round.xml b/app/src/main/res/drawable/ic_stellar_round.xml deleted file mode 100644 index f7ae98231f..0000000000 --- a/app/src/main/res/drawable/ic_stellar_round.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_tezos_round.xml b/app/src/main/res/drawable/ic_tezos_round.xml deleted file mode 100644 index 4976e634e3..0000000000 --- a/app/src/main/res/drawable/ic_tezos_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_tron_round.xml b/app/src/main/res/drawable/ic_tron_round.xml deleted file mode 100644 index 9eaf6cfc9b..0000000000 --- a/app/src/main/res/drawable/ic_tron_round.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/ic_xrp_round.xml b/app/src/main/res/drawable/ic_xrp_round.xml deleted file mode 100644 index 6e29010df6..0000000000 --- a/app/src/main/res/drawable/ic_xrp_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 2ed6276780..8696a129c5 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -191,6 +191,8 @@ Share Copy Erfolg + You Send + You Receive Give Permission To continue you need to allow 1inch smart contracts to use your %s Amount %s diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 5c7572747e..160175b03b 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -191,6 +191,8 @@ Share Copy Avec succès + You Send + You Receive Give Permission To continue you need to allow 1inch smart contracts to use your %s Amount %s diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml index 2b7d521568..3f70ed2bb4 100644 --- a/core/res/src/main/res/values-it/strings.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -191,6 +191,8 @@ Share Copy Con successo + You Send + You Receive Give Permission To continue you need to allow 1inch smart contracts to use your %s Amount %s diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 95b3fc88e0..59ada270a7 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -191,6 +191,8 @@ Поделиться Копировать Успешно + Вы отправляете + Вы получаете Дать разрешение Чтобы продолжить, вам нужно разрешить смарт-контракту 1inch использовать ваш %s Количество %s diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 6bfdbb64e0..db70283a4c 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -191,6 +191,8 @@ Share Copy Success + You Send + You Receive Give Permission To continue you need to allow 1inch smart contracts to use your %s Amount %s diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt index 8eb31f6669..2f513195bb 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt @@ -268,10 +268,11 @@ private fun CardInfoBox( if (isLoading) { Box( modifier = Modifier + .size(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12) .shimmer() - .size( - width = TangemTheme.dimens.size80, - height = TangemTheme.dimens.size20, + .background( + color = TangemTheme.colors.button.secondary, + shape = RoundedCornerShape(TangemTheme.dimens.radius3), ), ) } else { diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/CurrencyPlaceholderIcon.kt b/core/ui/src/main/java/com/tangem/core/ui/components/CurrencyPlaceholderIcon.kt new file mode 100644 index 0000000000..418f0692ed --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/CurrencyPlaceholderIcon.kt @@ -0,0 +1,59 @@ +package com.tangem.core.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.sp +import com.tangem.core.ui.res.TangemTheme + +@Composable +fun CurrencyPlaceholderIcon(modifier: Modifier = Modifier, id: String) { + + val letterColor: Color = TangemTheme.colors.text.primary2 + val circleColor: Color = TangemTheme.colors.icon.secondary + + Box( + contentAlignment = Alignment.Center, + modifier = modifier + .background(circleColor, shape = CircleShape), + ) + { + Text( + text = id.firstOrNull()?.titlecase() ?: "", + textAlign = TextAlign.Center, + color = letterColor, + modifier = Modifier.padding(TangemTheme.dimens.spacing4), + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + ) + } +} + +// region preview + +@Preview(showBackground = true, heightDp = 40, widthDp = 40) +@Composable +fun Preview_CurrencyPlaceholderIcon_InLightTheme() { + TangemTheme(isDark = false) { + CurrencyPlaceholderIcon(id = "DAI") + } +} + +@Preview(showBackground = true, heightDp = 40, widthDp = 40) +@Composable +fun Preview_CurrencyPlaceholderIcon_InDarkTheme() { + TangemTheme(isDark = true) { + CurrencyPlaceholderIcon(id = "DAI") + } +} + +// endregion preview \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/ResizableText.kt b/core/ui/src/main/java/com/tangem/core/ui/components/ResizableText.kt new file mode 100644 index 0000000000..4b7fe2c1ac --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/ResizableText.kt @@ -0,0 +1,69 @@ +package com.tangem.core.ui.components + +import androidx.compose.material.LocalTextStyle +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.sp + +@Composable +fun ResizableText( + text: String, + modifier: Modifier = Modifier, + color: Color = Color.Unspecified, + style: TextStyle = LocalTextStyle.current, + fontSizeRange: FontSizeRange, +) { + val fontSizeValue = remember { mutableStateOf(fontSizeRange.max.value) } + val readyToDraw = remember { mutableStateOf(false) } + + val textState = remember { mutableStateOf(text) } + if (textState.value != text) { + readyToDraw.value = false + fontSizeValue.value = fontSizeRange.max.value + textState.value = text + } + + Text( + modifier = modifier.drawWithContent { if (readyToDraw.value) drawContent() }, + text = text, + color = color, + softWrap = false, + style = style, + fontSize = fontSizeValue.value.sp, + onTextLayout = { + if (it.hasVisualOverflow) { + val nextFontSizeValue = fontSizeValue.value - fontSizeRange.step.value + if (nextFontSizeValue <= fontSizeRange.min.value) { + fontSizeValue.value = fontSizeRange.min.value + readyToDraw.value = true + } else { + fontSizeValue.value = nextFontSizeValue * 0.8f + } + } else { + readyToDraw.value = true + } + }, + ) +} + +data class FontSizeRange( + val min: TextUnit, + val max: TextUnit, + val step: TextUnit = DEFAULT_TEXT_STEP, +) { + init { + require(min < max) { "min should be less than max, $this" } + require(step.value > 0) { "step should be greater than 0, $this" } + } + + companion object { + private val DEFAULT_TEXT_STEP = 1.sp + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/SuccessScreen.kt b/core/ui/src/main/java/com/tangem/core/ui/components/SuccessScreen.kt index c0f603db45..9464d4355c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/SuccessScreen.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/SuccessScreen.kt @@ -44,7 +44,7 @@ fun SuccessScreenContent( modifier = modifier .fillMaxSize() .verticalScroll(rememberScrollState()) - .background(TangemTheme.colors.background.secondary) + .background(TangemTheme.colors.background.primary) .padding( horizontal = TangemTheme.dimens.spacing16, vertical = TangemTheme.dimens.spacing32, diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/Blockchain.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/Blockchain.kt new file mode 100644 index 0000000000..052f2f26b6 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/Blockchain.kt @@ -0,0 +1,37 @@ +package com.tangem.core.ui.extensions + +import androidx.annotation.DrawableRes +import com.tangem.core.ui.R + +@DrawableRes +fun getActiveIconRes(blockchainId: String): Int { + return when (blockchainId) { + "ARBITRUM-ONE", "ARBITRUM/test" -> R.drawable.img_arbitrum_22 + "BTC", "BTC/test" -> R.drawable.img_btc_22 + "BCH" -> R.drawable.img_btc_cash_22 + "LTC" -> R.drawable.img_litecoin_22 + "ETH", "ETH/test" -> R.drawable.img_eth_22 + "ETC", "ETC/test" -> R.drawable.img_eth_classic_22 + "RSK" -> R.drawable.img_rsk_22 + "CARDANO", "CARDANO-S" -> R.drawable.img_cardano_22 + "XTZ" -> R.drawable.img_tezos_22 + "XRP" -> R.drawable.img_xrp_22 + "XLM", "XLM/test" -> R.drawable.img_stellar_22 + "AVALANCHE", "AVALANCHE/test" -> R.drawable.img_avalanche_22 + "POLYGON", "POLYGON/test" -> R.drawable.img_polygon_22 + "SOLANA", "SOLANA/test" -> R.drawable.img_solana_22 + "FTM", "FTM/test" -> R.drawable.img_fantom_22 + "BSC", "BSC/test", "BINANCE", "BINANCE/test" -> R.drawable.img_bsc_22 + "DOGE" -> R.drawable.img_dogecoin_22 + "TRON", "TRON/test" -> R.drawable.img_tron_22 + "GNO" -> R.drawable.img_gnosis_22 + "ETH-Pow", "ETH-Pow/test" -> R.drawable.img_eth_pow_22 + "ETH-Fair" -> R.drawable.img_eth_fair_22 + "Polkadot", "Polkadot/test" -> R.drawable.img_polkadot_22 + "Kusama" -> R.drawable.img_kusama_22 + "OPTIMISM", "OPTIMISM/test" -> R.drawable.img_optimism_22 + "DASH" -> R.drawable.img_dash_22 + else -> R.drawable.ic_alert_24 + } +} + diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt index 061e1f81c6..e5cf24728b 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt @@ -20,6 +20,7 @@ data class TangemDimens internal constructor( // endregion Elevation // region Radius val radius2: Dp = 2.dp, + val radius3: Dp = 3.dp, val radius4: Dp = 4.dp, val radius6: Dp = 6.dp, val radius8: Dp = 8.dp, @@ -61,6 +62,7 @@ data class TangemDimens internal constructor( val size200: Dp = 200.dp, // endregion Size // region Spacing + val spacing0_5: Dp = 0.5.dp, val spacing2: Dp = 2.dp, val spacing4: Dp = 4.dp, val spacing8: Dp = 8.dp, diff --git a/core/ui/src/main/res/drawable/img_arbitrum_22.xml b/core/ui/src/main/res/drawable/img_arbitrum_22.xml new file mode 100644 index 0000000000..db6401d732 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_arbitrum_22.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/core/ui/src/main/res/drawable/img_avalanche_22.xml b/core/ui/src/main/res/drawable/img_avalanche_22.xml new file mode 100644 index 0000000000..ce6d69f6c0 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_avalanche_22.xml @@ -0,0 +1,14 @@ + + + + diff --git a/core/ui/src/main/res/drawable/img_binance_22.xml b/core/ui/src/main/res/drawable/img_binance_22.xml new file mode 100644 index 0000000000..e54d84d8a5 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_binance_22.xml @@ -0,0 +1,14 @@ + + + + diff --git a/core/ui/src/main/res/drawable/img_bsc_22.xml b/core/ui/src/main/res/drawable/img_bsc_22.xml new file mode 100644 index 0000000000..4aa4221fd0 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_bsc_22.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/core/ui/src/main/res/drawable/img_btc_22.xml b/core/ui/src/main/res/drawable/img_btc_22.xml new file mode 100644 index 0000000000..7624b7c1ad --- /dev/null +++ b/core/ui/src/main/res/drawable/img_btc_22.xml @@ -0,0 +1,13 @@ + + + + diff --git a/core/ui/src/main/res/drawable/img_btc_cash_22.xml b/core/ui/src/main/res/drawable/img_btc_cash_22.xml new file mode 100644 index 0000000000..4b64ee0ba3 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_btc_cash_22.xml @@ -0,0 +1,13 @@ + + + + diff --git a/core/ui/src/main/res/drawable/img_cardano_22.xml b/core/ui/src/main/res/drawable/img_cardano_22.xml new file mode 100644 index 0000000000..bda1a6ce6b --- /dev/null +++ b/core/ui/src/main/res/drawable/img_cardano_22.xml @@ -0,0 +1,13 @@ + + + + diff --git a/core/ui/src/main/res/drawable/img_dash_22.xml b/core/ui/src/main/res/drawable/img_dash_22.xml new file mode 100644 index 0000000000..3e52aa86b1 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_dash_22.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/core/ui/src/main/res/drawable/img_dogecoin_22.xml b/core/ui/src/main/res/drawable/img_dogecoin_22.xml new file mode 100644 index 0000000000..2f083f774a --- /dev/null +++ b/core/ui/src/main/res/drawable/img_dogecoin_22.xml @@ -0,0 +1,14 @@ + + + + diff --git a/core/ui/src/main/res/drawable/img_eth_22.xml b/core/ui/src/main/res/drawable/img_eth_22.xml new file mode 100644 index 0000000000..0c8469373e --- /dev/null +++ b/core/ui/src/main/res/drawable/img_eth_22.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/core/ui/src/main/res/drawable/img_eth_classic_22.xml b/core/ui/src/main/res/drawable/img_eth_classic_22.xml new file mode 100644 index 0000000000..99bdcffe57 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_eth_classic_22.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_ethereumfair_round.xml b/core/ui/src/main/res/drawable/img_eth_fair_22.xml similarity index 100% rename from app/src/main/res/drawable/ic_ethereumfair_round.xml rename to core/ui/src/main/res/drawable/img_eth_fair_22.xml diff --git a/app/src/main/res/drawable/ic_ethereumpow_round.xml b/core/ui/src/main/res/drawable/img_eth_pow_22.xml similarity index 100% rename from app/src/main/res/drawable/ic_ethereumpow_round.xml rename to core/ui/src/main/res/drawable/img_eth_pow_22.xml diff --git a/core/ui/src/main/res/drawable/img_fantom_22.xml b/core/ui/src/main/res/drawable/img_fantom_22.xml new file mode 100644 index 0000000000..3327ab40ae --- /dev/null +++ b/core/ui/src/main/res/drawable/img_fantom_22.xml @@ -0,0 +1,14 @@ + + + + diff --git a/core/ui/src/main/res/drawable/img_gnosis_22.xml b/core/ui/src/main/res/drawable/img_gnosis_22.xml new file mode 100644 index 0000000000..222aa994d2 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_gnosis_22.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/core/ui/src/main/res/drawable/img_kusama_22.xml b/core/ui/src/main/res/drawable/img_kusama_22.xml new file mode 100644 index 0000000000..f3d98bd342 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_kusama_22.xml @@ -0,0 +1,13 @@ + + + + diff --git a/core/ui/src/main/res/drawable/img_litecoin_22.xml b/core/ui/src/main/res/drawable/img_litecoin_22.xml new file mode 100644 index 0000000000..5354cc63be --- /dev/null +++ b/core/ui/src/main/res/drawable/img_litecoin_22.xml @@ -0,0 +1,13 @@ + + + + diff --git a/core/ui/src/main/res/drawable/img_optimism_22.xml b/core/ui/src/main/res/drawable/img_optimism_22.xml new file mode 100644 index 0000000000..5ce92be0bc --- /dev/null +++ b/core/ui/src/main/res/drawable/img_optimism_22.xml @@ -0,0 +1,14 @@ + + + + diff --git a/core/ui/src/main/res/drawable/img_polkadot_22.xml b/core/ui/src/main/res/drawable/img_polkadot_22.xml new file mode 100644 index 0000000000..9c4c5e7f4b --- /dev/null +++ b/core/ui/src/main/res/drawable/img_polkadot_22.xml @@ -0,0 +1,14 @@ + + + + diff --git a/core/ui/src/main/res/drawable/img_rsk_22.xml b/core/ui/src/main/res/drawable/img_rsk_22.xml new file mode 100644 index 0000000000..d8ae9d522f --- /dev/null +++ b/core/ui/src/main/res/drawable/img_rsk_22.xml @@ -0,0 +1,13 @@ + + + + diff --git a/core/ui/src/main/res/drawable/img_solana_22.xml b/core/ui/src/main/res/drawable/img_solana_22.xml new file mode 100644 index 0000000000..8948b33621 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_solana_22.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + diff --git a/core/ui/src/main/res/drawable/img_stellar_22.xml b/core/ui/src/main/res/drawable/img_stellar_22.xml new file mode 100644 index 0000000000..a87e860293 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_stellar_22.xml @@ -0,0 +1,13 @@ + + + + diff --git a/core/ui/src/main/res/drawable/img_tezos_22.xml b/core/ui/src/main/res/drawable/img_tezos_22.xml new file mode 100644 index 0000000000..cf9c1d5100 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_tezos_22.xml @@ -0,0 +1,13 @@ + + + + diff --git a/core/ui/src/main/res/drawable/img_tron_22.xml b/core/ui/src/main/res/drawable/img_tron_22.xml new file mode 100644 index 0000000000..f1dfceca1f --- /dev/null +++ b/core/ui/src/main/res/drawable/img_tron_22.xml @@ -0,0 +1,13 @@ + + + + diff --git a/core/ui/src/main/res/drawable/img_xrp_22.xml b/core/ui/src/main/res/drawable/img_xrp_22.xml new file mode 100644 index 0000000000..41fed91f22 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_xrp_22.xml @@ -0,0 +1,13 @@ + + + + diff --git a/features/swap/presentation/build.gradle.kts b/features/swap/presentation/build.gradle.kts index fc127f077b..be71e61c6d 100644 --- a/features/swap/presentation/build.gradle.kts +++ b/features/swap/presentation/build.gradle.kts @@ -60,6 +60,7 @@ dependencies { implementation(Compose.foundation) implementation(Compose.material) implementation(Compose.uiTooling) + implementation(Compose.coil) /** Domain */ implementation(project(":features:swap:domain")) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapPermissionStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapPermissionStateHolder.kt new file mode 100644 index 0000000000..76d4ee8587 --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapPermissionStateHolder.kt @@ -0,0 +1,22 @@ +package com.tangem.feature.swap.models + +data class SwapPermissionStateHolder( + val currency: String, + val amount: String, + val walletAddress: String, + val spenderAddress: String, + val fee: String, + val approveButton: ApprovePermissionButton, + val cancelButton: CancelPermissionButton, +) + +data class ApprovePermissionButton( + val enabled: Boolean, + val loading: Boolean = false, + val onClick: () -> Unit, +) + +data class CancelPermissionButton( + val enabled: Boolean, + val onClick: () -> Unit, +) \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSelectTokenStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSelectTokenStateHolder.kt new file mode 100644 index 0000000000..0f12e3010b --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSelectTokenStateHolder.kt @@ -0,0 +1,7 @@ +package com.tangem.feature.swap.models + +data class SwapSelectTokenStateHolder( + val tokens: List, + val onSearchEntered: (String) -> Unit, + val onTokenSelected: (String) -> Unit, +) \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt new file mode 100644 index 0000000000..19e2b2d63f --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt @@ -0,0 +1,79 @@ +package com.tangem.feature.swap.models + +import androidx.annotation.DrawableRes + +data class SwapStateHolder( + val sendCardData: SwapCardData, + val receiveCardData: SwapCardData, + val networkCurrency: String, + val fee: FeeState = FeeState.Loaded(), + val warnings: List = emptyList(), + + val permissionState: SwapPermissionStateHolder? = null, + val successState: SwapSuccessStateHolder? = null, + val selectTokenState: SwapSelectTokenStateHolder? = null, + + val swapButton: SwapButton, + + val onRefresh: () -> Unit, + val onBackClicked: () -> Unit, + val onChangeCardsClicked: () -> Unit, + val onSelectTokenClick: (() -> Unit)? = null, + val onSuccess: (() -> Unit)? = null, +) + +data class TokenToSelect( + val id: String, + val name: String, + val symbol: String, + val addedTokenBalanceData: TokenBalanceData? = null, +) + +data class TokenBalanceData( + val amount: String?, + val amountEquivalent: String?, +) + +data class SwapCardData( + val type: TransactionCardType, + val amount: String?, + val amountEquivalent: String?, + val tokenIconUrl: String, + val tokenCurrency: String, + @DrawableRes val networkIconRes: Int? = null, + val canSelectAnotherToken: Boolean = false, +) + +data class SwapButton( + val enabled: Boolean, + val loading: Boolean = false, + val onClick: () -> Unit, +) + +sealed interface FeeState { + data class Loaded( + val fee: String = "", + ) : FeeState + + object Loading : FeeState + + data class NotEnoughFundsWarning(val fee: String) : FeeState +} + +sealed interface TransactionCardType { + + data class SendCard( + val balance: String, + val permissionIsGiven: Boolean, + ) : TransactionCardType + + data class ReceiveCard( + val highPriceImpact: String? = null, + ) : TransactionCardType +} + +sealed interface SwapWarning { + data class PermissionNeeded(val tokenCurrency: String) : SwapWarning + // data class RateExpired(val onClick: () -> Unit) : SwapWarning + // object HighPriceImpact : SwapWarning +} \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSuccessStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSuccessStateHolder.kt new file mode 100644 index 0000000000..9fad250c3a --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSuccessStateHolder.kt @@ -0,0 +1,5 @@ +package com.tangem.feature.swap.models + +data class SwapSuccessStateHolder( + val message: String, +) \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt index cc841afcb7..acedb67519 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt @@ -1,16 +1,21 @@ package com.tangem.feature.swap.presentation import android.os.Bundle -import android.transition.TransitionInflater import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.animation.Crossfade import androidx.compose.ui.platform.ComposeView import androidx.core.view.WindowCompat import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import com.tangem.feature.swap.router.SwapRouter +import com.tangem.feature.swap.router.SwapScreen +import com.tangem.feature.swap.ui.SwapScreen +import com.tangem.feature.swap.ui.SwapSuccessScreen import com.tangem.feature.swap.viewmodels.SwapViewModel import dagger.hilt.android.AndroidEntryPoint +import java.lang.ref.WeakReference @AndroidEntryPoint class SwapFragment : Fragment() { @@ -19,10 +24,24 @@ class SwapFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) } - viewModel + viewModel.setRouter(SwapRouter(fragmentManager = WeakReference(parentFragmentManager))) return ComposeView(inflater.context).apply { setContent { - //todo add compose screen here + Crossfade(targetState = viewModel.currentScreen) { screen -> + when (screen) { + SwapScreen.Main -> SwapScreen(stateHolder = viewModel.uiState) + SwapScreen.Success -> { + val successState = viewModel.uiState.successState + if (successState != null) { + SwapSuccessScreen(state = successState, viewModel.uiState.onBackClicked) + } else { + SwapScreen(stateHolder = viewModel.uiState) + } + } + SwapScreen.SelectToken -> SwapScreen(stateHolder = viewModel.uiState)// TODO: use proper screen + } + } + } } } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/router/SwapRouter.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/router/SwapRouter.kt new file mode 100644 index 0000000000..bf3f7446fd --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/router/SwapRouter.kt @@ -0,0 +1,31 @@ +package com.tangem.feature.swap.router + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.fragment.app.FragmentManager +import java.lang.ref.WeakReference + +internal class SwapRouter( + private val fragmentManager: WeakReference, +) { + + var currentScreen by mutableStateOf(SwapScreen.Main) + private set + + fun openScreen(screen: SwapScreen) { + currentScreen = screen + } + + fun back() { + if (currentScreen == SwapScreen.SelectToken) { + currentScreen = SwapScreen.Main + } else { + fragmentManager.get()?.popBackStack() + } + } +} + +enum class SwapScreen { + Main, Success, SelectToken +} \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheetContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheetContent.kt new file mode 100644 index 0000000000..90082105f4 --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheetContent.kt @@ -0,0 +1,219 @@ +package com.tangem.feature.swap.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Divider +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.PrimaryButtonIconRight +import com.tangem.core.ui.components.SecondaryButton +import com.tangem.core.ui.components.SpacerH10 +import com.tangem.core.ui.components.SpacerH12 +import com.tangem.core.ui.components.SpacerH16 +import com.tangem.core.ui.components.SpacerH28 +import com.tangem.core.ui.components.SpacerH32 +import com.tangem.core.ui.components.atoms.Hand +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.swap.models.ApprovePermissionButton +import com.tangem.feature.swap.models.CancelPermissionButton +import com.tangem.feature.swap.models.SwapPermissionStateHolder +import com.tangem.feature.swap.presentation.R + +@Composable +fun SwapPermissionBottomSheetContent( + data: SwapPermissionStateHolder, + onCancel: () -> Unit, +) { + Column( + modifier = Modifier + .background(color = TangemTheme.colors.background.primary) + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.spacing16), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Hand() + + SpacerH10() + + Text( + text = stringResource(id = R.string.swapping_permission_header), + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.subtitle1, + ) + + SpacerH10() + + Text( + text = stringResource(id = R.string.swapping_permission_subheader, data.currency), + color = TangemTheme.colors.text.secondary, + style = TangemTheme.typography.body2, + textAlign = TextAlign.Center, + modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing8), + ) + + SpacerH16() + + ApprovalBottomSheetInfo( + currency = data.currency, + amount = data.amount, + walletAddress = data.walletAddress, + spenderAddress = data.spenderAddress, + fee = data.fee, + ) + + SpacerH28() + + PrimaryButtonIconRight( + text = stringResource(id = R.string.swapping_permission_buttons_approve), + icon = painterResource(id = R.drawable.ic_tangem_24), + modifier = Modifier.fillMaxWidth(), + onClick = data.approveButton.onClick, + ) + + SpacerH12() + + SecondaryButton( + text = stringResource(id = R.string.common_cancel), + modifier = Modifier.fillMaxWidth(), + ) { + data.cancelButton.onClick() + onCancel() + } + + SpacerH32() + + } +} + +@Composable +private fun ApprovalBottomSheetInfo( + currency: String, amount: String, walletAddress: String, spenderAddress: String, fee: String, +) { + Column( + modifier = Modifier + .background(color = TangemTheme.colors.background.primary) + .fillMaxWidth() + .padding( + start = TangemTheme.dimens.spacing12, + end = TangemTheme.dimens.spacing16, + ), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + + AmountItem(currency = currency, amount = amount) + DividerBottomSheet() + WalletAddressItem(walletAddress = walletAddress) + DividerBottomSheet() + SpenderItem(spenderAddress = spenderAddress) + DividerBottomSheet() + FeeItem(fee = fee) + } +} + +@Composable +private fun DividerBottomSheet() { + Divider( + color = TangemTheme.colors.stroke.primary, + thickness = TangemTheme.dimens.size0_5, + ) +} + +@Composable +private fun InformationItem(subtitle: String, value: String) { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(TangemTheme.dimens.spacing16), + ) { + + Text( + text = subtitle, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.subtitle1, + maxLines = 1, + ) + + SpacerH16() + + Text( + text = value, + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.body2, + maxLines = 1, + ) + } +} + +@Composable +private fun AmountItem(currency: String, amount: String) { + InformationItem( + subtitle = stringResource(id = R.string.swapping_permission_rows_amount, currency), + value = amount, + ) +} + +@Composable +private fun WalletAddressItem(walletAddress: String) { + InformationItem( + subtitle = stringResource(id = R.string.swapping_permission_rows_your_wallet), + value = walletAddress, + ) +} + +@Composable +private fun SpenderItem(spenderAddress: String) { + InformationItem( + subtitle = stringResource(id = R.string.swapping_permission_rows_spender), + value = spenderAddress, + ) +} + +@Composable +private fun FeeItem(fee: String) { + InformationItem( + subtitle = stringResource(id = R.string.send_fee_label), + value = fee, + ) +} + +// region preview + +@Preview +@Composable +fun Preview_AgreementBottomSheet_InLightTheme() { + TangemTheme(isDark = false) { + SwapPermissionBottomSheetContent(data = previewData) {} + } +} + +@Preview +@Composable +fun Preview_AgreementBottomSheet_InDarkTheme() { + TangemTheme(isDark = true) { + SwapPermissionBottomSheetContent(data = previewData) {} + } +} + +private val previewData = SwapPermissionStateHolder( + currency = "DAI", + amount = "∞", + walletAddress = "", + spenderAddress = "", + fee = "2,14$", + approveButton = ApprovePermissionButton(true) {}, + cancelButton = CancelPermissionButton(true) {}, +) + +//endregion preview \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt new file mode 100644 index 0000000000..5011b9ec33 --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt @@ -0,0 +1,66 @@ +package com.tangem.feature.swap.ui + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.BottomSheetScaffold +import androidx.compose.material.BottomSheetState +import androidx.compose.material.BottomSheetValue +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.rememberBottomSheetScaffoldState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.res.stringResource +import com.tangem.core.ui.components.appbar.AppBarWithBackButton +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.swap.models.SwapStateHolder +import com.tangem.feature.swap.presentation.R +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterialApi::class) +@Composable +internal fun SwapScreen(stateHolder: SwapStateHolder) { + val coroutineScope = rememberCoroutineScope() + val bottomSheetScaffoldState = rememberBottomSheetScaffoldState( + bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed), + ) + + TangemTheme { + BottomSheetScaffold( + topBar = { + AppBarWithBackButton( + text = stringResource(R.string.swapping_swap), + onBackClick = stateHolder.onBackClicked, + iconRes = R.drawable.ic_close_24, + ) + }, + sheetContent = { + if (stateHolder.permissionState != null) { + SwapPermissionBottomSheetContent( + data = stateHolder.permissionState, + onCancel = { coroutineScope.launch { bottomSheetScaffoldState.bottomSheetState.collapse() } }, + ) + } + }, + scaffoldState = bottomSheetScaffoldState, + sheetShape = RoundedCornerShape( + topStart = TangemTheme.dimens.radius16, + topEnd = TangemTheme.dimens.radius16, + ), + sheetElevation = TangemTheme.dimens.elevation24, + sheetPeekHeight = TangemTheme.dimens.size0, + content = { + SwapScreenContent( + state = stateHolder, + onPermissionWarningClick = { + coroutineScope.launch { + if (bottomSheetScaffoldState.bottomSheetState.isCollapsed) { + bottomSheetScaffoldState.bottomSheetState.expand() + } else { + bottomSheetScaffoldState.bottomSheetState.collapse() + } + } + }, + ) + }, + ) + } +} \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt new file mode 100644 index 0000000000..03249e7a4a --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -0,0 +1,226 @@ +package com.tangem.feature.swap.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Card +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.ClickableWarningCard +import com.tangem.core.ui.components.PrimaryButtonIconRight +import com.tangem.core.ui.components.SmallInfoCard +import com.tangem.core.ui.components.SmallInfoCardWithWarning +import com.tangem.core.ui.components.SpacerH16 +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.swap.models.FeeState +import com.tangem.feature.swap.models.SwapButton +import com.tangem.feature.swap.models.SwapCardData +import com.tangem.feature.swap.models.SwapStateHolder +import com.tangem.feature.swap.models.SwapWarning +import com.tangem.feature.swap.models.TransactionCardType +import com.tangem.feature.swap.presentation.R + +@Composable +internal fun SwapScreenContent( + modifier: Modifier = Modifier, + state: SwapStateHolder, + onPermissionWarningClick: () -> Unit, +) { + Column( + modifier = modifier + .fillMaxWidth() + .background(color = TangemTheme.colors.background.primary) + .verticalScroll(rememberScrollState()) + .padding( + start = TangemTheme.dimens.spacing16, + end = TangemTheme.dimens.spacing16, + top = TangemTheme.dimens.spacing16, + bottom = TangemTheme.dimens.spacing32, + ), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16), + ) { + + MainInfo(state) + + FeeItem(feeState = state.fee, currency = state.networkCurrency) + + if (state.warnings.isNotEmpty()) { + SwapWarnings( + warnings = state.warnings, + onApproveWarningClick = onPermissionWarningClick, + ) + } + + PrimaryButtonIconRight( + modifier = Modifier.fillMaxWidth(), + text = stringResource(id = R.string.swapping_swap), + icon = painterResource(id = R.drawable.ic_tangem_24), + enabled = state.swapButton.enabled, + showProgress = state.swapButton.loading, + onClick = state.swapButton.onClick, + ) + } +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +private fun MainInfo(state: SwapStateHolder) { + Box( + modifier = Modifier + .fillMaxWidth(), + contentAlignment = Alignment.Center, + ) { + Column { + TransactionCard( + type = state.sendCardData.type, + amount = state.sendCardData.amount, + amountEquivalent = state.sendCardData.amountEquivalent, + tokenIconUrl = state.sendCardData.tokenIconUrl, + tokenCurrency = state.sendCardData.tokenCurrency, + networkIconRes = state.sendCardData.networkIconRes, + onChangeTokenClick = if (state.sendCardData.canSelectAnotherToken) state.onSelectTokenClick else null, + ) + SpacerH16() + TransactionCard( + type = state.receiveCardData.type, + amount = state.receiveCardData.amount, + amountEquivalent = state.receiveCardData.amountEquivalent, + tokenIconUrl = state.receiveCardData.tokenIconUrl, + tokenCurrency = state.receiveCardData.tokenCurrency, + networkIconRes = state.receiveCardData.networkIconRes, + onChangeTokenClick = if (state.receiveCardData.canSelectAnotherToken) state.onSelectTokenClick else null, + ) + } + Card( + elevation = TangemTheme.dimens.elevation3, + shape = CircleShape, + backgroundColor = TangemTheme.colors.background.plain, + contentColor = TangemTheme.colors.text.primary1, + modifier = Modifier + .size(TangemTheme.dimens.size48), + onClick = state.onChangeCardsClicked, + ) { + Icon( + painter = painterResource(id = com.tangem.core.ui.R.drawable.ic_exchange_vertical_24), + contentDescription = null, + tint = TangemTheme.colors.text.primary1, + modifier = Modifier + .padding(TangemTheme.dimens.spacing12), + ) + } + } +} + +@Composable +private fun FeeItem(feeState: FeeState, currency: String) { + + val titleString = stringResource(id = R.string.send_fee_label) + when (feeState) { + is FeeState.Loaded -> { + SmallInfoCard(startText = titleString, endText = feeState.fee, isLoading = false) + } + FeeState.Loading -> { + SmallInfoCard(startText = titleString, endText = "", isLoading = true) + } + is FeeState.NotEnoughFundsWarning -> { + SmallInfoCardWithWarning( + startText = titleString, + endText = feeState.fee, + warningText = stringResource(id = R.string.token_details_send_blocked_fee_format, currency), + ) + } + } +} + +@Composable +private fun SwapWarnings( + warnings: List, onApproveWarningClick: () -> Unit, +) { + Column( + modifier = Modifier + .background(color = MaterialTheme.colors.primary) + .fillMaxWidth(), + ) { + warnings.forEach { warning -> + when (warning) { + // SwapWarning.HighPriceImpact -> { + // WarningCard(title = stringResource(id = R.string.), description = stringResource(id = R.string.)) + // } + is SwapWarning.PermissionNeeded -> { + ClickableWarningCard( + title = stringResource(id = R.string.swapping_permission_header), + description = stringResource( + id = R.string.swapping_permission_subheader, + warning.tokenCurrency, + ), + onClick = onApproveWarningClick, + ) + } + // is SwapWarning.RateExpired -> { + // RefreshableWaringCard( + // title = stringResource(id = R.string.), + // description = stringResource(id = R.string.), + // onClick = warning.onClick, + // ) + // } + } + } + } +} + +// region preview + +private val sendCard = SwapCardData( + type = TransactionCardType.SendCard("123", false), + amount = "1 000 000", + amountEquivalent = "1 000 000", + tokenIconUrl = "", + tokenCurrency = "DAI", + networkIconRes = R.drawable.img_polygon_22, + canSelectAnotherToken = false, +) + +private val receiveCard = SwapCardData( + type = TransactionCardType.ReceiveCard(), + amount = "1 000 000", + amountEquivalent = "1 000 000", + tokenIconUrl = "", + tokenCurrency = "DAI", + networkIconRes = R.drawable.img_polygon_22, + canSelectAnotherToken = true, +) + +private val state = SwapStateHolder( + sendCardData = sendCard, + receiveCardData = receiveCard, + fee = FeeState.Loaded(fee = "0.155 MATIC (0.14 $)"), + warnings = emptyList(), + networkCurrency = "MATIC", + swapButton = SwapButton(enabled = true, loading = false, onClick = {}), + onRefresh = {}, onBackClicked = {}, onChangeCardsClicked = {}, +) + +@Preview +@Composable +fun SwapScreenContentPreview() { + TangemTheme(isDark = false) { + SwapScreenContent(state = state) {} + } +} + +// endregion preview diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt new file mode 100644 index 0000000000..2311562db4 --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt @@ -0,0 +1,31 @@ +package com.tangem.feature.swap.ui + +import androidx.compose.material.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import com.tangem.core.ui.components.SuccessScreenContent +import com.tangem.core.ui.components.appbar.AppBarWithBackButton +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.swap.models.SwapSuccessStateHolder +import com.tangem.feature.swap.presentation.R + +@Composable +fun SwapSuccessScreen(state: SwapSuccessStateHolder, onBack: () -> Unit) { + TangemTheme { + Scaffold( + content = { + SuccessScreenContent( + successMessage = state.message, + onButtonClick = onBack, + ) + }, + topBar = { + AppBarWithBackButton( + text = stringResource(R.string.swapping_swap), + onBackClick = onBack, + iconRes = R.drawable.ic_close_24, + ) + }, + ) + } +} \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt new file mode 100644 index 0000000000..d780d7e126 --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt @@ -0,0 +1,378 @@ +package com.tangem.feature.swap.ui + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Card +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import coil.compose.SubcomposeAsyncImage +import coil.request.ImageRequest +import com.tangem.core.ui.R +import com.tangem.core.ui.components.CurrencyPlaceholderIcon +import com.tangem.core.ui.components.FontSizeRange +import com.tangem.core.ui.components.ResizableText +import com.tangem.core.ui.components.SpacerH4 +import com.tangem.core.ui.components.SpacerW12 +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.swap.models.TransactionCardType +import com.valentinilk.shimmer.shimmer + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun TransactionCard( + type: TransactionCardType, + amount: String?, + amountEquivalent: String?, + tokenIconUrl: String, + tokenCurrency: String, + @DrawableRes networkIconRes: Int? = null, + onChangeTokenClick: (() -> Unit)? = null, + modifier: Modifier = Modifier, +) { + Card( + modifier = modifier, + shape = RoundedCornerShape(TangemTheme.dimens.radius12), + backgroundColor = TangemTheme.colors.background.primary, + elevation = TangemTheme.dimens.elevation2, + onClick = onChangeTokenClick ?: {}, + enabled = onChangeTokenClick != null, + ) { + Box(modifier = Modifier.fillMaxSize()) { + Column( + modifier = Modifier + .fillMaxWidth() + .height(TangemTheme.dimens.size116), + verticalArrangement = Arrangement.SpaceBetween, + horizontalAlignment = Alignment.Start, + ) { + + Header(type = type) + + Content( + type = type, + amount = amount, + amountEquivalent = amountEquivalent, + ) + } + + Box(modifier = Modifier.align(Alignment.BottomEnd)) { + Token( + tokenIconUrl = tokenIconUrl, + tokenCurrency = tokenCurrency, + networkIconRes = networkIconRes, + ) + } + + if (onChangeTokenClick != null) { + Box(modifier = Modifier.align(Alignment.CenterEnd)) { + ChangeTokenSelector() + } + } + } + + } +} + +@Composable +private fun Header( + type: TransactionCardType, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier + .fillMaxWidth() + .padding( + bottom = dimensionResource(id = R.dimen.spacing8), + start = TangemTheme.dimens.spacing16, + end = TangemTheme.dimens.spacing16, + ), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + val title = when (type) { + is TransactionCardType.ReceiveCard -> R.string.exchange_receive_view_header + is TransactionCardType.SendCard -> R.string.exchange_send_view_header + } + Text( + text = stringResource(id = title), + color = TangemTheme.colors.text.tertiary, + maxLines = 1, + style = MaterialTheme.typography.subtitle2, + modifier = Modifier.padding(top = TangemTheme.dimens.spacing12), + ) + if (type is TransactionCardType.SendCard) { + Text( + text = stringResource(R.string.common_balance, type.balance), + color = TangemTheme.colors.text.tertiary, + maxLines = 1, + style = MaterialTheme.typography.body2, + modifier = Modifier.padding(top = TangemTheme.dimens.spacing12), + ) + } + } +} + +@Composable +private fun Content( + type: TransactionCardType, + amount: String?, + amountEquivalent: String?, +) { + Row( + modifier = Modifier + .padding( + top = TangemTheme.dimens.spacing8, + start = TangemTheme.dimens.spacing16, + bottom = TangemTheme.dimens.spacing16, + ), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.Bottom, + ) { + + if (type is TransactionCardType.SendCard && !type.permissionIsGiven) { + Box( + modifier = Modifier + .size(TangemTheme.dimens.size56) + .background( + color = TangemTheme.colors.button.secondary, + shape = RoundedCornerShape(TangemTheme.dimens.size12), + ), + contentAlignment = Alignment.Center, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_locked_24), + contentDescription = null, + tint = TangemTheme.colors.icon.primary1, + ) + } + + SpacerW12() + } + + Column( + modifier = Modifier + .padding( + end = TangemTheme.dimens.spacing92, + ), + verticalArrangement = Arrangement.SpaceBetween, + horizontalAlignment = Alignment.Start, + ) { + + if (amount != null) { + ResizableText( + text = amount, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.h2, + fontSizeRange = + FontSizeRange(min = 16.sp, max = TangemTheme.typography.h2.fontSize), + ) + } else { + Box( + modifier = Modifier + .size(width = TangemTheme.dimens.size102, height = TangemTheme.dimens.size24) + .shimmer() + .background( + color = TangemTheme.colors.button.secondary, + shape = RoundedCornerShape(TangemTheme.dimens.radius6), + ), + ) + } + + Spacer( + modifier = Modifier + .size(TangemTheme.dimens.size4) + .weight(1f), + ) + + if (amountEquivalent != null) { + Text( + text = amountEquivalent, + color = TangemTheme.colors.text.tertiary, + maxLines = 1, + style = TangemTheme.typography.body2, + ) + } else { + Box( + modifier = Modifier + .size(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12) + .shimmer() + .background( + color = TangemTheme.colors.button.secondary, + shape = RoundedCornerShape(TangemTheme.dimens.radius3), + ), + ) + } + } + + } +} + +@Composable +fun Token( + tokenIconUrl: String, + tokenCurrency: String, + @DrawableRes networkIconRes: Int? = null, +) { + Column( + modifier = Modifier + .padding( + end = TangemTheme.dimens.spacing12, + bottom = TangemTheme.dimens.spacing12, + ), + verticalArrangement = Arrangement.Bottom, + horizontalAlignment = Alignment.End, + ) { + + Box( + modifier = Modifier + .padding( + end = TangemTheme.dimens.spacing16, + ) + .size(TangemTheme.dimens.size42), + ) { + val tokenImageModifier = Modifier + .align(Alignment.BottomStart) + .size(TangemTheme.dimens.size36) + + SubcomposeAsyncImage( + modifier = tokenImageModifier, + model = ImageRequest.Builder(LocalContext.current) + .data(tokenIconUrl) + .crossfade(true) + .build(), + loading = { TokenImageShimmer(modifier = tokenImageModifier) }, + error = { CurrencyPlaceholderIcon(modifier = tokenImageModifier, tokenCurrency) }, + contentDescription = null, + ) + + if (networkIconRes != null) { + Box( + modifier = Modifier + .align(Alignment.TopEnd) + .size(TangemTheme.dimens.size18) + .background( + color = Color.White, + shape = CircleShape, + ), + contentAlignment = Alignment.Center, + ) { + Image( + modifier = Modifier.padding(all = (0.5).dp), + painter = painterResource(id = networkIconRes), + contentDescription = null, + ) + } + } + } + + SpacerH4() + + Text( + text = tokenCurrency, + color = TangemTheme.colors.text.primary1, + maxLines = 1, + style = TangemTheme.typography.subtitle2, + textAlign = TextAlign.Center, + modifier = Modifier + .defaultMinSize(minWidth = TangemTheme.dimens.size80), + ) + } +} + +@Composable +fun ChangeTokenSelector() { + Box( + modifier = Modifier + .fillMaxHeight() + .padding( + top = TangemTheme.dimens.spacing12, + start = TangemTheme.dimens.spacing24, + end = TangemTheme.dimens.spacing12, + ), + contentAlignment = Alignment.CenterEnd, + ) { + Icon( + modifier = Modifier.size(TangemTheme.dimens.size20), + painter = painterResource(id = R.drawable.ic_chevron_24), + contentDescription = null, + ) + } +} + +@Composable +private fun TokenImageShimmer( + modifier: Modifier = Modifier, +) { + Box(modifier = modifier.shimmer()) { + Box( + modifier = Modifier + .matchParentSize() + .background( + color = TangemTheme.colors.button.secondary, + shape = CircleShape, + ), + ) + } +} + +// region preview + +@Preview(widthDp = 328, heightDp = 116, showBackground = true) +@Composable +fun Preview_SwapMainCard_InLightTheme() { + TangemTheme(isDark = false) { + TransactionCardPreview() + } +} + +@Preview(widthDp = 328, heightDp = 116, showBackground = true) +@Composable +fun Preview_SwapMainCard_InDarkTheme() { + TangemTheme(isDark = true) { + TransactionCardPreview() + } +} + +@Composable +private fun TransactionCardPreview() { + TransactionCard( + type = TransactionCardType.SendCard("123", false), + amount = "1 000 000", + amountEquivalent = "1 000 000", + tokenIconUrl = "", + tokenCurrency = "DAI", + networkIconRes = R.drawable.img_polygon_22, + onChangeTokenClick = {}, + ) +} + +// endregion preview \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index fd8a40070f..245d7b404b 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -1,14 +1,31 @@ package com.tangem.feature.swap.viewmodels import android.util.Log +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.tangem.feature.swap.domain.SwapInteractor import com.tangem.feature.swap.domain.models.data.Currency +import com.tangem.feature.swap.models.ApprovePermissionButton +import com.tangem.feature.swap.models.CancelPermissionButton +import com.tangem.feature.swap.models.FeeState +import com.tangem.feature.swap.models.SwapButton +import com.tangem.feature.swap.models.SwapCardData +import com.tangem.feature.swap.models.SwapPermissionStateHolder +import com.tangem.feature.swap.models.SwapStateHolder +import com.tangem.feature.swap.models.SwapSuccessStateHolder +import com.tangem.feature.swap.models.SwapWarning +import com.tangem.feature.swap.models.TransactionCardType +import com.tangem.feature.swap.presentation.R +import com.tangem.feature.swap.router.SwapRouter +import com.tangem.feature.swap.router.SwapScreen import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import javax.inject.Inject +import kotlin.properties.Delegates @HiltViewModel internal class SwapViewModel @Inject constructor( @@ -16,6 +33,27 @@ internal class SwapViewModel @Inject constructor( private val dispatchers: CoroutineDispatcherProvider, ) : ViewModel() { + var uiState: SwapStateHolder by mutableStateOf(createInitialUiState()) + private set + + private var swapRouter: SwapRouter by Delegates.notNull() + + var currentScreen = SwapScreen.Main + get() = swapRouter.currentScreen + + fun setRouter(router: SwapRouter) { + swapRouter = router + uiState = uiState.copy( + onBackClicked = router::back, + onSelectTokenClick = { router.openScreen(SwapScreen.Success) }, + onSuccess = { router.openScreen(SwapScreen.Success) }, + ) + } + + private fun createInitialUiState(): SwapStateHolder { // TODO: create using real state + return createTestUiState() + } + // region temp for test init { viewModelScope.launch { @@ -39,5 +77,48 @@ internal class SwapViewModel @Inject constructor( "1", ) } + + private fun createTestUiState(): SwapStateHolder { + val sendCard = SwapCardData( + type = TransactionCardType.SendCard("123", false), + amount = "1 000 000 000 000 000 000", + amountEquivalent = "1 000 000 000 000 000 000", + tokenIconUrl = "", + tokenCurrency = "DAI", + networkIconRes = R.drawable.img_polygon_22, + ) + + val receiveCard = SwapCardData( + type = TransactionCardType.ReceiveCard(), + amount = "1 000 000", + amountEquivalent = "1 000 000", + tokenIconUrl = "", + tokenCurrency = "DAI", + networkIconRes = R.drawable.img_polygon_22, + canSelectAnotherToken = true, + ) + + val permissionState = SwapPermissionStateHolder( + currency = "DAI", + amount = "∞", + walletAddress = "", + spenderAddress = "", + fee = "2,14$", + approveButton = ApprovePermissionButton(true) {}, + cancelButton = CancelPermissionButton(true) {}, + ) + + return SwapStateHolder( + sendCardData = sendCard, + receiveCardData = receiveCard, + fee = FeeState.Loaded(fee = "0.155 MATIC (0.14 $)"), + warnings = listOf(SwapWarning.PermissionNeeded("DAI")), + networkCurrency = "MATIC", + swapButton = SwapButton(enabled = true, loading = false, onClick = {}), + onRefresh = {}, onBackClicked = {}, onChangeCardsClicked = {}, + permissionState = permissionState, + successState = SwapSuccessStateHolder("Success"), + ) + } // endregion temp for test } \ No newline at end of file From 2c7a317313c9926fd9e0100b07b4aac5aa9cc98e Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 16 Dec 2022 00:04:18 +0300 Subject: [PATCH 021/145] Updated on 2026-08-14 --- .../tap/domain/configurable/config/ConfigManager.kt | 8 +++++--- .../tap/domain/extensions/WalletManagerFactory.kt | 3 +-- .../products/wallet/saltPay/GnosisRegistrator.kt | 5 ++--- .../products/wallet/saltPay/SaltPayConfig.kt | 12 +++++++++++- buildSrc/src/main/java/Versions.kt | 3 ++- .../com/tangem/domain/common/SaltPayWorkaround.kt | 9 +-------- .../java/com/tangem/domain/common/ScanResponse.kt | 2 +- .../tangem/domain/common/extensions/Blockchain.kt | 4 +--- 8 files changed, 24 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt index b95d540bf0..5073c369a4 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt @@ -66,12 +66,12 @@ class ConfigManager { config = config.copy( isTopUpEnabled = model.isTopUpEnabled, isSendingToPayIdEnabled = model.isSendingToPayIdEnabled, - isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed + isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed, ) defaultConfig = defaultConfig.copy( isTopUpEnabled = model.isTopUpEnabled, isSendingToPayIdEnabled = model.isSendingToPayIdEnabled, - isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed + isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed, ) } @@ -88,7 +88,8 @@ class ConfigManager { blockchairAuthorizationToken = values.blockchairAuthorizationToken, blockcypherTokens = values.blockcypherTokens, infuraProjectId = values.infuraProjectId, - tronGridApiKey = values.tronGridApiKey + tronGridApiKey = values.tronGridApiKey, + saltPayAuthToken = values.saltPay.credentials.token, ), appsFlyerDevKey = values.appsFlyerDevKey, amplitudeApiKey = values.amplitudeApiKey, @@ -107,6 +108,7 @@ class ConfigManager { blockchairAuthorizationToken = values.blockchairAuthorizationToken, blockcypherTokens = values.blockcypherTokens, infuraProjectId = values.infuraProjectId, + saltPayAuthToken = values.saltPay.credentials.token, ), appsFlyerDevKey = values.appsFlyerDevKey, amplitudeApiKey = values.amplitudeApiKey, diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt index 874e562a2a..e3c30fa01b 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt @@ -143,8 +143,7 @@ fun WalletManagerFactory.makeSaltPayWalletManager( scanResponse: ScanResponse, ): EthereumWalletManager { val blockchain = scanResponse.getBlockchain() - if (blockchain != Blockchain.SaltPay && blockchain != Blockchain.SaltPayTestnet) - throw IllegalArgumentException() + if (blockchain != Blockchain.SaltPay) throw IllegalArgumentException() val token = SaltPayWorkaround.tokenFrom(blockchain) val cardWallet = scanResponse.card.wallets.firstOrNull().guard { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt index 771fca7d4b..42c54a9395 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt @@ -38,12 +38,11 @@ class GnosisRegistrator( } private val otpProcessorContractAddress: String = when (walletManager.wallet.blockchain) { - Blockchain.SaltPay -> "0x3B4397C817A26521Df8bD01a949AFDE2251d91C2" - Blockchain.SaltPayTestnet -> "0x710BF23486b549836509a08c184cE0188830f197" + Blockchain.SaltPay -> "0xc659f4FEd7A84a188F54cBA4A7a49D77c1a20522" else -> throw IllegalArgumentException("GnosisRegistrator supports only the SaltPay blockchain") } - private val addressTreasureSafe = "0x8e9260a049d3Aa9ac60D0d4F27017320E0e2396B" + private val addressTreasureSafe = "0x24A3c2382497075b6D93258f5938f7B661c06318" private val atomicNonce = AtomicLong() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt index d853819a57..19048168de 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay import com.tangem.tap.common.zendesk.ZendeskConfig +import org.spongycastle.util.encoders.Base64.toBase64String /** [REDACTED_AUTHOR] @@ -8,12 +9,14 @@ import com.tangem.tap.common.zendesk.ZendeskConfig data class SaltPayConfig( val zendesk: ZendeskConfig, val kycProvider: KYCProvider, + val credentials: Credentials, ) { companion object { fun stub(): SaltPayConfig { return SaltPayConfig( zendesk = ZendeskConfig("", "", "", "", ""), kycProvider = KYCProvider("", "", "", ""), + credentials = Credentials("", ""), ) } } @@ -24,4 +27,11 @@ data class KYCProvider( val externalIdParameterKey: String, val sidParameterKey: String, val sidValue: String, -) \ No newline at end of file +) + +data class Credentials( + val user: String, + val password: String, +) { + val token: String by lazy { "Basic ${toBase64String("$user:$password".toByteArray())}" } +} \ No newline at end of file diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index e5985c63df..3a15fb0a04 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -58,7 +58,8 @@ object Versions { // endregion Other libraries // region Tangem - const val tangemBlockchainSdk = "develop-141" + const val tangemBlockchainSdk = "develop-142" + // const val tangemBlockchainSdk = "0.0.1" const val tangemCardSgk = "develop-173" // endregion Tangem diff --git a/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt b/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt index c397160c2d..1328643534 100644 --- a/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt +++ b/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt @@ -11,17 +11,10 @@ object SaltPayWorkaround { Blockchain.SaltPay -> Token( name = "WXDAI", symbol = "wxDAI", - contractAddress = "0x4346186e7461cB4DF06bCFCB4cD591423022e417", + contractAddress = "0x4200000000000000000000000000000000000006", decimals = 18, id = "wrapped-xdai", ) - Blockchain.SaltPayTestnet -> Token( - name = "WXDAI Test", - symbol = "MyERC20", - contractAddress = "0x69cca8D8295de046C7c14019D9029Ccc77987A48", - decimals = 0, - id = "wrapped-xdai", - ) else -> throw IllegalArgumentException() } } diff --git a/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt b/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt index ad0538d7a5..01ef016471 100644 --- a/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt +++ b/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt @@ -30,7 +30,7 @@ data class ScanResponse( ) : CommandResponse { fun getBlockchain(): Blockchain { return when (productType) { - ProductType.SaltPay -> if (card.isTestCard) Blockchain.SaltPayTestnet else Blockchain.SaltPay + ProductType.SaltPay -> Blockchain.SaltPay ProductType.Note -> card.getTangemNoteBlockchain() ?: Blockchain.Unknown else -> { val blockchainName: String = walletData?.blockchain ?: return Blockchain.Unknown 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 0fde777794..c684dbe3f5 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 @@ -48,7 +48,6 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { "optimistic-ethereum/test" -> Blockchain.OptimismTestnet "dash" -> Blockchain.Dash "sxdai" -> Blockchain.SaltPay - "sxdai/test" -> Blockchain.SaltPayTestnet else -> null } } @@ -101,7 +100,6 @@ fun Blockchain.toNetworkId(): String { Blockchain.OptimismTestnet -> "optimistic-ethereum/test" Blockchain.Dash -> "dash" Blockchain.SaltPay -> "sxdai" - Blockchain.SaltPayTestnet -> "sxdai/test" } } @@ -133,7 +131,7 @@ fun Blockchain.toCoinId(): String { Blockchain.Kusama -> "kusama" Blockchain.Optimism, Blockchain.OptimismTestnet -> "ethereum" Blockchain.Dash -> "dash" - Blockchain.SaltPay, Blockchain.SaltPayTestnet -> "xdai" + Blockchain.SaltPay -> "xdai" Blockchain.Unknown -> "unknown" } } From 7f4886eaf23ef84b676d2c20e08a6d34e9c3e40d Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 16 Dec 2022 09:58:29 +0300 Subject: [PATCH 022/145] Updated on 2026-08-14 --- .../com/tangem/tap/common/DialogManager.kt | 14 +++-- .../redux/OnboardingWalletMiddleware.kt | 53 +++++++++---------- .../wallet/redux/OnboardingWalletState.kt | 13 +++-- ...Dialog.kt => InterruptOnboardingDialog.kt} | 7 +-- .../wallet/saltPay/dialog/SaltPayDialog.kt | 2 - 5 files changed, 44 insertions(+), 45 deletions(-) rename app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/{TryToInterruptRegistrationDialog.kt => InterruptOnboardingDialog.kt} (79%) diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index f25d1ddf4c..94dd3ff6c1 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -17,10 +17,11 @@ import com.tangem.tap.features.onboarding.AddressInfoBottomSheetDialog import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.onboarding.products.twins.ui.dialog.CreateWalletInterruptDialog import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog +import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingDialog +import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.InterruptOnboardingDialog import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.NoFundsForActivationDialog import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.RegistrationErrorDialog import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog -import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.TryToInterruptRegistrationDialog import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AddMoreBackupCardsDialog import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.BackupInProgressDialog import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.ConfirmDiscardingBackupDialog @@ -72,8 +73,8 @@ class DialogManager : StoreSubscriber { is AppDialog.ScanFailsDialog -> ScanFailsDialog.create(context) is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context) is AppDialog.TestActionsDialog -> TestActionsBottomSheetDialog(state.dialog, context) - is TwinCardsAction.Wallet.ShowInterruptDialog -> - CreateWalletInterruptDialog.create(state.dialog, context) + is TwinCardsAction.Wallet.ShowInterruptDialog -> CreateWalletInterruptDialog.create(state.dialog, context) + is OnboardingDialog.InterruptOnboarding -> InterruptOnboardingDialog.create(context, state.dialog) is WalletConnectDialog.UnsupportedCard -> SimpleAlertDialog.create( titleRes = R.string.wallet_connect_title, @@ -109,10 +110,8 @@ class DialogManager : StoreSubscriber { ChooseNetworkDialog.create(state.dialog.session, state.dialog.networks, context) is WalletConnectDialog.ClipboardOrScanQr -> ClipboardOrScanQrDialog.create(state.dialog.clipboardUri, context) - is WalletConnectDialog.RequestTransaction -> - TransactionDialog.create(state.dialog.dialogData, context) - is WalletConnectDialog.PersonalSign -> - PersonalSignDialog.create(state.dialog.data, context) + is WalletConnectDialog.RequestTransaction -> TransactionDialog.create(state.dialog.dialogData, context) + is WalletConnectDialog.PersonalSign -> PersonalSignDialog.create(state.dialog.data, context) is WalletConnectDialog.BnbTransactionDialog -> BnbTransactionDialog.create( data = state.dialog.data, @@ -132,7 +131,6 @@ class DialogManager : StoreSubscriber { is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(context) is BackupDialog.ConfirmDiscardingBackup -> ConfirmDiscardingBackupDialog.create(context) is SaltPayDialog.Activation.NoGas -> NoFundsForActivationDialog.create(context) - is SaltPayDialog.Activation.TryToInterrupt -> TryToInterruptRegistrationDialog.create(context, state.dialog) is SaltPayDialog.Activation.OnError -> RegistrationErrorDialog.create(context, state.dialog) is WalletDialog.CurrencySelectionDialog -> CurrencySelectionDialog.create(state.dialog, context) is WalletDialog.ChooseTradeActionDialog -> ChooseTradeActionBottomSheetDialog(context) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 37e629800a..c199ac3195 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -22,7 +22,6 @@ import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.onboarding.OnboardingHelper -import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState import com.tangem.tap.features.wallet.redux.Artwork @@ -188,12 +187,7 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: } newAction?.let { store.dispatch(it) } } - OnboardingWalletAction.OnBackPressed -> { - when { - onboardingWalletState.isSaltPay -> handleOnBackPressedSaltPay(onboardingWalletState) - else -> handleOnBackPressed(onboardingWalletState) - } - } + OnboardingWalletAction.OnBackPressed -> handleOnBackPressed(onboardingWalletState) } } @@ -408,30 +402,33 @@ private fun initSaltPayOnBackupFinishedIfNeeded( } } -private fun handleOnBackPressedSaltPay(state: OnboardingWalletState) { +private fun handleOnBackPressed(state: OnboardingWalletState) { when (state.backupState.backupStep) { - BackupStep.Finished -> { - store.dispatchDialogShow( - SaltPayDialog.Activation.TryToInterrupt( - onOk = { store.dispatch(NavigationAction.PopBackTo()) }, - onCancel = { /* do nothing */ }, - ), - ) + BackupStep.InitBackup, BackupStep.ScanOriginCard, BackupStep.AddBackupCards, BackupStep.EnterAccessCode, + BackupStep.ReenterAccessCode, BackupStep.SetAccessCode, BackupStep.WritePrimaryCard, + -> { + showInterruptOnboardingDialog() + } + is BackupStep.WriteBackupCard -> { + store.dispatch(GlobalAction.ShowDialog(BackupDialog.BackupInProgress)) + } + BackupStep.Finished -> { + if (state.isSaltPay) { + showInterruptOnboardingDialog() + } else { + store.dispatch(NavigationAction.PopBackTo()) + } } - else -> handleOnBackPressed(state) } } -private fun handleOnBackPressed(state: OnboardingWalletState) { - when (state.backupState.backupStep) { - BackupStep.InitBackup, BackupStep.Finished -> store.dispatch(NavigationAction.PopBackTo()) - BackupStep.ScanOriginCard, BackupStep.AddBackupCards, BackupStep.EnterAccessCode, - BackupStep.ReenterAccessCode, BackupStep.SetAccessCode, BackupStep.WritePrimaryCard, - -> { - store.dispatch(BackupAction.DiscardBackup) - store.dispatch(NavigationAction.PopBackTo()) - } - is BackupStep.WriteBackupCard -> - store.dispatch(GlobalAction.ShowDialog(BackupDialog.BackupInProgress)) - } +private fun showInterruptOnboardingDialog() { + store.dispatchDialogShow( + OnboardingDialog.InterruptOnboarding( + onOk = { + store.dispatch(BackupAction.DiscardBackup) + store.dispatch(NavigationAction.PopBackTo()) + }, + ), + ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt index 6fc233d092..aa04272085 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt @@ -2,6 +2,7 @@ package com.tangem.tap.features.onboarding.products.wallet.redux import android.graphics.Bitmap import com.tangem.common.CardFilter +import com.tangem.common.extensions.VoidCallback import com.tangem.domain.common.SaltPayWorkaround import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState @@ -104,9 +105,13 @@ sealed class BackupStep { object Finished : BackupStep() } +sealed class OnboardingDialog: StateDialog { + data class InterruptOnboarding(val onOk: VoidCallback) : BackupDialog() +} + sealed class BackupDialog : StateDialog { - object AddMoreBackupCards : StateDialog - object BackupInProgress : StateDialog - object UnfinishedBackupFound : StateDialog - object ConfirmDiscardingBackup : StateDialog + object AddMoreBackupCards : BackupDialog() + object BackupInProgress : BackupDialog() + object UnfinishedBackupFound : BackupDialog() + object ConfirmDiscardingBackup : BackupDialog() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/TryToInterruptRegistrationDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/InterruptOnboardingDialog.kt similarity index 79% rename from app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/TryToInterruptRegistrationDialog.kt rename to app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/InterruptOnboardingDialog.kt index 6f02df9120..1636675ae1 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/TryToInterruptRegistrationDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/InterruptOnboardingDialog.kt @@ -4,20 +4,21 @@ import android.app.Dialog import android.content.Context import androidx.appcompat.app.AlertDialog import com.tangem.tap.common.extensions.dispatchDialogHide +import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingDialog import com.tangem.tap.store import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ -class TryToInterruptRegistrationDialog { +class InterruptOnboardingDialog { companion object { - fun create(context: Context, dialog: SaltPayDialog.Activation.TryToInterrupt): Dialog { + fun create(context: Context, dialog: OnboardingDialog.InterruptOnboarding): Dialog { return AlertDialog.Builder(context).apply { setTitle(context.getString(R.string.onboarding_exit_alert_title)) setMessage(context.getString(R.string.onboarding_exit_alert_message)) setPositiveButton(R.string.common_ok) { _, _ -> dialog.onOk() } - setNegativeButton(R.string.common_cancel) { _, _ -> dialog.onCancel() } + setNegativeButton(R.string.common_cancel) { _, _ -> } setOnDismissListener { store.dispatchDialogHide() } setCancelable(false) }.create() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt index fda1331a37..b8b512aef1 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt @@ -1,6 +1,5 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog -import com.tangem.common.extensions.VoidCallback import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError @@ -11,6 +10,5 @@ sealed class SaltPayDialog : StateDialog { sealed class Activation : SaltPayDialog() { object NoGas : SaltPayDialog() data class OnError(val error: SaltPayActivationError) : SaltPayDialog() - data class TryToInterrupt(val onOk: VoidCallback, val onCancel: VoidCallback) : SaltPayDialog() } } \ No newline at end of file From 6e0f0d166d02122491b28aed49cef569aacf55a6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 23 Dec 2022 15:42:16 +0300 Subject: [PATCH 023/145] Updated on 2026-08-14 --- .../com/tangem/tap/domain/extensions/WalletManagerFactory.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt index e3c30fa01b..cfa69de062 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt @@ -143,7 +143,8 @@ fun WalletManagerFactory.makeSaltPayWalletManager( scanResponse: ScanResponse, ): EthereumWalletManager { val blockchain = scanResponse.getBlockchain() - if (blockchain != Blockchain.SaltPay) throw IllegalArgumentException() + if (blockchain != Blockchain.SaltPay) + throw IllegalArgumentException("WalletManager for the SaltPay can be created based only on Blockchain.SaltPay") val token = SaltPayWorkaround.tokenFrom(blockchain) val cardWallet = scanResponse.card.wallets.firstOrNull().guard { From 6dee2eeed7ccb6c01f7c9b68cb28a57d963e67ef Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 26 Dec 2022 15:06:51 +0400 Subject: [PATCH 024/145] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + app/src/main/AndroidManifest.xml | 11 + .../com/tangem/tap/common/IntentHandler.kt | 44 ++- .../converters/BasicEventConverter.kt | 10 +- .../tangem/tap/common/extensions/String.kt | 7 +- .../AccessCodeRequestPolicyMiddleware.kt | 27 ++ .../com/tangem/tap/common/redux/AppState.kt | 1 + .../tap/common/redux/global/GlobalAction.kt | 2 +- .../tap/common/redux/global/GlobalReducer.kt | 2 +- .../tap/common/shop/TangemShopService.kt | 4 +- .../com/tangem/tap/domain/TangemSdkManager.kt | 28 +- .../com/tangem/tap/domain/TapWalletManager.kt | 6 +- .../tap/domain/model/TotalFiatBalance.kt | 4 - .../com/tangem/tap/domain/model/UserWallet.kt | 14 +- .../tap/domain/model/WalletDataModel.kt | 14 - .../tap/domain/model/WalletStoreModel.kt | 53 ++- .../model/builders/UserWalletBuilder.kt | 72 +++- .../model/builders/WalletStoreBuilder.kt | 13 +- .../tap/domain/scanCard/ScanCardProcessor.kt | 19 +- .../DefaultTotalFiatBalanceCalculator.kt | 13 - .../domain/twins/CreateFirstTwinWalletTask.kt | 3 + .../twins/CreateSecondTwinWalletTask.kt | 2 + .../tap/domain/twins/FinalizeTwinTask.kt | 2 + .../tap/domain/twins/TwinCardsManager.kt | 5 +- .../userWalletList/UserWalletsListManager.kt | 13 +- .../BiometricUserWalletsListManager.kt | 211 +++++------- .../DummyUserWalletsListManager.kt | 10 +- .../model/UserWalletInformation.kt | 1 + .../repository/UserWalletsKeysRepository.kt | 25 ++ .../UserWalletsPublicInformationRepository.kt | 2 + ...erWalletsSensitiveInformationRepository.kt | 3 +- .../BiometricUserWalletsKeysRepository.kt | 173 ++++++++-- ...tUserWalletsPublicInformationRepository.kt | 52 +-- ...erWalletsSensitiveInformationRepository.kt | 185 +++++++---- .../tap/domain/userWalletList/utils/Mapper.kt | 2 + .../WalletCurrenciesManager.kt | 3 +- .../DefaultWalletCurrenciesManager.kt | 212 ++++++------ .../walletStores/WalletStoresManager.kt | 6 +- .../DefaultWalletStoresManager.kt | 63 ++-- .../DummyWalletStoresManager.kt | 6 +- .../utils/CompletitionsResultOperations.kt | 27 -- .../repository/WalletAmountsRepository.kt | 28 +- .../repository/WalletManagersRepository.kt | 7 +- .../repository/WalletStoresRepository.kt | 1 + .../DefaultWalletAmountsRepository.kt | 309 +++++++++--------- .../DefaultWalletManagersRepository.kt | 53 +-- .../DefaultWalletStoresRepository.kt | 44 +-- .../utils/WalletDataOperations.kt | 3 +- .../utils/WalletStoreOperations.kt | 69 ++-- .../storage/WalletManagerStorage.kt | 6 +- .../storage/WalletStoresStorage.kt | 21 +- .../details/redux/DetailsMiddleware.kt | 40 ++- .../ui/cardsettings/CardSettingsScreen.kt | 8 +- .../details/ui/details/DetailsScreen.kt | 6 + .../disclaimer/redux/DisclaimerAction.kt | 14 +- .../disclaimer/redux/DisclaimerMiddleware.kt | 10 +- .../disclaimer/redux/DisclaimerReducer.kt | 3 +- .../disclaimer/redux/DisclaimerState.kt | 1 + .../disclaimer/ui/DisclaimerFragment.kt | 7 + .../tap/features/home/redux/HomeMiddleware.kt | 17 +- .../features/onboarding/OnboardingHelper.kt | 15 - .../twins/redux/TwinCardsMiddleware.kt | 24 +- .../products/twins/ui/TwinsCardsFragment.kt | 6 + .../saveWallet/redux/SaveWalletMiddleware.kt | 7 - .../send/redux/middlewares/SendMiddleware.kt | 8 +- .../features/tokens/redux/TokensMiddleware.kt | 4 +- .../features/tokens/ui/AddTokensFragment.kt | 3 +- .../tokens/ui/compose/CurrenciesScreen.kt | 6 + .../tap/features/wallet/redux/WalletAction.kt | 21 +- .../tap/features/wallet/redux/WalletState.kt | 106 +++--- .../wallet/redux/middlewares/Mapper.kt | 133 ++++++++ .../middlewares/MultiWalletMiddleware.kt | 2 +- .../middlewares/TradeCryptoMiddleware.kt | 6 +- .../middlewares/WalletDialogsMiddleware.kt | 2 +- .../redux/middlewares/WalletMiddleware.kt | 90 ++--- .../redux/middlewares/WarningsMiddleware.kt | 6 +- .../redux/reducers/MultiWalletReducer.kt | 48 ++- .../redux/reducers/OnWalletLoadedReducer.kt | 10 +- .../redux/reducers/TotalBalanceOperations.kt | 1 + .../wallet/redux/reducers/WalletReducer.kt | 187 +++-------- .../tap/features/wallet/ui/BalanceWidget.kt | 3 +- .../wallet/ui/WalletDetailsFragment.kt | 156 ++++++--- .../tap/features/wallet/ui/WalletFragment.kt | 13 +- .../wallet/ui/adapters/WalletAdapter.kt | 5 +- .../ui/test/TestWalletDetailsActions.kt | 2 +- .../wallet/ui/wallet/MultiWalletView.kt | 74 ++++- .../ui/wallet/SaltPaySingleWalletView.kt | 2 +- .../wallet/ui/wallet/SingleWalletView.kt | 6 +- .../features/wallet/ui/wallet/WalletView.kt | 2 + .../walletSelector/redux/UserWalletModel.kt | 3 +- .../redux/WalletSelectorAction.kt | 10 +- .../redux/WalletSelectorMiddleware.kt | 180 +++++----- .../redux/WalletSelectorReducer.kt | 18 +- .../redux/WalletSelectorState.kt | 3 +- .../tap/features/walletSelector/ui/Mapper.kt | 6 +- .../ui/WalletSelectorBottomSheetFragment.kt | 23 +- .../ui/WalletSelectorScreenState.kt | 5 +- .../ui/WalletSelectorViewModel.kt | 50 +-- .../walletSelector/ui/components/MockData.kt | 11 +- .../WallectSelectorScreenContent.kt | 165 +++++----- .../ui/components/WalletItem.kt | 15 +- .../walletSelector/ui/model/UserWalletItem.kt | 15 +- .../welcome/redux/WelcomeMiddleware.kt | 11 +- .../tangem/tap/proxy/DerivationManagerImpl.kt | 2 +- app/src/main/res/layout/fragment_wallet.xml | 4 +- .../res/layout/fragment_wallet_details.xml | 38 +-- .../layout/item_currency_wallet_content.xml | 128 ++++---- .../res/layout/layout_card_total_balance.xml | 47 ++- buildSrc/src/main/java/Dependency.kt | 1 + buildSrc/src/main/java/Versions.kt | 3 +- .../fragments/ComposeBottomSheetFragment.kt | 10 +- .../tangem/core/ui/fragments/FragmentExt.kt | 14 + 112 files changed, 2146 insertions(+), 1562 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt delete mode 100644 app/src/main/java/com/tangem/tap/domain/walletStores/implementation/utils/CompletitionsResultOperations.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/fragments/FragmentExt.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a15171a939..1876b0038c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -198,6 +198,7 @@ dependencies { implementation(Library.armadillo) implementation(Library.googlePlayServicesWallet) implementation(Library.composeShimmer) + implementation(Library.mviCoreWatcher) /** Testing libraries */ testImplementation(Test.junit) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e8a5618a92..e7d11764b4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -101,6 +101,17 @@ + + + + + + + + + + + diff --git a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt b/app/src/main/java/com/tangem/tap/common/IntentHandler.kt index cfee84fd69..4ac3547aa0 100644 --- a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/IntentHandler.kt @@ -3,6 +3,7 @@ package com.tangem.tap.common import android.content.Intent import android.nfc.NfcAdapter import android.nfc.Tag +import com.tangem.tap.common.extensions.removePrefixOrNull import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.walletconnect.WalletConnectManager @@ -14,14 +15,20 @@ import timber.log.Timber class IntentHandler { - private val TRANSACTION_ID_PARAM = "transactionId" - private val CURRENCY_CODE_PARAM = "baseCurrencyCode" - private val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount" - private val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress" - fun handleWalletConnectLink(intent: Intent?) { - if (intent?.scheme == WalletConnectManager.WC_SCHEME) { - store.dispatch(WalletConnectAction.HandleDeepLink(intent.data?.toString())) + val wcUri = when (intent?.scheme) { + WalletConnectManager.WC_SCHEME -> { + intent.data?.toString() + } + TANGEM_SCHEME -> { + intent.data?.toString()?.removePrefixOrNull(TANGEM_WC_PREFIX) + } + else -> { + null + } + } + if (wcUri != null) { + store.dispatch(WalletConnectAction.HandleDeepLink(wcUri)) } } @@ -54,16 +61,25 @@ class IntentHandler { Timber.d("MoonPay Sell: $amount $currency to $destinationAddress") - store.dispatch(WalletAction.TradeCryptoAction.SendCrypto( - currencyId = currency, - amount = amount, - destinationAddress = destinationAddress, - transactionId = transactionID - )) + store.dispatch( + WalletAction.TradeCryptoAction.SendCrypto( + currencyId = currency, + amount = amount, + destinationAddress = destinationAddress, + transactionId = transactionID, + ), + ) } catch (exception: Exception) { Timber.d("Not MoonPay URL") } } - + companion object { + private const val TRANSACTION_ID_PARAM = "transactionId" + private const val CURRENCY_CODE_PARAM = "baseCurrencyCode" + private const val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount" + private const val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress" + private const val TANGEM_SCHEME = "tangem" + private const val TANGEM_WC_PREFIX = "tangem://wc?uri=" + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt index 301ae0cb07..45d55aac2a 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt @@ -25,7 +25,7 @@ class BasicSignInEventConverter( val cardCurrency = ParamCardCurrencyConverter().convert(scanResponse) ?: return null return Basic.SignedIn( - state = AnalyticsParam.CardBalanceState.from(value.walletsData), + state = AnalyticsParam.CardBalanceState.from(value.walletsDataFromStores), currency = cardCurrency, batch = scanResponse.card.batchId, ).apply { @@ -44,7 +44,7 @@ class BasicTopUpEventConverter( val data = BasicTopUpFilter.Data( walletId = scanResponse.card.userWalletId.stringValue, - cardBalanceState = AnalyticsParam.CardBalanceState.from(value.walletsData), + cardBalanceState = AnalyticsParam.CardBalanceState.from(value.walletsDataFromStores), ) return Basic.ToppedUp(cardCurrency).apply { filterData = data } @@ -61,16 +61,16 @@ private fun AnalyticsParam.CardBalanceState.Companion.from(walletsData: List acc + i } - if (balancesCount != state.wallets.size) return false + if (balancesCount != state.walletsStores.size) return false return true } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/String.kt b/app/src/main/java/com/tangem/tap/common/extensions/String.kt index 7d54a7bf92..021cc3a7f2 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/String.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/String.kt @@ -62,4 +62,9 @@ fun String.toQrCode(): Bitmap { return bmp } -fun String.urlEncode(): String = Uri.encode(this) \ No newline at end of file +fun String.urlEncode(): String = Uri.encode(this) + +fun String.removePrefixOrNull(prefix: String): String? = when { + startsWith(prefix) -> substring(prefix.length) + else -> null +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt new file mode 100644 index 0000000000..e9aaabfd70 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt @@ -0,0 +1,27 @@ +package com.tangem.tap.common.redux + +import com.tangem.domain.common.ScanResponse +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.preferencesStorage +import com.tangem.tap.tangemSdkManager +import org.rekotlin.Middleware + +class AccessCodeRequestPolicyMiddleware { + val middleware: Middleware = { _, _ -> + { next -> + { action -> + if (action is GlobalAction.SaveScanResponse) { + updateAccessCodeRequestPolicy(action.scanResponse) + } + next(action) + } + } + } + + private fun updateAccessCodeRequestPolicy(scanResponse: ScanResponse) { + tangemSdkManager.setAccessCodeRequestPolicy( + useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && + scanResponse.card.isAccessCodeSet, + ) + } +} \ No newline at end of file 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 fad0770f65..9f7c34b90f 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 @@ -93,6 +93,7 @@ data class AppState( SaveWalletMiddleware().middleware, WalletSelectorMiddleware().middleware, LockUserWalletsTimerMiddleware().middleware, + AccessCodeRequestPolicyMiddleware().middleware, ) } } diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index d4c5620fb3..1f51374cae 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -64,7 +64,7 @@ sealed class GlobalAction : Action { object Increment : GlobalAction() } - data class SaveScanNoteResponse(val scanResponse: ScanResponse) : GlobalAction() + data class SaveScanResponse(val scanResponse: ScanResponse) : GlobalAction() data class SetIfCardVerifiedOnline(val verified: Boolean) : GlobalAction() 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 f4aa80ad03..543e734f8f 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 @@ -33,7 +33,7 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde is GlobalAction.ScanFailsCounter.Reset -> { globalState.copy(scanCardFailsCounter = 0) } - is GlobalAction.SaveScanNoteResponse -> { + is GlobalAction.SaveScanResponse -> { appStateHolder.scanResponse = action.scanResponse domainStore.dispatch(DomainGlobalAction.SaveScanNoteResponse(action.scanResponse)) globalState.copy(scanResponse = action.scanResponse) diff --git a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt index 271886dea1..808cb5bbbc 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt @@ -208,8 +208,8 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) { } companion object { - const val TANGEM_WALLET_2_CARDS_SKU = "TG115x2" - const val TANGEM_WALLET_3_CARDS_SKU = "TG115x3" + const val TANGEM_WALLET_2_CARDS_SKU = "TG115X2-S" + const val TANGEM_WALLET_3_CARDS_SKU = "TG115X3-S" val SKUS_TO_DISPLAY = listOf(TANGEM_WALLET_2_CARDS_SKU, TANGEM_WALLET_3_CARDS_SKU) } } diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 81ddf880c3..01350c6c63 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -46,6 +46,14 @@ import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) { + + private val userCodeRepository by lazy { + UserCodeRepository( + biometricManager = tangemSdk.biometricManager, + secureStorage = tangemSdk.secureStorage, + ) + } + val canUseBiometry: Boolean get() = tangemSdk.biometricManager.canAuthenticate || needEnrollBiometrics @@ -119,8 +127,8 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co } suspend fun saveAccessCode(accessCode: String, cardsIds: Set): CompletionResult { - return createUserCodeRepository().save( - cardIds = cardsIds, + return userCodeRepository.save( + cardsIds = cardsIds, userCode = UserCode( type = UserCodeType.AccessCode, stringValue = accessCode, @@ -128,8 +136,12 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co ) } + suspend fun deleteSavedUserCodes(cardsIds: Set): CompletionResult { + return userCodeRepository.delete(cardsIds.toSet()) + } + suspend fun clearSavedUserCodes(): CompletionResult { - return createUserCodeRepository().clear() + return userCodeRepository.clear() } suspend fun setPasscode(cardId: String?): CompletionResult { @@ -166,9 +178,10 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co suspend fun scanCard( cardId: String? = null, + allowRequestAccessCodeFromRepository: Boolean = false, ): CompletionResult { return runTaskAsyncReturnOnMain( - runnable = ScanTask(), + runnable = ScanTask(allowRequestAccessCodeFromRepository), cardId = cardId, initialMessage = Message(context.getString(R.string.initial_message_tap_header)), ) @@ -221,13 +234,6 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co } } - private fun createUserCodeRepository() = with(tangemSdk) { - UserCodeRepository( - biometricManager = biometricManager, - secureStorage = secureStorage, - ) - } - companion object { val config = Config( linkedTerminal = true, 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 7eeb396463..ea26d15848 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -33,6 +33,7 @@ import com.tangem.tap.features.wallet.models.toBlockchainNetworks import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager import com.tangem.tap.userTokensRepository import com.tangem.tap.walletStoresManager import kotlinx.coroutines.Dispatchers @@ -94,6 +95,7 @@ class TapWalletManager { val card = scanResponse.card val attestationFailed = card.attestation.status == Attestation.Status.Failed + tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse) store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(scanResponse) updateConfigManager(scanResponse) @@ -101,7 +103,7 @@ class TapWalletManager { store.dispatch(WalletAction.UserWalletChanged(userWallet)) store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse)) store.dispatch(WalletConnectAction.ResetState) - store.dispatch(GlobalAction.SaveScanNoteResponse(scanResponse)) + store.dispatch(GlobalAction.SaveScanResponse(scanResponse)) store.dispatch(WalletConnectAction.RestoreSessions(scanResponse)) store.dispatch(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed)) store.dispatch(WalletAction.Warnings.CheckIfNeeded) @@ -149,7 +151,7 @@ class TapWalletManager { withMainContext { store.dispatch(WalletAction.ResetState(data.card)) store.dispatch(WalletConnectAction.ResetState) - store.dispatch(GlobalAction.SaveScanNoteResponse(data)) + store.dispatch(GlobalAction.SaveScanResponse(data)) store.dispatch(WalletAction.SetIfTestnetCard(data.card.isTestCard)) store.dispatch(WalletAction.MultiWallet.SetIsMultiwalletAllowed(data.card.isMultiwalletAllowed)) store.dispatch(WalletConnectAction.RestoreSessions(data)) diff --git a/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt b/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt index ac3ac6d56a..dd9a0b27a1 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt @@ -11,10 +11,6 @@ sealed class TotalFiatBalance { object Loading : TotalFiatBalance() - data class Refreshing( - override val amount: BigDecimal, - ) : TotalFiatBalance() - data class Error( override val amount: BigDecimal, ) : TotalFiatBalance() diff --git a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt index 8c3403ede9..4dec0df7d1 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt @@ -6,22 +6,23 @@ import com.tangem.domain.common.util.UserWalletId /** * Represents user's wallet which stored in app persistence - * @param name User's wallet name - * @param walletId User's wallet [UserWalletId] + * @param name User wallet name + * @param walletId User wallet [UserWalletId] * @param artworkUrl User wallet card artwork URL * @param cardsInWallet List of cards IDs assigned with this user's wallet + * @param isMultiCurrency Indicates whether this user wallet can work with more than one currency * @param scanResponse [ScanResponse] of primary user's wallet card. * TODO: Replace with [com.tangem.domain.common.CardDTO] * @property cardId ID of user's wallet primary card * @property hasAccessCode Indicates if the user's wallet primary card has access code * @property isLocked Indicates if this primary card has no currency wallets - * @property isSaved Indicates if this user wallet is saved * */ data class UserWallet( val name: String, val walletId: UserWalletId, val artworkUrl: String, val cardsInWallet: Set, + val isMultiCurrency: Boolean, val scanResponse: ScanResponse, ) { val cardId: String @@ -32,17 +33,18 @@ data class UserWallet( val isLocked: Boolean get() = scanResponse.card.wallets.isEmpty() - - internal var isSaved: Boolean = true } /** * !!! Workaround !!! * * Calculate same [UserWalletId] for twins instead + * + * TODO: Remove after [REDACTED_JIRA] * */ fun UserWallet.isTwinnedWith(other: UserWallet): Boolean { - if (!scanResponse.isTangemTwins()) return false + if (!scanResponse.isTangemTwins() || !other.scanResponse.isTangemTwins()) return false + if (scanResponse.secondTwinPublicKey == null || other.scanResponse.secondTwinPublicKey == null) return false if (other.scanResponse.secondTwinPublicKey == scanResponse.card.wallets.firstOrNull()?.publicKey?.toHexString()) { return true } diff --git a/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt b/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt index 439b900c39..4462f4d8ec 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt @@ -35,12 +35,6 @@ data class WalletDataModel( open val pendingTransactions: List = emptyList() open val errorMessage: String? = null open val isErrorStatus: Boolean = false - - fun asRefreshing() = Refreshing( - amount = amount, - pendingTransactions = pendingTransactions, - errorMessage = errorMessage, - ) } object Loading : Status() @@ -74,12 +68,4 @@ data class WalletDataModel( object MissedDerivation : Status() { override val isErrorStatus: Boolean = true } - - data class Refreshing( - override val amount: BigDecimal, - override val pendingTransactions: List, - override val errorMessage: String?, - ) : Status() { - override val isErrorStatus: Boolean = errorMessage != null - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt b/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt index cba89801a3..a0511784e2 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt @@ -1,6 +1,8 @@ package com.tangem.tap.domain.model +import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.WalletManager +import com.tangem.common.hdWallet.DerivationPath import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.model.WalletStoreModel.WalletRent import com.tangem.tap.domain.tokens.models.BlockchainNetwork @@ -8,21 +10,28 @@ import java.math.BigDecimal /** * Contains info about the blockchain and its currencies - * @param userWalletId ID of the [UserWallet] which uses that store - * @param blockchainNetwork Store's [BlockchainNetwork] - * @param walletManager Store's [WalletManager], may be null if it fails to create this manager. TODO: Remove after - * WalletMiddleware refactoring + * @param userWalletId ID of the associated [UserWallet] + * @param blockchain [Blockchain] of this WalletStore + * @param derivationPath [DerivationPath] of this store, null if the card does not support the + * [HD Wallet](https://coinsutra.com/hd-wallets-deterministic-wallet/) * @param walletsData List of [WalletDataModel] which represents store's blockchain currency and tokens currencies - * @param walletRent Store's [WalletRent], null if store has no rent or currency balance is greater then + * @param walletRent [WalletRent], null if store has no rent or currency balance is greater then * [WalletRent.exemptionAmount] + * @param blockchainNetwork [BlockchainNetwork]. + * TODO: Remove after WalletMiddleware refactoring + * @param walletManager [WalletManager], may be null if it fails to create this manager. + * TODO: Remove after WalletMiddleware refactoring * */ data class WalletStoreModel( val userWalletId: UserWalletId, + val blockchain: Blockchain, + val derivationPath: DerivationPath?, + val walletsData: List, + val walletRent: WalletRent?, + @Deprecated("Don't use it, will be removed") val blockchainNetwork: BlockchainNetwork, @Deprecated("Don't use it, will be removed") val walletManager: WalletManager?, - val walletsData: List, - val walletRent: WalletRent?, ) { /** @@ -35,4 +44,34 @@ data class WalletStoreModel( val rent: BigDecimal, val exemptionAmount: BigDecimal, ) + + // TODO: Remove the generated methods after blockchainNetwork and walletManager are removed from the model + // region Generated + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is WalletStoreModel) return false + + if (userWalletId != other.userWalletId) return false + if (blockchain != other.blockchain) return false + if (derivationPath != other.derivationPath) return false + if (walletsData != other.walletsData) return false + if (walletRent != other.walletRent) return false + + return true + } + + override fun hashCode(): Int { + var result = userWalletId.hashCode() + result = 31 * result + blockchain.hashCode() + result = 31 * result + (derivationPath?.hashCode() ?: 0) + result = 31 * result + walletsData.hashCode() + result = 31 * result + (walletRent?.hashCode() ?: 0) + return result + } + + override fun toString(): String { + return "WalletStoreModel(userWalletId=$userWalletId, blockchain=$blockchain, derivationPath=$derivationPath, " + + "walletsData=$walletsData, walletRent=$walletRent)" + } + // endregion Generated } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt index 377b8cf8c6..6e4d834d38 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt @@ -1,15 +1,48 @@ package com.tangem.tap.domain.model.builders +import com.tangem.common.extensions.toHexString +import com.tangem.common.services.Result +import com.tangem.domain.common.CardDTO +import com.tangem.domain.common.ProductType import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.TapWorkarounds.isStart2Coin +import com.tangem.domain.common.TwinCardNumber +import com.tangem.domain.common.TwinsHelper import com.tangem.domain.common.util.userWalletId -import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl +import com.tangem.operations.attestation.OnlineCardVerifier +import com.tangem.operations.attestation.TangemApi import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.features.wallet.redux.Artwork class UserWalletBuilder( private val scanResponse: ScanResponse, + private val onlineCardVerifier: OnlineCardVerifier = OnlineCardVerifier(), ) { private var backupCardsIds: Set = emptySet() + private val CardDTO.isBackupNotAllowed: Boolean + get() = !this.settings.isBackupAllowed + + private val ScanResponse.userWalletName: String + get() = when (productType) { + ProductType.Note -> "Note" + ProductType.Twins -> "Twin" + ProductType.SaltPay -> "SaltPay" + ProductType.Wallet -> when { + card.isBackupNotAllowed -> "Tangem card" + card.isStart2Coin -> "Start2Coin" + else -> "Wallet" + } + } + + private val ScanResponse.isMultiCurrency: Boolean + get() = when (productType) { + ProductType.Note -> false + ProductType.Twins -> false + ProductType.SaltPay -> false + ProductType.Wallet -> !card.isStart2Coin + } + fun backupCardsIds(backupCardsIds: Set?) = this.apply { if (backupCardsIds != null) { this.backupCardsIds = backupCardsIds @@ -20,11 +53,44 @@ class UserWalletBuilder( return with(scanResponse) { UserWallet( walletId = card.userWalletId, - name = productType.name, - artworkUrl = card.getOrLoadCardArtworkUrl(), + name = userWalletName, + artworkUrl = loadArtworkUrl(card.cardId, card.cardPublicKey), cardsInWallet = backupCardsIds.plus(card.cardId), scanResponse = this, + isMultiCurrency = isMultiCurrency, ) } } + + private suspend fun loadArtworkUrl(cardId: String, cardPublicKey: ByteArray): String { + return when (val result = onlineCardVerifier.getCardInfo(cardId, cardPublicKey)) { + is Result.Success -> { + val artworkId = result.data.artwork?.id + if (artworkId.isNullOrEmpty()) { + getFallbackArtworkUrl(cardId) + } else { + getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId) + } + } + + is Result.Failure -> getFallbackArtworkUrl(cardId) + } + } + + private fun getFallbackArtworkUrl(cardId: String): String { + return when { + cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL + cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL + else -> when (TwinsHelper.getTwinCardNumber(cardId)) { + TwinCardNumber.First -> Artwork.TWIN_CARD_1 + TwinCardNumber.Second -> Artwork.TWIN_CARD_2 + else -> Artwork.DEFAULT_IMG_URL + } + } + } + + private fun getUrlForArtwork(cardId: String, cardPublicKeyHex: String, artworkId: String): String { + return TangemApi.Companion.BaseUrl.VERIFY.url + TangemApi.ARTWORK + + "?artworkId=${artworkId}&CID=${cardId}&publicKey=$cardPublicKeyHex" + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt index f41c664276..e76a291668 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt @@ -4,6 +4,7 @@ import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.WalletManager +import com.tangem.common.hdWallet.DerivationPath import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.model.WalletDataModel import com.tangem.tap.domain.model.WalletStoreModel @@ -54,10 +55,12 @@ private class BlockchainNetworkWalletStoreBuilderImpl( return WalletStoreModel( userWalletId = userWalletId, - blockchainNetwork = blockchainNetwork, - walletManager = walletManager, + blockchain = blockchainNetwork.blockchain, + derivationPath = blockchainNetwork.derivationPath?.let { DerivationPath(it) }, walletsData = (listOf(blockchainWalletData) + tokensWalletsData), walletRent = null, + walletManager = walletManager, + blockchainNetwork = blockchainNetwork, ) } } @@ -74,10 +77,12 @@ private class WalletMangerWalletStoreBuilderImpl( return WalletStoreModel( userWalletId = userWalletId, - blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager), - walletManager = walletManager, + blockchain = wallet.blockchain, + derivationPath = wallet.publicKey.derivationPath, walletsData = (listOf(blockchainWalletData) + listOfNotNull(tokenWalletsData)), walletRent = null, + walletManager = walletManager, + blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager), ) } } diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt index 6ab45894b9..d940a338ad 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt @@ -82,6 +82,7 @@ object ScanCardProcessor { nextHandler = { scanResponse1 -> showDisclaimerIfNeed( scanResponse = scanResponse1, + onProgressStateChange = onProgressStateChange, nextHandler = { scanResponse2 -> onScanSuccess( scanResponse = scanResponse2, @@ -127,6 +128,7 @@ object ScanCardProcessor { private suspend inline fun showDisclaimerIfNeed( scanResponse: ScanResponse, + crossinline onProgressStateChange: suspend (showProgress: Boolean) -> Unit, crossinline nextHandler: suspend (ScanResponse) -> Unit, ) { val disclaimerType = DisclaimerType.get(scanResponse) @@ -137,11 +139,18 @@ object ScanCardProcessor { } else scope.launch(Dispatchers.Main) { delay(DELAY_SDK_DIALOG_CLOSE) store.dispatchOnMain( - DisclaimerAction.Show { - scope.launch(Dispatchers.Main) { - nextHandler(scanResponse) - } - }, + DisclaimerAction.Show( + onAcceptCallback = { + scope.launch(Dispatchers.Main) { + nextHandler(scanResponse) + } + }, + onDismissCallback = { + scope.launch(Dispatchers.Main) { + onProgressStateChange(false) + } + }, + ), ) } } diff --git a/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt b/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt index 512e986cbf..0c0f909c80 100644 --- a/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt +++ b/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt @@ -33,9 +33,6 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator { when (walletsData.findStatus()) { TotalFiatBalanceStatus.Loading -> TotalFiatBalance.Loading - TotalFiatBalanceStatus.Refreshing -> TotalFiatBalance.Refreshing( - amount = prevAmount ?: BigDecimal.ZERO, - ) TotalFiatBalanceStatus.Error -> TotalFiatBalance.Error(calculateAmount()) TotalFiatBalanceStatus.Loaded -> TotalFiatBalance.Loaded(calculateAmount()) } @@ -54,7 +51,6 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator { private fun Sequence.mapToStatus(): Sequence { return this.map { walletData -> when (walletData.status) { - is WalletDataModel.Refreshing -> TotalFiatBalanceStatus.Refreshing is WalletDataModel.VerifiedOnline, is WalletDataModel.SameCurrencyTransactionInProgress, is WalletDataModel.TransactionInProgress, @@ -84,18 +80,10 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator { ): TotalFiatBalanceStatus { return when (prevStatus) { TotalFiatBalanceStatus.Loading -> prevStatus - TotalFiatBalanceStatus.Refreshing -> when (newStatus) { - TotalFiatBalanceStatus.Loading -> prevStatus - TotalFiatBalanceStatus.Refreshing, - TotalFiatBalanceStatus.Error, - TotalFiatBalanceStatus.Loaded, - -> newStatus - } TotalFiatBalanceStatus.Loaded, TotalFiatBalanceStatus.Error, -> when (newStatus) { TotalFiatBalanceStatus.Loading, - TotalFiatBalanceStatus.Refreshing, TotalFiatBalanceStatus.Error, -> newStatus TotalFiatBalanceStatus.Loaded -> prevStatus @@ -105,7 +93,6 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator { private enum class TotalFiatBalanceStatus { Loading, - Refreshing, Error, Loaded, } diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt index ae9a72152a..28016d704f 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt @@ -10,6 +10,9 @@ import com.tangem.operations.wallet.CreateWalletTask import com.tangem.operations.wallet.PurgeWalletCommand class CreateFirstTwinWalletTask(private val firstCardId: String) : CardSessionRunnable { + + override val allowsRequestAccessCodeFromRepository: Boolean = false + override fun run( session: CardSession, callback: (result: CompletionResult) -> Unit, diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt index 57b66d7a62..628c041112 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt @@ -20,6 +20,8 @@ class CreateSecondTwinWalletTask( private val creatingWalletMessage: Message, ) : CardSessionRunnable { + override val allowsRequestAccessCodeFromRepository: Boolean = false + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { val card = session.environment.card val publicKey = card?.wallets?.firstOrNull()?.publicKey diff --git a/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt index 73b9e2f40a..d4e00cb3fe 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt @@ -13,6 +13,8 @@ class FinalizeTwinTask( private val twinPublicKey: ByteArray, private val issuerKeys: KeyPair, ) : CardSessionRunnable { + override val allowsRequestAccessCodeFromRepository: Boolean = false + override fun run( session: CardSession, callback: (result: CompletionResult) -> Unit, diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index 8310047019..b910fcb589 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -20,11 +20,9 @@ class TwinCardsManager( assetReader: AssetReader, ) { private val firstCardId: String = card.cardId - private var secondCardId: String? = null private var currentCardPublicKey: String? = null - var secondCardPublicKey: String? = null - private set + private var secondCardPublicKey: String? = null private val issuerKeyPair: KeyPair = getIssuerKeys(assetReader, card.issuer.publicKey.toHexString()) @@ -57,7 +55,6 @@ class TwinCardsManager( when (response) { is CompletionResult.Success -> { secondCardPublicKey = response.data.wallet.publicKey.toHexString() - secondCardId = response.data.cardId } is CompletionResult.Failure -> {} } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt index 62ea1a37a4..dfd62c05f5 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt @@ -14,24 +14,23 @@ interface UserWalletsListManager { val hasSavedUserWallets: Boolean suspend fun unlockWithBiometry(): CompletionResult - suspend fun unlockWithCard(userWallet: UserWallet): CompletionResult fun lock() - suspend fun selectWallet(walletId: UserWalletId): CompletionResult + suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult /** - * Save user's wallet - * @param userWallet User's wallet to save + * Save user wallet + * @param userWallet [UserWallet] to save * @param canOverride If false, then terminate with [UserWalletListError.WalletAlreadySaved] when user tries to save an * already saved card - * @return [CompletionResult] operation result + * @return [CompletionResult] of operation * */ suspend fun save(userWallet: UserWallet, canOverride: Boolean = false): CompletionResult - suspend fun delete(walletIds: List): CompletionResult + suspend fun delete(userWalletIds: List): CompletionResult suspend fun clear(): CompletionResult - suspend fun get(walletId: UserWalletId): CompletionResult + suspend fun get(userWalletId: UserWalletId): CompletionResult companion object } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index 248719dd20..f7781ca770 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -28,21 +28,19 @@ internal class BiometricUserWalletsListManager( override val userWallets: Flow> get() = state - .mapLatest { it.wallets } + .mapLatest { it.userWallets } .distinctUntilChanged() override val selectedUserWallet: Flow get() = state .mapLatest { state -> - state.wallets.find { - it.walletId == state.selectedWalletId - } + findSelectedUserWallet(state.userWallets) } .filterNotNull() .distinctUntilChanged() override val selectedUserWalletSync: UserWallet? - get() = findSelectedWallet() + get() = findSelectedUserWallet() override val isLocked: Flow get() = state @@ -53,44 +51,13 @@ internal class BiometricUserWalletsListManager( get() = state.value.isLocked override val hasSavedUserWallets: Boolean - get() = selectedUserWalletRepository.get() != null + get() = publicInformationRepository.isNotEmpty() override suspend fun unlockWithBiometry(): CompletionResult { return unlockWithBiometryInternal() .map { selectedUserWalletSync } } - override suspend fun unlockWithCard(userWallet: UserWallet): CompletionResult { - state.update { prevState -> - // If the previous state contains a saved user wallet with the same ID, it is also saved - userWallet.isSaved = prevState.wallets.any { - it.walletId == userWallet.walletId && it.isSaved - } - - val newEncryptionKeys = prevState.encryptionKeys - .plus(UserWalletEncryptionKey(userWallet)) - .distinctBy { it.walletId } - val newUserWallets = prevState.wallets - .plus(userWallet) - .distinctBy { it.walletId } - - prevState.copy( - encryptionKeys = newEncryptionKeys, - wallets = newUserWallets, - selectedWalletId = userWallet.walletId, - ) - } - - return loadModels() - .map { - state.update { prevState -> - prevState.copy( - isLocked = prevState.wallets.any { it.isLocked }, - ) - } - } - } - override fun lock() { state.update { prevState -> prevState.copy( @@ -100,85 +67,62 @@ internal class BiometricUserWalletsListManager( } } - override suspend fun selectWallet(walletId: UserWalletId): CompletionResult = catching { - if (state.value.selectedWalletId == walletId) { - return@catching findSelectedWallet()!! + override suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult = catching { + if (state.value.selectedUserWalletId == userWalletId) { + return@catching findSelectedUserWallet()!! } - if (!state.value.isLocked) { - selectedUserWalletRepository.set(walletId) + selectedUserWalletRepository.set(userWalletId) - state.update { prevState -> - prevState.copy( - selectedWalletId = walletId, - ) - } + state.update { prevState -> + prevState.copy( + selectedUserWalletId = userWalletId, + ) } - findSelectedWallet()!! + findSelectedUserWallet()!! } - override suspend fun save( - userWallet: UserWallet, - canOverride: Boolean, - ): CompletionResult = withUnlock { - val isWalletSaved = state.value.wallets - .filter { it.isSaved } - .any { - // Workaround, check [UserWallet.isTwinnedWith] - it.cardsInWallet.contains(userWallet.cardId) || it.isTwinnedWith(userWallet) - } - - if (isWalletSaved && !canOverride) { - CompletionResult.Failure(UserWalletListError.WalletAlreadySaved) + override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult { + return if (canOverride) { + saveInternal(userWallet) } else { - val newEncryptionKeys = state.value.encryptionKeys - .plus(UserWalletEncryptionKey(userWallet)) - .distinctBy { it.walletId } + val isWalletSaved = state.value.userWallets + .any { + // Workaround, check [UserWallet.isTwinnedWith] + it.cardsInWallet.contains(userWallet.cardId) || it.isTwinnedWith(userWallet) + } - keysRepository.store(newEncryptionKeys) - .doOnSuccess { - state.update { prevState -> - prevState.copy( - encryptionKeys = newEncryptionKeys, - ) - } - } - .flatMap { publicInformationRepository.save(userWallet) } - .flatMap { sensitiveInformationRepository.save(userWallet) } - .flatMap { loadModels() } - .doOnSuccess { - userWallet.isSaved = true - } + if (isWalletSaved) { + CompletionResult.Failure(UserWalletListError.WalletAlreadySaved) + } else { + saveInternal(userWallet) + } } } - override suspend fun delete(walletIds: List): CompletionResult { - val walletIdsToRemove = state.value.wallets - .map { it.walletId } - .filter { it in walletIds } - val remainingEncryptionKeys = state.value.encryptionKeys - .filter { it.walletId !in walletIdsToRemove } + override suspend fun delete(userWalletIds: List): CompletionResult { + if (userWalletIds.isEmpty()) { + return CompletionResult.Success(Unit) + } - changeSelectedWalletIfNeeded(walletIdsToRemove) + changeSelectedUserWalletIdIfNeeded(userWalletIds) - return sensitiveInformationRepository.delete(walletIdsToRemove) - .flatMap { publicInformationRepository.delete(walletIdsToRemove) } - .flatMap { keysRepository.store(remainingEncryptionKeys) } + return sensitiveInformationRepository.delete(userWalletIds) + .flatMap { publicInformationRepository.delete(userWalletIds) } + .flatMap { keysRepository.delete(userWalletIds) } .map { state.update { prevState -> prevState.copy( - encryptionKeys = remainingEncryptionKeys, - wallets = prevState.wallets.filter { it.walletId !in walletIdsToRemove }, + encryptionKeys = prevState.encryptionKeys.filter { it.walletId !in userWalletIds }, + userWallets = prevState.userWallets.filter { it.walletId !in userWalletIds }, ) } } } override suspend fun clear(): CompletionResult { - return sensitiveInformationRepository.delete( - walletIds = state.value.wallets.map { it.walletId }, - ) + return sensitiveInformationRepository.clear() .flatMap { publicInformationRepository.clear() } .flatMap { keysRepository.clear() } .map { @@ -187,17 +131,37 @@ internal class BiometricUserWalletsListManager( } } - override suspend fun get(walletId: UserWalletId): CompletionResult { + override suspend fun get(userWalletId: UserWalletId): CompletionResult { return catching { - state.value.wallets.first { it.walletId == walletId } + state.value.userWallets.first { it.walletId == userWalletId } } } - private suspend inline fun withUnlock( - block: () -> CompletionResult, - ): CompletionResult { - return (if (state.value.isLocked) unlockWithBiometryInternal() else CompletionResult.Success(Unit)) - .flatMap { block() } + private suspend fun saveInternal(userWallet: UserWallet): CompletionResult { + val newEncryptionKeys = state.value.encryptionKeys + .plus(UserWalletEncryptionKey(userWallet)) + .distinctBy { it.walletId } + + return keysRepository.store(newEncryptionKeys) + .doOnSuccess { + state.update { prevState -> + prevState.copy( + encryptionKeys = newEncryptionKeys, + selectedUserWalletId = userWallet.walletId, + ) + } + } + .flatMap { publicInformationRepository.save(userWallet) } + .flatMap { sensitiveInformationRepository.save(userWallet) } + .map { selectedUserWalletRepository.set(userWallet.walletId) } + .flatMap { loadModels() } + .doOnSuccess { + state.update { prevState -> + prevState.copy( + isLocked = prevState.userWallets.any { it.isLocked }, + ) + } + } } private suspend fun unlockWithBiometryInternal(): CompletionResult { @@ -223,11 +187,11 @@ internal class BiometricUserWalletsListManager( return getSavedUserWallets() .map { userWallets -> if (userWallets.isNotEmpty()) state.update { prevState -> - val wallets = (userWallets + prevState.wallets).distinctBy { it.walletId } + val wallets = (userWallets + prevState.userWallets).distinctBy { it.walletId } prevState.copy( - wallets = wallets, - selectedWalletId = findOrSetSelectedWallet(prevState.selectedWalletId, wallets), + userWallets = wallets, + selectedUserWalletId = findOrSetSelectedUserWalletId(prevState.selectedUserWalletId, wallets), ) } } @@ -247,56 +211,55 @@ internal class BiometricUserWalletsListManager( } } - private fun findOrSetSelectedWallet( + private fun findOrSetSelectedUserWalletId( prevSelectedWalletId: UserWalletId?, userWallets: List, ): UserWalletId? { - return prevSelectedWalletId - ?: (selectedUserWalletRepository.get() - ?: (userWallets.firstOrNull()?.walletId - ?.also { selectedUserWalletRepository.set(it) })) + val findUnlockedAndSet = { + userWallets.firstOrNull { !it.isLocked } + ?.walletId + ?.also { selectedUserWalletRepository.set(it) } + } + + return prevSelectedWalletId ?: (selectedUserWalletRepository.get() ?: findUnlockedAndSet()) } - private fun changeSelectedWalletIfNeeded( - walletsIdsToRemove: List, - ) { - val remainingWallets = state.value.wallets.filter { + private fun changeSelectedUserWalletIdIfNeeded(walletsIdsToRemove: List) { + val remainingWallets = state.value.userWallets.filter { it.walletId !in walletsIdsToRemove } - val selectedWallet = findSelectedWallet() + val selectedWallet = findSelectedUserWallet() when { remainingWallets.isEmpty() -> { state.update { prevState -> prevState.copy( - selectedWalletId = null, + selectedUserWalletId = null, ) } selectedUserWalletRepository.set(null) } !remainingWallets.contains(selectedWallet) -> { - val newSelectedWallet = remainingWallets.first() + val newSelectedWallet = remainingWallets.firstOrNull { !it.isLocked } state.update { prevState -> prevState.copy( - selectedWalletId = newSelectedWallet.walletId, + selectedUserWalletId = newSelectedWallet?.walletId, ) } - selectedUserWalletRepository.set(newSelectedWallet.walletId) + selectedUserWalletRepository.set(newSelectedWallet?.walletId) } } } - private fun findSelectedWallet(): UserWallet? { - return with(state.value) { - wallets.find { - it.walletId == selectedWalletId - } + private fun findSelectedUserWallet(userWallets: List = state.value.userWallets): UserWallet? { + return userWallets.find { + it.walletId == state.value.selectedUserWalletId } } private data class State( val encryptionKeys: List = emptyList(), - val wallets: List = emptyList(), - val selectedWalletId: UserWalletId? = null, + val userWallets: List = emptyList(), + val selectedUserWalletId: UserWalletId? = null, val isLocked: Boolean = true, ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt index a3c3a3e929..e616c48758 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt @@ -26,15 +26,11 @@ class DummyUserWalletsListManager : UserWalletsListManager { return CompletionResult.Success(null) } - override suspend fun unlockWithCard(userWallet: UserWallet): CompletionResult { - return CompletionResult.Success(Unit) - } - override fun lock() { /* no-op */ } - override suspend fun selectWallet(walletId: UserWalletId): CompletionResult { + override suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult { return catching { error("Not implemented") } @@ -44,7 +40,7 @@ class DummyUserWalletsListManager : UserWalletsListManager { return CompletionResult.Success(Unit) } - override suspend fun delete(walletIds: List): CompletionResult { + override suspend fun delete(userWalletIds: List): CompletionResult { return CompletionResult.Success(Unit) } @@ -52,7 +48,7 @@ class DummyUserWalletsListManager : UserWalletsListManager { return CompletionResult.Success(Unit) } - override suspend fun get(walletId: UserWalletId): CompletionResult { + override suspend fun get(userWalletId: UserWalletId): CompletionResult { return catching { error("Not implemented") } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt index b2308343ff..d383e11e3f 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt @@ -17,4 +17,5 @@ internal data class UserWalletPublicInformation( val artworkUrl: String, val cardsInWallet: Set, val scanResponse: ScanResponse, + val isMultiCurrency: Boolean, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt index 974879ca31..f10e0beeb5 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt @@ -1,10 +1,35 @@ package com.tangem.tap.domain.userWalletList.repository import com.tangem.common.CompletionResult +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey internal interface UserWalletsKeysRepository { + /** + * Obtaining the encryption keys of all user wallets from the biometric vault. Biometric authentication required + * If that operation runs more than biometric cipher key expiration time then the user will not receive all + * encryption keys + * @return [CompletionResult] of operation with stored [UserWalletEncryptionKey] list + * */ suspend fun getAll(): CompletionResult> + + /** + * Store the encryption keys for user wallets. Biometric authentication not required + * @param encryptionKeys List of encryption keys for user wallets + * @return [CompletionResult] of operation + * */ suspend fun store(encryptionKeys: List): CompletionResult + + /** + * Delete encryption keys for user wallets. Biometric authentication not required + * @param userWalletsIds List of [UserWalletId] whose encryption keys will be deleted + * @return [CompletionResult] of operation + * */ + suspend fun delete(userWalletsIds: List): CompletionResult + + /** + * Clear all encryption keys for user wallets. Biometric authentication not required + * @return [CompletionResult] of operation + * */ suspend fun clear(): CompletionResult } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt index 8765a6b804..f0387d3f0e 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt @@ -12,4 +12,6 @@ internal interface UserWalletsPublicInformationRepository { suspend fun delete(walletIds: List): CompletionResult suspend fun clear(): CompletionResult + + fun isNotEmpty(): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt index dcc5677814..7e03055150 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt @@ -12,5 +12,6 @@ internal interface UserWalletsSensitiveInformationRepository { encryptionKeys: List, ): CompletionResult> - suspend fun delete(walletIds: List): CompletionResult + suspend fun delete(userWalletsIds: List): CompletionResult + suspend fun clear(): CompletionResult } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt index cd39605d96..5e57d10474 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt @@ -6,62 +6,183 @@ import com.squareup.moshi.Types import com.tangem.common.CompletionResult import com.tangem.common.biometric.BiometricManager import com.tangem.common.biometric.BiometricStorage +import com.tangem.common.core.TangemSdkError +import com.tangem.common.doOnFailure +import com.tangem.common.fold import com.tangem.common.map import com.tangem.common.mapFailure import com.tangem.common.services.secure.SecureStorage +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.userWalletList.UserWalletListError import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext internal class BiometricUserWalletsKeysRepository( moshi: Moshi, - secureStorage: SecureStorage, biometricManager: BiometricManager, + private val secureStorage: SecureStorage, ) : UserWalletsKeysRepository { private val biometricStorage = BiometricStorage( biometricManager = biometricManager, secureStorage = secureStorage, ) - private val walletsKeysAdapter: JsonAdapter> = moshi.adapter( - Types.newParameterizedType(List::class.java, UserWalletEncryptionKey::class.java), + private val encryptionKeyAdapter: JsonAdapter = moshi.adapter( + UserWalletEncryptionKey::class.java, + ) + private val userWalletsIdsListAdapter: JsonAdapter> = moshi.adapter( + Types.newParameterizedType(List::class.java, UserWalletId::class.java), ) override suspend fun getAll(): CompletionResult> { - return biometricStorage.get(key = StorageKey.WalletEncryptionKeys.name) - .map { encryptionKeys -> - encryptionKeys.decodeToKeys() - } - .mapFailure { error -> - UserWalletListError.ReceiveEncryptionKeysError(error.cause ?: error) - } + return withContext(Dispatchers.IO) { + getAllInternal() + .mapFailure { error -> + UserWalletListError.ReceiveEncryptionKeysError(error.cause ?: error) + } + } } override suspend fun store(encryptionKeys: List): CompletionResult { - return biometricStorage.store( - key = StorageKey.WalletEncryptionKeys.name, - data = encryptionKeys.encode(), - ) - .mapFailure { error -> - UserWalletListError.SaveEncryptionKeysError(error.cause ?: error) + return withContext(Dispatchers.IO) { + encryptionKeys.map { storeEncryptionKey(it) } + .fold() + .mapFailure { error -> + UserWalletListError.SaveEncryptionKeysError(error.cause ?: error) + } + } + } + + override suspend fun delete(userWalletsIds: List): CompletionResult { + return withContext(Dispatchers.IO) { + userWalletsIds.map { userWalletId -> + deleteEncryptionKey(userWalletId) } + .fold() + .map { deleteUserWalletsIds(userWalletsIds) } + } } override suspend fun clear(): CompletionResult { - return biometricStorage.delete(key = StorageKey.WalletEncryptionKeys.name) + return withContext(Dispatchers.IO) { + getUserWalletsIds() + .map { userWalletId -> + deleteEncryptionKey(userWalletId) + } + .fold() + .map { + clearUserWalletsIds() + } + } } - private fun List.encode(): ByteArray { - return this.let(walletsKeysAdapter::toJson) - .encodeToByteArray(throwOnInvalidSequence = true) + private suspend fun getAllInternal(): CompletionResult> { + return getUserWalletsIds() + .map { userWalletId -> + // This is possible because the Card SDK cipher key has an expiration time + // If this operation runs more than that expiration time, the user will not receive all encryption keys + getEncryptionKey(userWalletId) + .doOnFailure { error -> + // If the user cancels biometric authentication, cancel the request for all keys + if (error is TangemSdkError.BiometricsAuthenticationFailed) { + return CompletionResult.Failure(error) + } + } + } + .fold(listOf()) { acc, data -> + if (data != null) acc + data else acc + } } - private fun ByteArray?.decodeToKeys(): List { - return this?.decodeToString(throwOnInvalidSequence = true) - ?.let(walletsKeysAdapter::fromJson) - .orEmpty() + private suspend fun getEncryptionKey(userWalletId: UserWalletId): CompletionResult { + return biometricStorage.get(StorageKey.WalletEncryptionKey(userWalletId).name) + .map { it.decodeToKey() } } - private enum class StorageKey { - WalletEncryptionKeys + private suspend fun storeEncryptionKey(encryptionKey: UserWalletEncryptionKey): CompletionResult { + return biometricStorage.store( + key = StorageKey.WalletEncryptionKey(encryptionKey.walletId).name, + data = encryptionKey.encode(), + ) + .map { storeUserWalletId(encryptionKey.walletId) } + } + + private suspend fun deleteEncryptionKey(userWalletId: UserWalletId): CompletionResult { + return biometricStorage.delete(StorageKey.WalletEncryptionKey(userWalletId).name) + } + + private suspend fun getUserWalletsIds(): List { + return withContext(Dispatchers.IO) { + secureStorage.get(StorageKey.UserWalletIds.name) + .decodeToUserWalletsIds() + } + } + + private suspend fun storeUserWalletId(userWalletId: UserWalletId) { + val userWalletIds = (getUserWalletsIds() + userWalletId).distinct() + + withContext(Dispatchers.IO) { + secureStorage.store(userWalletIds.encode(), StorageKey.UserWalletIds.name) + } + } + + private suspend fun deleteUserWalletsIds(userWalletsIds: List) { + val remainingIds = (getUserWalletsIds() - userWalletsIds.toSet()) + + withContext(Dispatchers.IO) { + secureStorage.store(remainingIds.encode(), StorageKey.UserWalletIds.name) + } + } + + private suspend fun clearUserWalletsIds() { + withContext(Dispatchers.IO) { + secureStorage.delete(StorageKey.UserWalletIds.name) + } + } + + private suspend fun UserWalletEncryptionKey.encode(): ByteArray { + return withContext(Dispatchers.Default) { + this@encode + .let(encryptionKeyAdapter::toJson) + .encodeToByteArray(throwOnInvalidSequence = true) + } + } + + private suspend fun ByteArray?.decodeToKey(): UserWalletEncryptionKey? { + return withContext(Dispatchers.Default) { + this@decodeToKey + ?.decodeToString(throwOnInvalidSequence = true) + ?.let(encryptionKeyAdapter::fromJson) + } + } + + private suspend fun List.encode(): ByteArray { + return withContext(Dispatchers.Default) { + this@encode + .let(userWalletsIdsListAdapter::toJson) + .encodeToByteArray(throwOnInvalidSequence = true) + } + } + + private suspend fun ByteArray?.decodeToUserWalletsIds(): List { + return withContext(Dispatchers.Default) { + this@decodeToUserWalletsIds + ?.decodeToString(throwOnInvalidSequence = true) + ?.let(userWalletsIdsListAdapter::fromJson) + .orEmpty() + } + } + + private sealed interface StorageKey { + val name: String + + class WalletEncryptionKey(userWalletId: UserWalletId) : StorageKey { + override val name: String = "user_wallet_encryption_key_${userWalletId.stringValue}" + } + + object UserWalletIds : StorageKey { + override val name: String = "user_wallets_ids_with_saved_keys" + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt index 46d0fc073b..46a016f8c0 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt @@ -25,19 +25,21 @@ internal class DefaultUserWalletsPublicInformationRepository( ) override suspend fun save(userWallet: UserWallet): CompletionResult { - return getAll() - .flatMap { savedInformation -> - val infoToSave = withContext(Dispatchers.Default) { - savedInformation.toMutableList() - .apply { - replaceByOrAdd(userWallet.publicInformation) { - userWallet.walletId == it.walletId + return withContext(Dispatchers.IO) { + getAll() + .flatMap { savedInformation -> + val infoToSave = withContext(Dispatchers.Default) { + savedInformation.toMutableList() + .apply { + replaceByOrAdd(userWallet.publicInformation) { + userWallet.walletId == it.walletId + } } - } - } + } - save(infoToSave) - } + save(infoToSave) + } + } } override suspend fun getAll(): CompletionResult> = catching { @@ -50,20 +52,28 @@ internal class DefaultUserWalletsPublicInformationRepository( } override suspend fun delete(walletIds: List): CompletionResult { - return getAll() - .flatMap { publicInformation -> - val infoToRemove = publicInformation - .filter { it.walletId in walletIds } - .toSet() + return withContext(Dispatchers.IO) { + getAll() + .flatMap { publicInformation -> + val infoToRemove = publicInformation + .filter { it.walletId in walletIds } + .toSet() - save( - publicInformation = publicInformation - infoToRemove, - ) - } + save( + publicInformation = publicInformation - infoToRemove, + ) + } + } } override suspend fun clear(): CompletionResult = catching { - secureStorage.delete(StorageKey.UserWalletPublicInformation.name) + withContext(Dispatchers.IO) { + secureStorage.delete(StorageKey.UserWalletPublicInformation.name) + } + } + + override fun isNotEmpty(): Boolean { + return secureStorage.get(StorageKey.UserWalletPublicInformation.name)?.isNotEmpty() == true } @JvmName("saveWithPublicInformation") diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt index c338d17c67..5918312b6e 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt @@ -3,12 +3,14 @@ package com.tangem.tap.domain.userWalletList.repository.implementation import android.security.keystore.KeyProperties import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi +import com.squareup.moshi.Types import com.tangem.common.CompletionResult import com.tangem.common.catching import com.tangem.common.mapFailure import com.tangem.common.services.secure.SecureStorage import com.tangem.domain.common.util.UserWalletId import com.tangem.domain.common.util.encryptionKey +import com.tangem.tap.common.extensions.filterNotNull import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.userWalletList.UserWalletListError import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey @@ -28,29 +30,23 @@ internal class DefaultUserWalletsSensitiveInformationRepository( private val sensitiveInformationAdapter: JsonAdapter = moshi.adapter( UserWalletSensitiveInformation::class.java, ) + private val encryptedSensitiveInformationMapAdapter: JsonAdapter> = moshi.adapter( + Types.newParameterizedType(Map::class.java, String::class.java, ByteArray::class.java), + ) + private val cipher: Cipher by lazy { Cipher.getInstance("$algorithm/$blockMode/$encryptionPadding") } override suspend fun save(userWallet: UserWallet): CompletionResult { return catching { - withContext(Dispatchers.Default) { - userWallet.sensitiveInformation - .let(sensitiveInformationAdapter::toJson) - .encodeToByteArray(throwOnInvalidSequence = true) - .encryptAndStoreIv( - walletId = userWallet.walletId, - encryptionKey = userWallet.scanResponse.card.encryptionKey, - ) - .let { encryptedInformation -> - withContext(Dispatchers.IO) { - secureStorage.store( - data = encryptedInformation, - account = StorageKey.SensitiveInformation(userWallet.walletId).name, - ) - } - } - } + val encryptedSensitiveInformation = userWallet.sensitiveInformation + .encode() + .encryptAndStoreIv(userWallet.walletId.stringValue, userWallet.scanResponse.card.encryptionKey) + + getAllEncrypted().toMutableMap() + .apply { set(userWallet.walletId.stringValue, encryptedSensitiveInformation) } + .let { saveInternal(it) } } .mapFailure { error -> UserWalletListError.SaveSensitiveInformationError(error.cause ?: error) @@ -60,77 +56,130 @@ internal class DefaultUserWalletsSensitiveInformationRepository( override suspend fun getAll( encryptionKeys: List, ): CompletionResult> { + if (encryptionKeys.isEmpty()) { + return CompletionResult.Success(emptyMap()) + } + return catching { - if (encryptionKeys.isEmpty()) { - return@catching emptyMap() - } + val encryptedSensitiveInformation = getAllEncrypted() - val keyToEncryptedInformation = withContext(Dispatchers.IO) { - encryptionKeys.associateWith { encryptionKey -> - secureStorage.get(StorageKey.SensitiveInformation(encryptionKey.walletId).name) + encryptionKeys + .associateBy { it.walletId } + .mapValues { (userWalletId, encryptionKey) -> + encryptedSensitiveInformation[userWalletId.stringValue] + ?.getIvAndDecrypt(userWalletId.stringValue, encryptionKey.encryptionKey) + .decodeToSensitiveInformation() } - } - - withContext(Dispatchers.Default) { - val keyToInformation = - mutableMapOf() - - keyToEncryptedInformation.forEach { (key, encryptedInformation) -> - val information = encryptedInformation - ?.getIvAndDecrypt( - walletId = key.walletId, - encryptionKey = key.encryptionKey, - ) - ?.decodeToString(throwOnInvalidSequence = true) - ?.let(sensitiveInformationAdapter::fromJson) - - if (information != null) { - keyToInformation[key.walletId] = information - } - } - - keyToInformation - } + .filterNotNull() } .mapFailure { error -> UserWalletListError.ReceiveSensitiveInformationError(error.cause ?: error) } } - override suspend fun delete(walletIds: List): CompletionResult = catching { - walletIds - .forEach { walletId -> - secureStorage.delete(StorageKey.SensitiveInformation(walletId).name) - } + override suspend fun delete(userWalletsIds: List): CompletionResult { + return catching { deleteInternal(userWalletsIds) } } - private fun ByteArray.encryptAndStoreIv(walletId: UserWalletId, encryptionKey: ByteArray): ByteArray { - val secretKey = SecretKeySpec(encryptionKey, algorithm) - cipher.init(Cipher.ENCRYPT_MODE, secretKey) - val encryptedData = cipher.doFinal(this) - secureStorage.store(data = cipher.iv, account = StorageKey.SensitiveInformationIv(walletId).name) - return encryptedData + override suspend fun clear(): CompletionResult { + return catching { clearInternal() } } - private fun ByteArray.getIvAndDecrypt(walletId: UserWalletId, encryptionKey: ByteArray): ByteArray? { - val iv = secureStorage.get(StorageKey.SensitiveInformationIv(walletId).name) - ?: error("IV not found") - val ivParam = IvParameterSpec(iv) - val secretKeySpec = SecretKeySpec(encryptionKey, algorithm) - return cipher - .also { it.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParam) } - .doFinal(this) + private suspend fun getAllEncrypted(): Map { + return withContext(Dispatchers.IO) { + secureStorage.get(StorageKey.UserWalletsSensitiveInformation.name) + .decodeToEncryptedSensitiveInformation() + } + } + + private suspend fun saveInternal(sensitiveInformation: Map) { + return withContext(Dispatchers.IO) { + secureStorage.store( + account = StorageKey.UserWalletsSensitiveInformation.name, + data = sensitiveInformation.encode(), + ) + } + } + + private suspend fun deleteInternal(userWalletsIds: List) { + return saveInternal( + sensitiveInformation = getAllEncrypted() - userWalletsIds.map { it.stringValue }.toSet(), + ) + } + + private suspend fun clearInternal() { + withContext(Dispatchers.IO) { + secureStorage.delete(StorageKey.UserWalletsSensitiveInformation.name) + } + } + + private suspend fun ByteArray?.decodeToEncryptedSensitiveInformation(): Map { + return withContext(Dispatchers.Default) { + this@decodeToEncryptedSensitiveInformation + ?.decodeToString(throwOnInvalidSequence = true) + ?.let(encryptedSensitiveInformationMapAdapter::fromJson) + .orEmpty() + } + } + + private suspend fun ByteArray?.decodeToSensitiveInformation(): UserWalletSensitiveInformation? { + return withContext(Dispatchers.Default) { + this@decodeToSensitiveInformation + ?.decodeToString(throwOnInvalidSequence = true) + ?.let(sensitiveInformationAdapter::fromJson) + } + } + + private suspend fun UserWalletSensitiveInformation.encode(): ByteArray { + return withContext(Dispatchers.Default) { + this@encode + .let(sensitiveInformationAdapter::toJson) + .encodeToByteArray(throwOnInvalidSequence = true) + } + } + + private suspend fun Map.encode(): ByteArray { + return withContext(Dispatchers.Default) { + this@encode + .let(encryptedSensitiveInformationMapAdapter::toJson) + .encodeToByteArray(throwOnInvalidSequence = true) + } + } + + private suspend fun ByteArray.encryptAndStoreIv(userWalletId: String, encryptionKey: ByteArray): ByteArray { + return withContext(Dispatchers.Default) { + val secretKey = SecretKeySpec(encryptionKey, algorithm) + cipher.init(Cipher.ENCRYPT_MODE, secretKey) + val encryptedData = cipher.doFinal(this@encryptAndStoreIv) + secureStorage.store(data = cipher.iv, account = StorageKey.SensitiveInformationIv(userWalletId).name) + encryptedData + } + } + + private suspend fun ByteArray.getIvAndDecrypt( + userWalletId: String, + encryptionKey: ByteArray, + ): ByteArray? { + return withContext(Dispatchers.Default) { + val iv = secureStorage.get(StorageKey.SensitiveInformationIv(userWalletId).name) + ?: error("IV not found") + val ivParam = IvParameterSpec(iv) + val secretKeySpec = SecretKeySpec(encryptionKey, algorithm) + cipher + .also { it.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParam) } + .doFinal(this@getIvAndDecrypt) + } } private sealed interface StorageKey { val name: String - class SensitiveInformation(walletId: UserWalletId) : StorageKey { - override val name: String = "user_wallet_sensitive_information_${walletId.stringValue}" + object UserWalletsSensitiveInformation : StorageKey { + override val name: String = "user_wallets_sensitive_information" } - class SensitiveInformationIv(walletId: UserWalletId) : StorageKey { - override val name: String = "user_wallet_sensitive_information_iv_${walletId.stringValue}" + class SensitiveInformationIv(userWalletId: String) : StorageKey { + override val name: String = "user_wallet_sensitive_information_iv_${userWalletId}" } } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt index 810ba463f6..7d8170895c 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt @@ -14,6 +14,7 @@ internal val UserWallet.publicInformation: UserWalletPublicInformation walletId = walletId, artworkUrl = artworkUrl, cardsInWallet = cardsInWallet, + isMultiCurrency = isMultiCurrency, scanResponse = scanResponse.copy( card = scanResponse.card.copy( wallets = emptyList(), @@ -28,6 +29,7 @@ internal fun UserWalletPublicInformation.toUserWallet(): UserWallet { artworkUrl = artworkUrl, cardsInWallet = cardsInWallet, scanResponse = scanResponse, + isMultiCurrency = isMultiCurrency, ) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt index f1115ee3ef..fb77a29584 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt @@ -2,13 +2,12 @@ package com.tangem.tap.domain.walletCurrencies import com.tangem.common.CompletionResult import com.tangem.tap.domain.model.UserWallet -import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency interface WalletCurrenciesManager { suspend fun update( userWallet: UserWallet, - blockchainNetwork: BlockchainNetwork, + currency: Currency, ): CompletionResult suspend fun addCurrencies( diff --git a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt index 837ff8452c..5fd2e25234 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt @@ -1,25 +1,25 @@ package com.tangem.tap.domain.walletCurrencies.implementation +import com.tangem.blockchain.common.DerivationStyle import com.tangem.common.CompletionResult -import com.tangem.common.catching import com.tangem.common.flatMap +import com.tangem.common.fold +import com.tangem.common.map import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.derivationStyle +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.builders.WalletStoreBuilder import com.tangem.tap.domain.tokens.UserTokensRepository import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.domain.walletCurrencies.WalletCurrenciesManager -import com.tangem.tap.domain.walletStores.implementation.utils.fold import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository import com.tangem.tap.features.wallet.models.Currency -import com.tangem.tap.features.wallet.models.getTokens -import com.tangem.tap.features.wallet.models.toCurrencies +import com.tangem.tap.features.wallet.models.toBlockchainNetworks import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext internal class DefaultWalletCurrenciesManager( @@ -31,18 +31,18 @@ internal class DefaultWalletCurrenciesManager( ) : WalletCurrenciesManager { override suspend fun update( userWallet: UserWallet, - blockchainNetwork: BlockchainNetwork, - ): CompletionResult { - val walletStore = walletStoresRepository.get(userWallet.walletId).first() + currency: Currency, + ): CompletionResult = withContext(Dispatchers.Default) { + val walletStore = walletStoresRepository.getSync(userWallet.walletId) .find { - it.blockchainNetwork.blockchain == blockchainNetwork.blockchain - && it.blockchainNetwork.derivationPath == blockchainNetwork.derivationPath + it.blockchain == currency.blockchain + && it.derivationPath?.rawPath == currency.derivationPath } - return if (walletStore != null) { - walletAmountsRepository.update( - userWallet = userWallet, + if (walletStore != null) { + walletAmountsRepository.updateAmountsForWalletStore( walletStore = walletStore, + userWallet = userWallet, fiatCurrency = appCurrencyProvider(), ) } else CompletionResult.Success(Unit) @@ -52,25 +52,42 @@ internal class DefaultWalletCurrenciesManager( userWallet: UserWallet, currenciesToAdd: List, ): CompletionResult = withContext(Dispatchers.Default) { - var newBlockchainNetworks = listOf() - catching { - val card = userWallet.scanResponse.card - val savedCurrencies = withContext(Dispatchers.IO) { - userTokensRepository.getUserTokens(card) - } - newBlockchainNetworks = (savedCurrencies + currenciesToAdd) - .toBlockchainNetworks(userWallet.scanResponse.card) - val newCurrencies = newBlockchainNetworks.toCurrencies() + val card = userWallet.scanResponse.card + val updatedBlockchainNetworks = currenciesToAdd + .addMissingBlockchains(card) + .toBlockchainNetworks() + val newCurrencies = (getSavedCurrencies(userWallet.walletId) + currenciesToAdd) + .addMissingBlockchains(card) - withContext(Dispatchers.IO) { - userTokensRepository.saveUserTokens( - card = card, - tokens = newCurrencies, + updateWalletStores(userWallet, updatedBlockchainNetworks) + .map { + saveUserCurrencies(card, newCurrencies) + } + .flatMap { + walletAmountsRepository.updateAmountsForUserWallet( + userWallet = userWallet, + fiatCurrency = appCurrencyProvider(), ) } - } + } + + override suspend fun removeCurrencies( + userWallet: UserWallet, + currenciesToRemove: List, + ): CompletionResult = withContext(Dispatchers.Default) { + val card = userWallet.scanResponse.card + val remainingCurrencies = getSavedCurrencies(userWallet.walletId) + .filter { it !in currenciesToRemove } + val remainingBlockchains = remainingCurrencies + .filterIsInstance() + .map { it.blockchain } + + walletStoresRepository.deleteDifference(userWallet.walletId, remainingBlockchains) .flatMap { - newBlockchainNetworks.updateWalletStores(userWallet) + updateWalletStores(userWallet, remainingCurrencies.toBlockchainNetworks()) + } + .map { + saveUserCurrencies(card, remainingCurrencies) } } @@ -81,108 +98,69 @@ internal class DefaultWalletCurrenciesManager( return removeCurrencies(userWallet, listOf(currencyToRemove)) } - override suspend fun removeCurrencies( - userWallet: UserWallet, - currenciesToRemove: List, - ): CompletionResult = withContext(Dispatchers.Default) { - var remainingBlockchainsNetworks = emptyList() - catching { - val card = userWallet.scanResponse.card - val savedCurrencies = withContext(Dispatchers.IO) { - userTokensRepository.getUserTokens(card) - } - - val remainingCurrencies = arrayListOf() - savedCurrencies.forEach { savedCurrency -> - if (savedCurrency !in currenciesToRemove) { - remainingCurrencies.add(savedCurrency) + private suspend fun getSavedCurrencies(userWalletId: UserWalletId): List { + return withContext(Dispatchers.Default) { + walletStoresRepository.getSync(userWalletId) + .flatMap { walletStore -> + walletStore.walletsData.map { it.currency } } - } - - remainingBlockchainsNetworks = remainingCurrencies.toBlockchainNetworks(userWallet.scanResponse.card) - - withContext(Dispatchers.IO) { - userTokensRepository.saveUserTokens( - card = card, - tokens = remainingCurrencies, - ) - } } - .flatMap { - remainingBlockchainsNetworks.updateWalletStores(userWallet) - } } - private fun List.toBlockchainNetworks(card: CardDTO): List { - val blockchainNetworks = arrayListOf() - val findDerivationPath: (currency: Currency) -> String? = { currency -> - currency.derivationPath - ?: currency.blockchain.derivationPath(card.derivationStyle) - ?.rawPath + private suspend fun saveUserCurrencies(card: CardDTO, currencies: List) { + withContext(Dispatchers.IO) { + userTokensRepository.saveUserTokens( + card = card, + tokens = currencies, + ) } + } - for (currency in this.sortedByDescending { it.isBlockchain() }) { + private fun List.addMissingBlockchains(card: CardDTO): List { + val newCurrencies = arrayListOf() + + for (currency in this.sortedByDescending { it is Currency.Blockchain }) { when (currency) { is Currency.Blockchain -> { - val blockchainNetwork = BlockchainNetwork( - blockchain = currency.blockchain, - derivationPath = findDerivationPath(currency), - tokens = getTokens(currency), - ) - - blockchainNetworks.add(blockchainNetwork) + newCurrencies.add(currency.updateDerivationPath(card.derivationStyle)) } - is Currency.Token -> { - val tokenBlockchainNetworkIndex = blockchainNetworks - .indexOfFirst { - it.blockchain == currency.blockchain && - it.derivationPath == currency.derivationPath - } - if (tokenBlockchainNetworkIndex == -1) { - blockchainNetworks.add( - BlockchainNetwork( + is Currency.Token -> { + val containsTokenBlockchain = newCurrencies.any { + it.isBlockchain() && it.blockchain == currency.blockchain + } + + if (containsTokenBlockchain) { + newCurrencies.add(currency.updateDerivationPath(card.derivationStyle)) + } else { + val derivationPath = findDerivationPath(currency, card.derivationStyle) + newCurrencies.add( + Currency.Blockchain( blockchain = currency.blockchain, - derivationPath = findDerivationPath(currency), - tokens = listOf(currency.token), + derivationPath = derivationPath, ), ) - } else { - val tokenBlockchainNetwork = blockchainNetworks[tokenBlockchainNetworkIndex] - if (currency.token in tokenBlockchainNetwork.tokens) { - continue - } else { - blockchainNetworks.add( - tokenBlockchainNetworkIndex, - tokenBlockchainNetwork.copy( - tokens = tokenBlockchainNetwork.tokens + currency.token, - ), - ) - } + newCurrencies.add( + currency.copy(derivationPath = derivationPath), + ) } } } } - return blockchainNetworks + return newCurrencies } - private suspend fun List.updateWalletStores( + private suspend fun updateWalletStores( userWallet: UserWallet, + blockchainNetworks: List, ): CompletionResult { val userWalletId = userWallet.walletId - return this - .also { blockchainNetworks -> - walletStoresRepository.deleteDifference( - userWalletId = userWalletId, - currentBlockchains = blockchainNetworks.map { it.blockchain }, - ) - } + return blockchainNetworks .map { blockchainNetwork -> - walletManagersRepository.findOrMake( + walletManagersRepository.findOrMakeMultiCurrencyWalletManager( userWallet = userWallet, blockchainNetwork = blockchainNetwork, - refresh = true, ) .flatMap { walletManager -> walletStoresRepository.storeOrUpdate( @@ -194,11 +172,25 @@ internal class DefaultWalletCurrenciesManager( } } .fold() - .flatMap { - walletAmountsRepository.update( - userWallet = userWallet, - fiatCurrency = appCurrencyProvider(), - ) - } + } + + private fun Currency.updateDerivationPath(cardDerivationStyle: DerivationStyle?): Currency { + val findDerivationPath: () -> String? = { + findDerivationPath(this, cardDerivationStyle) + } + + return when (this) { + is Currency.Blockchain -> this.copy( + derivationPath = findDerivationPath(), + ) + + is Currency.Token -> this.copy( + derivationPath = findDerivationPath(), + ) + } + } + + private fun findDerivationPath(currency: Currency, cardDerivationStyle: DerivationStyle?): String? { + return currency.derivationPath ?: currency.blockchain.derivationPath(cardDerivationStyle)?.rawPath } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt index 346a6108e8..e834cf0541 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt @@ -10,7 +10,7 @@ interface WalletStoresManager { fun getAll(): Flow>> fun get(userWalletId: UserWalletId): Flow> - suspend fun delete(userWalletsIds: List): CompletionResult + suspend fun delete(userWalletsIds: List): CompletionResult suspend fun clear(): CompletionResult suspend fun fetch( @@ -23,5 +23,9 @@ interface WalletStoresManager { refresh: Boolean = false, ): CompletionResult + suspend fun updateAmounts( + userWallets: List, + ): CompletionResult + companion object } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt index 041ea91557..219d7f4af1 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt @@ -2,19 +2,19 @@ package com.tangem.tap.domain.walletStores.implementation import com.tangem.blockchain.common.WalletManager import com.tangem.common.CompletionResult +import com.tangem.common.doOnSuccess import com.tangem.common.flatMap import com.tangem.common.flatMapOnFailure +import com.tangem.common.fold import com.tangem.common.map import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.common.entities.FiatCurrency -import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.model.builders.WalletStoreBuilder import com.tangem.tap.domain.tokens.UserTokensRepository import com.tangem.tap.domain.walletStores.WalletStoresError import com.tangem.tap.domain.walletStores.WalletStoresManager -import com.tangem.tap.domain.walletStores.implementation.utils.fold import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository @@ -22,6 +22,7 @@ import com.tangem.tap.features.wallet.models.toBlockchainNetworks import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.update import kotlinx.coroutines.withContext @@ -40,12 +41,12 @@ internal class DefaultWalletStoresManager( override fun get(userWalletId: UserWalletId): Flow> { return walletStoresRepository.get(userWalletId) + .distinctUntilChanged() } - override suspend fun delete(userWalletsIds: List): CompletionResult { - val walletIds = userWalletsIds.map { UserWalletId(it) } - return walletStoresRepository.delete(walletIds) - .flatMap { walletManagersRepository.delete(walletIds) } + override suspend fun delete(userWalletsIds: List): CompletionResult { + return walletStoresRepository.delete(userWalletsIds) + .flatMap { walletManagersRepository.delete(userWalletsIds) } } override suspend fun clear(): CompletionResult { @@ -55,7 +56,7 @@ internal class DefaultWalletStoresManager( override suspend fun fetch( userWallets: List, refresh: Boolean, - ): CompletionResult { + ): CompletionResult = withContext(Dispatchers.Default) { val fiatCurrency = appCurrencyProvider.invoke() val isFiatCurrencyChanged = state.value.fiatCurrency != fiatCurrency @@ -65,18 +66,18 @@ internal class DefaultWalletStoresManager( ) } - return userWallets + userWallets .mapNotNull { userWallet -> val hasNotWalletStoresForUserWallet = !walletStoresRepository.contains(userWallet.walletId) if (refresh || hasNotWalletStoresForUserWallet || isFiatCurrencyChanged) { - fetchWalletsIfNeeded(userWallet, refresh) + fetchWalletsIfNeeded(userWallet) } else null } - .fold(initial = arrayListOf()) { acc, data -> + .fold(arrayListOf()) { acc, data -> acc.apply { add(data) } } .flatMap { - walletAmountsRepository.update(it, fiatCurrency) + walletAmountsRepository.updateAmountsForUserWallets(it, fiatCurrency) } } @@ -87,22 +88,29 @@ internal class DefaultWalletStoresManager( return fetch(listOf(userWallet), refresh) } - private suspend fun fetchWalletsIfNeeded( - userWallet: UserWallet, - refresh: Boolean, - ): CompletionResult { - return if (userWallet.scanResponse.card.isMultiwalletAllowed) { - fetchMultiWallets(userWallet, refresh) + override suspend fun updateAmounts(userWallets: List): CompletionResult { + val fiatCurrency = appCurrencyProvider.invoke() + + return walletAmountsRepository.updateAmountsForUserWallets(userWallets, fiatCurrency) + .doOnSuccess { + state.update { prevState -> + prevState.copy( + fiatCurrency = fiatCurrency, + ) + } + } + } + + private suspend fun fetchWalletsIfNeeded(userWallet: UserWallet): CompletionResult { + return if (userWallet.isMultiCurrency) { + fetchMultiWallets(userWallet) } else { - fetchSingleWallet(userWallet, refresh) + fetchSingleWallet(userWallet) } .map { userWallet } } - private suspend fun fetchMultiWallets( - userWallet: UserWallet, - refresh: Boolean, - ): CompletionResult { + private suspend fun fetchMultiWallets(userWallet: UserWallet): CompletionResult { val scanResponse = userWallet.scanResponse val userTokens = withContext(Dispatchers.IO) { userTokensRepository.getUserTokens(scanResponse.card) @@ -128,10 +136,9 @@ internal class DefaultWalletStoresManager( ) } - walletManagersRepository.findOrMake( + walletManagersRepository.findOrMakeMultiCurrencyWalletManager( userWallet = userWallet, blockchainNetwork = blockchainNetwork, - refresh = refresh, ) .flatMap { walletManager -> storeWalletStore(walletManager) @@ -149,13 +156,9 @@ internal class DefaultWalletStoresManager( } } - private suspend fun fetchSingleWallet( - userWallet: UserWallet, - refresh: Boolean, - ): CompletionResult { - return walletManagersRepository.findOrMake( + private suspend fun fetchSingleWallet(userWallet: UserWallet): CompletionResult { + return walletManagersRepository.findOrMakeSingleCurrencyWalletManager( userWallet = userWallet, - refresh = refresh, ) .flatMap { walletManager -> val userWalletId = userWallet.walletId diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt index 93c5c780c1..b1729a0b11 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt @@ -17,7 +17,7 @@ internal class DummyWalletStoresManager : WalletStoresManager { return emptyFlow() } - override suspend fun delete(userWalletsIds: List): CompletionResult { + override suspend fun delete(userWalletsIds: List): CompletionResult { return CompletionResult.Success(Unit) } @@ -32,4 +32,8 @@ internal class DummyWalletStoresManager : WalletStoresManager { override suspend fun fetch(userWallets: List, refresh: Boolean): CompletionResult { return CompletionResult.Success(Unit) } + + override suspend fun updateAmounts(userWallets: List): CompletionResult { + return CompletionResult.Success(Unit) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/utils/CompletitionsResultOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/utils/CompletitionsResultOperations.kt deleted file mode 100644 index 3c94784ec8..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/utils/CompletitionsResultOperations.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.tangem.tap.domain.walletStores.implementation.utils - -import com.tangem.common.CompletionResult - -internal fun List>.fold(): CompletionResult { - return fold(Unit) { _, _ -> Unit } -} - -@Suppress("UNCHECKED_CAST") -internal inline fun List>.fold( - initial: R, - operation: (acc: R, data: D) -> R, -): CompletionResult { - var resultData = initial - for (result in this) { - when (result) { - is CompletionResult.Success -> { - resultData = operation(resultData, result.data) - } - is CompletionResult.Failure -> { - return result as CompletionResult.Failure - } - } - } - - return CompletionResult.Success(resultData) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt index f46a552dfe..0af5b8f85e 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt @@ -6,19 +6,39 @@ import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel interface WalletAmountsRepository { - suspend fun update( + + /** + * Fetch wallet amounts and fiat rates then update [com.tangem.tap.domain.walletStores.storage.WalletStoresStorage] + * and [com.tangem.tap.domain.walletStores.storage.WalletManagerStorage] with new data + * @param userWallets list of [UserWallet] which will be used to get the list of associated [WalletStoreModel] + * @param fiatCurrency current app [FiatCurrency] + * */ + suspend fun updateAmountsForUserWallets( userWallets: List, fiatCurrency: FiatCurrency, ): CompletionResult - suspend fun update( + /** + * Fetch wallet amounts and fiat rates then update [com.tangem.tap.domain.walletStores.storage.WalletStoresStorage] + * and [com.tangem.tap.domain.walletStores.storage.WalletManagerStorage] with new data + * @param userWallet [UserWallet] which will be used to get the list of associated [WalletStoreModel] + * @param fiatCurrency current app [FiatCurrency] + * */ + suspend fun updateAmountsForUserWallet( userWallet: UserWallet, fiatCurrency: FiatCurrency, ): CompletionResult - suspend fun update( - userWallet: UserWallet, + /** + * Fetch wallet amounts and fiat rates then update [com.tangem.tap.domain.walletStores.storage.WalletStoresStorage] + * and [com.tangem.tap.domain.walletStores.storage.WalletManagerStorage] with new data + * @param walletStore [WalletStoreModel] to update + * @param userWallet [UserWallet] associated with provided [walletStore] + * @param fiatCurrency current app [FiatCurrency] + * */ + suspend fun updateAmountsForWalletStore( walletStore: WalletStoreModel, + userWallet: UserWallet, fiatCurrency: FiatCurrency, ): CompletionResult diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletManagersRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletManagersRepository.kt index 9c8f9b1c13..0d8a2044be 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletManagersRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletManagersRepository.kt @@ -8,12 +8,13 @@ import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.tokens.models.BlockchainNetwork interface WalletManagersRepository { - suspend fun findOrMake( + suspend fun findOrMakeMultiCurrencyWalletManager( userWallet: UserWallet, - blockchainNetwork: BlockchainNetwork? = null, - refresh: Boolean = false, + blockchainNetwork: BlockchainNetwork, ): CompletionResult + suspend fun findOrMakeSingleCurrencyWalletManager(userWallet: UserWallet): CompletionResult + suspend fun delete(userWalletIds: List): CompletionResult suspend fun delete( diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletStoresRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletStoresRepository.kt index a5184fd40f..7b07a6eee0 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletStoresRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletStoresRepository.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.Flow interface WalletStoresRepository { fun getAll(): Flow>> fun get(userWalletId: UserWalletId): Flow> + suspend fun getSync(userWalletId: UserWalletId): List suspend fun contains(userWalletId: UserWalletId): Boolean diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt index fd8a037dba..640c08fe67 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt @@ -9,9 +9,10 @@ import com.tangem.blockchain.extensions.Result.Failure import com.tangem.blockchain.extensions.Result.Success import com.tangem.common.CompletionResult import com.tangem.common.catching -import com.tangem.common.doOnSuccess +import com.tangem.common.core.TangemError import com.tangem.common.flatMap import com.tangem.common.flatMapOnFailure +import com.tangem.common.fold import com.tangem.common.map import com.tangem.common.services.Result import com.tangem.datasource.api.tangemTech.TangemTechService @@ -22,9 +23,9 @@ import com.tangem.tap.common.extensions.replaceByOrAdd import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.walletStores.WalletStoresError -import com.tangem.tap.domain.walletStores.implementation.utils.fold import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository import com.tangem.tap.domain.walletStores.repository.implementation.utils.replaceWalletStore +import com.tangem.tap.domain.walletStores.repository.implementation.utils.replaceWalletStores import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithAmounts import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithError import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithFiatRates @@ -40,7 +41,7 @@ import com.tangem.tap.network.NetworkConnectivity import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext import timber.log.Timber import java.math.BigDecimal @@ -51,30 +52,30 @@ internal class DefaultWalletAmountsRepository( private val walletStoresStorage = WalletStoresStorage private val walletManagersStorage = WalletManagerStorage - override suspend fun update( + override suspend fun updateAmountsForUserWallets( userWallets: List, fiatCurrency: FiatCurrency, ): CompletionResult { return if (userWallets.isEmpty()) CompletionResult.Success(Unit) else withContext(Dispatchers.Default) { awaitAll( - async { fetchAmounts(userWallets) }, + async { fetchAmountsForUserWallets(userWallets) }, async { fetchFiatRates(userWallets, fiatCurrency) }, ) .fold() } } - override suspend fun update( + override suspend fun updateAmountsForUserWallet( userWallet: UserWallet, fiatCurrency: FiatCurrency, ): CompletionResult { - return update(listOf(userWallet), fiatCurrency) + return updateAmountsForUserWallets(listOf(userWallet), fiatCurrency) } - override suspend fun update( - userWallet: UserWallet, + override suspend fun updateAmountsForWalletStore( walletStore: WalletStoreModel, + userWallet: UserWallet, fiatCurrency: FiatCurrency, ): CompletionResult = withContext(Dispatchers.Default) { val walletId = userWallet.walletId @@ -84,33 +85,18 @@ internal class DefaultWalletAmountsRepository( async { // TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository] val walletManager = walletStore.walletManager - fetchAmounts(walletId, scanResponse, walletStore, walletManager) - .flatMap { fetchRentIfNeeded(walletStore, walletManager) } + fetchAmountsForWalletStore(walletId, scanResponse, walletStore, walletManager) }, async { fetchFiatRates(listOf(userWallet), fiatCurrency) }, ) .fold() } - private suspend fun fetchAmounts( + private suspend fun fetchAmountsForUserWallets( userWallets: List, - ): CompletionResult = coroutineScope { - userWallets.map { userWallet -> - val walletId = userWallet.walletId - val scanResponse = userWallet.scanResponse - val walletStores = walletStoresStorage.getSync(walletId) - - walletStores.map { walletStore -> - async { - // TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository] - val walletManager = walletStore.walletManager - fetchAmounts(walletId, scanResponse, walletStore, walletManager) - .flatMap { fetchRentIfNeeded(walletStore, walletManager) } - } - } - .awaitAll() - .fold() - } + ): CompletionResult = withContext(Dispatchers.Default) { + userWallets.map { async { fetchAmountsForUserWallet(it) } } + .awaitAll() .fold() } @@ -120,7 +106,11 @@ internal class DefaultWalletAmountsRepository( ): CompletionResult { val walletsIds = userWallets.map { it.walletId } val walletStores = walletsIds - .flatMap { walletStoresStorage.getSync(it) } + .flatMap { + walletStoresStorage.getAll() + .first() + .getOrElse(it) { emptyList() } + } val currencies = walletStores .asSequence() @@ -138,21 +128,11 @@ internal class DefaultWalletAmountsRepository( return when (fiatRatesResult) { is Result.Success -> { - Timber.d( - """ - Fetched fiat rates - |- User wallets ids: $walletsIds - |- Coins ids: $coinsIds - """.trimIndent(), + updateWalletStoresWithFiatRates( + walletStores = walletStores, + fiatRates = fiatRatesResult.data.rates, ) - walletStores.forEach { walletStore -> - updateWithFiatRates( - walletStore = walletStore, - fiatRates = fiatRatesResult.data.rates, - ) - } - CompletionResult.Success(Unit) } is Result.Failure -> { @@ -175,102 +155,66 @@ internal class DefaultWalletAmountsRepository( } } - private suspend fun fetchAmounts( + private suspend fun fetchAmountsForUserWallet( + userWallet: UserWallet, + ): CompletionResult = withContext(Dispatchers.Default) { + val walletId = userWallet.walletId + val scanResponse = userWallet.scanResponse + val walletStores = walletStoresStorage.getAll() + .first() + .getOrElse(walletId) { emptyList() } + + walletStores.map { walletStore -> + async { + // TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository] + val walletManager = walletStore.walletManager + fetchAmountsForWalletStore(walletId, scanResponse, walletStore, walletManager) + } + } + .awaitAll() + .fold() + } + + private suspend fun fetchAmountsForWalletStore( walletId: UserWalletId, scanResponse: ScanResponse, walletStore: WalletStoreModel, walletManager: WalletManager?, ): CompletionResult { - val hasMissedDerivations = with(walletStore.blockchainNetwork) { - derivationPath != null && !scanResponse.hasDerivation(blockchain, derivationPath) + val hasMissedDerivations = with(walletStore) { + derivationPath != null && !scanResponse.hasDerivation(blockchain, derivationPath.rawPath) } - val blockchain = walletStore.blockchainNetwork.blockchain - val tokens = walletStore.blockchainNetwork.tokens.map { it.name } return when { - hasMissedDerivations -> { - Timber.e( - """ - Missed derivation - |- User wallet id: $walletId - |- Blockchain: $blockchain - """.trimIndent(), - ) - - updateWithMissedDerivation( - walletStore = walletStore, - ) - - CompletionResult.Success(Unit) - } - walletManager == null -> { - Timber.e( - """ - Wallet manager is null - |- User wallet id: $walletId - |- Blockchain: $blockchain - """.trimIndent(), - ) - - updateWithUnreachable( - walletStore = walletStore, - ) - - CompletionResult.Success(Unit) - } + hasMissedDerivations -> updateWalletStoreWithMissedDerivation(walletStore) + walletManager == null -> updateWalletStoreWithUnreachable(walletStore) else -> { withInternetConnection { walletManager.update() } .map { updateWalletManagerWithAmounts(walletId, walletManager) } - .doOnSuccess { - Timber.d( - """ - Fetched amounts - |- User wallet id: $walletId - |- Blockchain: $blockchain - |- Tokens: $tokens - """.trimIndent(), - ) - - updateWithAmounts( + .flatMap { + updateWalletStoreWithAmounts( walletStore = walletStore, - wallet = walletManager.wallet, + updatedWallet = walletManager.wallet, ) } + .flatMap { fetchWalletStoreRentIfNeeded(walletStore, walletManager) } .flatMapOnFailure { error -> - Timber.e( - error, - """ - Unable to fetch amounts - |- User wallet id: $walletId - |- Blockchain: $blockchain - |- Tokens: $tokens - """.trimIndent(), + updateWalletStoreWithError( + walletStore = walletStore, + wallet = walletManager.wallet, + error = error, ) - - if (error is BlockchainSdkError) { - updateWithError( - walletStore = walletStore, - wallet = walletManager.wallet, - error = error, - ) - CompletionResult.Success(Unit) - } else { - CompletionResult.Failure(error) - } } } } } - private suspend fun fetchRentIfNeeded( + private suspend fun fetchWalletStoreRentIfNeeded( walletStore: WalletStoreModel, - walletManager: WalletManager?, + walletManager: WalletManager, ): CompletionResult { val rentProvider = walletManager as? RentProvider - - if (walletManager == null || rentProvider == null) { - return CompletionResult.Success(Unit) - } + ?: return CompletionResult.Success(Unit) when (val result = rentProvider.minimalBalanceForRentExemption()) { is Success -> { @@ -288,7 +232,7 @@ internal class DefaultWalletAmountsRepository( balance < rest } - updateWithRent( + updateWalletStoreWithRent( walletStore = walletStore, rent = if (setRent) { WalletStoreModel.WalletRent( @@ -333,76 +277,123 @@ internal class DefaultWalletAmountsRepository( } } - private suspend fun updateWithError( + private suspend fun updateWalletStoreWithError( walletStore: WalletStoreModel, wallet: Wallet, - error: BlockchainSdkError, + error: TangemError, ) = withContext(Dispatchers.Default) { - walletStoresStorage.update { prevState -> - prevState.replaceWalletStore( - walletId = walletStore.userWalletId, - walletStore = walletStore, - update = { - it.updateWithError( - wallet = wallet, - error = error, - ) - }, - ) + Timber.e( + error, + """ + Unable to fetch amounts + |- User wallet id: ${walletStore.userWalletId} + |- Blockchain: ${walletStore.blockchain} + """.trimIndent(), + ) + + if (error is BlockchainSdkError) { + walletStoresStorage.update { prevState -> + prevState.replaceWalletStore( + walletStoreToUpdate = walletStore, + update = { + it.updateWithError( + wallet = wallet, + error = error, + ) + }, + ) + } + + CompletionResult.Success(Unit) + } else { + CompletionResult.Failure(error) } } - private suspend fun updateWithAmounts( + private suspend fun updateWalletStoreWithAmounts( walletStore: WalletStoreModel, - wallet: Wallet, + updatedWallet: Wallet, ) = withContext(Dispatchers.Default) { + Timber.d( + """ + Fetched amounts + |- User wallet id: ${walletStore.userWalletId} + |- Blockchain: ${walletStore.blockchain} + """.trimIndent(), + ) + walletStoresStorage.update { prevState -> prevState.replaceWalletStore( - walletId = walletStore.userWalletId, - walletStore = walletStore, + walletStoreToUpdate = walletStore, update = { - it.updateWithAmounts(wallet = wallet) + it.updateWithAmounts(wallet = updatedWallet) }, ) } + + CompletionResult.Success(Unit) } - private suspend fun updateWithMissedDerivation( + private suspend fun updateWalletStoreWithMissedDerivation( walletStore: WalletStoreModel, ) = withContext(Dispatchers.Default) { + Timber.e( + """ + Missed derivation + |- User wallet id: ${walletStore.userWalletId} + |- Blockchain: ${walletStore.blockchain} + """.trimIndent(), + ) + walletStoresStorage.update { prevState -> prevState.replaceWalletStore( - walletId = walletStore.userWalletId, - walletStore = walletStore, + walletStoreToUpdate = walletStore, update = { it.updateWithMissedDerivation() }, ) } + + CompletionResult.Success(Unit) } - private suspend fun updateWithUnreachable( + private suspend fun updateWalletStoreWithUnreachable( walletStore: WalletStoreModel, ) = withContext(Dispatchers.Default) { + Timber.e( + """ + Wallet manager is null + |- User wallet id: ${walletStore.userWalletId} + |- Blockchain: ${walletStore.blockchain} + """.trimIndent(), + ) + walletStoresStorage.update { prevState -> prevState.replaceWalletStore( - walletId = walletStore.userWalletId, - walletStore = walletStore, + walletStoreToUpdate = walletStore, update = { it.updateWithUnreachable() }, ) } + + CompletionResult.Success(Unit) } - private suspend fun updateWithFiatRates( - walletStore: WalletStoreModel, + private suspend fun updateWalletStoresWithFiatRates( + walletStores: List, fiatRates: Map, ) = withContext(Dispatchers.Default) { + Timber.d( + """ + Fetched fiat rates + |- User wallets ids: ${walletStores.map { it.userWalletId }.distinct()} + """.trimIndent(), + ) + walletStoresStorage.update { prevState -> - prevState.replaceWalletStore( - walletId = walletStore.userWalletId, - walletStore = walletStore, + prevState.replaceWalletStores( + walletStoresToUpdate = walletStores, update = { it.updateWithFiatRates(rates = fiatRates) }, @@ -410,18 +401,28 @@ internal class DefaultWalletAmountsRepository( } } - private suspend fun updateWithRent( + private suspend fun updateWalletStoreWithRent( walletStore: WalletStoreModel, rent: WalletStoreModel.WalletRent?, ) = withContext(Dispatchers.Default) { - walletStoresStorage.update { prevState -> - prevState.replaceWalletStore( - walletId = walletStore.userWalletId, - walletStore = walletStore, - update = { - it.updateWithRent(rent) - }, - ) + Timber.d( + """ + Fetched wallet rent + |- User wallet id: ${walletStore.userWalletId} + |- Blockchain: ${walletStore.blockchain} + |- Rent: $rent + """.trimIndent(), + ) + + if (rent != walletStore.walletRent) { + walletStoresStorage.update { prevState -> + prevState.replaceWalletStore( + walletStoreToUpdate = walletStore, + update = { + it.updateWithRent(rent) + }, + ) + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt index 600120ad42..3de7a3f6b1 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt @@ -1,6 +1,10 @@ package com.tangem.tap.domain.walletStores.repository.implementation -import com.tangem.blockchain.common.* +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.DerivationParams +import com.tangem.blockchain.common.DerivationStyle +import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.common.CompletionResult import com.tangem.common.catching import com.tangem.common.hdWallet.DerivationPath @@ -18,6 +22,7 @@ import com.tangem.tap.domain.walletStores.WalletStoresError import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository import com.tangem.tap.domain.walletStores.storage.WalletManagerStorage import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext import timber.log.Timber @@ -26,26 +31,31 @@ internal class DefaultWalletManagersRepository( ) : WalletManagersRepository { private val walletManagersStorage = WalletManagerStorage - override suspend fun findOrMake( + override suspend fun findOrMakeMultiCurrencyWalletManager( + userWallet: UserWallet, + blockchainNetwork: BlockchainNetwork, + ): CompletionResult { + return findOrMakeInternal(userWallet, blockchainNetwork) + } + + override suspend fun findOrMakeSingleCurrencyWalletManager(userWallet: UserWallet): CompletionResult { + return findOrMakeInternal(userWallet, blockchainNetwork = null) + } + + private suspend fun findOrMakeInternal( userWallet: UserWallet, blockchainNetwork: BlockchainNetwork?, - refresh: Boolean, ): CompletionResult = withContext(Dispatchers.Default) { - if (refresh) { - deleteInternal(userWallet.walletId, blockchainNetwork?.blockchain) - makeAndStore(userWallet, blockchainNetwork) - } else { - val foundWalletManager = findWalletManager( - userWalletId = userWallet.walletId, - blockchain = blockchainNetwork?.blockchain, - ) + val foundWalletManager = findWalletManager( + userWalletId = userWallet.walletId, + blockchain = blockchainNetwork?.blockchain, + ) - foundWalletManager?.updateTokens( - scanResponse = userWallet.scanResponse, - blockchainNetwork = blockchainNetwork, - ) - ?: makeAndStore(userWallet, blockchainNetwork) - } + foundWalletManager?.updateTokens( + scanResponse = userWallet.scanResponse, + blockchainNetwork = blockchainNetwork, + ) + ?: makeAndStore(userWallet, blockchainNetwork) } private suspend fun makeAndStore( @@ -150,8 +160,11 @@ internal class DefaultWalletManagersRepository( return catching { val tokens = blockchainNetwork?.tokens ?: listOfNotNull(scanResponse.getPrimaryToken()) - if (tokens.isNotEmpty()) { - walletManager.addTokens(tokens) + if (tokens != cardTokens) { + cardTokens.clear() + if (tokens.isNotEmpty()) { + walletManager.cardTokens.addAll(tokens) + } } walletManager @@ -170,7 +183,7 @@ internal class DefaultWalletManagersRepository( userWalletId: UserWalletId, blockchain: Blockchain?, ): WalletManager? { - return walletManagersStorage.getAllSync()[userWalletId]?.let { userWalletManagers -> + return walletManagersStorage.getAll().first()[userWalletId]?.let { userWalletManagers -> if (blockchain == null) userWalletManagers.firstOrNull() else userWalletManagers.find { it.wallet.blockchain == blockchain } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletStoresRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletStoresRepository.kt index f12cd945cf..b9087070e4 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletStoresRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletStoresRepository.kt @@ -12,6 +12,8 @@ import com.tangem.tap.domain.walletStores.repository.implementation.utils.update import com.tangem.tap.domain.walletStores.storage.WalletStoresStorage import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext internal class DefaultWalletStoresRepository : WalletStoresRepository { @@ -22,11 +24,15 @@ internal class DefaultWalletStoresRepository : WalletStoresRepository { } override fun get(userWalletId: UserWalletId): Flow> { - return walletStoresStorage.get(userWalletId) + return getAll().map { it[userWalletId].orEmpty() } + } + + override suspend fun getSync(userWalletId: UserWalletId): List { + return get(userWalletId).firstOrNull() ?: emptyList() } override suspend fun contains(userWalletId: UserWalletId): Boolean { - return walletStoresStorage.getSync(userWalletId).isNotEmpty() + return getSync(userWalletId).isNotEmpty() } override suspend fun delete(userWalletsIds: List): CompletionResult = catching { @@ -39,11 +45,13 @@ internal class DefaultWalletStoresRepository : WalletStoresRepository { userWalletId: UserWalletId, currentBlockchains: List, ): CompletionResult = catching { - walletStoresStorage.update { prevStores -> - prevStores.apply { - this[userWalletId] = this[userWalletId] - ?.filter { it.blockchainNetwork.blockchain in currentBlockchains } - .orEmpty() + if (currentBlockchains != getSync(userWalletId).map { it.blockchain }) { + walletStoresStorage.update { prevStores -> + prevStores.apply { + this[userWalletId] = this[userWalletId] + ?.filter { it.blockchain in currentBlockchains } + .orEmpty() + } } } } @@ -65,24 +73,22 @@ internal class DefaultWalletStoresRepository : WalletStoresRepository { userWalletId: UserWalletId, walletStore: WalletStoreModel, ): HashMap> = withContext(Dispatchers.Default) { - val prevStores = this@addOrUpdate - val walletStores = prevStores[userWalletId] + val currentWalletStores = this@addOrUpdate + val userWalletStores = currentWalletStores[userWalletId] - if (walletStores.isNullOrEmpty()) { - prevStores.apply { + if (userWalletStores.isNullOrEmpty()) { + currentWalletStores.apply { set(userWalletId, listOf(walletStore)) } } else { - val oldWalletStore = walletStores.find(walletStore::isSameWalletStore) - - if (oldWalletStore == null) { - prevStores.apply { - set(userWalletId, walletStores + walletStore) + val currentWalletStore = userWalletStores.find(walletStore::isSameWalletStore) + if (currentWalletStore == null) { + currentWalletStores.apply { + set(userWalletId, userWalletStores + walletStore) } } else { - prevStores.replaceWalletStore( - walletId = userWalletId, - walletStore = oldWalletStore, + currentWalletStores.replaceWalletStore( + walletStoreToUpdate = currentWalletStore, update = { it.updateWithSelf(walletStore) }, ) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt index 39d4adfadc..7ad0e38363 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt @@ -125,10 +125,9 @@ internal fun WalletDataModel.updateWithSelf( status = when (val newStatus = newWalletData.status) { is WalletDataModel.Loading -> when (oldStatus) { is WalletDataModel.MissedDerivation -> WalletDataModel.Loading - else -> oldStatus.asRefreshing() + else -> oldStatus } is WalletDataModel.MissedDerivation, - is WalletDataModel.Refreshing, is WalletDataModel.NoAccount, is WalletDataModel.Unreachable, is WalletDataModel.SameCurrencyTransactionInProgress, diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt index 8d29c62825..445ff655f2 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt @@ -4,16 +4,28 @@ import com.tangem.blockchain.common.Wallet import com.tangem.common.core.TangemError import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.model.WalletStoreModel +import timber.log.Timber internal inline fun HashMap>.replaceWalletStore( - walletId: UserWalletId, - walletStore: WalletStoreModel, + walletStoreToUpdate: WalletStoreModel, + update: (walletStore: WalletStoreModel) -> WalletStoreModel, +): HashMap> { + return replaceWalletStores(listOf(walletStoreToUpdate), update) +} + +internal inline fun HashMap>.replaceWalletStores( + walletStoresToUpdate: List, update: (walletStore: WalletStoreModel) -> WalletStoreModel, ): HashMap> { return this.apply { - this[walletId] = this[walletId] - ?.replaceWalletStore(walletStore, update) - .orEmpty() + val currentWalletStores = this + walletStoresToUpdate + .groupBy { it.userWalletId } + .forEach { (userWalletId, walletStoresToUpdate) -> + currentWalletStores[userWalletId] = currentWalletStores[userWalletId] + ?.replaceWalletStores(walletStoresToUpdate, update) + .orEmpty() + } } } @@ -74,20 +86,39 @@ internal fun WalletStoreModel.updateWithRent(rent: WalletStoreModel.WalletRent?) ) } -internal inline fun List.replaceWalletStore( - newWalletStore: WalletStoreModel, - update: (walletStore: WalletStoreModel) -> WalletStoreModel, -): List { - val mutableStores = ArrayList(this) - for ((index, walletStore) in this.withIndex()) { - if (walletStore.isSameWalletStore(newWalletStore)) { - mutableStores[index] = update(walletStore) - break - } - } - return mutableStores +internal fun WalletStoreModel.isSameWalletStore(other: WalletStoreModel): Boolean { + return this.blockchain == other.blockchain && + this.derivationPath == other.derivationPath } -internal fun WalletStoreModel.isSameWalletStore(other: WalletStoreModel): Boolean { - return blockchainNetwork == other.blockchainNetwork +private inline fun List.replaceWalletStores( + walletStoresToUpdate: List, + update: (walletStore: WalletStoreModel) -> WalletStoreModel, +): List { + val mutableStores = ArrayList(this) + + walletStoresToUpdate + .asSequence() + .map { it.blockchain to it.derivationPath } + .forEach { (blockchain, derivationPath) -> + val index = mutableStores.indexOfFirst { + it.blockchain == blockchain && it.derivationPath == derivationPath + } + val currentWalletStore = mutableStores[index] + val updatedWalletStore = update(currentWalletStore) + + if (currentWalletStore != updatedWalletStore) { + Timber.d( + """ + Update wallet store in storage + |- User wallet ID: ${updatedWalletStore.userWalletId} + |- Blockchain: ${updatedWalletStore.blockchain} + """.trimIndent(), + ) + + mutableStores[index] = updatedWalletStore + } + } + + return mutableStores } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt index 1dae6db10e..3646e38f6a 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt @@ -4,6 +4,8 @@ import com.tangem.blockchain.common.WalletManager import com.tangem.domain.common.util.UserWalletId import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -16,8 +18,8 @@ internal object WalletManagerStorage { managers.tryEmit(hashMapOf()) } - suspend fun getAllSync(): Map> { - return managers.first() + fun getAll(): SharedFlow>> { + return managers.asSharedFlow() } private val mutex = Mutex() diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt index d4b6061dfc..93cdfb508f 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt @@ -2,12 +2,11 @@ package com.tangem.tap.domain.walletStores.storage import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.model.WalletStoreModel -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -19,20 +18,8 @@ internal object WalletStoresStorage { stores.tryEmit(hashMapOf()) } - fun getAll(): Flow>> { - return stores - } - - @OptIn(ExperimentalCoroutinesApi::class) - fun get(userWalletId: UserWalletId): Flow> { - return stores - .mapLatest { stores -> - stores[userWalletId].orEmpty() - } - } - - suspend fun getSync(userWalletId: UserWalletId): List { - return stores.first().getOrElse(userWalletId) { emptyList() } + fun getAll(): SharedFlow>> { + return stores.asSharedFlow() } private val mutex = Mutex() 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 829103e4e7..319071642c 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.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess +import com.tangem.common.extensions.toHexString import com.tangem.common.flatMap import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.domain.common.util.userWalletId @@ -61,7 +62,7 @@ class DetailsMiddleware { is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action) is DetailsAction.AppSettings -> managePrivacyMiddleware.handle(state, action) is DetailsAction.ShowDisclaimer -> { - val uri = store.state.detailsState.cardTermsOfUseUrl + val uri = state.cardTermsOfUseUrl if (uri != null) { store.dispatch(NavigationAction.OpenDocument(uri)) } @@ -71,19 +72,26 @@ class DetailsMiddleware { store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins)) } is DetailsAction.CreateBackup -> { - store.state.detailsState.scanResponse?.let { + state.scanResponse?.let { store.dispatch(GlobalAction.Onboarding.Start(it, canSkipBackup = false)) store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) } } DetailsAction.ScanCard -> { scope.launch { - tangemSdkManager.scanCard(cardId = state.scanResponse?.card?.cardId) + tangemSdkManager.scanCard(allowRequestAccessCodeFromRepository = true) .doOnSuccess { card -> - val currentCardId = store.state.globalState.scanResponse?.card - ?.userWalletId - ?.stringValue - if (card.userWalletId.stringValue == currentCardId) { + val isSameWallet = state.scanResponse?.card?.userWalletId + ?.equals(card.userWalletId) + ?: false + + // !!! Workaround !!! + // TODO: Remove after [REDACTED_JIRA] + val isTwinned = card.wallets.firstOrNull()?.publicKey?.toHexString() + ?.equals(state.scanResponse?.secondTwinPublicKey) + ?: false + + if (isSameWallet || isTwinned) { store.dispatchOnMain(DetailsAction.PrepareCardSettingsData(card)) } else { store.dispatchDialogShow( @@ -116,6 +124,7 @@ class DetailsMiddleware { scope.launch { tangemSdkManager.resetToFactorySettings(card.cardId) .flatMap { userWalletsListManager.delete(listOf(card.userWalletId)) } + .flatMap { tangemSdkManager.deleteSavedUserCodes(setOf(card.cardId)) } .doOnSuccess { Analytics.send(Settings.CardSettings.FactoryResetFinished()) @@ -255,10 +264,10 @@ class DetailsMiddleware { private fun toggleSaveAccessCodes(state: DetailsState, enable: Boolean) = scope.launch { if (state.saveAccessCodes == enable) return@launch if (enable) { + saveAccessCodes(state) if (!state.saveWallets) { saveCurrentWallet(state) } - saveAccessCodes(state) } else { deleteSavedAccessCodes() } @@ -334,12 +343,15 @@ class DetailsMiddleware { useBiometricsForAccessCode = false, ) - store.dispatchOnMain( - DetailsAction.AppSettings.SwitchPrivacySetting.Success( - setting = PrivacySetting.SaveAccessCode, - enable = false, - ), - ) + tangemSdkManager.clearSavedUserCodes() + .doOnSuccess { + store.dispatchOnMain( + DetailsAction.AppSettings.SwitchPrivacySetting.Success( + setting = PrivacySetting.SaveAccessCode, + enable = false, + ), + ) + } } } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt index 6158d9bf38..0b1063c381 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt @@ -15,8 +15,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.layout.ContentScale @@ -38,10 +36,6 @@ fun CardSettingsScreen( modifier: Modifier = Modifier, ) { val needReadCard = state.cardDetails == null - val backgroundColor by rememberUpdatedState( - newValue = if (needReadCard) TangemTheme.colors.background.primary - else TangemTheme.colors.background.secondary, - ) SettingsScreensScaffold( content = { @@ -52,7 +46,7 @@ fun CardSettingsScreen( } }, titleRes = R.string.card_settings_title, - backgroundColor = backgroundColor, + backgroundColor = TangemTheme.colors.background.secondary, onBackClick = onBackPressed, ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt index 47946d1e73..1346fa4f24 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt @@ -25,6 +25,8 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.res.TangemColorPalette import com.tangem.tap.common.compose.TangemTypography import com.tangem.tap.features.details.ui.common.ScreenTitle import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold @@ -36,6 +38,10 @@ fun DetailsScreen( onBackPressed: () -> Unit, modifier: Modifier = Modifier, ) { + SystemBarsEffect { + setSystemBarsColor(color = TangemColorPalette.Light1) + } + SettingsScreensScaffold( content = { Content(state = state, modifier = modifier) }, onBackClick = onBackPressed, diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt index ca82ff1fee..5461c66441 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt @@ -9,9 +9,19 @@ sealed class DisclaimerAction : Action { val type: DisclaimerType, ) : DisclaimerAction() - data class Show(val onAcceptCallback: VoidCallback? = null) : DisclaimerAction() + data class Show( + val onAcceptCallback: VoidCallback? = null, + val onDismissCallback: VoidCallback? = null, + ) : DisclaimerAction() + data class AcceptDisclaimer(val type: DisclaimerType) : DisclaimerAction() internal data class UpdateState(val type: DisclaimerType, val accepted: Boolean) : DisclaimerAction() - internal data class SetOnAcceptCallback(val onAcceptCallback: VoidCallback? = null) : DisclaimerAction() + + internal data class SetCallbacks( + val onAcceptCallback: VoidCallback? = null, + val onDismissCallback: VoidCallback? = null, + ) : DisclaimerAction() + + object OnBackPressed : DisclaimerAction() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt index 1b42d7b4fc..01e5de971a 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt @@ -28,7 +28,7 @@ private fun handleDisclaimerMiddleware(action: Action, appState: AppState) { } is DisclaimerAction.Show -> { handleUpdateState = state.type.createUpdateState() - store.dispatch(DisclaimerAction.SetOnAcceptCallback(action.onAcceptCallback)) + store.dispatch(DisclaimerAction.SetCallbacks(action.onAcceptCallback, action.onDismissCallback)) store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer)) } is DisclaimerAction.AcceptDisclaimer -> { @@ -36,7 +36,13 @@ private fun handleDisclaimerMiddleware(action: Action, appState: AppState) { handleUpdateState = action.type.createUpdateState() store.dispatch(NavigationAction.PopBackTo()) state.onAcceptCallback?.invoke() - store.dispatch(DisclaimerAction.SetOnAcceptCallback(null)) + store.dispatch(DisclaimerAction.SetCallbacks(null, null)) + } + is DisclaimerAction.OnBackPressed -> { + state.onDismissCallback?.invoke() + store.dispatch(DisclaimerAction.SetCallbacks(null, null)) + store.dispatch(NavigationAction.PopBackTo()) + } } } diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt index 1b282877f2..766c6a42ca 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt @@ -19,8 +19,9 @@ private fun internalReduce(action: Action, state: AppState): DisclaimerState { type = action.type, accepted = action.accepted, ) - is DisclaimerAction.SetOnAcceptCallback -> disclaimerState.copy( + is DisclaimerAction.SetCallbacks -> disclaimerState.copy( onAcceptCallback = action.onAcceptCallback, + onDismissCallback = action.onDismissCallback, ) else -> disclaimerState } diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt index ee0700f5b5..d7ca859973 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt @@ -11,6 +11,7 @@ data class DisclaimerState( val accepted: Boolean = false, val type: DisclaimerType = DisclaimerType.Tangem, val onAcceptCallback: VoidCallback? = null, + val onDismissCallback: VoidCallback? = null, ) : StateType sealed class DisclaimerType( diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt index 93d93a8a2e..282c5fe0e0 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt @@ -4,6 +4,8 @@ import android.os.Bundle import android.view.View import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding +import com.tangem.core.ui.fragments.setStatusBarColor +import com.tangem.tap.common.extensions.configureSettings import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.BaseFragment @@ -32,6 +34,7 @@ class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubs override fun onStart() { super.onStart() + setStatusBarColor(R.color.backgroundLightGray) store.subscribe(subscriber = this) { state -> state .skipRepeats { oldState, newState -> oldState.disclaimerState == newState.disclaimerState } @@ -65,4 +68,8 @@ class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubs webView.loadUrl(state.type.uri.toString()) } + + override fun handleOnBackPressed() { + store.dispatch(DisclaimerAction.OnBackPressed) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index c879e6ca04..0c5b990b06 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -79,13 +79,17 @@ private fun readCard() = scope.launch { onProgressStateChange = { showProgress -> if (showProgress) { changeButtonState(ButtonState.PROGRESS) - } else { - changeButtonState(ButtonState.ENABLED) } + // else { //todo hide this because + // changeButtonState(ButtonState.ENABLED) + // } }, onScanStateChange = { scanInProgress -> store.dispatch(HomeAction.ScanInProgress(scanInProgress)) }, + onFailure = { + changeButtonState(ButtonState.ENABLED) + }, onSuccess = { scanResponse -> scope.launch { if (preferencesStorage.shouldSaveUserWallets) { @@ -93,21 +97,18 @@ private fun readCard() = scope.launch { userWalletsListManager.save(userWallet) .doOnFailure { error -> Timber.e(error, "Unable to save user wallet") - tangemSdkManager.setAccessCodeRequestPolicy(useBiometricsForAccessCode = false) store.onCardScanned(scanResponse) } .doOnSuccess { - tangemSdkManager.setAccessCodeRequestPolicy( - useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && - userWallet.hasAccessCode, - ) - store.onUserWalletSelected(userWallet) + scope.launch { store.onUserWalletSelected(userWallet) } } .doOnResult { + changeButtonState(ButtonState.ENABLED) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) } } else { store.onCardScanned(scanResponse) + changeButtonState(ButtonState.ENABLED) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index b1ce166222..9b5c5e9958 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -1,20 +1,16 @@ package com.tangem.tap.features.onboarding -import com.tangem.common.doOnSuccess import com.tangem.domain.common.ProductType import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.onCardScanned -import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.features.saveWallet.redux.SaveWalletAction import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager -import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -59,17 +55,6 @@ class OnboardingHelper { backupCardsIds: List? = null, ) { when { - // When should save user wallets but manager is locked, then unlock manager with card - preferencesStorage.shouldSaveUserWallets && - userWalletsListManager.isLockedSync -> scope.launch { - val userWallet = UserWalletBuilder(scanResponse).build() - - tangemSdkManager.setAccessCodeRequestPolicy(useBiometricsForAccessCode = false) - userWalletsListManager.unlockWithCard(userWallet) - .doOnSuccess { - store.onUserWalletSelected(userWallet) - } - } // When should save user wallets, then save card without navigate to save wallet screen preferencesStorage.shouldSaveUserWallets -> scope.launch { store.dispatchOnMain( diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index 8853e2766e..ae60d9f637 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -6,6 +6,7 @@ import com.tangem.common.CompletionResult import com.tangem.common.extensions.guard import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext +import com.tangem.domain.common.util.userWalletId import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -31,6 +32,7 @@ import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store +import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Action @@ -69,7 +71,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { fun updateScanResponse(response: ScanResponse) { when (twinCardsState.mode) { CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse = response - CreateTwinWalletMode.RecreateWallet -> store.dispatch(GlobalAction.SaveScanNoteResponse(response)) + CreateTwinWalletMode.RecreateWallet -> store.dispatch(GlobalAction.SaveScanResponse(response)) } } @@ -151,10 +153,15 @@ private fun handle(action: Action, dispatch: DispatchFunction) { finishCardActivation() postUi(500) { store.dispatch(TwinCardsAction.Confetti.Show) } } - + TwinCardsStep.CreateFirstWallet -> { + scope.launch { + userWalletsListManager.delete( + listOf(getScanResponse().card.userWalletId), + ) + } + } TwinCardsStep.None, TwinCardsStep.Warning, - TwinCardsStep.CreateFirstWallet, TwinCardsStep.CreateSecondWallet, TwinCardsStep.CreateThirdWallet, -> Unit @@ -294,13 +301,14 @@ private fun handle(action: Action, dispatch: DispatchFunction) { when (twinCardsState.mode) { CreateTwinWalletMode.CreateWallet -> { store.dispatchOnMain(GlobalAction.Onboarding.Stop) - OnboardingHelper.trySaveWalletAndNavigateToWalletScreen( - scanResponse = scanResponse, - backupCardsIds = listOfNotNull(twinCardsState.twinCardsManager?.secondCardPublicKey), - ) + OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse) } CreateTwinWalletMode.RecreateWallet -> { - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) + if (preferencesStorage.shouldSaveUserWallets) { + OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse) + } else { + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) + } } } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt index 9511376155..36e386325e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt @@ -13,6 +13,7 @@ import coil.load import com.tangem.Message import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.VoidCallback +import com.tangem.core.ui.fragments.setStatusBarColor import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TwinCardNumber import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget @@ -97,6 +98,11 @@ class TwinsCardsFragment : BaseOnboardingFragment() { } } + override fun onStart() { + super.onStart() + setStatusBarColor(R.color.backgroundWhite) + } + private fun reconfigureLayoutForTwins(containerBinding: LayoutOnboardingContainerTopBinding) = with(containerBinding) { imvFrontCard.hide() diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index d11d5139fb..106ebfa9af 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -94,7 +94,6 @@ internal class SaveWalletMiddleware { saveAccessCodeIfNeeded(state.backupInfo?.accessCode, userWallet.cardsInWallet) .flatMap { userWalletsListManager.save(userWallet, canOverride = true) } - .flatMap { userWalletsListManager.selectWallet(userWallet.walletId) } .doOnFailure { error -> store.dispatchOnMain(SaveWalletAction.Save.Error(error)) } @@ -105,12 +104,6 @@ internal class SaveWalletMiddleware { preferencesStorage.shouldSaveAccessCodes = isFirstSavedWallet || preferencesStorage.shouldSaveAccessCodes - - tangemSdkManager.setAccessCodeRequestPolicy( - useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && - userWallet.hasAccessCode, - ) - store.dispatchOnMain(SaveWalletAction.Save.Success) store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index b093418152..beeb35f0f6 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -48,6 +48,7 @@ import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.tap.features.send.redux.states.ExternalTransactionData import com.tangem.tap.features.send.redux.states.MainCurrencyType import com.tangem.tap.features.send.redux.states.TransactionExtrasState +import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.scope import com.tangem.tap.store @@ -328,12 +329,15 @@ private fun updateWarnings(dispatch: (Action) -> Unit) { } private suspend fun updateWallet(walletManager: WalletManager) { - val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager) val selectedUserWallet = userWalletsListManager.selectedUserWalletSync if (selectedUserWallet != null) { + val wallet = walletManager.wallet walletCurrenciesManager.update( userWallet = selectedUserWallet, - blockchainNetwork = blockchainNetwork, + currency = Currency.Blockchain( + blockchain = wallet.blockchain, + derivationPath = wallet.publicKey.derivationPath?.rawPath, + ), ) } else { store.dispatchOnMain(WalletAction.LoadWallet(BlockchainNetwork.fromWalletManager(walletManager))) 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 8465e9d228..b07a4fd3ca 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 @@ -221,7 +221,7 @@ class TokensMiddleware { val updatedScanResponse = scanResponse.copy( derivedKeys = updatedDerivedKeys, ) - store.dispatchOnMain(GlobalAction.SaveScanNoteResponse(updatedScanResponse)) + store.dispatchOnMain(GlobalAction.SaveScanResponse(updatedScanResponse)) delay(DELAY_SDK_DIALOG_CLOSE) onSuccess(updatedScanResponse) @@ -383,7 +383,7 @@ class TokensMiddleware { } } - val addedCurrencies = store.state.walletState.wallets.map { walletStore -> + val addedCurrencies = store.state.walletState.walletsStores.map { walletStore -> walletStore.walletsData.map { walletData -> walletData.currency } }.flatten().map { when (it) { diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt index ea9a272f74..2fa1ab2971 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt @@ -57,7 +57,7 @@ class AddTokensFragment : BaseFragment(R.layout.fragment_add_tokens), StoreSubsc super.onViewCreated(view, savedInstanceState) (activity as? AppCompatActivity)?.setSupportActionBar(toolbar) - toolbar.setNavigationOnClickListener { activity?.onBackPressed() } + toolbar.setNavigationOnClickListener { handleOnBackPressed() } val onSaveChanges = { tokens: List, blockchains: List -> Analytics.send(ManageTokens.ButtonSaveChanges()) @@ -144,7 +144,6 @@ class AddTokensFragment : BaseFragment(R.layout.fragment_add_tokens), StoreSubsc } override fun handleOnBackPressed() { - super.handleOnBackPressed() store.dispatch(NavigationAction.PopBackTo()) store.dispatch(TokensAction.ResetState) } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt index 6d751e8956..149fd2b517 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.tangem.blockchain.common.Blockchain +import com.tangem.core.ui.components.SystemBarsEffect import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.tap.common.analytics.Analytics @@ -81,6 +82,11 @@ fun CurrenciesScreen( } } + val statusBarColor = colorResource(id = R.color.backgroundLightGray) + SystemBarsEffect { + setSystemBarsColor(color = statusBarColor) + } + Scaffold( floatingActionButton = { if (tokensState.value.allowToAdd) { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index be432defdc..0cc8039e96 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -12,11 +12,11 @@ import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.redux.NotificationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.warningMessage.WarningMessage -import com.tangem.tap.domain.model.TotalFiatBalance import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.features.wallet.models.TotalBalance import com.tangem.tap.features.wallet.redux.models.WalletDialog import com.tangem.wallet.R import org.rekotlin.Action @@ -36,7 +36,7 @@ sealed class WalletAction : Action { data class LoadWallet( val blockchain: BlockchainNetwork? = null, - val walletManager: WalletManager? = null + val walletManager: WalletManager? = null, ) : WalletAction() { data class Success(val wallet: Wallet, val blockchain: BlockchainNetwork) : WalletAction() data class NoAccount( @@ -90,7 +90,8 @@ sealed class WalletAction : Action { val blockchain: BlockchainNetwork, ) : MultiWallet() - data class SelectWallet(val walletData: WalletData?) : MultiWallet() + data class SelectWallet(val currency: Currency?) : MultiWallet() + data class SetSingleWalletCurrency(val currency: Currency?) : MultiWallet() data class TryToRemoveWallet(val currency: Currency) : MultiWallet() data class RemoveWallet(val currency: Currency) : MultiWallet() @@ -128,7 +129,7 @@ sealed class WalletAction : Action { val wallet: Wallet? = null, val coinsList: List? = null, ) : WalletAction() { data class Success( - val fiatRates: Map + val fiatRates: Map, ) : WalletAction() object Failure : WalletAction() @@ -163,7 +164,7 @@ sealed class WalletAction : Action { object ChooseTradeActionDialog : DialogAction() data class ChooseCurrency(val amounts: List?) : DialogAction() data class RussianCardholdersWarningDialog( - val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data? = null + val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data? = null, ) : DialogAction() object Hide : DialogAction() @@ -181,12 +182,13 @@ sealed class WalletAction : Action { data class Buy( val checkUserLocation: Boolean = true, ) : TradeCryptoAction() + data class FinishSelling(val transactionId: String) : TradeCryptoAction() data class SendCrypto( val currencyId: String, val amount: String, val destinationAddress: String, - val transactionId: String + val transactionId: String, ) : TradeCryptoAction() } @@ -206,6 +208,9 @@ sealed class WalletAction : Action { } data class UserWalletChanged(val userWallet: UserWallet) : WalletAction() - data class WalletStoresChanged(val walletStores: List) : WalletAction() - data class TotalFiatBalanceChanged(val balance: TotalFiatBalance) : WalletAction() + data class WalletStoresChanged(val walletStores: List) : WalletAction() { + data class UpdateWalletStores(val reduxWalletStores: List) : WalletAction() + } + + data class TotalFiatBalanceChanged(val balance: TotalBalance) : WalletAction() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index b8413753cb..49773a9854 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -36,7 +36,7 @@ data class WalletState( val cardImage: Artwork? = null, val hashesCountVerified: Boolean? = null, val mainWarningsList: List = mutableListOf(), - val wallets: List = listOf(), + val walletsStores: List = listOf(), val isMultiwalletAllowed: Boolean = false, val cardCurrency: CryptoCurrencyName? = null, val selectedCurrency: Currency? = null, @@ -50,6 +50,12 @@ data class WalletState( val walletCardsCount: Int? = null, ) : StateType { + val walletsDataFromStores: List + get() = walletsStores.map { it.walletsData }.flatten() + + val selectedWalletData: WalletData? + get() = walletsDataFromStores.firstOrNull { it.currency == selectedCurrency } + // if you do not delegate - the application crashes on startup, // because twinCardsState has not been created yet val twinCardsState: TwinCardsState by ReadOnlyProperty { _, _ -> @@ -63,20 +69,17 @@ data class WalletState( get() = store.state.globalState.exchangeManager.featureIsSwitchedOn() val blockchains: List - get() = wallets.mapNotNull { it.walletManager?.wallet?.blockchain } + get() = walletsStores.mapNotNull { it.walletManager?.wallet?.blockchain } val currencies: List - get() = wallets.flatMap { it.walletsData }.map { it.currency } - - val walletsData: List - get() = wallets.flatMap { it.walletsData } + get() = walletsStores.flatMap { it.walletsData }.map { it.currency } val walletManagers: List - get() = wallets.mapNotNull { it.walletManager } + get() = walletsStores.mapNotNull { it.walletManager } - val primaryWallet: WalletData? = wallets.firstOrNull()?.walletsData?.firstOrNull() + val primaryWallet: WalletData? = walletsStores.firstOrNull()?.walletsData?.firstOrNull() - val primaryWalletManager: WalletManager? = if (wallets.isNotEmpty()) wallets[0].walletManager else null + val primaryWalletManager: WalletManager? = if (walletsStores.isNotEmpty()) walletsStores[0].walletManager else null val shouldShowDetails: Boolean = primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard && @@ -91,12 +94,12 @@ data class WalletState( } fun getWalletManager(blockchain: BlockchainNetwork): WalletManager? { - return wallets.find { it.blockchainNetwork == blockchain }?.walletManager + return walletsStores.find { it.blockchainNetwork == blockchain }?.walletManager } fun getWalletData(blockchain: BlockchainNetwork?): WalletData? { if (blockchain == null) return null - return walletsData.find { + return walletsDataFromStores.find { it.currency is Currency.Blockchain && it.currency.blockchain == blockchain.blockchain && it.currency.derivationPath == blockchain.derivationPath @@ -105,7 +108,7 @@ data class WalletState( fun getWalletStore(currency: Currency?): WalletStore? { if (currency == null) return null - return wallets.firstOrNull { + return walletsStores.firstOrNull { it.blockchainNetwork.derivationPath == currency.derivationPath && (it.blockchainNetwork.blockchain == currency.blockchain) } @@ -120,7 +123,7 @@ data class WalletState( fun getWalletStore(blockchainNetwork: BlockchainNetwork?): WalletStore? { if (blockchainNetwork == null) return null - return wallets.firstOrNull { + return walletsStores.firstOrNull { it.blockchainNetwork.derivationPath == blockchainNetwork.derivationPath && (it.blockchainNetwork.blockchain == blockchainNetwork.blockchain) } @@ -131,10 +134,6 @@ data class WalletState( return getWalletStore(currency)?.walletsData?.firstOrNull { it.currency == currency } } - fun getSelectedWalletData(): WalletData? { - return walletsData.find { it.currency == selectedCurrency } - } - private fun isPrimaryCurrency(walletData: WalletData): Boolean { return (walletData.currency is Currency.Blockchain && walletData.currency.blockchain == store.state.walletState.primaryBlockchain) @@ -142,10 +141,10 @@ data class WalletState( walletData.currency.token == store.state.walletState.primaryToken) } - fun replaceWalletInWallets(wallet: WalletStore?): List { - if (wallet == null) return wallets + fun replaceWalletStoreInWalletsStores(wallet: WalletStore?): List { + if (wallet == null) return walletsStores var changed = false - val updatedWallets = wallets.map { + val updatedWallets = walletsStores.map { if (it.blockchainNetwork == wallet.blockchainNetwork) { changed = true wallet @@ -153,7 +152,7 @@ data class WalletState( it } } - return if (changed) updatedWallets else wallets + wallet + return if (changed) updatedWallets else walletsStores + wallet } fun updateWalletData(walletData: WalletData?): WalletState { @@ -161,26 +160,23 @@ data class WalletState( return updateWalletsData(listOf(walletData)) } - fun updateWalletsData( - walletsData: List - ): WalletState { - + fun updateWalletsData(walletsData: List): WalletState { val walletStores = walletsData .map { BlockchainNetwork(it.currency.blockchain, it.currency.derivationPath, emptyList()) } .distinct().map { getWalletStore(it) }.mapNotNull { it?.updateWallets(walletsData) } - return updateWalletStores(walletStores) + return updateWalletsStores(walletStores) } fun updateWalletStore(walletStore: WalletStore?): WalletState { - return copy(wallets = replaceWalletInWallets(walletStore)) + return copy(walletsStores = replaceWalletStoreInWalletsStores(walletStore)) .updateTotalBalance() .updateProgressState() } - private fun updateWalletStores(walletStores: List): WalletState { + private fun updateWalletsStores(walletStores: List): WalletState { val walletStoresMutable = walletStores.toMutableList() - val updatedWallets = wallets.map { oldWalletStore -> + val updatedWallets = walletsStores.map { oldWalletStore -> val walletStore = walletStoresMutable.find { it.blockchainNetwork == oldWalletStore.blockchainNetwork } @@ -191,20 +187,20 @@ data class WalletState( oldWalletStore } } - return copy(wallets = updatedWallets + walletStoresMutable) + return copy(walletsStores = updatedWallets + walletStoresMutable) .updateTotalBalance() .updateProgressState() } - fun removeWallet(walletData: WalletData?): WalletState { + fun removeWalletData(walletData: WalletData?): WalletState { if (walletData == null) return this return when (val currency = walletData.currency) { is Currency.Blockchain -> { - val walletStores = wallets.filterNot { + val walletStores = walletsStores.filterNot { it.blockchainNetwork.blockchain == currency.blockchain && it.blockchainNetwork.derivationPath == currency.derivationPath } - copy(wallets = walletStores) + copy(walletsStores = walletStores) .updateTotalBalance() .updateProgressState() } @@ -223,23 +219,8 @@ data class WalletState( } } - fun replaceSomeWallets(newWallets: List): List { - val remainingWallets: MutableList = newWallets.toMutableList() - val updatedWallets = walletsData.map { wallet -> - val newWallet = newWallets - .firstOrNull { wallet.currency == it.currency } - if (newWallet == null) { - wallet - } else { - remainingWallets.remove(newWallet) - newWallet - } - } - return updatedWallets + remainingWallets - } - private fun updateTotalBalance(): WalletState { - val walletsData = this.wallets + val walletsData = this.walletsStores .flatMap(WalletStore::walletsData) return if (walletsData.isNotEmpty()) { @@ -256,7 +237,7 @@ data class WalletState( } private fun updateProgressState(): WalletState { - val walletsData = this.wallets + val walletsData = this.walletsStores .flatMap(WalletStore::walletsData) return if (walletsData.isNotEmpty()) { @@ -264,7 +245,7 @@ data class WalletState( this.copy( state = walletsData.findProgressState(), - error = this.error.takeIf { newProgressState == ProgressState.Error } + error = this.error.takeIf { newProgressState == ProgressState.Error }, ) } else this } @@ -276,6 +257,21 @@ data class WalletState( } } +fun List.replaceSomeWalletsData(newWallets: List): List { + val remainingWallets: MutableList = newWallets.toMutableList() + val updatedWallets = this.map { wallet -> + val newWallet = newWallets + .firstOrNull { wallet.currency == it.currency } + if (newWallet == null) { + wallet + } else { + remainingWallets.remove(newWallet) + newWallet + } + } + return updatedWallets + remainingWallets +} + enum class ProgressState : WidgetState { Loading, Refreshing, Done, Error } enum class ErrorType { NoInternetConnection } @@ -287,7 +283,7 @@ sealed class WalletMainButton(enabled: Boolean) : Button(enabled) { data class WalletAddresses( val selectedAddress: AddressData, - val list: List + val list: List, ) data class AddressData( @@ -301,7 +297,7 @@ data class AddressData( data class Artwork( val artworkId: String, - val artwork: Bitmap? = null + val artwork: Bitmap? = null, ) { companion object { const val DEFAULT_IMG_URL = "https://app.tangem.com/cards/card_default.png" @@ -374,7 +370,7 @@ data class WalletData( } } - private fun assembleTokenWarnings(walletWarnings: MutableList){ + private fun assembleTokenWarnings(walletWarnings: MutableList) { if (!currency.isToken()) return val blockchainFullName = currency.blockchain.fullName @@ -391,7 +387,7 @@ data class WalletData( data class WalletStore( val walletManager: WalletManager?, val blockchainNetwork: BlockchainNetwork, - val walletsData: List + val walletsData: List, ) { fun updateWallets(walletDataList: List): WalletStore { val relevantWalletDataList = walletDataList.filter { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt new file mode 100644 index 0000000000..dbb030f67f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt @@ -0,0 +1,133 @@ +package com.tangem.tap.features.wallet.redux.middlewares + +import com.tangem.common.extensions.isZero +import com.tangem.tap.common.extensions.stripZeroPlainString +import com.tangem.tap.common.extensions.toFiatRateString +import com.tangem.tap.common.extensions.toFiatValue +import com.tangem.tap.common.extensions.toFormattedCurrencyString +import com.tangem.tap.common.extensions.toFormattedFiatValue +import com.tangem.tap.domain.model.TotalFiatBalance +import com.tangem.tap.domain.model.WalletDataModel +import com.tangem.tap.domain.model.WalletStoreModel +import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.features.wallet.models.TotalBalance +import com.tangem.tap.features.wallet.models.WalletRent +import com.tangem.tap.features.wallet.redux.ProgressState +import com.tangem.tap.features.wallet.redux.WalletAddresses +import com.tangem.tap.features.wallet.redux.WalletData +import com.tangem.tap.features.wallet.redux.WalletMainButton +import com.tangem.tap.features.wallet.redux.WalletStore +import com.tangem.tap.features.wallet.ui.BalanceStatus +import com.tangem.tap.features.wallet.ui.BalanceWidgetData +import com.tangem.tap.features.wallet.ui.TokenData +import com.tangem.tap.store + +internal fun List.mapToReduxModels( + isMultiWalletAllowed: Boolean, +): List { + return this.map { walletStoreModel -> + walletStoreModel.mapToReduxModel(isMultiWalletAllowed) + } +} + +internal fun TotalFiatBalance.mapToReduxModel(): TotalBalance { + return TotalBalance( + state = when (this) { + is TotalFiatBalance.Loading -> ProgressState.Loading + is TotalFiatBalance.Error -> ProgressState.Error + is TotalFiatBalance.Loaded -> ProgressState.Done + }, + fiatAmount = amount, + fiatCurrency = store.state.globalState.appCurrency, + ) +} + +internal fun WalletStoreModel.mapToReduxModel( + isMultiWalletAllowed: Boolean, +): WalletStore { + return WalletStore( + walletManager = walletManager, + blockchainNetwork = blockchainNetwork, + walletsData = walletsData.mapToReduxModel(isMultiWalletAllowed, walletRent), + ) +} + +private fun List.mapToReduxModel( + isMultiWalletAllowed: Boolean, + walletRent: WalletStoreModel.WalletRent?, +): List { + return this.map { walletDataModel -> + with(walletDataModel) { + val amount = status.amount + val amountFormatted = amount.toFormattedCurrencyString( + decimals = currency.decimals, + currency = currency.currencySymbol, + ) + val appCurrency = store.state.globalState.appCurrency + val fiatAmount = fiatRate?.let { status.amount.toFiatValue(it) } + val fiatAmountFormatted = fiatAmount + ?.takeIf { !status.isErrorStatus } + ?.toFormattedFiatValue(appCurrency.symbol) + val fiatRateFormatted = fiatRate?.toFiatRateString(appCurrency.symbol) + + WalletData( + currency = currency, + walletAddresses = walletAddresses.getOrNull(0)?.let { selectedAddress -> + WalletAddresses( + selectedAddress = selectedAddress, + list = walletAddresses, + ) + }, + existentialDepositString = existentialDeposit?.toPlainString(), + fiatRate = fiatRate, + fiatRateString = fiatRateFormatted, + pendingTransactions = status.pendingTransactions, + mainButton = WalletMainButton.SendButton( + enabled = !status.amount.isZero() && status.pendingTransactions.isEmpty(), + ), + walletRent = walletRent?.let { + WalletRent( + minRentValue = "${it.rent.stripZeroPlainString()} ${currency.blockchain.currency}", + rentExemptValue = "${it.exemptionAmount.stripZeroPlainString()} ${currency.blockchain.currency}", + ) + }, + currencyData = BalanceWidgetData( + status = when (status) { + is WalletDataModel.Loading -> BalanceStatus.Loading + is WalletDataModel.NoAccount -> BalanceStatus.NoAccount + is WalletDataModel.SameCurrencyTransactionInProgress -> BalanceStatus.SameCurrencyTransactionInProgress + is WalletDataModel.TransactionInProgress -> BalanceStatus.TransactionInProgress + is WalletDataModel.Unreachable -> BalanceStatus.Unreachable + is WalletDataModel.MissedDerivation -> BalanceStatus.MissedDerivation + is WalletDataModel.VerifiedOnline -> BalanceStatus.VerifiedOnline + }, + currency = currency.currencyName, + currencySymbol = currency.currencySymbol, + blockchainAmount = status.amount, + amount = amount, + amountFormatted = amountFormatted, + fiatAmount = fiatAmount, + fiatAmountFormatted = fiatAmountFormatted, + token = when { + !isMultiWalletAllowed && currency is Currency.Token -> { + TokenData( + amount = amount, + amountFormatted = amountFormatted, + fiatAmount = fiatAmount, + fiatAmountFormatted = fiatAmountFormatted, + tokenSymbol = currency.currencySymbol, + fiatRate = fiatRate, + fiatRateString = fiatRateFormatted, + ) + } + else -> null + }, + amountToCreateAccount = (status as? WalletDataModel.NoAccount) + ?.amountToCreateAccount + ?.toString(), + errorMessage = status.errorMessage, + ), + ) + } + } +} \ No newline at end of file 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 bbe6f90378..9bba0011a5 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 @@ -51,7 +51,7 @@ class MultiWalletMiddleware { handleAddingWalletManagers(globalState, action.walletManagers) } is WalletAction.MultiWallet.SelectWallet -> { - if (action.walletData != null) { + if (action.currency != null) { store.dispatch(NavigationAction.NavigateTo(AppScreen.WalletDetails)) } } 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 5d6a9fc2d7..df69426a2c 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 @@ -44,7 +44,7 @@ class TradeCryptoMiddleware { return } - val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return + val selectedWalletData = store.state.walletState.selectedWalletData ?: return val card = store.state.globalState.scanResponse?.card ?: return val addresses = selectedWalletData.walletAddresses?.list.orEmpty() @@ -85,7 +85,7 @@ class TradeCryptoMiddleware { } private fun proceedSellAction() { - val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return + val selectedWalletData = store.state.walletState.selectedWalletData ?: return val appCurrency = store.state.globalState.appCurrency val addresses = selectedWalletData.walletAddresses?.list.orEmpty() @@ -107,7 +107,7 @@ class TradeCryptoMiddleware { } private fun preconfigureAndOpenSendScreen(action: WalletAction.TradeCryptoAction.SendCrypto) { - val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return + val selectedWalletData = store.state.walletState.selectedWalletData ?: return Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(selectedWalletData.currency))) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt index f94e6def86..fb6ff1dd3f 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt @@ -17,7 +17,7 @@ class WalletDialogsMiddleware { store.dispatchDialogShow(WalletDialog.SignedHashesMultiWalletDialog) } is WalletAction.DialogAction.ChooseTradeActionDialog -> { - store.state.walletState.getSelectedWalletData()?.let { + store.state.walletState.selectedWalletData?.let { Analytics.send(Token.ButtonExchange(AnalyticsParam.CurrencyType.Currency(it.currency))) } store.dispatchDialogShow(WalletDialog.ChooseTradeActionDialog) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index f6e0c47011..5dcd8b81bd 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -97,11 +97,7 @@ class WalletMiddleware { when (action) { is WalletAction.TradeCryptoAction -> tradeCryptoMiddleware.handle(state, action) is WalletAction.Warnings -> warningsMiddleware.handle(action, globalState) - is WalletAction.MultiWallet -> multiWalletMiddleware.handle( - action, - walletState, - globalState - ) + is WalletAction.MultiWallet -> multiWalletMiddleware.handle(action, walletState, globalState) is WalletAction.AppCurrencyAction -> appCurrencyMiddleware.handle(action) is WalletAction.DialogAction -> walletDialogMiddleware.handle(action) is WalletAction.LoadWallet -> { @@ -129,9 +125,7 @@ class WalletMiddleware { true, ), ) - store.dispatch( - action = WalletAction.LoadWallet.Success(action.wallet, action.blockchain) - ) + store.dispatch(WalletAction.LoadWallet.Success(action.wallet, action.blockchain)) } } store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) @@ -150,7 +144,7 @@ class WalletMiddleware { action.coinsList != null -> action.coinsList else -> { if (walletState.isMultiwalletAllowed) { - walletState.walletsData.map { it.currency } + walletState.walletsDataFromStores.map { it.currency } } else { val derivationPath = walletState.primaryWallet?.currency?.derivationPath val primaryBlockchain = walletState.primaryBlockchain @@ -175,7 +169,7 @@ class WalletMiddleware { Timber.e( throwable, "Loading rates failed for [%s]", - currency.currencySymbol + currency.currencySymbol, ) } } @@ -188,14 +182,11 @@ class WalletMiddleware { } is WalletAction.CreateWallet -> { scope.launch { - val result = tangemSdkManager.createWallet( - globalState.scanResponse?.card?.cardId - ) + val result = tangemSdkManager.createWallet(globalState.scanResponse?.card?.cardId) when (result) { is CompletionResult.Success -> { - val scanNoteResponse = - globalState.scanResponse?.copy(card = result.data) - scanNoteResponse?.let { store.onCardScanned(scanNoteResponse) } + val scanResponse = globalState.scanResponse?.copy(card = result.data) + scanResponse?.let { store.onCardScanned(scanResponse) } } is CompletionResult.Failure -> {} } @@ -210,9 +201,7 @@ class WalletMiddleware { store.dispatchOnMain(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed)) scope.launch { - val response = OnlineCardVerifier().getCardInfo( - action.card.cardId, action.card.cardPublicKey - ) + val response = OnlineCardVerifier().getCardInfo(action.card.cardId, action.card.cardPublicKey) when (response) { is Result.Success -> { val actionList = listOf( @@ -237,11 +226,12 @@ class WalletMiddleware { refresh = action is WalletAction.LoadData.Refresh, ) } else { - val scanNoteResponse = globalState.scanResponse ?: return@launch - if (walletState.walletsData.isNotEmpty()) { - globalState.tapWalletManager.reloadData(scanNoteResponse) + val scanResponse = globalState.scanResponse ?: return@launch + + if (walletState.walletsDataFromStores.isNotEmpty()) { + globalState.tapWalletManager.reloadData(scanResponse) } else { - globalState.tapWalletManager.loadData(scanNoteResponse) + globalState.tapWalletManager.loadData(scanResponse) } } } @@ -249,8 +239,8 @@ class WalletMiddleware { is NetworkStateChanged -> { store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) val selectedUserWallet = userWalletsListManagerSafe?.selectedUserWalletSync - if (selectedUserWallet != null) scope.launch { - globalState.tapWalletManager.loadData(selectedUserWallet) + if (selectedUserWallet != null) { + scope.launch { globalState.tapWalletManager.loadData(selectedUserWallet) } } else { globalState.scanResponse?.let { scanNoteResponse -> scope.launch { globalState.tapWalletManager.loadData(scanNoteResponse) } @@ -295,6 +285,7 @@ class WalletMiddleware { } is WalletAction.UserWalletChanged -> Unit is WalletAction.WalletStoresChanged -> { + updateWalletStores(action.walletStores, walletState) fetchTotalFiatBalance(action.walletStores, walletState) findMissedDerivations(action.walletStores) tryToShowAppRatingWarning(action.walletStores) @@ -303,12 +294,29 @@ class WalletMiddleware { } } + private fun updateWalletStores(wallStores: List, state: WalletState) { + scope.launch(Dispatchers.Default) { + if (!state.isMultiwalletAllowed) { + wallStores.firstOrNull()?.walletsData?.firstOrNull()?.let { + store.dispatchOnMain(WalletAction.MultiWallet.SetSingleWalletCurrency(it.currency)) + } + } + + val reduxWalletStores = wallStores.mapToReduxModels(state.isMultiwalletAllowed) + store.dispatchOnMain( + WalletAction.WalletStoresChanged.UpdateWalletStores( + reduxWalletStores = reduxWalletStores.toList(), + ), + ) + } + } + private fun fetchTotalFiatBalance(walletStores: List, state: WalletState) { scope.launch(Dispatchers.Default) { val totalFiatBalance = totalFiatBalanceCalculator.calculateOrNull( prevAmount = state.totalBalance?.fiatAmount, walletStores = walletStores, - ) + )?.mapToReduxModel() if (totalFiatBalance != null) { store.dispatchOnMain(WalletAction.TotalFiatBalanceChanged(totalFiatBalance)) @@ -361,7 +369,7 @@ class WalletMiddleware { } private fun prepareSendAction(amount: Amount?, state: WalletState?): Action { - val selectedWalletData = state?.getSelectedWalletData() + val selectedWalletData = state?.selectedWalletData val currency = selectedWalletData?.currency val walletStore = state?.getWalletStore(currency) @@ -376,36 +384,34 @@ class WalletMiddleware { if (currency != null && state.isMultiwalletAllowed) { when (currency) { is Currency.Blockchain -> { - val amountToSend = - amounts?.find { it.currencySymbol == currency.blockchain.currency } - ?: return WalletAction.DialogAction.ChooseCurrency(amounts) + val amountToSend = amounts?.find { it.currencySymbol == currency.blockchain.currency } + ?: return WalletAction.DialogAction.ChooseCurrency(amounts) PrepareSendScreen( coinAmount = amountToSend, coinRate = selectedWalletData.fiatRate, - walletManager = walletStore.walletManager + walletManager = walletStore.walletManager, ) } is Currency.Token -> { - val amountToSend = - amounts?.find { it.currencySymbol == currency.token.symbol } - ?: return WalletAction.DialogAction.ChooseCurrency(amounts) + val amountToSend = amounts?.find { it.currencySymbol == currency.token.symbol } + ?: return WalletAction.DialogAction.ChooseCurrency(amounts) prepareSendActionForToken( amount = amountToSend, state = state, selectedWalletData = selectedWalletData, - walletStore = walletStore + walletStore = walletStore, ) } } } else { - if (amounts?.size ?: 0 > 1) { + if ((amounts?.size ?: 0) > 1) { WalletAction.DialogAction.ChooseCurrency(amounts) } else { val amountToSend = amounts?.first() PrepareSendScreen( coinAmount = amountToSend, coinRate = selectedWalletData?.fiatRate, - walletManager = walletStore?.walletManager + walletManager = walletStore?.walletManager, ) } } @@ -416,7 +422,7 @@ class WalletMiddleware { amount: Amount, state: WalletState?, selectedWalletData: WalletData?, - walletStore: WalletStore? + walletStore: WalletStore?, ): PrepareSendScreen { val coinRate = state?.getWalletData(walletStore?.blockchainNetwork)?.fiatRate val tokenRate = if (state?.isMultiwalletAllowed == true) { @@ -431,7 +437,7 @@ class WalletMiddleware { coinRate = coinRate, walletManager = walletStore?.walletManager, tokenAmount = amount, - tokenRate = tokenRate + tokenRate = tokenRate, ) } @@ -447,7 +453,7 @@ class WalletMiddleware { val balance = walletManager.wallet.fundsAvailable(AmountType.Coin) val outgoingTxs = walletManager.wallet.getPendingTransactions( - PendingTransactionType.Outgoing + PendingTransactionType.Outgoing, ).filterByCoin() val rentExempt = result.data @@ -465,8 +471,8 @@ class WalletMiddleware { WalletAction.SetWalletRent( wallet = walletManager.wallet, minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency"), - rentExempt = ("${rentExempt.stripZeroPlainString()} $currency") - ) + rentExempt = ("${rentExempt.stripZeroPlainString()} $currency"), + ), ) } else { dispatchOnMain(WalletAction.RemoveWalletRent(walletManager.wallet)) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt index e911c48e3c..b2ef29f6b3 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt @@ -125,7 +125,8 @@ class WarningsMiddleware { if (scanResponse.isTangemTwins() || scanResponse.isDemoCard()) return null if (scanResponse.card.isMultiwalletAllowed) { - return if (scanResponse.card.hasSignedHashes()) { + val isBackupForbidden = with(scanResponse.card.settings) { !(isBackupAllowed || isHDWalletAllowed) } + return if (scanResponse.card.hasSignedHashes() && isBackupForbidden) { WarningMessagesManager.signedHashesMultiWalletWarning() } else { store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId) @@ -133,8 +134,7 @@ class WarningsMiddleware { } } - val validator = store.state.walletState.walletManagers.firstOrNull() - as? SignatureCountValidator + val validator = store.state.walletState.walletManagers.firstOrNull() as? SignatureCountValidator return if (validator == null) { if (scanResponse.card.hasSignedHashes()) { WarningMessagesManager.alreadySignedHashesWarning() 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 6885dbc11d..729d99f960 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 @@ -36,7 +36,7 @@ class MultiWalletReducer { fun reduce(action: WalletAction.MultiWallet, state: WalletState): WalletState { return when (action) { is WalletAction.MultiWallet.AddBlockchains -> { - val wallets: List = action.blockchains.map { blockchain -> + val walletStores: List = action.blockchains.mapNotNull { blockchain -> val walletManager = action.walletManagers.firstOrNull { it.wallet.blockchain == blockchain.blockchain && (it.wallet.publicKey.derivationPath?.rawPath == blockchain.derivationPath) @@ -52,13 +52,13 @@ class MultiWalletReducer { status = BalanceStatus.Loading, currency = blockchain.blockchain.fullName, currencySymbol = blockchain.blockchain.currency, - token = cardToken + token = cardToken, ), walletAddresses = createAddressList(wallet), mainButton = WalletMainButton.SendButton(false), currency = Currency.Blockchain( blockchain.blockchain, - blockchain.derivationPath + blockchain.derivationPath, ), existentialDepositString = getExistentialDeposit(walletManager), ) @@ -66,16 +66,19 @@ class MultiWalletReducer { WalletStore( walletManager = walletManager, blockchainNetwork = blockchain, - walletsData = listOf(walletData) + walletsData = listOf(walletData), ) } val selectedCurrency = if (!state.isMultiwalletAllowed) { - wallets.firstOrNull()?.walletsData?.firstOrNull()?.currency + walletStores.firstOrNull()?.walletsData?.firstOrNull()?.currency } else { state.selectedCurrency } - state.copy(wallets = wallets, selectedCurrency = selectedCurrency) + state.copy( + walletsStores = walletStores, + selectedCurrency = selectedCurrency, + ) } is WalletAction.MultiWallet.AddBlockchain -> { val walletManager = action.walletManager ?: state.getWalletManager(action.blockchain) @@ -91,14 +94,14 @@ class MultiWalletReducer { mainButton = WalletMainButton.SendButton(false), currency = Currency.Blockchain( action.blockchain.blockchain, - action.blockchain.derivationPath + action.blockchain.derivationPath, ), existentialDepositString = getExistentialDeposit(walletManager), ) val walletStore = WalletStore( walletManager = walletManager, blockchainNetwork = action.blockchain, - walletsData = listOf(walletData) + walletsData = listOf(walletData), ) val newState = state.updateWalletStore(walletStore) @@ -142,31 +145,40 @@ class MultiWalletReducer { status = tokenBalanceStatus, amount = action.amount.value, amountFormatted = action.amount.value?.toFormattedCurrencyString( - action.amount.decimals, action.amount.currencySymbol + action.amount.decimals, action.amount.currencySymbol, ), fiatAmountFormatted = tokenWalletData.fiatRate?.let { action.amount.value?.toFiatString(it, store.state.globalState.appCurrency.symbol) } ?: UNKNOWN_AMOUNT_SIGN, - blockchainAmount = wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO + blockchainAmount = wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO, ), pendingTransactions = pendingTransactions.removeUnknownTransactions(), mainButton = WalletMainButton.SendButton(isTokenSendButtonEnabled), currency = Currency.Token( token = action.token, blockchain = action.blockchain.blockchain, - derivationPath = action.blockchain.derivationPath + derivationPath = action.blockchain.derivationPath, ), - walletRent = findWalletRent(state.getWalletStore(walletManager.wallet)) + walletRent = findWalletRent(state.getWalletStore(walletManager.wallet)), ) state.updateWalletData(newTokenWalletData) } - is WalletAction.MultiWallet.SetIsMultiwalletAllowed -> state.copy(isMultiwalletAllowed = action.isMultiwalletAllowed) - is WalletAction.MultiWallet.SelectWallet -> state.copy(selectedCurrency = action.walletData?.currency) + is WalletAction.MultiWallet.SetIsMultiwalletAllowed -> + state.copy(isMultiwalletAllowed = action.isMultiwalletAllowed) + + is WalletAction.MultiWallet.SelectWallet -> { + state.copy(selectedCurrency = action.currency) + } + is WalletAction.MultiWallet.SetSingleWalletCurrency -> { + state.copy(selectedCurrency = action.currency) + } is WalletAction.MultiWallet.TryToRemoveWallet -> state - is WalletAction.MultiWallet.RemoveWallet -> state.removeWallet(state.getWalletData(action.currency)) + is WalletAction.MultiWallet.RemoveWallet -> { + state.removeWalletData(state.getWalletData(action.currency)) + } is WalletAction.MultiWallet.RemoveWallets -> { var updatedState = state - action.currencies.forEach { updatedState = updatedState.removeWallet(state.getWalletData(it)) } + action.currencies.forEach { updatedState = updatedState.removeWalletData(state.getWalletData(it)) } updatedState } is WalletAction.MultiWallet.SetPrimaryBlockchain -> state.copy(primaryBlockchain = action.blockchain) @@ -205,10 +217,10 @@ fun Token.toWallet(state: WalletState, blockchain: BlockchainNetwork): WalletDat currencyData = BalanceWidgetData( status = BalanceStatus.Loading, currency = this.name, - currencySymbol = this.symbol + currencySymbol = this.symbol, ), walletAddresses = walletAddresses, mainButton = WalletMainButton.SendButton(false), - currency = currency + 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 79e84eec36..68e6080883 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 @@ -17,6 +17,7 @@ import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.features.wallet.redux.WalletMainButton import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN +import com.tangem.tap.features.wallet.redux.replaceSomeWalletsData import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.features.wallet.ui.TokenData @@ -106,18 +107,15 @@ class OnWalletLoadedReducer { mainButton = WalletMainButton.SendButton(isTokenSendButtonEnabled), ) } - val newWallets = tokens + newWalletData - val wallets = walletState.replaceSomeWallets((newWallets)) + val newWalletsData = tokens + newWalletData + val walletsData = walletState.walletsDataFromStores.replaceSomeWalletsData(newWalletsData) - return walletState - .updateWalletsData(wallets) + return walletState.updateWalletsData(walletsData) } private fun onSingleWalletLoaded(wallet: Wallet, walletState: WalletState): WalletState { if (wallet.blockchain != walletState.primaryBlockchain) return walletState - // val ratesRespository = store.state.globalState.tapWalletManager.ratesRepository - // val tokenCurrency = val fiatCurrencyName = store.state.globalState.appCurrency.code val token = wallet.getFirstToken() val tokenData = if (token != null) { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt index 79470dae62..08a7def4bd 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt @@ -36,6 +36,7 @@ private fun List.mapToProgressState(): List { BalanceStatus.Unreachable, BalanceStatus.EmptyCard, BalanceStatus.UnknownBlockchain, + BalanceStatus.MissedDerivation, -> ProgressState.Error BalanceStatus.Loading, null, 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 61ea00554a..254fe48e3b 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 @@ -3,29 +3,38 @@ package com.tangem.tap.features.wallet.redux.reducers import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Wallet -import com.tangem.common.extensions.isZero import com.tangem.common.extensions.mapNotNullValues import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.domain.common.TwinCardNumber import com.tangem.tap.common.entities.FiatCurrency -import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.extensions.toFiatRateString +import com.tangem.tap.common.extensions.toFiatString +import com.tangem.tap.common.extensions.toFiatValue +import com.tangem.tap.common.extensions.toFormattedCurrencyString +import com.tangem.tap.common.extensions.toFormattedFiatValue import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.getArtworkUrl import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.domain.getFirstToken -import com.tangem.tap.domain.model.TotalFiatBalance -import com.tangem.tap.domain.model.WalletDataModel -import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.TotalBalance import com.tangem.tap.features.wallet.models.WalletRent -import com.tangem.tap.features.wallet.redux.* +import com.tangem.tap.features.wallet.redux.AddressData +import com.tangem.tap.features.wallet.redux.Artwork +import com.tangem.tap.features.wallet.redux.ErrorType +import com.tangem.tap.features.wallet.redux.ProgressState +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.features.wallet.redux.WalletAddresses +import com.tangem.tap.features.wallet.redux.WalletData +import com.tangem.tap.features.wallet.redux.WalletMainButton +import com.tangem.tap.features.wallet.redux.WalletState +import com.tangem.tap.features.wallet.redux.WalletStore +import com.tangem.tap.features.wallet.redux.replaceSomeWalletsData import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData -import com.tangem.tap.features.wallet.ui.TokenData import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.store import org.rekotlin.Action @@ -63,7 +72,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS is WalletAction.EmptyWallet -> { newState = newState.copy( state = ProgressState.Done, - wallets = listOf( + walletsStores = listOf( WalletStore( walletManager = null, blockchainNetwork = BlockchainNetwork( @@ -85,7 +94,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS is WalletAction.LoadData.Failure -> { when (action.error) { is TapError.NoInternetConnection -> { - val wallets = newState.wallets + val wallets = newState.walletsStores .map { store -> store.copy( walletsData = store.walletsData.map { @@ -95,20 +104,19 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS ), ) }, - - ) + ) } newState = newState.copy( state = ProgressState.Error, error = ErrorType.NoInternetConnection, - wallets = wallets, + walletsStores = wallets, ) } is TapError.UnknownBlockchain -> { newState = newState.copy( state = ProgressState.Done, - wallets = listOf( + walletsStores = listOf( WalletStore( walletManager = null, blockchainNetwork = BlockchainNetwork( @@ -153,7 +161,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS BalanceStatus.Loading } if (action.blockchain == null) { - val wallets = newState.wallets.map { walletStore -> + val wallets = newState.walletsStores.map { walletStore -> walletStore.copy( walletsData = walletStore.walletsData.map { walletData -> walletData.copy( @@ -170,7 +178,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS } newState = newState.copy( state = ProgressState.Loading, - wallets = wallets, + walletsStores = wallets, ) } else { val walletManager = newState.getWalletManager(action.blockchain) ?: return newState @@ -178,7 +186,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS walletManager.cardTokens.map { Currency.fromBlockchainNetwork(action.blockchain, it) } - val newWallets = newState.walletsData.filter { currencies.contains(it.currency) } + val newWalletsData = newState.walletsDataFromStores.filter { currencies.contains(it.currency) } .map { wallet -> wallet.copy( currencyData = wallet.currencyData.copy( @@ -189,8 +197,8 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS mainButton = WalletMainButton.SendButton(false), ) } - val wallets = newState.replaceSomeWallets(newWallets) - val walletStore = newState.getWalletStore(action.blockchain)?.updateWallets(wallets) + val walletsData = newState.walletsDataFromStores.replaceSomeWalletsData(newWalletsData) + val walletStore = newState.getWalletStore(action.blockchain)?.updateWallets(walletsData) newState = newState.updateWalletStore(walletStore) } } @@ -260,7 +268,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS newState = newState.updateWalletsData(updatedWallets) val progressState = - if (newState.walletsData.any { it.currencyData.status == BalanceStatus.Loading }) { + if (newState.walletsDataFromStores.any { it.currencyData.status == BalanceStatus.Loading }) { ProgressState.Loading } else { ProgressState.Done @@ -292,15 +300,13 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS } is WalletAction.TradeCryptoAction -> return newState is WalletAction.ChangeSelectedAddress -> { - val selectedWalletData = newState.getWalletData(newState.selectedCurrency) - - val walletAddresses = - newState.getWalletData(selectedWalletData?.currency)?.walletAddresses - ?: return newState + val walletAddresses = newState.getWalletData(newState.selectedCurrency)?.walletAddresses + ?: return newState val address = walletAddresses.list.firstOrNull { it.type == action.type } ?: return newState + newState = newState.updateWalletData( - selectedWalletData?.copy( + newState.selectedWalletData?.copy( walletAddresses = WalletAddresses( selectedAddress = address, list = walletAddresses.list, @@ -328,37 +334,41 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS val card = scanResponse.card newState = WalletState( cardId = card.cardId, - isMultiwalletAllowed = card.isMultiwalletAllowed, + isMultiwalletAllowed = isMultiCurrency, cardImage = Artwork( artworkId = artworkUrl, ), isTestnet = card.isTestCard, state = ProgressState.Loading, - wallets = newState.wallets, - showBackupWarning = card.isMultiwalletAllowed && + showBackupWarning = isMultiCurrency && card.settings.isBackupAllowed && card.backupStatus == CardDTO.BackupStatus.NoBackup, walletCardsCount = card.findCardsCount(), + totalBalance = if (isMultiCurrency) { + TotalBalance(ProgressState.Loading, BigDecimal.ZERO, store.state.globalState.appCurrency) + } else { + null + }, ) } - is WalletAction.WalletStoresChanged -> { + is WalletAction.WalletStoresChanged.UpdateWalletStores -> { newState = newState.copy( - wallets = action.walletStores.mapToReduxModel(newState.isMultiwalletAllowed), + walletsStores = action.reduxWalletStores, ) } is WalletAction.TotalFiatBalanceChanged -> { newState = newState.copy( - totalBalance = action.balance.mapToReduxModel(), + totalBalance = action.balance, ) } is WalletAction.LoadData.Success -> { val selectedCurrency = if (!newState.isMultiwalletAllowed) { - newState.wallets.firstOrNull() + newState.walletsStores.firstOrNull() ?.walletsData ?.firstOrNull() ?.currency } else { - newState.selectedCurrency + newState.selectedWalletData?.currency } newState = newState.copy( @@ -377,117 +387,6 @@ private fun CardDTO.findCardsCount(): Int? { ?.takeIf { this.isMultiwalletAllowed } } -@JvmName("walletStoreModelToReduxModel") -private fun List.mapToReduxModel( - isMultiWalletAllowed: Boolean, -): List { - return this.map { walletStoreModel -> - with(walletStoreModel) { - WalletStore( - walletManager = walletManager, - blockchainNetwork = blockchainNetwork, - walletsData = walletsData.mapToReduxModel(isMultiWalletAllowed, walletStoreModel.walletRent), - ) - } - } -} - -@JvmName("walletDataModelToReduxModel") -private fun List.mapToReduxModel( - isMultiWalletAllowed: Boolean, - walletRent: WalletStoreModel.WalletRent?, -): List { - return this.map { walletDataModel -> - with(walletDataModel) { - val amount = status.amount - val amountFormatted = amount.toFormattedCurrencyString( - decimals = currency.decimals, - currency = currency.currencySymbol, - ) - val appCurrency = store.state.globalState.appCurrency - val fiatAmount = fiatRate?.let { status.amount.toFiatValue(it) } - val fiatAmountFormatted = fiatAmount - ?.takeIf { !status.isErrorStatus } - ?.toFormattedFiatValue(appCurrency.symbol) - val fiatRateFormatted = fiatRate?.toFiatRateString(appCurrency.symbol) - - WalletData( - currency = currency, - walletAddresses = walletAddresses.getOrNull(0)?.let { selectedAddress -> - WalletAddresses( - selectedAddress = selectedAddress, - list = walletAddresses, - ) - }, - existentialDepositString = existentialDeposit?.toPlainString(), - fiatRate = fiatRate, - fiatRateString = fiatRateFormatted, - pendingTransactions = status.pendingTransactions, - mainButton = WalletMainButton.SendButton( - enabled = !status.amount.isZero() && status.pendingTransactions.isEmpty(), - ), - walletRent = walletRent?.let { - WalletRent( - minRentValue = "${it.rent.stripZeroPlainString()} ${currency.blockchain.currency}", - rentExemptValue = "${it.exemptionAmount.stripZeroPlainString()} ${currency.blockchain.currency}", - ) - }, - currencyData = BalanceWidgetData( - status = when (status) { - is WalletDataModel.Loading -> BalanceStatus.Loading - is WalletDataModel.NoAccount -> BalanceStatus.NoAccount - is WalletDataModel.Refreshing -> BalanceStatus.Refreshing - is WalletDataModel.SameCurrencyTransactionInProgress -> BalanceStatus.SameCurrencyTransactionInProgress - is WalletDataModel.TransactionInProgress -> BalanceStatus.TransactionInProgress - is WalletDataModel.Unreachable, - is WalletDataModel.MissedDerivation, - -> BalanceStatus.Unreachable - is WalletDataModel.VerifiedOnline -> BalanceStatus.VerifiedOnline - }, - currency = currency.currencyName, - currencySymbol = currency.currencySymbol, - blockchainAmount = status.amount, - amount = amount, - amountFormatted = amountFormatted, - fiatAmount = fiatAmount, - fiatAmountFormatted = fiatAmountFormatted, - token = when { - !isMultiWalletAllowed && currency is Currency.Token -> { - TokenData( - amount = amount, - amountFormatted = amountFormatted, - fiatAmount = fiatAmount, - fiatAmountFormatted = fiatAmountFormatted, - tokenSymbol = currency.currencySymbol, - fiatRate = fiatRate, - fiatRateString = fiatRateFormatted, - ) - } - else -> null - }, - amountToCreateAccount = (status as? WalletDataModel.NoAccount) - ?.amountToCreateAccount - ?.toString(), - errorMessage = status.errorMessage, - ), - ) - } - } -} - -private fun TotalFiatBalance.mapToReduxModel(): TotalBalance { - return TotalBalance( - state = when (this) { - is TotalFiatBalance.Loading -> ProgressState.Loading - is TotalFiatBalance.Refreshing -> ProgressState.Refreshing - is TotalFiatBalance.Error -> ProgressState.Error - is TotalFiatBalance.Loaded -> ProgressState.Done - }, - fiatAmount = amount, - fiatCurrency = store.state.globalState.appCurrency, - ) -} - fun createAddressList(wallet: Wallet?, walletAddresses: WalletAddresses? = null): WalletAddresses? { if (wallet == null) return null diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt index 71bdbb7b8c..4afc4c08db 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt @@ -16,7 +16,8 @@ enum class BalanceStatus { Refreshing, NoAccount, EmptyCard, - UnknownBlockchain + UnknownBlockchain, + MissedDerivation, } data class BalanceWidgetData( diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index d737ac736b..a7f230dc35 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -1,16 +1,23 @@ package com.tangem.tap.features.wallet.ui import android.os.Bundle -import android.view.* +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup import android.widget.TextView import androidx.activity.OnBackPressedCallback import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding +import com.badoo.mvicore.modelWatcher +import com.tangem.common.doOnResult import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.tangem_sdk_new.extensions.dpToPx import com.tangem.tap.common.SnackbarHandler @@ -18,26 +25,38 @@ import com.tangem.tap.common.TestActions import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.DetailsScreen import com.tangem.tap.common.analytics.events.Token -import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.extensions.appendIfNotNull +import com.tangem.tap.common.extensions.beginDelayedTransition +import com.tangem.tap.common.extensions.fitChipsByGroupWidth +import com.tangem.tap.common.extensions.getColor +import com.tangem.tap.common.extensions.getString +import com.tangem.tap.common.extensions.hide +import com.tangem.tap.common.extensions.show +import com.tangem.tap.common.extensions.toQrCode import com.tangem.tap.common.recyclerView.SpaceItemDecoration import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.onboarding.getQRReceiveMessage import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.PendingTransaction -import com.tangem.tap.features.wallet.redux.* +import com.tangem.tap.features.wallet.redux.ErrorType +import com.tangem.tap.features.wallet.redux.ProgressState +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.features.wallet.redux.WalletData +import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter import com.tangem.tap.features.wallet.ui.adapters.WalletDetailWarningMessagesAdapter import com.tangem.tap.features.wallet.ui.images.load import com.tangem.tap.features.wallet.ui.test.TestWallet -import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.userWalletsListManagerSafe import com.tangem.tap.walletCurrenciesManager import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentWalletDetailsBinding +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.rekotlin.StoreSubscriber class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), @@ -48,6 +67,42 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), private val binding: FragmentWalletDetailsBinding by viewBinding(FragmentWalletDetailsBinding::bind) + private val walletDataWatcher = modelWatcher { + WalletData::pendingTransactions { + showPendingTransactionsIfPresent(it) + } + WalletData::currency { + handleCurrencyIcon(it) + } + WalletData::currencyData { + setupBalanceData(it) + } + (WalletData::currencyData or WalletData::currency) { walletData -> + setupCurrency(walletData.currencyData, walletData.currency) + setupSwipeRefresh(walletData.currencyData, walletData.currency) + } + } + + private val walletStateWatcher = modelWatcher { + (WalletState::selectedCurrency or WalletState::selectedWalletData) { state -> + val selectedWalletData = state.selectedWalletData + if (selectedWalletData != null) { + walletDataWatcher.invoke(selectedWalletData) + setupButtons(selectedWalletData, state.isExchangeServiceFeatureOn) + setupAddressCard(selectedWalletData) + handleWarnings(selectedWalletData) + } + } + (WalletState::selectedCurrency or WalletState::isExchangeServiceFeatureOn) { state -> + if (state.selectedWalletData != null) { + setupButtons(state.selectedWalletData!!, state.isExchangeServiceFeatureOn) + } + } + (WalletState::state or WalletState::error) { state -> + setupNoInternetHandling(state.state, state.error) + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) @@ -79,6 +134,12 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), store.unsubscribe(this) } + override fun onDestroy() { + walletDataWatcher.clear() + walletStateWatcher.clear() + super.onDestroy() + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) (activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar) @@ -129,50 +190,10 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), override fun newState(state: WalletState) { if (activity == null || view == null) return - if (state.selectedCurrency == null) return - val selectedWallet = state.getSelectedWalletData() ?: return + if (state.selectedWalletData == null) return + walletStateWatcher.invoke(state) - - showPendingTransactionsIfPresent(selectedWallet.pendingTransactions) - setupCurrency(selectedWallet.currencyData, selectedWallet.currency) - setupAddressCard(selectedWallet) - setupNoInternetHandling(state) - setupBalanceData(selectedWallet.currencyData) - setupButtons(selectedWallet, state.isExchangeServiceFeatureOn) - - handleCurrencyIcon(selectedWallet) - handleWarnings(selectedWallet) updateViewMeasurements() - - binding.srlWalletDetails.setOnRefreshListener { - if (selectedWallet.currencyData.status != BalanceStatus.Loading) { - Analytics.send(Token.Refreshed()) - val blockchainNetwork = BlockchainNetwork( - blockchain = selectedWallet.currency.blockchain, - derivationPath = selectedWallet.currency.derivationPath, - tokens = emptyList(), - ) - val selectedUserWallet = userWalletsListManagerSafe?.selectedUserWalletSync - if (selectedUserWallet != null) scope.launch { - walletCurrenciesManager.update(selectedUserWallet, blockchainNetwork) - } else { - store.dispatch( - WalletAction.LoadWallet( - blockchain = BlockchainNetwork( - selectedWallet.currency.blockchain, - selectedWallet.currency.derivationPath, - emptyList(), - ), - ), - ) - store.dispatch(WalletAction.LoadFiatRate(coinsList = listOf(selectedWallet.currency))) - } - } - } - - if (selectedWallet.currencyData.status != BalanceStatus.Loading) { - binding.srlWalletDetails.isRefreshing = false - } } private fun updateViewMeasurements() { @@ -203,6 +224,37 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), } } + private fun setupSwipeRefresh(currencyData: BalanceWidgetData, currency: Currency) { + binding.srlWalletDetails.setOnRefreshListener { + if (currencyData.status != BalanceStatus.Loading && currencyData.status != BalanceStatus.Refreshing) { + Analytics.send(Token.Refreshed()) + lifecycleScope.launch(Dispatchers.Default) { + val selectedUserWallet = userWalletsListManagerSafe?.selectedUserWalletSync + if (selectedUserWallet != null) { + walletCurrenciesManager.update(selectedUserWallet, currency) + .doOnResult { + withContext(Dispatchers.Main) { + binding.srlWalletDetails.isRefreshing = false + } + } + } else { + val blockchainNetwork = BlockchainNetwork( + blockchain = currency.blockchain, + derivationPath = currency.derivationPath, + tokens = emptyList(), + ) + + store.dispatch(WalletAction.LoadWallet(blockchainNetwork)) + store.dispatch(WalletAction.LoadFiatRate(coinsList = listOf(currency))) + } + } + } + } + + binding.srlWalletDetails.isRefreshing = currencyData.status == BalanceStatus.Loading || + currencyData.status == BalanceStatus.Refreshing + } + private fun setupButtons(selectedWallet: WalletData, isExchangeServiceFeatureOn: Boolean) = with(binding) { lWalletDetails.btnCopy.setOnClickListener { selectedWallet.walletAddresses?.selectedAddress?.address?.let { addressString -> @@ -231,9 +283,9 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), rvWarningMessages.show(warningDetails.isNotEmpty()) } - private fun handleCurrencyIcon(wallet: WalletData) = with(binding.lWalletDetails.lBalance) { + private fun handleCurrencyIcon(currency: Currency) = with(binding.lWalletDetails.lBalance) { ivCurrency.load( - currency = wallet.currency, + currency = currency, derivationStyle = store.state.globalState .scanResponse ?.card @@ -281,9 +333,9 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), } } - private fun setupNoInternetHandling(state: WalletState) { - if (state.state == ProgressState.Error) { - if (state.error == ErrorType.NoInternetConnection) { + private fun setupNoInternetHandling(progressState: ProgressState, errorType: ErrorType?) { + if (progressState == ProgressState.Error) { + if (errorType == ErrorType.NoInternetConnection) { binding.srlWalletDetails.isRefreshing = false (activity as? SnackbarHandler)?.showSnackbar( text = R.string.wallet_notification_no_internet, @@ -350,7 +402,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { R.id.menu_remove -> { - store.state.walletState.getSelectedWalletData()?.let { walletData -> + store.state.walletState.selectedWalletData?.let { walletData -> store.dispatch(WalletAction.MultiWallet.TryToRemoveWallet(walletData.currency)) true } 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 962c7cd390..c0bde194bf 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 @@ -15,6 +15,7 @@ import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding import coil.load import coil.size.Scale +import com.tangem.core.ui.fragments.setStatusBarColor import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.tap.MainActivity import com.tangem.tap.common.analytics.Analytics @@ -80,15 +81,18 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber state.select { it.walletState } } walletView.setFragment(this, binding) - viewModel.launch() } override fun onStop() { @@ -97,6 +101,11 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber(D BalanceStatus.Unreachable -> { root.getString(R.string.wallet_balance_blockchain_unreachable) } + BalanceStatus.MissedDerivation -> { + root.getString(R.string.wallet_balance_missing_derivation) + } else -> null } @@ -95,7 +98,7 @@ class WalletAdapter : ListAdapter(D if (wallet.walletAddresses != null) { cardWallet.setOnClickListener { Analytics.send(Portfolio.TokenTapped()) - store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet)) + store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet.currency)) } } else { cardWallet.setOnClickListener(null) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWalletDetailsActions.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWalletDetailsActions.kt index 4f384d33f7..857cdd2631 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWalletDetailsActions.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWalletDetailsActions.kt @@ -156,7 +156,7 @@ private class SolanaRentWarningActionEmitter { } private fun getBlockchainNetwork(): BlockchainNetwork { - val currency = store.state.walletState.getSelectedWalletData()!!.currency + val currency = store.state.walletState.selectedWalletData!!.currency return BlockchainNetwork(currency.blockchain, currency.derivationPath, listOf()) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index 1ceda1b7b4..f7c8dc8a66 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -3,6 +3,8 @@ package com.tangem.tap.features.wallet.ui.wallet import android.widget.Button import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager +import com.badoo.mvicore.DiffStrategy +import com.badoo.mvicore.modelWatcher import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.tap.common.analytics.Analytics @@ -33,6 +35,45 @@ class MultiWalletView : WalletView() { private lateinit var walletsAdapter: WalletAdapter + private val watcher = modelWatcher { + val totalBalanceStrategy: DiffStrategy = { old, new -> + old.cardId != new.cardId || + old.totalBalance != new.totalBalance || + old.state != new.state || + old.walletsStores.size != new.walletsStores.size + } + + // !!! Workaround !!! + // Checking state properties instead of state params can reduce application performance, + // but here it is necessary because the WalletStore has an unsuitable equals method + WalletState::walletsDataFromStores { + walletsAdapter.submitList(it) + } + WalletState::loadingUserTokens { + binding?.pbLoadingUserTokens?.show(it) + } + WalletState::walletCardsCount { walletCardsCount -> + binding?.let { + setupWalletCardNumber(it, walletCardsCount) + } + } + WalletState::missingDerivations { missingDerivations -> + binding?.let { + handleRescanWarning(it, missingDerivations.isNotEmpty()) + } + } + WalletState::showBackupWarning { showBackupWarnings -> + binding?.let { + handleBackupWarning(it, showBackupWarnings) + } + } + watch({ it }, totalBalanceStrategy) { walletState -> + binding?.let { + handleTotalBalance(it, walletState.totalBalance, walletState.state, walletState.walletsDataFromStores.size) + } + } + } + override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) { setFragment(fragment, binding) onViewCreated() @@ -40,6 +81,7 @@ class MultiWalletView : WalletView() { } private fun showMultiWalletView(binding: FragmentWalletBinding) = with(binding) { + watcher.clear() tvTwinCardNumber.hide() rvPendingTransaction.hide() lCardBalance.root.hide() @@ -68,13 +110,7 @@ class MultiWalletView : WalletView() { val fragment = fragment ?: return val binding = binding ?: return - handleTotalBalance(binding, state.totalBalance, state.state, state.walletsData.size) - handleBackupWarning(binding, state.showBackupWarning) - handleRescanWarning(binding, state.missingDerivations.isNotEmpty()) - setupWalletCardNumber(binding, state.walletCardsCount) - walletsAdapter.submitList(state.walletsData) - - binding.pbLoadingUserTokens.show(state.loadingUserTokens) + watcher.invoke(state) binding.btnAddToken.setOnClickListener { val card = store.state.globalState.scanResponse!!.card @@ -91,7 +127,7 @@ class MultiWalletView : WalletView() { store.dispatch(TokensAction.AllowToAddTokens(true)) store.dispatch( TokensAction.SetAddedCurrencies( - wallets = state.walletsData, + wallets = state.walletsDataFromStores, derivationStyle = card.derivationStyle, ), ) @@ -139,21 +175,26 @@ class MultiWalletView : WalletView() { walletsCount: Int, ) = with(binding.lCardTotalBalance) { if (walletsCount == 0) { - root.isVisible = false + if (progressState != ProgressState.Loading) { + root.isVisible = false + } } else { if (totalBalance == null) { - veilBalance.animateVisibility(show = true) - root.isVisible = progressState == ProgressState.Loading + if (progressState != ProgressState.Loading) { + root.isVisible = false + } } else { root.isVisible = true - // Skip changes when on refreshing state if (totalBalance.state == ProgressState.Refreshing || progressState == ProgressState.Refreshing) { return@with } - veilBalance.animateVisibility(show = totalBalance.state == ProgressState.Loading) - tvBalance.animateVisibility(show = totalBalance.state != ProgressState.Loading) + if (totalBalance.state == ProgressState.Loading) { + veilBalance.veil() + } else { + veilBalance.unVeil() + } tvProcessing.animateVisibility(show = totalBalance.state == ProgressState.Error) tvBalance.text = totalBalance.fiatAmount.formatAmountAsSpannedString( @@ -218,6 +259,11 @@ class MultiWalletView : WalletView() { rowButtons.btnSend.text = fragment?.getText(R.string.wallet_button_create_wallet) rowButtons.onSendClick = { store.dispatch(WalletAction.CreateWallet) } } + + override fun onDestroyFragment() { + super.onDestroyFragment() + watcher.clear() + } } private val WalletDetailsButtonsRow.btnBuy: Button diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt index e765e738e8..44ff7329fc 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt @@ -52,4 +52,4 @@ class SaltPaySingleWalletView : WalletView() { ), ).setup() } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index abca20330d..e8fc90a242 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -153,14 +153,12 @@ class SingleWalletView : WalletView() { (binding.lAddress.root as? ViewGroup)?.beginDelayedTransition() chipGroupAddressType.show() chipGroupAddressType.fitChipsByGroupWidth() - val checkedId = - MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type) + val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type) if (checkedId != View.NO_ID) chipGroupAddressType.check(checkedId) chipGroupAddressType.setOnCheckedChangeListener { group, checkedId -> if (checkedId == -1) return@setOnCheckedChangeListener - val type = - MultipleAddressUiHelper.idToType(checkedId, state.currency.blockchain) + val type = MultipleAddressUiHelper.idToType(checkedId, state.currency.blockchain) type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) } } } else { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt index a3b09aab12..d7d7cbb405 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt @@ -17,6 +17,8 @@ abstract class WalletView { binding = null } + open fun onDestroyFragment() {} + abstract fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) abstract fun onViewCreated() abstract fun onNewState(state: WalletState) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt index 1417914782..b381b423c3 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt @@ -1,9 +1,10 @@ package com.tangem.tap.features.walletSelector.redux +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.model.TotalFiatBalance data class UserWalletModel( - val id: String, + val id: UserWalletId, val name: String, val artworkUrl: String, val type: Type, diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt index 957661126f..0985f4b68c 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt @@ -34,20 +34,16 @@ internal sealed interface WalletSelectorAction : Action { } data class SelectWallet( - val walletId: String, - ) : WalletSelectorAction - - data class UnlockWalletWithCard( - val walletId: String, + val userWalletId: UserWalletId, ) : WalletSelectorAction data class RenameWallet( - val walletId: String, + val userWalletId: UserWalletId, val newName: String, ) : WalletSelectorAction data class RemoveWallets( - val walletIdsToRemove: List, + val userWalletsIds: List, ) : WalletSelectorAction object AddWallet : WalletSelectorAction { diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index dd4c4ff88b..b6b31419de 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -19,13 +19,16 @@ import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.scanCard.ScanCardProcessor -import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager import com.tangem.tap.totalFiatBalanceCalculator import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletStoresManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import org.rekotlin.Middleware import timber.log.Timber @@ -59,18 +62,17 @@ internal class WalletSelectorMiddleware { addWallet() } is WalletSelectorAction.SelectWallet -> { - selectWallet(action.walletId) - } - is WalletSelectorAction.UnlockWalletWithCard -> { - unlockWalletWithCard(action.walletId) + selectWallet(action.userWalletId) } is WalletSelectorAction.RemoveWallets -> { - removeWallets(action.walletIdsToRemove, state) + deleteWallets(action.userWalletsIds, state) } is WalletSelectorAction.RenameWallet -> { - renameWallet(action.walletId, action.newName) + renameWallet(action.userWalletId, action.newName) + } + is WalletSelectorAction.ChangeAppCurrency -> { + refreshUserWalletsAmounts() } - is WalletSelectorAction.ChangeAppCurrency, is WalletSelectorAction.AddWallet.Success, is WalletSelectorAction.AddWallet.Error, is WalletSelectorAction.SelectedWalletChanged, @@ -96,13 +98,17 @@ internal class WalletSelectorMiddleware { private fun updateBalances(walletStores: Map>, state: WalletSelectorState) { walletStores.forEach { (walletId, walletStores) -> - scope.launch { - val updatedWallet = state.wallets - .find { it.id == walletId.stringValue } - ?.updateWalletStoresAndCalculateFiatBalance(walletStores) + scope.launch(Dispatchers.Default) { + val foundWallet = state.wallets + .find { it.id == walletId } - if (updatedWallet != null) { - store.dispatchOnMain(WalletSelectorAction.BalanceLoaded(updatedWallet)) + if (foundWallet != null) { + val updatedWallet = foundWallet + .updateWalletStoresAndCalculateFiatBalance(walletStores) + + if (foundWallet != updatedWallet) { + store.dispatchOnMain(WalletSelectorAction.BalanceLoaded(updatedWallet)) + } } } } @@ -135,45 +141,38 @@ internal class WalletSelectorMiddleware { .doOnSuccess { Analytics.send(MyWallets.CardWasScanned) - userWalletsListManager.selectWallet(userWallet.walletId) store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) - updateAccessCodeRequestPolicy(userWallet) store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) store.onUserWalletSelected(userWallet) } } } - private fun selectWallet(id: String) { + private fun selectWallet(userWalletId: UserWalletId) { scope.launch { - userWalletsListManager.selectWallet(UserWalletId(id)) - .doOnFailure { error -> - store.dispatchOnMain(WalletSelectorAction.HandleError(error)) - } - .doOnSuccess { selectedWallet -> - updateAccessCodeRequestPolicy(selectedWallet) - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) - store.onUserWalletSelected(selectedWallet) - } - } - } - - private fun unlockWalletWithCard(id: String) { - scope.launch { - userWalletsListManager.get(UserWalletId(id)) + userWalletsListManager.get(userWalletId) .flatMap { userWallet -> - updateUserWalletWithScannedCard(userWallet) - } - .flatMap { updatedUserWallet -> - unlockUserWallet(updatedUserWallet) + if (userWallet.isLocked) { + unlockUserWalletWithScannedCard(userWallet) + } else { + userWalletsListManager.selectWallet(userWalletId) + } } .doOnFailure { error -> store.dispatchOnMain(WalletSelectorAction.HandleError(error)) } + .doOnSuccess { + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync + if (selectedUserWallet != null) { + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + store.onUserWalletSelected(selectedUserWallet) + } + } } } - private suspend fun updateUserWalletWithScannedCard(userWallet: UserWallet): CompletionResult { + private suspend fun unlockUserWalletWithScannedCard(userWallet: UserWallet): CompletionResult { + tangemSdkManager.changeDisplayedCardIdNumbersCount(userWallet.scanResponse) return tangemSdkManager.scanCard(userWallet.cardId) .map { scannedCard -> userWallet.copy( @@ -182,23 +181,26 @@ internal class WalletSelectorMiddleware { ), ) } - } - - private suspend fun unlockUserWallet(userWallet: UserWallet): CompletionResult { - return userWalletsListManager.unlockWithCard(userWallet) - .doOnSuccess { - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) - store.onUserWalletSelected(userWallet) + .flatMap { updatedUserWallet -> + userWalletsListManager.save(updatedUserWallet, canOverride = true) + } + .doOnFailure { + tangemSdkManager.changeDisplayedCardIdNumbersCount( + scanResponse = userWalletsListManager.selectedUserWalletSync?.scanResponse, + ) } } - private fun removeWallets(walletIdsToRemove: List, state: WalletSelectorState) { + private fun deleteWallets(userWalletsIds: List, state: WalletSelectorState) { Analytics.send(MyWallets.Button.DeleteWalletTapped) scope.launch { - when (walletIdsToRemove.size) { + when (userWalletsIds.size) { state.wallets.size -> clearUserWallets() - else -> removeUserWallets(walletIdsToRemove, state) + else -> deleteUserWallets( + userWalletsIds = userWalletsIds, + currentSelectedWalletId = state.selectedWalletId, + ) } .doOnFailure { error -> store.dispatchOnMain(WalletSelectorAction.HandleError(error)) @@ -206,11 +208,11 @@ internal class WalletSelectorMiddleware { } } - private fun renameWallet(walletId: String, newName: String) { + private fun renameWallet(userWalletId: UserWalletId, newName: String) { Analytics.send(MyWallets.Button.EditWalletTapped) scope.launch { - userWalletsListManager.get(walletId = UserWalletId(walletId)) + userWalletsListManager.get(userWalletId) .map { it.copy(name = newName) } .flatMap { userWalletsListManager.save(it, canOverride = true) } .doOnFailure { error -> @@ -219,6 +221,50 @@ internal class WalletSelectorMiddleware { } } + private fun refreshUserWalletsAmounts() { + scope.launch { + walletStoresManager.updateAmounts( + userWallets = userWalletsListManager.userWallets.first(), + ) + .doOnFailure { error -> + store.dispatchOnMain(WalletSelectorAction.HandleError(error)) + } + } + } + + private suspend fun clearUserWallets(): CompletionResult { + return userWalletsListManager.clear() + .flatMap { walletStoresManager.clear() } + .flatMap { tangemSdkManager.clearSavedUserCodes() } + .doOnSuccess { + // !!! Workaround !!! + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + delay(timeMillis = 280) + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) + } + } + + private suspend fun deleteUserWallets( + userWalletsIds: List, + currentSelectedWalletId: UserWalletId?, + ): CompletionResult { + return userWalletsListManager.delete(userWalletsIds) + .flatMap { walletStoresManager.delete(userWalletsIds) } + .flatMap { deleteAccessCodes(userWalletsIds) } + .doOnSuccess { + val selectedWallet = userWalletsListManager.selectedUserWalletSync + + when { + selectedWallet == null -> { + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) + } + currentSelectedWalletId != selectedWallet.walletId -> { + store.onUserWalletSelected(selectedWallet) + } + } + } + } + private suspend inline fun scanCardInternal( crossinline onCardScanned: suspend (ScanResponse) -> Unit, ) { @@ -236,37 +282,13 @@ internal class WalletSelectorMiddleware { ) } - private suspend fun clearUserWallets(): CompletionResult { - return userWalletsListManager.clear() - .flatMap { walletStoresManager.clear() } - .doOnSuccess { - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) - } - } + private suspend fun deleteAccessCodes(userWalletsIds: List): CompletionResult { + val cardsIds = userWalletsListManager.userWallets.firstOrNull().orEmpty() + .asSequence() + .filter { it.walletId in userWalletsIds } + .flatMap { it.cardsInWallet } - private suspend fun removeUserWallets( - walletIdsToRemove: List, - state: WalletSelectorState, - ): CompletionResult { - val prevSelectedWalletId = state.selectedWalletId - return userWalletsListManager.delete(walletIdsToRemove.map { UserWalletId(it) }) - .flatMap { walletStoresManager.delete(walletIdsToRemove) } - .doOnSuccess { - val selectedWallet = userWalletsListManager.selectedUserWalletSync ?: return@doOnSuccess - val isSelectedWalletRemoved = prevSelectedWalletId != selectedWallet.walletId.stringValue - - if (isSelectedWalletRemoved) { - updateAccessCodeRequestPolicy(selectedWallet) - store.onUserWalletSelected(selectedWallet) - } - } - } - - private fun updateAccessCodeRequestPolicy(userWallet: UserWallet) { - tangemSdkManager.setAccessCodeRequestPolicy( - useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && - userWallet.hasAccessCode, - ) + return tangemSdkManager.deleteSavedUserCodes(cardsIds.toSet()) } private suspend fun UserWalletModel.updateWalletStoresAndCalculateFiatBalance( diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt index 959c45c8d9..b64fe0a3ce 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt @@ -1,9 +1,7 @@ package com.tangem.tap.features.walletSelector.redux import com.tangem.domain.common.CardDTO -import com.tangem.tap.common.extensions.replaceByOrAdd import com.tangem.tap.common.redux.AppState -import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.domain.model.TotalFiatBalance import com.tangem.tap.domain.model.UserWallet import org.rekotlin.Action @@ -21,7 +19,7 @@ internal object WalletSelectorReducer { wallets = action.userWallets.updateWalletsModels(state.wallets), ) is WalletSelectorAction.SelectedWalletChanged -> state.copy( - selectedWalletId = action.selectedWallet.walletId.stringValue, + selectedWalletId = action.selectedWallet.walletId, ) is WalletSelectorAction.IsLockedChanged -> state.copy( isLocked = action.isLocked, @@ -56,7 +54,6 @@ internal object WalletSelectorReducer { ) is WalletSelectorAction.WalletStoresChanged, is WalletSelectorAction.SelectWallet, - is WalletSelectorAction.UnlockWalletWithCard, is WalletSelectorAction.RemoveWallets, is WalletSelectorAction.RenameWallet, -> state @@ -66,7 +63,7 @@ internal object WalletSelectorReducer { private fun List.updateWalletsModels(prevWallets: List): List { return this.map { userWallet -> prevWallets - .find { it.id == userWallet.walletId.stringValue } + .find { it.id == userWallet.walletId } ?.let { it.copy( name = userWallet.name, @@ -77,7 +74,7 @@ internal object WalletSelectorReducer { } ?: with(userWallet) { UserWalletModel( - id = walletId.stringValue, + id = walletId, name = name, artworkUrl = artworkUrl, type = getType(), @@ -92,12 +89,17 @@ internal object WalletSelectorReducer { userWalletModel: UserWalletModel, ): List { return ArrayList(this).apply { - replaceByOrAdd(userWalletModel) { it.id == userWalletModel.id } + val index = indexOfFirst { it.id == userWalletModel.id } + if (index == -1) { + add(userWalletModel) + } else { + this[index] = userWalletModel + } } } private fun UserWallet.getType(prevType: UserWalletModel.Type? = null): UserWalletModel.Type { - return if (scanResponse.card.isMultiwalletAllowed) { + return if (isMultiCurrency) { UserWalletModel.Type.MultiCurrency( cardsInWallet = (scanResponse.card.backupStatus as? CardDTO.BackupStatus.Active) ?.cardCount?.inc() diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorState.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorState.kt index df6ec0867d..797cc04593 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorState.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorState.kt @@ -1,12 +1,13 @@ package com.tangem.tap.features.walletSelector.redux import com.tangem.common.core.TangemError +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.common.entities.FiatCurrency import org.rekotlin.StateType data class WalletSelectorState( val wallets: List = emptyList(), - val selectedWalletId: String? = null, + val selectedWalletId: UserWalletId? = null, val isLocked: Boolean = false, val fiatCurrency: FiatCurrency = FiatCurrency.Default, val isCardSavingInProgress: Boolean = false, diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt index 6694b8b663..e5dc088a55 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt @@ -31,8 +31,8 @@ internal fun WalletSelectorScreenState.updateWithNewState( return this.copy( multiCurrencyWallets = multiCurrencyWallets, singleCurrencyWallets = singleCurrencyWallets, - selectedWalletId = newState.selectedWalletId, - editingWalletsIds = editingWalletsIds.filter { it in walletsIds }, + selectedUserWalletId = newState.selectedWalletId, + editingUserWalletsIds = editingUserWalletsIds.filter { it in walletsIds }, isLocked = newState.isLocked, showUnlockProgress = newState.isUnlockInProgress, showAddCardProgress = newState.isCardSavingInProgress, @@ -70,7 +70,7 @@ private fun List.toUiModels( imageUrl = artworkUrl, balance = balance, isLocked = isLocked, - tokenName = type.blockchainName ?: "—", + tokenName = type.blockchainName, ) } } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt index 717f82ed16..4db33980de 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt @@ -7,7 +7,13 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.SnackbarHost import androidx.compose.material.SnackbarHostState -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier @@ -39,18 +45,12 @@ internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment = emptyList(), val singleCurrencyWallets: List = emptyList(), - val selectedWalletId: String? = null, + val selectedUserWalletId: UserWalletId? = null, val isLocked: Boolean = false, - val editingWalletsIds: List = listOf(), + val editingUserWalletsIds: List = listOf(), val renameWalletDialog: RenameWalletDialog? = null, val showAddCardProgress: Boolean = false, val showUnlockProgress: Boolean = false, diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt index a7eaa3c4f4..1192b37c4a 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt @@ -2,6 +2,7 @@ package com.tangem.tap.features.walletSelector.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction import com.tangem.tap.features.walletSelector.redux.WalletSelectorState @@ -35,40 +36,38 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber { - store.dispatch(WalletSelectorAction.UnlockWalletWithCard(walletId)) + editingUserWalletsIds.isNotEmpty() && isWalletLocked(userWalletId, this) -> Unit + editingUserWalletsIds.isNotEmpty() && !editingUserWalletsIds.contains(userWalletId) -> { + editWallet(userWalletId) } - editingWalletsIds.isNotEmpty() && !editingWalletsIds.contains(walletId) -> { - editWallet(walletId) + editingUserWalletsIds.isNotEmpty() && editingUserWalletsIds.contains(userWalletId) -> { + cancelWalletEditing(userWalletId) } - editingWalletsIds.isNotEmpty() && editingWalletsIds.contains(walletId) -> { - cancelWalletEditing(walletId) - } - selectedWalletId != walletId -> { - store.dispatch(WalletSelectorAction.SelectWallet(walletId)) + selectedUserWalletId != userWalletId -> { + store.dispatch(WalletSelectorAction.SelectWallet(userWalletId)) } } } - fun walletLongClicked(walletId: String) = with(state.value) { - if (!isLocked && editingWalletsIds.isEmpty()) { - editWallet(walletId) + fun walletLongClicked(userWalletId: UserWalletId) = with(state.value) { + if (!isWalletLocked(userWalletId, this) && editingUserWalletsIds.isEmpty()) { + editWallet(userWalletId) } } fun cancelWalletsEditing() { stateInternal.update { prevState -> prevState.copy( - editingWalletsIds = emptyList(), + editingUserWalletsIds = emptyList(), ) } } fun renameWallet() = with(state.value) { - if (editingWalletsIds.isNotEmpty() && renameWalletDialog == null) { - val editedWalletId = editingWalletsIds.first() + if (editingUserWalletsIds.isNotEmpty() && renameWalletDialog == null) { + val editedWalletId = editingUserWalletsIds.first() val editedWallet = (multiCurrencyWallets + singleCurrencyWallets) .find { it.id == editedWalletId } @@ -80,7 +79,7 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber prevState.copy( renameWalletDialog = null, - editingWalletsIds = emptyList(), + editingUserWalletsIds = emptyList(), ) } }, @@ -103,8 +102,8 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber prevState.copy( - editingWalletsIds = prevState.editingWalletsIds + walletId, + editingUserWalletsIds = prevState.editingUserWalletsIds + userWalletId, ) } } - private fun cancelWalletEditing(walletId: String) { + private fun cancelWalletEditing(userWalletId: UserWalletId) { stateInternal.update { prevState -> prevState.copy( - editingWalletsIds = prevState.editingWalletsIds - walletId, + editingUserWalletsIds = prevState.editingUserWalletsIds - userWalletId, ) } } + private fun isWalletLocked(userWalletId: UserWalletId, state: WalletSelectorScreenState): Boolean = with(state) { + multiCurrencyWallets.find { it.id == userWalletId }?.isLocked + ?: singleCurrencyWallets.find { it.id == userWalletId }?.isLocked ?: isLocked + } + private fun subscribeToStoreChanges() { store.subscribe(this) { appState -> appState.skip { old, new -> old.walletSelectorState == new.walletSelectorState } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt index 2fab91adc1..65349386dc 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.walletSelector.ui.components +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.features.walletSelector.ui.WalletSelectorScreenState import com.tangem.tap.features.walletSelector.ui.model.MultiCurrencyUserWalletItem import com.tangem.tap.features.walletSelector.ui.model.SingleCurrencyUserWalletItem @@ -7,7 +8,7 @@ import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem internal object MockData { private val multiCurrencyUserWallet = MultiCurrencyUserWalletItem( - id = "wallet_1", + id = UserWalletId("wallet_1"), balance = UserWalletItem.Balance( amount = "6781.05 $", isLoading = false, @@ -20,7 +21,7 @@ internal object MockData { ) private val singleCurrencyUserWallet = SingleCurrencyUserWalletItem( - id = "wallet_4", + id = UserWalletId("wallet_4"), balance = UserWalletItem.Balance( amount = "6781.05 $", isLoading = false, @@ -34,11 +35,11 @@ internal object MockData { val state = WalletSelectorScreenState( multiCurrencyWallets = listOf( multiCurrencyUserWallet, - multiCurrencyUserWallet.copy(id = "wallet_2"), - multiCurrencyUserWallet.copy(id = "wallet_3", tokensCount = 2, cardsInWallet = 1), + multiCurrencyUserWallet.copy(id = UserWalletId("wallet_2")), + multiCurrencyUserWallet.copy(id = UserWalletId("wallet_3"), tokensCount = 2, cardsInWallet = 1), ), singleCurrencyWallets = listOf(singleCurrencyUserWallet), - selectedWalletId = multiCurrencyUserWallet.id, + selectedUserWalletId = multiCurrencyUserWallet.id, isLocked = false, ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt index 136f786705..23ea8c4c9e 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt @@ -1,8 +1,8 @@ package com.tangem.tap.features.walletSelector.ui.components +import androidx.annotation.StringRes import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -11,8 +11,8 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.Text @@ -23,61 +23,84 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.SecondaryButtonIconRight -import com.tangem.core.ui.components.SpacerH16 -import com.tangem.core.ui.components.SpacerH24 import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.components.atoms.Hand import com.tangem.core.ui.res.TangemTheme -import com.tangem.tap.features.details.ui.cardsettings.resolveReference +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.features.walletSelector.ui.WalletSelectorScreenState -import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem import com.tangem.wallet.R +@OptIn(ExperimentalFoundationApi::class) @Composable internal fun WalletSelectorScreenContent( - modifier: Modifier = Modifier, state: WalletSelectorScreenState, - onWalletClick: (walletId: String) -> Unit, - onWalletLongClick: (walletId: String) -> Unit, + onWalletClick: (UserWalletId) -> Unit, + onWalletLongClick: (UserWalletId) -> Unit, onUnlockClick: () -> Unit, onAddCardClick: () -> Unit, onClearSelectedClick: () -> Unit, onEditSelectedWalletClick: () -> Unit, onDeleteSelectedWalletsClick: () -> Unit, ) { - Column(modifier = modifier) { - Header( - modifier = Modifier - .padding(horizontal = TangemTheme.dimens.spacing16) - .fillMaxWidth(), - editingWalletsIds = state.editingWalletsIds, - onClearSelectedClick = onClearSelectedClick, - onEditSelectedWalletClick = onEditSelectedWalletClick, - onDeleteSelectedWalletsClick = onDeleteSelectedWalletsClick, - ) - Column( - modifier = Modifier - .verticalScroll( - state = rememberScrollState(), - ), - ) { - WalletsList( - multiCurrencyWallets = state.multiCurrencyWallets, - singleCurrencyWallets = state.singleCurrencyWallets, - selectedWalletId = state.selectedWalletId, - checkedWalletIds = state.editingWalletsIds, + LazyColumn { + stickyHeader { + Header( + editingWalletsIds = state.editingUserWalletsIds, + onClearSelectedClick = onClearSelectedClick, + onEditSelectedWalletClick = onEditSelectedWalletClick, + onDeleteSelectedWalletsClick = onDeleteSelectedWalletsClick, + ) + } + + item { + WalletsTitle(textResId = R.string.user_wallet_list_multi_header, wallets = state.multiCurrencyWallets) + } + + itemsIndexed( + items = state.multiCurrencyWallets, + key = { _, wallet -> wallet.id.stringValue }, + ) { _, wallet -> + + WalletItem( + wallet = wallet, + isSelected = remember(state.selectedUserWalletId) { wallet.id == state.selectedUserWalletId }, + isChecked = remember(state.editingUserWalletsIds) { wallet.id in state.editingUserWalletsIds }, onWalletClick = onWalletClick, onWalletLongClick = onWalletLongClick, ) - SpacerH24() + } + + item { + WalletsTitle(textResId = R.string.user_wallet_list_single_header, wallets = state.singleCurrencyWallets) + } + + itemsIndexed( + items = state.singleCurrencyWallets, + key = { _, wallet -> wallet.id.stringValue }, + ) { _, wallet -> + WalletItem( + wallet = wallet, + isSelected = remember(state.selectedUserWalletId) { wallet.id == state.selectedUserWalletId }, + isChecked = remember(state.editingUserWalletsIds) { wallet.id in state.editingUserWalletsIds }, + onWalletClick = onWalletClick, + onWalletLongClick = onWalletLongClick, + ) + } + + item { Footer( modifier = Modifier + .padding( + top = dimensionResource(id = R.dimen.spacing24), + bottom = dimensionResource(id = R.dimen.spacing16), + ) .padding(horizontal = TangemTheme.dimens.spacing16) .fillMaxWidth(), isLocked = state.isLocked, @@ -86,25 +109,28 @@ internal fun WalletSelectorScreenContent( onUnlockClick = onUnlockClick, onAddCardClick = onAddCardClick, ) - SpacerH16() } } } @Composable private fun Header( - modifier: Modifier = Modifier, - editingWalletsIds: List, + editingWalletsIds: List, onClearSelectedClick: () -> Unit, onEditSelectedWalletClick: () -> Unit, onDeleteSelectedWalletsClick: () -> Unit, ) { val editingWalletsSize by rememberUpdatedState(newValue = editingWalletsIds.size) - val hasEditingWallets by remember { - derivedStateOf { editingWalletsSize > 0 } - } + val hasEditingWallets by remember { derivedStateOf { editingWalletsSize > 0 } } - Column(modifier = modifier) { + Column( + modifier = Modifier + .fillMaxWidth() + .background( + color = TangemTheme.colors.background.plain, + shape = TangemTheme.shapes.bottomSheet, + ), + ) { Hand() Box( modifier = Modifier @@ -114,7 +140,9 @@ private fun Header( ) { if (hasEditingWallets) { EditWalletsBar( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), editingWalletsSize = editingWalletsSize, onClearSelectedClick = onClearSelectedClick, onEditSelectedWalletClick = onEditSelectedWalletClick, @@ -132,44 +160,15 @@ private fun Header( } } -@OptIn(ExperimentalFoundationApi::class) @Composable -private fun WalletsList( - modifier: Modifier = Modifier, - multiCurrencyWallets: List, - singleCurrencyWallets: List, - selectedWalletId: String?, - checkedWalletIds: List, - onWalletClick: (walletId: String) -> Unit, - onWalletLongClick: (walletId: String) -> Unit, -) { - Column(modifier = modifier) { - val walletsSection = @Composable { wallets: List -> - wallets.forEachIndexed { index, wallet -> - if (index == 0) { - Text( - modifier = Modifier.padding(start = TangemTheme.dimens.spacing16), - text = wallet.headerText.resolveReference(), - style = TangemTheme.typography.subtitle2, - color = TangemTheme.colors.text.tertiary, - ) - } - WalletItem( - modifier = Modifier - .combinedClickable( - onClick = { onWalletClick(wallet.id) }, - onLongClick = { onWalletLongClick(wallet.id) }, - ) - .padding(all = TangemTheme.dimens.spacing16), - wallet = wallet, - isSelected = wallet.id == selectedWalletId, - isChecked = wallet.id in checkedWalletIds, - ) - } - } - - walletsSection(multiCurrencyWallets) - walletsSection(singleCurrencyWallets) +private fun WalletsTitle(@StringRes textResId: Int, wallets: List<*>) { + if (wallets.isNotEmpty()) { + Text( + modifier = Modifier.padding(start = TangemTheme.dimens.spacing16), + text = stringResource(id = textResId), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + ) } } @@ -278,11 +277,6 @@ private fun WalletSelectorScreenContentSample( .background(color = TangemTheme.colors.background.primary), ) { WalletSelectorScreenContent( - modifier = Modifier - .background( - color = TangemTheme.colors.background.plain, - shape = TangemTheme.shapes.bottomSheet, - ), state = MockData.state.copy(isLocked = true), onWalletClick = { /* no-op */ }, onWalletLongClick = { /* no-op */ }, @@ -320,13 +314,8 @@ private fun WalletSelectorScreenContent_EditWallets_Sample( .background(TangemTheme.colors.background.primary), ) { WalletSelectorScreenContent( - modifier = Modifier - .background( - color = TangemTheme.colors.background.plain, - shape = TangemTheme.shapes.bottomSheet, - ), state = MockData.state - .copy(editingWalletsIds = listOf(MockData.state.multiCurrencyWallets[2].id)), + .copy(editingUserWalletsIds = listOf(MockData.state.multiCurrencyWallets[2].id)), onWalletClick = { /* no-op */ }, onWalletLongClick = { /* no-op */ }, onUnlockClick = { /* no-op */ }, diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt index 8ad4d7a2fc..3b6f021dba 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt @@ -1,7 +1,9 @@ package com.tangem.tap.features.walletSelector.ui.components +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Icon @@ -26,6 +28,7 @@ import com.tangem.core.ui.components.SpacerH2 import com.tangem.core.ui.components.SpacerW6 import com.tangem.core.ui.components.SpacerW8 import com.tangem.core.ui.res.TangemTheme +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.common.compose.TangemTypography import com.tangem.tap.features.walletSelector.ui.model.MultiCurrencyUserWalletItem import com.tangem.tap.features.walletSelector.ui.model.SingleCurrencyUserWalletItem @@ -33,15 +36,23 @@ import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem import com.tangem.wallet.R import com.valentinilk.shimmer.shimmer +@OptIn(ExperimentalFoundationApi::class) @Composable internal fun WalletItem( - modifier: Modifier = Modifier, wallet: UserWalletItem, isSelected: Boolean, isChecked: Boolean, + onWalletClick: (UserWalletId) -> Unit, + onWalletLongClick: (UserWalletId) -> Unit, ) { Row( - modifier = modifier, + modifier = Modifier + .combinedClickable( + onClick = { onWalletClick(wallet.id) }, + onLongClick = { onWalletLongClick(wallet.id) }, + ) + .height(72.dp) + .padding(all = TangemTheme.dimens.spacing16), verticalAlignment = Alignment.CenterVertically, ) { WalletCardImage( diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt index c546cd131e..ddd7518ca6 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt @@ -1,21 +1,14 @@ package com.tangem.tap.features.walletSelector.ui.model -import com.tangem.tap.features.details.ui.cardsettings.TextReference -import com.tangem.wallet.R +import com.tangem.domain.common.util.UserWalletId internal sealed interface UserWalletItem { - val id: String + val id: UserWalletId val name: String val imageUrl: String val balance: Balance val isLocked: Boolean - val headerText: TextReference - get() = when (this) { - is MultiCurrencyUserWalletItem -> TextReference.Res(R.string.user_wallet_list_multi_header) - is SingleCurrencyUserWalletItem -> TextReference.Res(R.string.user_wallet_list_single_header) - } - data class Balance( val amount: String, val isLoading: Boolean, @@ -23,7 +16,7 @@ internal sealed interface UserWalletItem { } internal data class MultiCurrencyUserWalletItem( - override val id: String, + override val id: UserWalletId, override val name: String, override val imageUrl: String, override val balance: UserWalletItem.Balance, @@ -33,7 +26,7 @@ internal data class MultiCurrencyUserWalletItem( ) : UserWalletItem internal data class SingleCurrencyUserWalletItem( - override val id: String, + override val id: UserWalletId, override val name: String, override val imageUrl: String, override val balance: UserWalletItem.Balance, diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index fb5b8a626d..7b0f37e36e 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -59,11 +59,6 @@ internal class WelcomeMiddleware { } .doOnSuccess { selectedUserWallet -> if (selectedUserWallet != null) { - tangemSdkManager.setAccessCodeRequestPolicy( - useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && - selectedUserWallet.hasAccessCode, - ) - store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Success) store.onUserWalletSelected(selectedUserWallet) @@ -78,8 +73,7 @@ internal class WelcomeMiddleware { scanCardInternal { scanResponse -> val userWallet = UserWalletBuilder(scanResponse).build() - tangemSdkManager.setAccessCodeRequestPolicy(useBiometricsForAccessCode = false) - userWalletsListManager.unlockWithCard(userWallet) + userWalletsListManager.save(userWallet, canOverride = true) .doOnFailure { error -> store.dispatchOnMain(WelcomeAction.ProceedWithCard.Error(error)) } @@ -100,6 +94,9 @@ internal class WelcomeMiddleware { private suspend inline fun scanCardInternal( crossinline onCardScanned: suspend (ScanResponse) -> Unit, ) { + tangemSdkManager.setAccessCodeRequestPolicy( + useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, + ) ScanCardProcessor.scan( onSuccess = { scanResponse -> scope.launch { onCardScanned(scanResponse) } diff --git a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt index 2b4d40e0bb..7be322fc1e 100644 --- a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt @@ -108,7 +108,7 @@ class DerivationManagerImpl( val updatedScanResponse = scanResponse.copy( derivedKeys = updatedDerivedKeys, ) - appStateHolder.mainStore?.dispatchOnMain(GlobalAction.SaveScanNoteResponse(updatedScanResponse)) + appStateHolder.mainStore?.dispatchOnMain(GlobalAction.SaveScanResponse(updatedScanResponse)) delay(DELAY_SDK_DIALOG_CLOSE) onSuccess(updatedScanResponse) } diff --git a/app/src/main/res/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml index ab388fa0cd..b3d1b54730 100644 --- a/app/src/main/res/layout/fragment_wallet.xml +++ b/app/src/main/res/layout/fragment_wallet.xml @@ -5,7 +5,7 @@ android:id="@+id/coordinator_wallet" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/backgroundLightGray" + android:background="@color/background_secondary" android:orientation="vertical"> diff --git a/app/src/main/res/layout/fragment_wallet_details.xml b/app/src/main/res/layout/fragment_wallet_details.xml index 1467361b47..f1cdf9a73a 100644 --- a/app/src/main/res/layout/fragment_wallet_details.xml +++ b/app/src/main/res/layout/fragment_wallet_details.xml @@ -5,7 +5,7 @@ android:id="@+id/coordinator_wallet" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/backgroundLightGray" + android:background="@color/background_secondary" android:orientation="vertical"> @@ -105,27 +105,27 @@ android:layout_marginStart="16dp" android:layout_marginEnd="16dp" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/l_wallet_details" /> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/l_wallet_details" /> + android:id="@+id/barrier" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:barrierDirection="bottom" + app:constraint_referenced_ids="l_wallet_details" /> + android:id="@+id/row_buttons" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="28dp" + android:layout_marginBottom="32dp" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + app:layout_constraintVertical_bias="1" + app:layout_constraintTop_toBottomOf="@id/rv_warning_messages" + app:layout_constraintBottom_toBottomOf="parent" /> diff --git a/app/src/main/res/layout/item_currency_wallet_content.xml b/app/src/main/res/layout/item_currency_wallet_content.xml index 18249f4c63..aa767d069c 100644 --- a/app/src/main/res/layout/item_currency_wallet_content.xml +++ b/app/src/main/res/layout/item_currency_wallet_content.xml @@ -14,77 +14,77 @@ app:layout_constraintGuide_percent="0.5" /> + android:id="@+id/tv_currency" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:ellipsize="end" + android:maxLines="1" + android:textColor="@color/text_primary_1" + android:textSize="16sp" + android:textStyle="bold" + app:layout_constraintBottom_toTopOf="@id/guideline_horizontal" + app:layout_constraintEnd_toStartOf="@+id/tv_amount_fiat" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:lineHeight="24dp" + tools:text="Binance Smart Chain Optimal" /> + android:id="@+id/tv_amount_fiat" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:maxLines="1" + android:textAlignment="viewEnd" + android:textColor="@color/text_primary_1" + android:textSize="16sp" + android:textStyle="bold" + app:layout_constraintBottom_toTopOf="@id/guideline_horizontal" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:lineHeight="24dp" + tools:text="1230.43 $" /> + android:id="@+id/tv_amount" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:maxLines="1" + android:textAlignment="viewEnd" + android:textColor="@color/text_tertiary" + android:textSize="12sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/guideline_horizontal" + app:lineHeight="20dp" + tools:text="2.002134 BTC" /> + android:id="@+id/tv_status" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:ellipsize="end" + android:maxLines="1" + android:textColor="@color/text_tertiary" + android:textSize="12sp" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/guideline_horizontal" + app:lineHeight="20dp" + tools:text="Unreachable..." /> + android:id="@+id/tv_exchange_rate" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:maxLines="1" + android:textColor="@color/text_secondary" + android:textSize="12sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/guideline_horizontal" + app:lineHeight="20dp" + tools:text="46 908 $" /> diff --git a/app/src/main/res/layout/layout_card_total_balance.xml b/app/src/main/res/layout/layout_card_total_balance.xml index 3f47e6b612..ed26db5407 100644 --- a/app/src/main/res/layout/layout_card_total_balance.xml +++ b/app/src/main/res/layout/layout_card_total_balance.xml @@ -12,8 +12,7 @@ android:layout_marginStart="16dp" android:layout_marginTop="18dp" android:layout_marginEnd="16dp" - android:layout_marginBottom="18dp" - android:animateLayoutChanges="true"> + android:layout_marginBottom="18dp"> - + app:layout_constraintTop_toBottomOf="@id/tv_title" + app:veilLayout_baseColor="@color/lightGray0" + app:veilLayout_highlightColor="@color/lightGray1" + app:veilLayout_layout="@layout/card_total_balance_shimmer" + app:veilLayout_radius="4dp" + app:veilLayout_shimmerEnable="true" + app:veilLayout_veiled="true"> - - - - + tools:text="22 325.40 $" + tools:visibility="visible" /> + diff --git a/buildSrc/src/main/java/Dependency.kt b/buildSrc/src/main/java/Dependency.kt index c1375a7b76..54ee97cd6d 100644 --- a/buildSrc/src/main/java/Dependency.kt +++ b/buildSrc/src/main/java/Dependency.kt @@ -66,6 +66,7 @@ object Library { const val zendeskMessaging = "com.zendesk:messaging:" + Versions.zendeskMessaging const val zxingQrBarcodeScanner = "me.dm7.barcodescanner:zxing:" + Versions.zxingQrBarcodeScanner const val zxingQrCore = "com.google.zxing:core:" + Versions.zxingQrCode + const val mviCoreWatcher = "com.github.badoo.mvicore:mvicore-diff:" + Versions.mviCore } object Tangem { diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index 3a15fb0a04..f63622d48b 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -55,13 +55,14 @@ object Versions { const val zendeskMessaging = "5.2.4" const val zxingQrBarcodeScanner = "1.9.8" const val zxingQrCode = "3.3.3" + const val mviCore = "1.3.1" // endregion Other libraries // region Tangem const val tangemBlockchainSdk = "develop-142" // const val tangemBlockchainSdk = "0.0.1" - const val tangemCardSgk = "develop-173" + const val tangemCardSgk = "develop-178" // endregion Tangem // region Testing diff --git a/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeBottomSheetFragment.kt b/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeBottomSheetFragment.kt index 2a7d4c0864..35dab6bca0 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeBottomSheetFragment.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeBottomSheetFragment.kt @@ -18,7 +18,6 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.tangem.core.ui.R -import com.tangem.core.ui.components.SystemBarsEffect import com.tangem.core.ui.res.TangemTheme abstract class ComposeBottomSheetFragment : BottomSheetDialogFragment() { @@ -52,13 +51,6 @@ abstract class ComposeBottomSheetFragment : BottomSheetDialogFragme return ComposeView(context).apply { setContent { TangemTheme { - val backgroundColor = TangemTheme.colors.background.plain - SystemBarsEffect { - setSystemBarsColor( - color = backgroundColor, - ) - } - ScreenContent( modifier = Modifier .fillMaxWidth() @@ -66,7 +58,7 @@ abstract class ComposeBottomSheetFragment : BottomSheetDialogFragme if (expandedHeightFraction != null) it.fillMaxHeight(expandedHeightFraction!!) else it } .background( - color = backgroundColor, + color = TangemTheme.colors.background.plain, shape = TangemTheme.shapes.bottomSheet, ), state = provideState().value, diff --git a/core/ui/src/main/java/com/tangem/core/ui/fragments/FragmentExt.kt b/core/ui/src/main/java/com/tangem/core/ui/fragments/FragmentExt.kt new file mode 100644 index 0000000000..0de62569bb --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/fragments/FragmentExt.kt @@ -0,0 +1,14 @@ +package com.tangem.core.ui.fragments + +import android.view.WindowManager +import androidx.annotation.ColorRes +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment + +fun Fragment.setStatusBarColor(@ColorRes colorResId: Int) { + with(requireActivity().window) { + clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) + statusBarColor = ContextCompat.getColor(requireContext(), colorResId) + } +} \ No newline at end of file From a6acbfd62a15d5d755bbf9f6ce713638cacf3186 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 26 Dec 2022 14:37:20 +0300 Subject: [PATCH 025/145] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 2 +- core/res/src/main/res/values-fr/strings.xml | 2 +- core/res/src/main/res/values-it/strings.xml | 2 +- core/res/src/main/res/values-ru/strings.xml | 8 ++++---- core/res/src/main/res/values/strings.xml | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 8696a129c5..1a72e38b69 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -100,7 +100,7 @@ WalletConnect Connect to Dapps Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. - Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet or use the card to recover the access code. + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. I understand that after performing this action, I will no longer have access to the current wallet Reset the Card Reset to Factory Settings diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 160175b03b..b4d6ef7995 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -100,7 +100,7 @@ WalletConnect Connect to Dapps Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. - Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet or use the card to recover the access code. + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. I understand that after performing this action, I will no longer have access to the current wallet Reset the Card Reset to Factory Settings diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml index 3f70ed2bb4..86c97ddc00 100644 --- a/core/res/src/main/res/values-it/strings.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -100,7 +100,7 @@ WalletConnect Connect to Dapps Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. - Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet or use the card to recover the access code. + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. I understand that after performing this action, I will no longer have access to the current wallet Reset the Card Reset to Factory Settings diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 59ada270a7..7e8a6300af 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -56,7 +56,7 @@ Например, USDC Символ токена Деривация по BIP44 - По-умолчанию + По умолчанию Сервер недоступен, повторите попытку позднее Баланс В сумме учтены не все монеты @@ -99,8 +99,8 @@ Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком. WalletConnect Подключение к Dapps - Сброс к заводским настройкам приведет к полному удалению кошелька на этой карте, а также отвязыванию карты из приложения. Кошелек невозможно будет восстановить. - Сброс к заводским настройкам приведет к полному удалению кошелька на этой карте, а также отвязыванию карты из приложения. Кошелек невозможно будет восстановить или использовать данную карту для восстановления кода доступа. + Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек. + Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать данную карту для восстановления кода доступа. Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку Сбросить карту Сброс к заводским настройкам @@ -455,4 +455,4 @@ %s -
\ No newline at end of file + diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index db70283a4c..4e3e0230f7 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -100,7 +100,7 @@ WalletConnect Connect to Dapps Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. - Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet or use the card to recover the access code. + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. I understand that after performing this action, I will no longer have access to the current wallet Reset the Card Reset to Factory Settings From e7c330a5e193cd10a911e3e967be0009b628bda1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 26 Dec 2022 16:28:20 +0300 Subject: [PATCH 026/145] Updated on 2026-08-14 --- .../tangem/tap/proxy/DerivationManagerImpl.kt | 2 +- .../tap/proxy/TransactionManagerImpl.kt | 99 ++++++++++++++++--- .../tangem/tap/proxy/UserWalletManagerImpl.kt | 4 +- .../api/oneinch/models/QuoteResponse.kt | 2 - .../domain/converter/TokensConverter.kt | 4 +- .../tangem/feature/swap/SwapRepositoryImpl.kt | 3 +- .../feature/swap/domain/SwapInteractor.kt | 9 +- .../feature/swap/domain/SwapInteractorImpl.kt | 73 +++++++++++--- .../swap/domain/cache/SwapDataCache.kt | 7 +- .../swap/domain/cache/SwapDataCacheImpl.kt | 25 ++++- .../converters/CryptoCurrencyConverter.kt | 30 ++++++ .../swap/domain/models/SwapDataHolder.kt | 13 ++- .../feature/swap/viewmodels/SwapViewModel.kt | 11 --- .../tangem/lib/crypto/TransactionManager.kt | 10 ++ .../com/tangem/lib/crypto/models/Currency.kt | 31 +++--- .../tangem/lib/crypto/models/ProxyAmount.kt | 16 +++ 16 files changed, 263 insertions(+), 76 deletions(-) create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/converters/CryptoCurrencyConverter.kt create mode 100644 libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyAmount.kt diff --git a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt index 7be322fc1e..ebd2b0db53 100644 --- a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt @@ -12,7 +12,7 @@ import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.lib.crypto.DerivationManager import com.tangem.lib.crypto.models.Currency -import com.tangem.lib.crypto.models.NonNativeToken +import com.tangem.lib.crypto.models.Currency.NonNativeToken import com.tangem.operations.derivation.ExtendedPublicKeysMap import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.common.extensions.dispatchDebugErrorNotification diff --git a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt index 9cbeec23f9..11b7525e9a 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -2,20 +2,24 @@ package com.tangem.tap.proxy import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.Message +import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras +import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.blockchain.common.Token +import com.tangem.blockchain.common.TransactionExtras import com.tangem.blockchain.common.TransactionSender import com.tangem.blockchain.common.TransactionSigner import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.SimpleResult import com.tangem.common.core.TangemSdkError +import com.tangem.common.extensions.hexToBytes import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.models.Currency -import com.tangem.lib.crypto.models.NativeToken -import com.tangem.lib.crypto.models.NonNativeToken +import com.tangem.lib.crypto.models.ProxyAmount import com.tangem.lib.crypto.models.transactions.SendTxResult import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.TangemSigner @@ -23,6 +27,7 @@ import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.store import com.tangem.tap.tangemSdk import java.math.BigDecimal +import java.math.BigInteger class TransactionManagerImpl( private val appStateHolder: AppStateHolder, @@ -39,22 +44,13 @@ class TransactionManagerImpl( ): SendTxResult { val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } val walletManager = getActualWalletManager(blockchain) - val amount = when (currencyToSend) { - is NativeToken -> { - Amount(value = amountToSend, blockchain = blockchain) - } - is NonNativeToken -> { - Amount( - convertNonNativeToken(currencyToSend), - amountToSend, - ) - } - } + val amount = createAmount(amountToSend, currencyToSend, blockchain) + val txData = walletManager.createTransaction( amount = amount, fee = Amount(value = feeAmount, blockchain = blockchain), destination = destinationAddress, - ).copy(hash = dataToSign) + ).copy(hash = dataToSign, extras = createExtras(walletManager, feeAmount, dataToSign)) val signer = transactionSigner(walletManager) @@ -67,6 +63,33 @@ class TransactionManagerImpl( return handleSendResult(sendResult) } + @Throws(IllegalStateException::class) + override suspend fun getFee( + networkId: String, + amountToSend: BigDecimal, + currencyToSend: Currency, + destinationAddress: String, + ): ProxyAmount { + val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + val walletManager = getActualWalletManager(blockchain) + val fee = (walletManager as TransactionSender).getFee( + amount = createAmount(amountToSend, currencyToSend, blockchain), + destination = destinationAddress, + ) + when (fee) { + is Result.Success -> { + return convertToProxyAmount(fee.data.firstOrNull() ?: error("no fee found")) + } + is Result.Failure -> { + error(fee.error.message ?: fee.error.customMessage) + } + } + } + + override fun getNativeAddress(networkId: String): String { + return "" //todo implement + } + private fun handleSendResult(result: SimpleResult): SendTxResult { when (result) { is SimpleResult.Success -> { @@ -121,7 +144,40 @@ class TransactionManagerImpl( } } - private fun convertNonNativeToken(token: NonNativeToken): Token { + private fun createExtras( + walletManager: WalletManager, + feeAmount: BigDecimal, + transactionHash: String, + ): TransactionExtras? { + return when (walletManager) { + is EthereumWalletManager -> { + return EthereumTransactionExtras( + data = transactionHash.removePrefix(HEX_PREFIX).hexToBytes(), + gasLimit = BigInteger.valueOf(DEFAULT_GAS_LIMIT), + ) + } + else -> { + null + } + } + } + + private fun createAmount( + amount: BigDecimal, + currency: Currency, + blockchain: Blockchain, + ): Amount { + return when (currency) { + is Currency.NativeToken -> { + Amount(value = amount, blockchain = blockchain) + } + is Currency.NonNativeToken -> { + Amount(convertNonNativeToken(currency), amount) + } + } + } + + private fun convertNonNativeToken(token: Currency.NonNativeToken): Token { return Token( name = token.name, symbol = token.symbol, @@ -130,4 +186,17 @@ class TransactionManagerImpl( id = token.id, ) } + + private fun convertToProxyAmount(amount: Amount): ProxyAmount { + return ProxyAmount( + currencySymbol = amount.currencySymbol, + value = amount.value ?: BigDecimal.ZERO, + decimals = amount.decimals, + ) + } + + companion object { + private const val HEX_PREFIX = "0x" + private const val DEFAULT_GAS_LIMIT = 300000L + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt index 218bf19ae5..5d53a5dadd 100644 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -11,8 +11,8 @@ import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.common.util.userWalletId import com.tangem.lib.crypto.UserWalletManager import com.tangem.lib.crypto.models.Currency -import com.tangem.lib.crypto.models.NativeToken -import com.tangem.lib.crypto.models.NonNativeToken +import com.tangem.lib.crypto.models.Currency.NativeToken +import com.tangem.lib.crypto.models.Currency.NonNativeToken import com.tangem.tap.domain.extensions.makeWalletManagerForApp import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.redux.WalletAction diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/QuoteResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/QuoteResponse.kt index dca5642f12..ecea3f4bc5 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/QuoteResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/QuoteResponse.kt @@ -9,7 +9,6 @@ import com.squareup.moshi.Json * @property toToken Destination token info * @property toTokenAmount Expected amount of destination token * @property fromTokenAmount Amount of source token - * @property protocols Selected protocols in a path * @property estimatedGas gas fee */ data class QuoteResponse( @@ -17,6 +16,5 @@ data class QuoteResponse( @Json(name = "toToken") val toToken: TokenOneInchDto, @Json(name = "toTokenAmount") val toTokenAmount: String, @Json(name = "fromTokenAmount") val fromTokenAmount: String, - @Json(name = "protocols") val protocols: List, @Json(name = "estimatedGas") val estimatedGas: Int, ) \ No newline at end of file diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt index 8abe25e73c..a477782d9f 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt @@ -2,8 +2,8 @@ package com.tangem.feature.referral.domain.converter import com.tangem.feature.referral.domain.models.TokenData import com.tangem.lib.crypto.models.Currency -import com.tangem.lib.crypto.models.NativeToken -import com.tangem.lib.crypto.models.NonNativeToken +import com.tangem.lib.crypto.models.Currency.NativeToken +import com.tangem.lib.crypto.models.Currency.NonNativeToken import com.tangem.utils.converter.Converter import javax.inject.Inject diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt index c7d8cd3d5c..25ffc2bea2 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt @@ -41,8 +41,7 @@ internal class SwapRepositoryImpl @Inject constructor( networkId: String, fromTokenAddress: String, toTokenAddress: String, amount: String, - ): - AggregatedSwapDataModel { + ): AggregatedSwapDataModel { return withContext(coroutineDispatcher.io) { try { val response = oneInchErrorsHandler.handleOneInchResponse( diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt index be45612480..d51ad84fb4 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt @@ -2,6 +2,7 @@ package com.tangem.feature.swap.domain import com.tangem.feature.swap.domain.models.data.Currency import com.tangem.feature.swap.domain.models.data.SwapState +import java.math.BigDecimal interface SwapInteractor { @@ -9,11 +10,13 @@ interface SwapInteractor { suspend fun getTokenBalance(tokenId: String): String - suspend fun givePermissionToSwap(tokenAddress: String) + suspend fun getFee(): BigDecimal + + suspend fun givePermissionToSwap(tokenToApprove: Currency) suspend fun findBestQuote( - fromTokenAddress: String, - toTokenAddress: String, + fromToken: Currency, + toToken: Currency, amount: String, ): SwapState diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 43862840fb..a2a54d0749 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -1,6 +1,7 @@ package com.tangem.feature.swap.domain import com.tangem.feature.swap.domain.cache.SwapDataCache +import com.tangem.feature.swap.domain.converters.CryptoCurrencyConverter import com.tangem.feature.swap.domain.models.data.Currency import com.tangem.feature.swap.domain.models.data.DataError import com.tangem.feature.swap.domain.models.data.SwapState @@ -17,6 +18,8 @@ internal class SwapInteractorImpl @Inject constructor( private val cache: SwapDataCache, ) : SwapInteractor { + private val cryptoCurrencyConverter = CryptoCurrencyConverter() + override suspend fun getTokensToSwap(networkId: String): List { val availableTokens = cache.getAvailableTokens(networkId) return availableTokens.ifEmpty { @@ -31,24 +34,48 @@ internal class SwapInteractorImpl @Inject constructor( TODO("Not yet implemented") } - override suspend fun givePermissionToSwap(tokenAddress: String) { + override suspend fun getFee(): BigDecimal { + return transactionManager.getFee( + networkId = cache.getNetworkId() ?: error("no networkId found, call getTokensToSwap first"), + amountToSend = cache.getAmountToSwap() ?: error("no amount found, call findBestQuote first"), + currencyToSend = cryptoCurrencyConverter.convert( + cache.getExchangeCurrencies()?.fromCurrency ?: error( + "no fromCurrency found, call findBestQuote first", + ), + ), + destinationAddress = getTokenAddress( + cache.getExchangeCurrencies()?.toCurrency ?: error( + "no toCurrency found, call findBestQuote first", + ), + ), + ).value + } + + override suspend fun givePermissionToSwap(tokenToApprove: Currency) { cache.getNetworkId()?.let { networkId -> - val oneInchAddress = repository.addressForTrust(networkId) - val transactionData = repository.dataToApprove(networkId, tokenAddress) - //todo for test - // transactionManager.sendTransaction( - // networkId = networkId, - // amountToSend = BigDecimal.ZERO, - // currencyToSend = , - // feeAmount = BigDecimal.ZERO, - // destinationAddress = oneInchAddress, - // dataToSign = transactionData.data - // ) + if (tokenToApprove is Currency.NonNativeToken) { + val estimatedGas = + requireNotNull(cache.getLastQuote()?.estimatedGas) { "estimatedGas not found call findBestQuote" } + val transactionData = repository.dataToApprove(networkId, tokenToApprove.contractAddress) + val gasPrice = transactionData.gasPrice.toBigDecimalOrNull() ?: error("cannot parse gasPrice") + val fee = gasPrice.multiply(estimatedGas.toBigDecimal()).movePointLeft(tokenToApprove.decimalCount) + val result = transactionManager.sendTransaction( + networkId = networkId, + amountToSend = BigDecimal.ZERO, + currencyToSend = cryptoCurrencyConverter.convert(tokenToApprove), + feeAmount = fee, + destinationAddress = transactionData.toAddress, + dataToSign = transactionData.data, + ) + } } } - override suspend fun findBestQuote(fromTokenAddress: String, toTokenAddress: String, amount: String): SwapState { + override suspend fun findBestQuote(fromToken: Currency, toToken: Currency, amount: String): SwapState { val networkId = cache.getNetworkId() + val fromTokenAddress = getTokenAddress(fromToken) + val toTokenAddress = getTokenAddress(toToken) + val bigDecimalAmount = amount.toBigDecimalOrNull() ?: error("wrong amount format, use only digits") val isAllowedToSpend = if (networkId.isNullOrEmpty()) { error("no networkId found, please call getTokensToSwap first") } else { @@ -68,7 +95,12 @@ internal class SwapInteractorImpl @Inject constructor( ).let { quotes -> val quoteDataModel = quotes.dataModel if (quoteDataModel != null) { - cache.cacheSwapParams(quoteDataModel.copy(isAllowedToSpend = isAllowedToSpend), amount) + cache.cacheSwapParams( + quoteModel = quoteDataModel.copy(isAllowedToSpend = isAllowedToSpend), + amount = bigDecimalAmount, + fromCurrency = fromToken, + toCurrency = toToken, + ) return quoteDataModel } else { return SwapState.SwapError(quotes.error) @@ -85,7 +117,7 @@ internal class SwapInteractorImpl @Inject constructor( networkId = networkId, fromTokenAddress = quoteModel.fromTokenAmount, toTokenAddress = quoteModel.toTokenAmount, - amount = amountToSwap, + amount = amountToSwap.toPlainString(), slippage = DEFAULT_SLIPPAGE, fromWalletAddress = getWalletAddress(networkId), ).let { @@ -109,6 +141,17 @@ internal class SwapInteractorImpl @Inject constructor( return userWalletManager.getWalletAddress(networkId) } + private fun getTokenAddress(currency: Currency): String { + return when (currency) { + is Currency.NativeToken -> { + transactionManager.getNativeAddress(currency.networkId) + } + is Currency.NonNativeToken -> { + currency.contractAddress + } + } + } + //todo implement after merge referral private fun signTransactionData(transaction: TransactionModel) { } diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt index 13cdd8e17d..99002c3de9 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt @@ -1,15 +1,18 @@ package com.tangem.feature.swap.domain.cache +import com.tangem.feature.swap.domain.models.ExchangeCurrencies import com.tangem.feature.swap.domain.models.data.Currency import com.tangem.feature.swap.domain.models.data.SwapState.QuoteModel +import java.math.BigDecimal interface SwapDataCache { fun cacheNetworkId(networkId: String) - fun cacheSwapParams(quoteModel: QuoteModel, amount: String) + fun cacheSwapParams(quoteModel: QuoteModel, amount: BigDecimal, fromCurrency: Currency, toCurrency: Currency) fun cacheAvailableToSwapTokens(networkId: String, tokens: List) fun getAvailableTokens(networkId: String): List fun getLastQuote(): QuoteModel? - fun getAmountToSwap(): String? + fun getAmountToSwap(): BigDecimal? fun getNetworkId(): String? + fun getExchangeCurrencies(): ExchangeCurrencies? } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt index 7aab37de03..953041e139 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt @@ -1,16 +1,35 @@ package com.tangem.feature.swap.domain.cache +import com.tangem.feature.swap.domain.models.ExchangeCurrencies import com.tangem.feature.swap.domain.models.SwapDataHolder import com.tangem.feature.swap.domain.models.data.Currency import com.tangem.feature.swap.domain.models.data.SwapState.QuoteModel +import java.math.BigDecimal class SwapDataCacheImpl : SwapDataCache { private var lastDataForSwap: SwapDataHolder = SwapDataHolder() private val availableTokensForNetwork: MutableMap> = mutableMapOf() - override fun cacheSwapParams(quoteModel: QuoteModel, amount: String) { - lastDataForSwap = lastDataForSwap.copy(quoteModel = quoteModel, amountToSwap = amount) + override fun cacheSwapParams( + quoteModel: QuoteModel, + amount: BigDecimal, + fromCurrency: Currency, + toCurrency: Currency, + ) { + lastDataForSwap = + lastDataForSwap.copy( + quoteModel = quoteModel, + amountToSwap = amount, + exchangeCurrencies = ExchangeCurrencies( + fromCurrency = fromCurrency, + toCurrency = toCurrency, + ), + ) + } + + override fun getExchangeCurrencies(): ExchangeCurrencies? { + return lastDataForSwap.exchangeCurrencies } override fun cacheAvailableToSwapTokens(networkId: String, tokens: List) { @@ -33,7 +52,7 @@ class SwapDataCacheImpl : SwapDataCache { return lastDataForSwap.quoteModel } - override fun getAmountToSwap(): String? { + override fun getAmountToSwap(): BigDecimal? { return lastDataForSwap.amountToSwap } } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/converters/CryptoCurrencyConverter.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/converters/CryptoCurrencyConverter.kt new file mode 100644 index 0000000000..161f9661a2 --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/converters/CryptoCurrencyConverter.kt @@ -0,0 +1,30 @@ +package com.tangem.feature.swap.domain.converters + +import com.tangem.feature.swap.domain.models.data.Currency +import com.tangem.utils.converter.Converter + +class CryptoCurrencyConverter : Converter { + + override fun convert(value: Currency): com.tangem.lib.crypto.models.Currency { + return when (value) { + is Currency.NonNativeToken -> { + com.tangem.lib.crypto.models.Currency.NonNativeToken( + id = value.id, + name = value.name, + symbol = value.symbol, + networkId = value.networkId, + contractAddress = value.contractAddress, + decimalCount = value.decimalCount, + ) + } + is Currency.NativeToken -> { + com.tangem.lib.crypto.models.Currency.NativeToken( + id = value.id, + name = value.name, + symbol = value.symbol, + networkId = value.networkId, + ) + } + } + } +} \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataHolder.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataHolder.kt index 4d50aa2da8..c2534d20fe 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataHolder.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataHolder.kt @@ -1,10 +1,17 @@ package com.tangem.feature.swap.domain.models +import com.tangem.feature.swap.domain.models.data.Currency import com.tangem.feature.swap.domain.models.data.SwapState.QuoteModel +import java.math.BigDecimal data class SwapDataHolder( - val quoteModel: - QuoteModel? = null, - val amountToSwap: String? = null, + val quoteModel: QuoteModel? = null, + val amountToSwap: BigDecimal? = null, val networkId: String? = null, + val exchangeCurrencies: ExchangeCurrencies? = null, +) + +data class ExchangeCurrencies( + val fromCurrency: Currency? = null, + val toCurrency: Currency? = null, ) \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index 245d7b404b..8a25fac02e 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -61,23 +61,12 @@ internal class SwapViewModel @Inject constructor( val tokens = referralInteractor.getTokensToSwap("binance-smart-chain") val token = tokens[1] val token2 = tokens[2] - if (token is Currency.NonNativeToken && token2 is Currency.NonNativeToken) { - findBestQuotes(token.contractAddress, token2.contractAddress) - } }.onFailure { Log.e("SwapViewModel", it.message ?: it.cause.toString()) } } } - private suspend fun findBestQuotes(fromTokenAddress: String, toTokenAddress: String) { - referralInteractor.findBestQuote( - fromTokenAddress, - toTokenAddress, - "1", - ) - } - private fun createTestUiState(): SwapStateHolder { val sendCard = SwapCardData( type = TransactionCardType.SendCard("123", false), diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt index 93a7ab6914..a1c8bd3631 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt @@ -1,6 +1,7 @@ package com.tangem.lib.crypto import com.tangem.lib.crypto.models.Currency +import com.tangem.lib.crypto.models.ProxyAmount import com.tangem.lib.crypto.models.transactions.SendTxResult import java.math.BigDecimal @@ -14,4 +15,13 @@ interface TransactionManager { destinationAddress: String, dataToSign: String, ): SendTxResult + + suspend fun getFee( + networkId: String, + amountToSend: BigDecimal, + currencyToSend: Currency, + destinationAddress: String, + ): ProxyAmount + + fun getNativeAddress(networkId: String): String } \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Currency.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Currency.kt index cd49b303ef..1bdb582455 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Currency.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Currency.kt @@ -9,20 +9,21 @@ sealed class Currency( open val name: String, open val symbol: String, open val networkId: String, -) +) { -data class NativeToken( - override val id: String, - override val name: String, - override val symbol: String, - override val networkId: String, -) : Currency(id, name, symbol, networkId) + data class NativeToken( + override val id: String, + override val name: String, + override val symbol: String, + override val networkId: String, + ) : Currency(id, name, symbol, networkId) -class NonNativeToken( - override val id: String, - override val name: String, - override val symbol: String, - override val networkId: String, - val contractAddress: String, - val decimalCount: Int, -) : Currency(id, name, symbol, networkId) \ No newline at end of file + class NonNativeToken( + override val id: String, + override val name: String, + override val symbol: String, + override val networkId: String, + val contractAddress: String, + val decimalCount: Int, + ) : Currency(id, name, symbol, networkId) +} \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyAmount.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyAmount.kt new file mode 100644 index 0000000000..aa4c58b0f3 --- /dev/null +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyAmount.kt @@ -0,0 +1,16 @@ +package com.tangem.lib.crypto.models + +import java.math.BigDecimal + +/** + * Proxy amount is always has a Coin type + * + * @property currencySymbol + * @property value amount in [BigDecimal] + * @property decimals count for token + */ +data class ProxyAmount( + val currencySymbol: String, + var value: BigDecimal, + val decimals: Int, +) \ No newline at end of file From cf4affa51367ead6dcf11daf776296a3147de8ef Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 26 Dec 2022 11:27:09 +0400 Subject: [PATCH 027/145] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 3 + core/res/src/main/res/values-fr/strings.xml | 3 + core/res/src/main/res/values-it/strings.xml | 3 + core/res/src/main/res/values-ru/strings.xml | 3 + core/res/src/main/res/values/strings.xml | 3 + .../com/tangem/core/ui/components/Spacers.kt | 5 + .../tangem/core/ui/components/TextFields.kt | 4 +- .../ui/components/appbar/AppBarWithSearch.kt | 245 ++++++++++++++++++ .../com/tangem/core/ui/res/TangemDimens.kt | 2 + .../ui/src/main/res/drawable/ic_search_24.xml | 10 + .../swap/models/SwapSelectTokenStateHolder.kt | 16 +- .../feature/swap/models/SwapStateHolder.kt | 12 - .../feature/swap/presentation/SwapFragment.kt | 14 +- .../feature/swap/ui/SwapSelectTokenScreen.kt | 237 +++++++++++++++++ .../tangem/feature/swap/ui/TransactionCard.kt | 15 +- .../feature/swap/viewmodels/SwapViewModel.kt | 19 +- 16 files changed, 572 insertions(+), 22 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithSearch.kt create mode 100644 core/ui/src/main/res/drawable/ic_search_24.xml create mode 100644 features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 8696a129c5..f93e15766c 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -204,6 +204,9 @@ Insufficient funds Give Permission Permit and Swap + Choose token + Your tokens + Other tokens Failed to load the information about the referral program. Please try again later. Failed to load the information about the referral program. Reason: %s. Please try again later. Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 160175b03b..f423ec134e 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -204,6 +204,9 @@ Insufficient funds Give Permission Permit and Swap + Choose token + Your tokens + Other tokens Failed to load the information about the referral program. Please try again later. Failed to load the information about the referral program. Reason: %s. Please try again later. Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml index 3f70ed2bb4..e40f07e68e 100644 --- a/core/res/src/main/res/values-it/strings.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -204,6 +204,9 @@ Insufficient funds Give Permission Permit and Swap + Choose token + Your tokens + Other tokens Failed to load the information about the referral program. Please try again later. Failed to load the information about the referral program. Reason: %s. Please try again later. Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 59ada270a7..102c36326c 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -204,6 +204,9 @@ Недостаточно средств Дать разрешение Разрешить и обменять + Выберите токен + Ваши токены + Другие токены Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. Не удалось загрузить информацию по реферальной программе. Причина: %s. Пожалуйста, попробуйте позже. Не удалось обработать вашу заявку на участие. Причина: %s. Пожалуйста, попробуйте позже. Если проблема сохранится, вы можете обратиться в техподдержку. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index db70283a4c..2fe8877dae 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -204,6 +204,9 @@ Insufficient funds Give Permission Permit and Swap + Choose token + Your tokens + Other tokens Failed to load the information about the referral program. Please try again later. Failed to load the information about the referral program. Reason: %s. Please try again later. Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt index 93813f92e5..b333d58fdf 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt @@ -100,6 +100,11 @@ fun SpacerW(width: Dp, modifier: Modifier = Modifier) { Spacer(modifier = modifier.width(width)) } +@Composable +fun SpacerW2(modifier: Modifier = Modifier) { + SpacerW(2.dp, modifier) +} + @Composable fun SpacerW4(modifier: Modifier = Modifier) { SpacerW(4.dp, modifier) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt b/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt index 03af4693a4..1ac3d8e677 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt @@ -192,7 +192,7 @@ private fun TangemTextFieldSize.toShape(): Shape = when (this) { TangemTextFieldSize.Default -> TangemTheme.shapes.roundedCornersSmall2 } -private object TangemTextFieldsDefault { +internal object TangemTextFieldsDefault { val defaultTextFieldColors: TangemTextFieldColors @Composable @Stable get() = TangemTextFieldColors( textColor = TangemTheme.colors.text.primary1, @@ -223,7 +223,7 @@ private object TangemTextFieldsDefault { } @Immutable -private data class TangemTextFieldColors( +internal data class TangemTextFieldColors( private val textColor: Color, private val disabledTextColor: Color, private val cursorColor: Color, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithSearch.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithSearch.kt new file mode 100644 index 0000000000..37b0acc905 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithSearch.kt @@ -0,0 +1,245 @@ +package com.tangem.core.ui.components.appbar + +import androidx.compose.animation.Crossfade +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.R +import com.tangem.core.ui.components.SpacerWMax +import com.tangem.core.ui.components.TangemTextFieldsDefault +import com.tangem.core.ui.res.TangemTheme + +/** + * App bar with close icon and search functionality + * + * @param title optional title + * @param placeholderSearchText optional text placeholder for search TextField + * @param expandedInitially whether the search is expanded on launch + * @param tint tint for most of the visual elements of toolbar + * @param onBackClick action when close button is clicked + * @param onSearchChanged action when search is modified + * @param onSearchDisplayClosed action when search is closed + * + * @see Figma component + */ +@Composable +fun ExpandableSearchView( + modifier: Modifier = Modifier, + title: String? = null, + placeholderSearchText: String = "", + expandedInitially: Boolean = false, + tint: Color = TangemTheme.colors.text.primary1, + onBackClick: () -> Unit, + onSearchChanged: (String) -> Unit, + onSearchDisplayClosed: () -> Unit, +) { + val (expanded, onExpandedChanged) = remember { + mutableStateOf(expandedInitially) + } + + + Crossfade(targetState = expanded) { isSearchFieldVisible -> + if (isSearchFieldVisible) { + ExpandedSearchView( + placeholderSearchText = placeholderSearchText, + onSearchChanged = onSearchChanged, + onSearchDisplayClosed = onSearchDisplayClosed, + onExpandedChanged = onExpandedChanged, + modifier = modifier, + tint = tint, + ) + } else { + CollapsedSearchView( + title = title, + onBackClick = onBackClick, + onExpandedChanged = onExpandedChanged, + modifier = modifier, + tint = tint, + ) + } + } +} + +@Composable +private fun SearchIcon(iconTint: Color) { + Icon( + painter = painterResource(id = R.drawable.ic_search_24), + contentDescription = null, + tint = iconTint, + ) +} + +@Composable +private fun CollapsedSearchView( + modifier: Modifier = Modifier, + title: String? = null, + onBackClick: () -> Unit, + onExpandedChanged: (Boolean) -> Unit, + tint: Color = TangemTheme.colors.background.primary, +) { + Row( + modifier = modifier + .background(TangemTheme.colors.background.primary) + .padding(TangemTheme.dimens.spacing16) + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + painter = painterResource(R.drawable.ic_close_24), + contentDescription = null, + modifier = Modifier + .size(TangemTheme.dimens.size24) + .clickable { onBackClick() }, + tint = MaterialTheme.colors.onPrimary, + ) + if (!title.isNullOrBlank()) { + Text( + text = title, + color = TangemTheme.colors.text.primary1, + maxLines = 1, + style = TangemTheme.typography.subtitle1, + ) + } + SpacerWMax() + Icon( + painter = painterResource(id = R.drawable.ic_search_24), + tint = tint, + contentDescription = null, + modifier = Modifier + .clickable { onExpandedChanged(true) }, + ) + } +} + +@Composable +private fun ExpandedSearchView( + placeholderSearchText: String, + onSearchChanged: (String) -> Unit, + onSearchDisplayClosed: () -> Unit, + onExpandedChanged: (Boolean) -> Unit, + modifier: Modifier = Modifier, + tint: Color = TangemTheme.colors.background.primary, +) { + val focusManager = LocalFocusManager.current + + val textFieldFocusRequester = remember { FocusRequester() } + + SideEffect { + textFieldFocusRequester.requestFocus() + } + + var textFieldValue by remember { + mutableStateOf(TextFieldValue("", TextRange("".length))) + } + + Row( + modifier = modifier + .background(TangemTheme.colors.background.primary) + .fillMaxWidth(), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically, + ) { + IconButton( + onClick = { + onExpandedChanged(false) + onSearchDisplayClosed() + }, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_back_24), + contentDescription = null, + tint = tint, + ) + } + TextField( + value = textFieldValue, + onValueChange = { + textFieldValue = it + onSearchChanged(it.text) + }, + singleLine = true, + modifier = Modifier + .fillMaxWidth() + .focusRequester(textFieldFocusRequester), + placeholder = { + Text(text = placeholderSearchText) + }, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Done, + ), + keyboardActions = KeyboardActions( + onDone = { + focusManager.clearFocus() + }, + ), + colors = TangemTextFieldsDefault.defaultTextFieldColors + .copy( + placeholderColor = TangemTheme.colors.text.disabled, + cursorColor = TangemTheme.colors.text.tertiary, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent, + ), + ) + } +} + +@Preview +@Composable +fun CollapsedSearchViewPreview() { + TangemTheme { + ExpandableSearchView( + title = "Choose Token", + onBackClick = {}, + placeholderSearchText = "Search", + onSearchChanged = {}, + onSearchDisplayClosed = {}, + ) + } +} + +@Preview +@Composable +fun ExpandedSearchViewPreview() { + TangemTheme { + ExpandableSearchView( + title = "Choose Token", + onBackClick = {}, + placeholderSearchText = "Search", + onSearchChanged = {}, + expandedInitially = true, + onSearchDisplayClosed = {}, + ) + } +} + diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt index e5cf24728b..3fdc09bdb6 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt @@ -65,6 +65,7 @@ data class TangemDimens internal constructor( val spacing0_5: Dp = 0.5.dp, val spacing2: Dp = 2.dp, val spacing4: Dp = 4.dp, + val spacing6: Dp = 6.dp, val spacing8: Dp = 8.dp, val spacing10: Dp = 10.dp, val spacing12: Dp = 12.dp, @@ -77,6 +78,7 @@ data class TangemDimens internal constructor( val spacing34: Dp = 34.dp, val spacing44: Dp = 44.dp, val spacing50: Dp = 50.dp, + val spacing52: Dp = 52.dp, val spacing54: Dp = 54.dp, val spacing56: Dp = 56.dp, val spacing92: Dp = 92.dp, diff --git a/core/ui/src/main/res/drawable/ic_search_24.xml b/core/ui/src/main/res/drawable/ic_search_24.xml new file mode 100644 index 0000000000..0e1e91bcd9 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_search_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSelectTokenStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSelectTokenStateHolder.kt index 0f12e3010b..4510f9ece6 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSelectTokenStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSelectTokenStateHolder.kt @@ -1,7 +1,21 @@ package com.tangem.feature.swap.models data class SwapSelectTokenStateHolder( - val tokens: List, + val addedTokens: List, + val otherTokens: List, val onSearchEntered: (String) -> Unit, val onTokenSelected: (String) -> Unit, +) + +data class TokenToSelect( + val id: String, + val name: String, + val symbol: String, + val iconUrl: String, + val addedTokenBalanceData: TokenBalanceData? = null, +) + +data class TokenBalanceData( + val amount: String?, + val amountEquivalent: String?, ) \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt index 19e2b2d63f..3e0272c54c 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt @@ -22,18 +22,6 @@ data class SwapStateHolder( val onSuccess: (() -> Unit)? = null, ) -data class TokenToSelect( - val id: String, - val name: String, - val symbol: String, - val addedTokenBalanceData: TokenBalanceData? = null, -) - -data class TokenBalanceData( - val amount: String?, - val amountEquivalent: String?, -) - data class SwapCardData( val type: TransactionCardType, val amount: String?, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt index acedb67519..118e2b6e34 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt @@ -12,6 +12,7 @@ import androidx.fragment.app.viewModels import com.tangem.feature.swap.router.SwapRouter import com.tangem.feature.swap.router.SwapScreen import com.tangem.feature.swap.ui.SwapScreen +import com.tangem.feature.swap.ui.SwapSelectTokenScreen import com.tangem.feature.swap.ui.SwapSuccessScreen import com.tangem.feature.swap.viewmodels.SwapViewModel import dagger.hilt.android.AndroidEntryPoint @@ -38,7 +39,18 @@ class SwapFragment : Fragment() { SwapScreen(stateHolder = viewModel.uiState) } } - SwapScreen.SelectToken -> SwapScreen(stateHolder = viewModel.uiState)// TODO: use proper screen + SwapScreen.SelectToken -> { + val tokenState = viewModel.uiState.selectTokenState + if (tokenState != null) { + SwapSelectTokenScreen( + state = tokenState, + onBack = viewModel.uiState.onBackClicked, + ) + } else { + SwapScreen(stateHolder = viewModel.uiState) + } + } + // screen } } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt new file mode 100644 index 0000000000..f8be3fa3c2 --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt @@ -0,0 +1,237 @@ +@file:OptIn(ExperimentalFoundationApi::class) + +package com.tangem.feature.swap.ui + +import androidx.annotation.StringRes +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Divider +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import coil.compose.SubcomposeAsyncImage +import coil.request.ImageRequest +import com.tangem.core.ui.components.CurrencyPlaceholderIcon +import com.tangem.core.ui.components.SpacerW2 +import com.tangem.core.ui.components.appbar.ExpandableSearchView +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.swap.models.SwapSelectTokenStateHolder +import com.tangem.feature.swap.models.TokenBalanceData +import com.tangem.feature.swap.models.TokenToSelect +import com.tangem.feature.swap.presentation.R +import com.valentinilk.shimmer.shimmer + +@Composable +fun SwapSelectTokenScreen(state: SwapSelectTokenStateHolder, onBack: () -> Unit) { + + TangemTheme { + Scaffold( + content = { + ListOfTokens(state = state) + }, + topBar = { + ExpandableSearchView( + title = stringResource(R.string.swapping_token_list_your_title), + onBackClick = onBack, + placeholderSearchText = stringResource(id = R.string.search_tokens_title), + onSearchChanged = state.onSearchEntered, + onSearchDisplayClosed = { state.onSearchEntered("") }, + ) + }, + ) + } +} + +@Composable +private fun ListOfTokens(state: SwapSelectTokenStateHolder) { + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .background(color = TangemTheme.colors.background.primary) + .padding( + start = TangemTheme.dimens.spacing16, + bottom = TangemTheme.dimens.spacing32, + ), + horizontalAlignment = Alignment.CenterHorizontally, + + ) { + + stickyHeader { + Header(title = R.string.swapping_token_list_your_tokens) + } + + + itemsIndexed(items = state.addedTokens) { index, item -> + TokenItem(token = item, onTokenClick = { state.onTokenSelected(item.id) }) + + if (index != state.addedTokens.lastIndex) Divider( + color = TangemTheme.colors.stroke.primary, + startIndent = TangemTheme.dimens.spacing54, + ) + } + + stickyHeader { + Header(title = R.string.swapping_token_list_other_tokens) + } + + itemsIndexed(items = state.otherTokens) { index, item -> + TokenItem(token = item, onTokenClick = { state.onTokenSelected(item.id) }) + if (index != state.otherTokens.lastIndex) Divider( + color = TangemTheme.colors.stroke.primary, + startIndent = TangemTheme.dimens.spacing54, + ) + } + } +} + +@Composable +private fun Header(@StringRes title: Int) { + Text( + text = stringResource(id = title).uppercase(), + style = TangemTheme.typography.overline, + color = TangemTheme.colors.text.tertiary, + modifier = Modifier + .fillMaxWidth() + .background(color = TangemTheme.colors.background.primary) + .padding(vertical = TangemTheme.dimens.spacing6), + textAlign = TextAlign.Start, + + ) +} + +@Composable +private fun TokenItem( + token: TokenToSelect, + onTokenClick: (String) -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding( + top = TangemTheme.dimens.spacing14, + bottom = TangemTheme.dimens.spacing14, + end = TangemTheme.dimens.spacing16, + ) + .clickable( + onClick = { onTokenClick(token.id) }, + ), + ) { + Box( + modifier = Modifier + .padding(end = TangemTheme.dimens.spacing12) + .align(Alignment.CenterVertically), + ) { + val iconModifier = Modifier + .size(TangemTheme.dimens.size40) + SubcomposeAsyncImage( + modifier = iconModifier, + model = ImageRequest.Builder(LocalContext.current) + .data(token.iconUrl) + .crossfade(true) + .build(), + contentDescription = token.id, + loading = { TokenImageShimmer(modifier = iconModifier) }, + error = { CurrencyPlaceholderIcon(modifier = iconModifier, id = token.id) }, + ) + } + + Column( + modifier = Modifier + .align(Alignment.CenterVertically), + ) { + Text( + text = token.name, + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + ) + SpacerW2() + Text( + text = token.symbol, + style = TangemTheme.typography.caption, + color = TangemTheme.colors.text.tertiary, + ) + } + + Spacer(modifier = Modifier.weight(1f)) + + if (token.addedTokenBalanceData != null) { + Column( + horizontalAlignment = Alignment.End, + verticalArrangement = Arrangement.Center, + modifier = Modifier + .padding(start = TangemTheme.dimens.spacing8), + ) { + Text( + text = token.addedTokenBalanceData.amount ?: "", + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + ) + SpacerW2() + Text( + text = token.addedTokenBalanceData.amountEquivalent ?: "", + style = TangemTheme.typography.caption, + color = TangemTheme.colors.text.tertiary, + ) + } + } + + } +} + +@Composable +private fun TokenImageShimmer( + modifier: Modifier = Modifier, +) { + Box(modifier = modifier.shimmer()) { + Box( + modifier = Modifier + .matchParentSize() + .background( + color = TangemTheme.colors.button.secondary, + shape = CircleShape, + ), + ) + } +} + +private val token = TokenToSelect( + id = "", + name = "USDC", + symbol = "USDC", + iconUrl = "", + addedTokenBalanceData = TokenBalanceData( + amount = "15 000 $", + amountEquivalent = "15 000 " + + "USDT", + ), +) + +@Preview +@Composable +fun TokenScreenPreview() { + SwapSelectTokenScreen( + state = SwapSelectTokenStateHolder( + listOf(token, token, token), listOf(token, token, token), {}, {}, + ), + ) { + + } +} \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt index d780d7e126..cf8bb208f0 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt @@ -3,6 +3,7 @@ package com.tangem.feature.swap.ui import androidx.annotation.DrawableRes import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -15,10 +16,10 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Card -import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Text @@ -46,9 +47,9 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.swap.models.TransactionCardType import com.valentinilk.shimmer.shimmer -@OptIn(ExperimentalMaterialApi::class) @Composable fun TransactionCard( + modifier: Modifier = Modifier, type: TransactionCardType, amount: String?, amountEquivalent: String?, @@ -56,15 +57,12 @@ fun TransactionCard( tokenCurrency: String, @DrawableRes networkIconRes: Int? = null, onChangeTokenClick: (() -> Unit)? = null, - modifier: Modifier = Modifier, ) { Card( modifier = modifier, shape = RoundedCornerShape(TangemTheme.dimens.radius12), backgroundColor = TangemTheme.colors.background.primary, elevation = TangemTheme.dimens.elevation2, - onClick = onChangeTokenClick ?: {}, - enabled = onChangeTokenClick != null, ) { Box(modifier = Modifier.fillMaxSize()) { Column( @@ -96,6 +94,13 @@ fun TransactionCard( Box(modifier = Modifier.align(Alignment.CenterEnd)) { ChangeTokenSelector() } + Box( + Modifier + .align(Alignment.CenterEnd) + .height(TangemTheme.dimens.size116) + .width(TangemTheme.dimens.size102) + .clickable { onChangeTokenClick() }, + ) } } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index 245d7b404b..b461e8a63a 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -14,9 +14,12 @@ import com.tangem.feature.swap.models.FeeState import com.tangem.feature.swap.models.SwapButton import com.tangem.feature.swap.models.SwapCardData import com.tangem.feature.swap.models.SwapPermissionStateHolder +import com.tangem.feature.swap.models.SwapSelectTokenStateHolder import com.tangem.feature.swap.models.SwapStateHolder import com.tangem.feature.swap.models.SwapSuccessStateHolder import com.tangem.feature.swap.models.SwapWarning +import com.tangem.feature.swap.models.TokenBalanceData +import com.tangem.feature.swap.models.TokenToSelect import com.tangem.feature.swap.models.TransactionCardType import com.tangem.feature.swap.presentation.R import com.tangem.feature.swap.router.SwapRouter @@ -45,7 +48,7 @@ internal class SwapViewModel @Inject constructor( swapRouter = router uiState = uiState.copy( onBackClicked = router::back, - onSelectTokenClick = { router.openScreen(SwapScreen.Success) }, + onSelectTokenClick = { router.openScreen(SwapScreen.SelectToken) }, onSuccess = { router.openScreen(SwapScreen.Success) }, ) } @@ -108,6 +111,19 @@ internal class SwapViewModel @Inject constructor( cancelButton = CancelPermissionButton(true) {}, ) + val token = TokenToSelect( + id = "USDC", + name = "USDC", + symbol = "USDC", + iconUrl = "", + addedTokenBalanceData = TokenBalanceData(amount = "15 000 $", amountEquivalent = "15 000 USDT"), + ) + + val selectTokenState = SwapSelectTokenStateHolder( + listOf(token, token, token, token, token, token, token, token, token), + listOf(token, token, token, token, token, token, token, token, token), {}, {}, + ) + return SwapStateHolder( sendCardData = sendCard, receiveCardData = receiveCard, @@ -118,6 +134,7 @@ internal class SwapViewModel @Inject constructor( onRefresh = {}, onBackClicked = {}, onChangeCardsClicked = {}, permissionState = permissionState, successState = SwapSuccessStateHolder("Success"), + selectTokenState = selectTokenState, ) } // endregion temp for test From 9aaf4bd3c401a83c2d0a26f95e6b3147edc9b588 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Dec 2022 15:29:08 +0400 Subject: [PATCH 028/145] Updated on 2026-08-14 --- .../compose/AddCustomTokenScreen.kt | 16 ++- .../compose/HangingOverKeyboardView.kt | 2 +- .../tokens/ui/compose/CurrenciesScreen.kt | 4 +- .../core/ui/components/KeybosardState.kt | 14 +- .../feature/swap/models/SwapStateHolder.kt | 3 + .../feature/swap/ui/SwapScreenContent.kt | 104 +++++++++++---- .../tangem/feature/swap/ui/TransactionCard.kt | 121 +++++++++++++++--- .../feature/swap/viewmodels/SwapViewModel.kt | 3 +- 8 files changed, 205 insertions(+), 62 deletions(-) rename app/src/main/java/com/tangem/tap/common/compose/KeyboardState.kt => core/ui/src/main/java/com/tangem/core/ui/components/KeybosardState.kt (75%) 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 93066fc926..cfa3c7a5b8 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 @@ -38,6 +38,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.keyboardAsState import com.tangem.domain.AddCustomTokenError import com.tangem.domain.common.form.DataField import com.tangem.domain.common.form.FieldId @@ -56,7 +57,6 @@ import com.tangem.tap.common.compose.AddCustomTokenWarning import com.tangem.tap.common.compose.ClosePopupTrigger 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.domain.moduleMessage.ModuleMessageConverter import com.tangem.tap.features.tokens.addCustomToken.compose.test.TestCase import com.tangem.tap.features.tokens.addCustomToken.compose.test.TestCasesList @@ -66,7 +66,7 @@ import kotlinx.coroutines.launch /** [REDACTED_AUTHOR] */ -private class AddCustomTokenScreen {} // for simple search +private class AddCustomTokenScreen // for simple search @OptIn(ExperimentalMaterialApi::class) @Composable @@ -91,11 +91,13 @@ fun AddCustomTokenScreen( }, sheetPeekHeight = 0.dp, ) { - Column() { - TestCasesList(onItemClick = { - selectedTestCase.value = it - toggleBottomSheet() - }) + Column { + TestCasesList( + onItemClick = { + selectedTestCase.value = it + toggleBottomSheet() + }, + ) ScreenContent(state, closePopupTrigger) } } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/HangingOverKeyboardView.kt b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/HangingOverKeyboardView.kt index 46e31151a8..4e251f0838 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/HangingOverKeyboardView.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/HangingOverKeyboardView.kt @@ -9,8 +9,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.Keyboard import com.tangem.tangem_sdk_new.extensions.pxToDp -import com.tangem.tap.common.compose.Keyboard /** [REDACTED_AUTHOR] diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt index 149fd2b517..7148c6ecb7 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt @@ -26,16 +26,16 @@ import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.tangem.blockchain.common.Blockchain +import com.tangem.core.ui.components.Keyboard import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.components.keyboardAsState import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.ManageTokens -import com.tangem.tap.common.compose.Keyboard import com.tangem.tap.common.compose.extensions.addAndNotify import com.tangem.tap.common.compose.extensions.removeAndNotify -import com.tangem.tap.common.compose.keyboardAsState import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.pixelsToDp import com.tangem.tap.common.redux.AppDialog diff --git a/app/src/main/java/com/tangem/tap/common/compose/KeyboardState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/KeybosardState.kt similarity index 75% rename from app/src/main/java/com/tangem/tap/common/compose/KeyboardState.kt rename to core/ui/src/main/java/com/tangem/core/ui/components/KeybosardState.kt index 2f08f3ae15..69d24e6980 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/KeyboardState.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/KeybosardState.kt @@ -1,8 +1,13 @@ -package com.tangem.tap.common.compose +package com.tangem.core.ui.components import android.graphics.Rect import android.view.ViewTreeObserver -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalView sealed class Keyboard { @@ -10,6 +15,9 @@ sealed class Keyboard { object Closed : Keyboard() } +/** + * Allows to subscribe to a soft keyboard to detect when it's open/closed + */ @Composable fun keyboardAsState(): State { val keyboardState: MutableState = remember { mutableStateOf(Keyboard.Closed) } @@ -25,7 +33,7 @@ fun keyboardAsState(): State { val screenHeight = view.rootView.height val keypadHeight: Int = screenHeight - (rect.bottom + rect.top) - discrepancy.value if (discrepancy.value == 0) { - discrepancy.value = keypadHeight; + discrepancy.value = keypadHeight if (keypadHeight == 0) discrepancy.value = 1 } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt index 3e0272c54c..f8e9dada94 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt @@ -20,6 +20,7 @@ data class SwapStateHolder( val onChangeCardsClicked: () -> Unit, val onSelectTokenClick: (() -> Unit)? = null, val onSuccess: (() -> Unit)? = null, + val onMaxAmountSelected: (() -> Unit)? = null, ) data class SwapCardData( @@ -53,6 +54,7 @@ sealed interface TransactionCardType { data class SendCard( val balance: String, val permissionIsGiven: Boolean, + val onAmountChanged: ((String) -> Unit), ) : TransactionCardType data class ReceiveCard( @@ -62,6 +64,7 @@ sealed interface TransactionCardType { sealed interface SwapWarning { data class PermissionNeeded(val tokenCurrency: String) : SwapWarning + data class GenericWarning(val message: String, val onClick: () -> Unit) : SwapWarning // data class RateExpired(val onClick: () -> Unit) : SwapWarning // object HighPriceImpact : SwapWarning } \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index 03249e7a4a..41c04fcfbd 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -1,9 +1,12 @@ package com.tangem.feature.swap.ui +import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -14,17 +17,24 @@ import androidx.compose.material.Card import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.ClickableWarningCard +import com.tangem.core.ui.components.Keyboard import com.tangem.core.ui.components.PrimaryButtonIconRight +import com.tangem.core.ui.components.RefreshableWaringCard import com.tangem.core.ui.components.SmallInfoCard import com.tangem.core.ui.components.SmallInfoCardWithWarning import com.tangem.core.ui.components.SpacerH16 +import com.tangem.core.ui.components.keyboardAsState import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.swap.models.FeeState import com.tangem.feature.swap.models.SwapButton @@ -40,40 +50,71 @@ internal fun SwapScreenContent( state: SwapStateHolder, onPermissionWarningClick: () -> Unit, ) { - Column( - modifier = modifier - .fillMaxWidth() - .background(color = TangemTheme.colors.background.primary) - .verticalScroll(rememberScrollState()) - .padding( - start = TangemTheme.dimens.spacing16, - end = TangemTheme.dimens.spacing16, - top = TangemTheme.dimens.spacing16, - bottom = TangemTheme.dimens.spacing32, - ), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16), + val keyboard by keyboardAsState() + + Box( + modifier = Modifier + .fillMaxSize() + .background(color = Color.Transparent), ) { - MainInfo(state) + Column( + modifier = modifier + .fillMaxWidth() + .background(color = TangemTheme.colors.background.primary) + .verticalScroll(rememberScrollState()) + .padding( + start = TangemTheme.dimens.spacing16, + end = TangemTheme.dimens.spacing16, + top = TangemTheme.dimens.spacing16, + bottom = TangemTheme.dimens.spacing32, + ), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16), + ) { - FeeItem(feeState = state.fee, currency = state.networkCurrency) + MainInfo(state) - if (state.warnings.isNotEmpty()) { - SwapWarnings( - warnings = state.warnings, - onApproveWarningClick = onPermissionWarningClick, + FeeItem(feeState = state.fee, currency = state.networkCurrency) + + if (state.warnings.isNotEmpty()) { + SwapWarnings( + warnings = state.warnings, + onApproveWarningClick = onPermissionWarningClick, + ) + } + + PrimaryButtonIconRight( + modifier = Modifier.fillMaxWidth(), + text = stringResource(id = R.string.swapping_swap), + icon = painterResource(id = R.drawable.ic_tangem_24), + enabled = state.swapButton.enabled, + showProgress = state.swapButton.loading, + onClick = state.swapButton.onClick, ) } - PrimaryButtonIconRight( - modifier = Modifier.fillMaxWidth(), - text = stringResource(id = R.string.swapping_swap), - icon = painterResource(id = R.drawable.ic_tangem_24), - enabled = state.swapButton.enabled, - showProgress = state.swapButton.loading, - onClick = state.swapButton.onClick, - ) + AnimatedVisibility( + visible = keyboard is Keyboard.Opened, + modifier = Modifier + .align(Alignment.BottomCenter), + ) { + Text( + text = stringResource(id = R.string.send_max_amount_label), + style = TangemTheme.typography.button, + color = TangemTheme.colors.text.primary1, + modifier = Modifier + .fillMaxWidth() + + .background(TangemTheme.colors.button.secondary) + .clickable { state.onMaxAmountSelected } + .padding( + horizontal = TangemTheme.dimens.spacing14, + vertical = TangemTheme.dimens.spacing16, + ), + textAlign = TextAlign.Start, + ) + } } } @@ -171,6 +212,13 @@ private fun SwapWarnings( onClick = onApproveWarningClick, ) } + is SwapWarning.GenericWarning -> { + RefreshableWaringCard( + title = stringResource(id = R.string.common_warning), + description = warning.message, + onClick = warning.onClick, + ) + } // is SwapWarning.RateExpired -> { // RefreshableWaringCard( // title = stringResource(id = R.string.), @@ -186,7 +234,7 @@ private fun SwapWarnings( // region preview private val sendCard = SwapCardData( - type = TransactionCardType.SendCard("123", false), + type = TransactionCardType.SendCard("123", false, {}), amount = "1 000 000", amountEquivalent = "1 000 000", tokenIconUrl = "", diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt index cf8bb208f0..f5448c3386 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -19,18 +20,31 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.Card import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.ParagraphIntrinsics +import androidx.compose.ui.text.font.createFontFamilyResolver +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -192,24 +206,34 @@ private fun Content( horizontalAlignment = Alignment.Start, ) { - if (amount != null) { - ResizableText( - text = amount, - color = TangemTheme.colors.text.primary1, - style = TangemTheme.typography.h2, - fontSizeRange = - FontSizeRange(min = 16.sp, max = TangemTheme.typography.h2.fontSize), - ) - } else { - Box( - modifier = Modifier - .size(width = TangemTheme.dimens.size102, height = TangemTheme.dimens.size24) - .shimmer() - .background( - color = TangemTheme.colors.button.secondary, - shape = RoundedCornerShape(TangemTheme.dimens.radius6), - ), - ) + when (type) { + is TransactionCardType.ReceiveCard -> { + if (amount != null) { + ResizableText( + text = amount, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.h2, + fontSizeRange = + FontSizeRange(min = 16.sp, max = TangemTheme.typography.h2.fontSize), + ) + } else { + Box( + modifier = Modifier + .size(width = TangemTheme.dimens.size102, height = TangemTheme.dimens.size24) + .shimmer() + .background( + color = TangemTheme.colors.button.secondary, + shape = RoundedCornerShape(TangemTheme.dimens.radius6), + ), + ) + } + } + is TransactionCardType.SendCard -> { + AutoSizeTextField( + amount = amount ?: "0", + onAmoutChanged = { type.onAmountChanged(it) }, + ) + } } Spacer( @@ -241,6 +265,65 @@ private fun Content( } } +@Composable +private fun AutoSizeTextField( + amount: String, + onAmoutChanged: (String) -> Unit, +) { + val focusManager = LocalFocusManager.current + val textFieldFocusRequester = remember { FocusRequester() } + + BoxWithConstraints(modifier = Modifier.fillMaxWidth()) { + + var shrunkFontSize = TangemTheme.typography.h2.fontSize + val calculateIntrinsics = @Composable { + ParagraphIntrinsics( + text = amount, + style = TangemTheme.typography.h2.copy( + color = TangemTheme.colors.text.primary1, + fontSize = shrunkFontSize, + ), + density = LocalDensity.current, + fontFamilyResolver = createFontFamilyResolver(LocalContext.current), + ) + } + + var intrinsics = calculateIntrinsics() + + with(LocalDensity.current) { + while (intrinsics.maxIntrinsicWidth > maxWidth.toPx()) { + shrunkFontSize *= 0.9f + intrinsics = calculateIntrinsics() + } + } + + BasicTextField( + value = amount, + onValueChange = { + onAmoutChanged(it) + }, + singleLine = true, + modifier = Modifier + .fillMaxWidth() + .focusRequester(textFieldFocusRequester), + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Done, + keyboardType = KeyboardType.Decimal, + ), + keyboardActions = KeyboardActions( + onDone = { + focusManager.clearFocus() + }, + ), + textStyle = TangemTheme.typography.h2.copy( + color = TangemTheme.colors.text.primary1, + fontSize = shrunkFontSize, + ), + cursorBrush = SolidColor(TangemTheme.colors.text.primary1), + ) + } +} + @Composable fun Token( tokenIconUrl: String, @@ -370,7 +453,7 @@ fun Preview_SwapMainCard_InDarkTheme() { @Composable private fun TransactionCardPreview() { TransactionCard( - type = TransactionCardType.SendCard("123", false), + type = TransactionCardType.SendCard("123", false, {}), amount = "1 000 000", amountEquivalent = "1 000 000", tokenIconUrl = "", diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index 06a0292de9..30004ba1ec 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -7,7 +7,6 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.tangem.feature.swap.domain.SwapInteractor -import com.tangem.feature.swap.domain.models.data.Currency import com.tangem.feature.swap.models.ApprovePermissionButton import com.tangem.feature.swap.models.CancelPermissionButton import com.tangem.feature.swap.models.FeeState @@ -72,7 +71,7 @@ internal class SwapViewModel @Inject constructor( private fun createTestUiState(): SwapStateHolder { val sendCard = SwapCardData( - type = TransactionCardType.SendCard("123", false), + type = TransactionCardType.SendCard("123", false, {}), amount = "1 000 000 000 000 000 000", amountEquivalent = "1 000 000 000 000 000 000", tokenIconUrl = "", From 394b5b960f9d4974f9d387899d8896fab5d5374a Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Dec 2022 20:48:39 +0300 Subject: [PATCH 029/145] Updated on 2026-08-14 --- .../common/compose/ComposeDialogManager.kt | 1 + .../IndeterminateProgressButtonWidget.kt | 1 + .../toggleWidget/RefreshBalanceWidget.kt | 1 + .../tap/features/home/redux/HomeReducer.kt | 1 + .../note/redux/OnboardingNoteMiddleware.kt | 4 +++- .../OnboardingOtherCardsFragment.kt | 1 + .../redux/OnboardingOtherCardsMiddleware.kt | 1 + .../twins/redux/TwinCardsMiddleware.kt | 1 + .../products/twins/redux/TwinCardsReducer.kt | 1 + .../products/twins/ui/TwinsCardsFragment.kt | 19 ++++++++++++------- .../redux/OnboardingWalletMiddleware.kt | 2 ++ .../saltPay/ui/OnboardingSaltPayView.kt | 1 + .../stateSubscribers/SendStateSubscriber.kt | 2 ++ .../tap/features/shop/redux/ShopMiddleware.kt | 1 + .../middlewares/MultiWalletMiddleware.kt | 1 + .../tap/features/wallet/ui/BalanceWidget.kt | 1 + .../wallet/ui/WalletDetailsFragment.kt | 1 + buildSrc/src/main/java/Versions.kt | 6 +++--- .../domain/redux/global/DomainGlobalHub.kt | 4 ++-- 19 files changed, 37 insertions(+), 13 deletions(-) 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 99042c3391..8d0de7df69 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 @@ -80,6 +80,7 @@ private fun ShowTheDialog(dialogState: MutableState) { onDismissRequest ) is DomainDialog.SelectTokenDialog -> SelectTokenNetworkDialog(dialog, onDismissRequest) + else -> {} } } diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt index 4b15ee3cb5..56f80d5d78 100644 --- a/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt +++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt @@ -38,6 +38,7 @@ open class IndeterminateProgressButtonWidget( when (progressState) { ProgressState.Done, ProgressState.Error -> switchToNone() ProgressState.Loading -> switchToProgress() + else -> {} } } diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt index 7bccdffc08..ca3bb64dbf 100644 --- a/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt +++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt @@ -74,6 +74,7 @@ class RefreshBalanceWidget( progressViewAnimation?.repeatCount = -1 progressViewAnimation?.let { arrowView.startAnimation(it) } } + else -> {} } } diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt index 511aea3ddc..e731e02945 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt @@ -24,6 +24,7 @@ private fun internalReduce(action: Action, state: AppState): HomeState { is HomeAction.ChangeScanCardButtonState -> { state = state.copy(btnScanState = action.state) } + else -> {} } return state diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt index 7119dfaf0d..cb113b0ab0 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt @@ -96,6 +96,7 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch onboardingManager.activationFinished(card.cardId) postUi(DELAY_SDK_DIALOG_CLOSE) { store.dispatch(OnboardingNoteAction.Confetti.Show) } } + else -> {} } } is OnboardingNoteAction.CreateWallet -> { @@ -186,9 +187,10 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch store.dispatchOpenUrl(topUpUrl) } - OnboardingNoteAction.Done -> { + is OnboardingNoteAction.Done -> { store.dispatch(GlobalAction.Onboarding.Stop) OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse) } + else -> {} } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt index b0b8ae343d..080ce21093 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt @@ -65,6 +65,7 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment setupCreateWalletState() OnboardingOtherCardsStep.Done -> setupDoneState() + else -> {} } showConfetti(state.showConfetti) } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt index 91536792a9..425212b27a 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt @@ -71,6 +71,7 @@ private fun handleOtherCardsAction(action: Action) { onboardingManager.activationFinished(card.cardId) postUi(200) { store.dispatch(OnboardingOtherCardsAction.Confetti.Show) } } + else -> {} } } is OnboardingOtherCardsAction.CreateWallet -> { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index ae60d9f637..bc94264e05 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -317,5 +317,6 @@ private fun handle(action: Action, dispatch: DispatchFunction) { scanResponse = action.scanResponse, ) } + else -> {} } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsReducer.kt index d482a5a647..d501a484a6 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsReducer.kt @@ -67,6 +67,7 @@ private fun internalReduce(action: Action, state: AppState): TwinCardsState { is TwinCardsAction.Confetti.Hide -> { state = state.copy(showConfetti = false) } + else -> {} } return state diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt index 36e386325e..3b60b81555 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt @@ -133,13 +133,14 @@ class TwinsCardsFragment : BaseOnboardingFragment() { when (state.currentStep) { is TwinCardsStep.WelcomeOnly -> setupWelcomeOnlyState(state, state.currentStep.scanResponse) - TwinCardsStep.Welcome -> setupWelcomeState(state) - TwinCardsStep.Warning -> setupWarningState(state) - TwinCardsStep.CreateFirstWallet -> setupCreateFirstWalletState(state) - TwinCardsStep.CreateSecondWallet -> setupCreateSecondWalletState(state) - TwinCardsStep.CreateThirdWallet -> setupCreateThirdWalletState(state) - TwinCardsStep.TopUpWallet -> setupTopUpWalletState(state) - TwinCardsStep.Done -> setupDoneState(state) + is TwinCardsStep.Welcome -> setupWelcomeState(state) + is TwinCardsStep.Warning -> setupWarningState(state) + is TwinCardsStep.CreateFirstWallet -> setupCreateFirstWalletState(state) + is TwinCardsStep.CreateSecondWallet -> setupCreateSecondWalletState(state) + is TwinCardsStep.CreateThirdWallet -> setupCreateThirdWalletState(state) + is TwinCardsStep.TopUpWallet -> setupTopUpWalletState(state) + is TwinCardsStep.Done -> setupDoneState(state) + else -> {} } setBalance(state) @@ -231,12 +232,14 @@ class TwinsCardsFragment : BaseOnboardingFragment() { false, ) } + else -> {} } } } TwinCardsStep.Welcome, TwinCardsStep.Warning -> { twinsWidget.toLeapfrog(onEnd = { switchToCard(state.cardNumber) }) } + else -> {} } val twinIndexNumber = state.cardNumber?.indexNumber() @@ -314,11 +317,13 @@ class TwinsCardsFragment : BaseOnboardingFragment() { twinsWidget.toActivate(false) } } + else -> {} } } TwinCardsStep.CreateThirdWallet -> { twinsWidget.toActivate() } + else -> {} } if (state.isBuyAllowed) { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index c199ac3195..c654c20ea2 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -188,6 +188,7 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: newAction?.let { store.dispatch(it) } } OnboardingWalletAction.OnBackPressed -> handleOnBackPressed(onboardingWalletState) + else -> {} } } @@ -355,6 +356,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) if (onboardingWalletState.isSaltPay) throw UnsupportedOperationException() store.dispatch(BackupAction.FinishBackup) } + else -> {} } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt index fcbcfb74ff..429a84cbab 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt @@ -146,6 +146,7 @@ internal class OnboardingSaltPayView( SaltPayActivationStep.ClaimInProgress -> handleClaim(state) SaltPayActivationStep.ClaimSuccess -> handleClaim(state) SaltPayActivationStep.Success -> handleSuccess(state) + else -> {} } progressButton?.changeState(state.mainButtonState) } diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt index de2fd70ef6..78bff6c996 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt @@ -289,6 +289,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : pbReceiptFee.hide() tvReceiptFeeValue.show() } + else -> {} } @@ -356,6 +357,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : } llTotalContainer.tvTotalTokenCryptoValue.update(willSent.toString()) } + else -> {} } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt index f7f126c05f..79f2e10a6c 100644 --- a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt @@ -122,5 +122,6 @@ private fun handle(action: Action) { shopService.waitForCheckout(shopState.selectedProduct) } } + else -> {} } } \ No newline at end of file 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 9bba0011a5..44f184a4c1 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 @@ -175,6 +175,7 @@ class MultiWalletMiddleware { store.dispatch(WalletAction.Scan) } } + else -> {} } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt index 4afc4c08db..025e3cc9ee 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt @@ -141,6 +141,7 @@ class BalanceWidget( tvErrorDescriptions.text = fragment.getString(R.string.wallet_error_unsupported_blockchain_subtitle) } + else -> {} } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index a7f230dc35..8083e9ba2b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -396,6 +396,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), data.amountToCreateAccount, data.currencySymbol, ) } + else -> {} } } diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index f63622d48b..1ad00eea91 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -4,7 +4,7 @@ object Versions { const val androidMavenGradlePlugin = "2.1" const val googleFirebaseCrashlytics = "2.7.1" const val googleServices = "4.3.10" - const val kotlin = "1.6.10" + const val kotlin = "1.7.10" // endregion Classpath // region AndroidX @@ -18,8 +18,8 @@ object Versions { // endregion AndroidX // region Compose - const val compose = "1.1.0" - const val composeFoundation = "1.2.0-rc03" + const val compose = "1.3.1" + const val composeFoundation = "1.3.1" // endregion Compose // region Other libraries 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 a0d0cb833b..38c30c5177 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 @@ -31,8 +31,8 @@ internal class DomainGlobalHub : BaseStoreHub("DomainGlobalHu val state = storeState.globalState - when (action) { - } + // when (action) { + // } } override fun getReducer(): ReStoreReducer = DomainGlobalReducer() From aabfca86e7f0def42829b20824b6cea6fe43a830 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 17:16:10 +0300 Subject: [PATCH 030/145] Updated on 2026-08-14 --- app/src/main/res/values/colors.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index b00c6603a8..db3d7d7743 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -4,7 +4,7 @@ #0029FF #0022D4 #19C878 - #801ACE80 + #4fd79e @color/accent From a8543c47d47d7b479e4a784185c8c9676fd80aff Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 17:56:50 +0300 Subject: [PATCH 031/145] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/common/compose/Button.kt | 4 ++-- app/src/main/res/color/selector_btn_green.xml | 5 ++--- app/src/main/res/values/colors.xml | 3 --- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/compose/Button.kt b/app/src/main/java/com/tangem/tap/common/compose/Button.kt index b20f7470b8..38ee83d9b1 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/Button.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/Button.kt @@ -92,7 +92,7 @@ fun PasteButton( when (content) { null -> { val tintColor = tint - ?: colorResource(id = if (enabled) R.color.accent else R.color.accent_disabled) + ?: colorResource(id = if (enabled) R.color.button_positive else R.color.button_positive_disabled) Icon( painterResource(id = R.drawable.ic_paste), contentDescription = "Paste", @@ -121,7 +121,7 @@ fun ClearButton( when (content) { null -> { val tintColor = tint - ?: colorResource(id = if (enabled) R.color.accent else R.color.accent_disabled) + ?: colorResource(id = if (enabled) R.color.button_positive else R.color.button_positive_disabled) Icon( painterResource(id = R.drawable.ic_clear), contentDescription = "Clear", diff --git a/app/src/main/res/color/selector_btn_green.xml b/app/src/main/res/color/selector_btn_green.xml index 32bd8b9847..0af5088cd4 100644 --- a/app/src/main/res/color/selector_btn_green.xml +++ b/app/src/main/res/color/selector_btn_green.xml @@ -1,6 +1,5 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index db3d7d7743..a5c157e311 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -4,12 +4,9 @@ #0029FF #0022D4 #19C878 - #4fd79e @color/accent - @color/accent - @color/accent_disabled @color/darkGray6 #6A6A6A From a7eba5683e6d55ddc29f944556f97fd433a014ac Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 09:05:35 +0300 Subject: [PATCH 032/145] Updated on 2026-08-14 --- .../redux/OnboardingWalletMiddleware.kt | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index c654c20ea2..980c0ef64b 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -124,16 +124,15 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: primaryCard = result.data.primaryCard, ) onboardingManager.scanResponse = updatedResponse - val blockchainNetworks = listOf( - BlockchainNetwork( - blockchain = Blockchain.Bitcoin, - card = result.data.card, - ), - BlockchainNetwork( - blockchain = Blockchain.Ethereum, - card = result.data.card, - ), - ) + + val blockchainNetworks = if (DemoHelper.isDemoCardId(result.data.card.cardId)) { + DemoHelper.config.demoBlockchains + } else { + listOf(Blockchain.Bitcoin, Blockchain.Ethereum) + }.map { blockchain -> + BlockchainNetwork(blockchain, result.data.card) + } + store.dispatch( WalletAction.MultiWallet.SaveCurrencies( blockchainNetworks = blockchainNetworks, From 25e601a1c9160793a09766691a9e5385118e67a8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 30 Dec 2022 11:19:06 +0400 Subject: [PATCH 033/145] Updated on 2026-08-14 --- .../com/tangem/tap/features/demo/DemoHelper.kt | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt index b0d473cde0..a5823818dd 100644 --- a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt @@ -1,6 +1,5 @@ package com.tangem.tap.features.demo -import com.tangem.blockchain.blockchains.bitcoin.BitcoinWalletManager import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.TransactionData @@ -494,21 +493,20 @@ class DemoTransactionSender( override suspend fun getFee(amount: Amount, destination: String): Result> { val blockchain = walletManager.wallet.blockchain - return when (walletManager) { - is BitcoinWalletManager -> Result.Success(listOf( + return Result.Success( + listOf( Amount(0.0001.toBigDecimal(), blockchain), + Amount(0.0002.toBigDecimal(), blockchain), Amount(0.0003.toBigDecimal(), blockchain), - Amount(0.00055.toBigDecimal(), blockchain), - )) - else -> sender.getFee(amount, destination) - } + ), + ) } override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult { val dataToSign = randomString(32).toByteArray() val signerResponse = signer.sign( hash = dataToSign, - publicKey = walletManager.wallet.publicKey + publicKey = walletManager.wallet.publicKey, ) return when (signerResponse) { is CompletionResult.Success -> SimpleResult.Failure(Exception(ID).toBlockchainSdkError()) @@ -529,5 +527,4 @@ class DemoTransactionSender( companion object { val ID = DemoTransactionSender::class.java.simpleName } - } \ No newline at end of file From 725705ecf27da5a121c94f6567ff663c75932016 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 30 Dec 2022 11:18:15 +0300 Subject: [PATCH 034/145] Updated on 2026-08-14 --- .../wallet/data/WalletRepositoryImpl.kt | 20 ++++++++++++++++++ .../wallet/domain/WalletRepository.kt | 10 +++++++++ .../middlewares/AppCurrencyMiddleware.kt | 21 +++++++------------ .../redux/middlewares/WalletMiddleware.kt | 4 +++- .../persistence/FiatCurrenciesPrefStorage.kt | 8 +++---- .../datasource/api/tangemTech/Responses.kt | 12 ----------- .../api/tangemTech/TangemTechApi.kt | 3 ++- .../api/tangemTech/TangemTechService.kt | 6 +----- .../tangemTech/models/CurrenciesResponse.kt | 16 ++++++++++++++ 9 files changed, 64 insertions(+), 36 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/data/WalletRepositoryImpl.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/domain/WalletRepository.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CurrenciesResponse.kt diff --git a/app/src/main/java/com/tangem/tap/features/wallet/data/WalletRepositoryImpl.kt b/app/src/main/java/com/tangem/tap/features/wallet/data/WalletRepositoryImpl.kt new file mode 100644 index 0000000000..a8d6086302 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/data/WalletRepositoryImpl.kt @@ -0,0 +1,20 @@ +package com.tangem.tap.features.wallet.data + +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse +import com.tangem.tap.features.wallet.domain.WalletRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +/** + * Implementation of repository for Wallet feature + * + * @property tangemTechApi API for server requests + */ +class WalletRepositoryImpl(private val tangemTechApi: TangemTechApi) : WalletRepository { + + override suspend fun getCurrencyList(): CurrenciesResponse = + withContext(Dispatchers.IO) { // TODO("After adding DI") replace with CoroutineDispatcherProvider + tangemTechApi.getCurrencyList() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/domain/WalletRepository.kt b/app/src/main/java/com/tangem/tap/features/wallet/domain/WalletRepository.kt new file mode 100644 index 0000000000..c3ba927d31 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/domain/WalletRepository.kt @@ -0,0 +1,10 @@ +package com.tangem.tap.features.wallet.domain + +import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse + +/** Repository for Wallet feature */ +interface WalletRepository { + + /** Get list of currency */ + suspend fun getCurrencyList(): CurrenciesResponse +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt index c60632f60e..3b6375373a 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt @@ -1,8 +1,6 @@ package com.tangem.tap.features.wallet.redux.middlewares -import com.tangem.common.services.Result -import com.tangem.datasource.api.tangemTech.CurrenciesResponse -import com.tangem.datasource.api.tangemTech.TangemTechService +import com.tangem.datasource.api.tangemTech.models.Currency import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.MainScreen @@ -11,6 +9,7 @@ import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.TapWalletManager import com.tangem.tap.features.details.redux.DetailsAction +import com.tangem.tap.features.wallet.domain.WalletRepository import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.models.WalletDialog import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction @@ -21,7 +20,7 @@ import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.launch class AppCurrencyMiddleware( - private val tangemTechService: TangemTechService, + private val walletRepository: WalletRepository, private val tapWalletManager: TapWalletManager, private val fiatCurrenciesPrefStorage: FiatCurrenciesPrefStorage, private val appCurrencyProvider: () -> FiatCurrency, @@ -45,12 +44,10 @@ class AppCurrencyMiddleware( } scope.launch { - when (val result = tangemTechService.currencies()) { - is Result.Success -> { - val currenciesList = result.data.currencies - if (currenciesList.isNotEmpty() && - currenciesList.toSet() != storedFiatCurrencies.toSet() - ) { + runCatching { walletRepository.getCurrencyList() } + .onSuccess { + val currenciesList = it.currencies + if (currenciesList.isNotEmpty() && !currenciesList.toSet().equals(storedFiatCurrencies.toSet())) { fiatCurrenciesPrefStorage.save(currenciesList) store.dispatchDialogShow( WalletDialog.CurrencySelectionDialog( @@ -60,8 +57,6 @@ class AppCurrencyMiddleware( ) } } - is Result.Failure -> {} - } } } @@ -82,7 +77,7 @@ class AppCurrencyMiddleware( } } - private fun List.mapToUiModel(): List { + private fun List.mapToUiModel(): List { return this.map { FiatCurrency( code = it.code, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index 5dcd8b81bd..656faa2527 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -36,6 +36,7 @@ import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.send.redux.PrepareSendScreen +import com.tangem.tap.features.wallet.data.WalletRepositoryImpl import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.PendingTransactionType import com.tangem.tap.features.wallet.models.filterByCoin @@ -72,7 +73,8 @@ class WalletMiddleware { private val walletDialogMiddleware = WalletDialogsMiddleware() private val appCurrencyMiddleware by lazy(mode = LazyThreadSafetyMode.NONE) { AppCurrencyMiddleware( - tangemTechService = store.state.domainNetworks.tangemTechService, + // TODO("After adding DI") get dependencies by DI + walletRepository = WalletRepositoryImpl(store.state.domainNetworks.tangemTechService.api), tapWalletManager = store.state.globalState.tapWalletManager, fiatCurrenciesPrefStorage = preferencesStorage.fiatCurrenciesPrefStorage, appCurrencyProvider = { store.state.globalState.appCurrency }, diff --git a/app/src/main/java/com/tangem/tap/persistence/FiatCurrenciesPrefStorage.kt b/app/src/main/java/com/tangem/tap/persistence/FiatCurrenciesPrefStorage.kt index 59c500f9a7..1a65320a5e 100644 --- a/app/src/main/java/com/tangem/tap/persistence/FiatCurrenciesPrefStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/FiatCurrenciesPrefStorage.kt @@ -3,7 +3,7 @@ package com.tangem.tap.persistence import android.content.SharedPreferences import androidx.core.content.edit import com.tangem.common.json.MoshiJsonConverter -import com.tangem.datasource.api.tangemTech.CurrenciesResponse +import com.tangem.datasource.api.tangemTech.models.Currency import com.tangem.tap.common.entities.FiatCurrency /** @@ -32,14 +32,14 @@ class FiatCurrenciesPrefStorage( preferences.edit { putString(APP_CURRENCY_KEY, json) } } - fun save(currencies: List) { + fun save(currencies: List) { val json: String = converter.toJson(currencies) return preferences.edit().putString(FIAT_CURRENCIES_KEY, json).apply() } - fun restore(): List { + fun restore(): List { val json = preferences.getString(FIAT_CURRENCIES_KEY, "") - val type = converter.typedList(CurrenciesResponse.Currency::class.java) + val type = converter.typedList(Currency::class.java) if (json.isNullOrBlank()) return emptyList() return converter.fromJson(json, type) ?: emptyList() diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt index 532a5debfb..865685b00a 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt @@ -34,18 +34,6 @@ data class CoinsResponse( //rates.keys = networkId's data class RatesResponse(val rates: Map) : TangemTechResponse -data class CurrenciesResponse(val currencies: List) { - - data class Currency( - val id: String, - val code: String, // this is an uppercase id - val name: String, - val rateBTC: String, - val unit: String, // $, €, ₽ - val type: String, - ) : TangemTechResponse -} - data class GeoResponse( val code: String, ) : TangemTechResponse diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index edfab6f5e2..d913dc7353 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -1,5 +1,6 @@ package com.tangem.datasource.api.tangemTech +import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.PUT @@ -29,7 +30,7 @@ interface TangemTechApi { ): RatesResponse @GET("currencies") - suspend fun currencies(): CurrenciesResponse + suspend fun getCurrencyList(): CurrenciesResponse @GET("geo") suspend fun geo(): GeoResponse diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt index 50540a226e..e609c1dafc 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt @@ -19,7 +19,7 @@ class TangemTechService( CacheControlHttpInterceptor(cacheMaxAge), ) - private var api: TangemTechApi = createApi() + var api: TangemTechApi = createApi() suspend fun coins( contractAddress: String? = null, @@ -54,10 +54,6 @@ class TangemTechService( performRequest { api.geo() } } - suspend fun currencies(): Result = withContext(Dispatchers.IO) { - performRequest { api.currencies() } - } - suspend fun getUserTokens(userId: String): Result = withContext(Dispatchers.IO) { performRequest { api.getUserTokens(userId) } } diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CurrenciesResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CurrenciesResponse.kt new file mode 100644 index 0000000000..608b133f1a --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CurrenciesResponse.kt @@ -0,0 +1,16 @@ +package com.tangem.datasource.api.tangemTech.models + +import com.squareup.moshi.Json + +data class CurrenciesResponse( + @Json(name = "currencies") val currencies: List +) + +data class Currency( + @Json(name = "id") val id: String, + @Json(name = "code") val code: String, // this is an uppercase id + @Json(name = "name") val name: String, + @Json(name = "rateBTC") val rateBTC: String, + @Json(name = "unit") val unit: String, // $, €, ₽ + @Json(name = "type") val type: String +) \ No newline at end of file From 04d22aa0929c28a6671788658b2441f22dc66284 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 29 Dec 2022 19:41:26 +0400 Subject: [PATCH 035/145] Updated on 2026-08-14 --- app/build.gradle.kts | 3 ++ .../com/tangem/tap/common/DialogManager.kt | 2 +- .../tap/common/extensions/Navigation.kt | 5 +++ .../redux/navigation/NavigationAction.kt | 2 + .../redux/navigation/NavigationState.kt | 1 + .../tap/features/wallet/redux/WalletAction.kt | 9 ++++- .../tap/features/wallet/redux/WalletState.kt | 3 ++ .../middlewares/TradeCryptoMiddleware.kt | 37 +++++++++++++++++++ .../middlewares/WalletDialogsMiddleware.kt | 8 +++- .../wallet/redux/models/WalletDialog.kt | 11 ++++-- .../wallet/ui/WalletDetailsFragment.kt | 18 ++++----- .../ChooseTradeActionBottomSheetDialog.kt | 34 +++++++++++++---- .../wallet/ui/view/WalletDetailsButtonsRow.kt | 9 ++--- .../wallet/ui/wallet/SingleWalletView.kt | 12 ++++-- .../main/res/layout/dialog_wallet_trade.xml | 19 +++++++++- build.gradle.kts | 1 + buildSrc/src/main/java/Dependency.kt | 1 + buildSrc/src/main/java/Versions.kt | 1 + .../main/res/drawable/ic_arrow_down_24.xml | 10 +++++ .../src/main/res/drawable/ic_arrow_up_24.xml | 10 +++++ features/swap/domain/build.gradle.kts | 5 ++- .../swap/domain/models/data/Currency.kt | 22 ++++++----- features/swap/presentation/build.gradle.kts | 2 + .../feature/swap/presentation/SwapFragment.kt | 4 ++ .../ui/SwapPermissionBottomSheetContent.kt | 21 ++++++----- .../com/tangem/feature/swap/ui/SwapScreen.kt | 6 +-- 26 files changed, 201 insertions(+), 55 deletions(-) create mode 100644 core/ui/src/main/res/drawable/ic_arrow_down_24.xml create mode 100644 core/ui/src/main/res/drawable/ic_arrow_up_24.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1876b0038c..9cae959b25 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,6 +2,7 @@ plugins { id("com.android.application") kotlin("android") kotlin("kapt") + kotlin("plugin.serialization") id("com.google.gms.google-services") id("com.google.firebase.crashlytics") id("com.google.dagger.hilt.android") @@ -102,6 +103,7 @@ android { } repositories { + mavenCentral() maven(url = "https://jitpack.io") } @@ -199,6 +201,7 @@ dependencies { implementation(Library.googlePlayServicesWallet) implementation(Library.composeShimmer) implementation(Library.mviCoreWatcher) + implementation(Library.kotlinSerialization) /** Testing libraries */ testImplementation(Test.junit) diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index 94dd3ff6c1..595d9b6fad 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -133,7 +133,7 @@ class DialogManager : StoreSubscriber { is SaltPayDialog.Activation.NoGas -> NoFundsForActivationDialog.create(context) is SaltPayDialog.Activation.OnError -> RegistrationErrorDialog.create(context, state.dialog) is WalletDialog.CurrencySelectionDialog -> CurrencySelectionDialog.create(state.dialog, context) - is WalletDialog.ChooseTradeActionDialog -> ChooseTradeActionBottomSheetDialog(context) + is WalletDialog.ChooseTradeActionDialog -> ChooseTradeActionBottomSheetDialog(context, state.dialog) is WalletDialog.SelectAmountToSendDialog -> AmountToSendBottomSheetDialog(context, state.dialog) is WalletDialog.SignedHashesMultiWalletDialog -> SignedHashesWarningDialog.create(context) is WalletDialog.TokensAreLinkedDialog -> SimpleAlertDialog.create( diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index 0e167cdcca..eaae03f471 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -1,5 +1,6 @@ package com.tangem.tap.common.extensions +import android.os.Bundle import androidx.activity.OnBackPressedCallback import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment @@ -7,6 +8,7 @@ import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentManager import com.tangem.common.extensions.VoidCallback import com.tangem.feature.referral.ReferralFragment +import com.tangem.feature.swap.presentation.SwapFragment import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.FragmentShareTransition import com.tangem.tap.features.details.ui.appsettings.AppSettingsFragment @@ -37,10 +39,12 @@ import timber.log.Timber fun FragmentActivity.openFragment( screen: AppScreen, addToBackstack: Boolean, + bundle: Bundle? = null, fgShareTransition: FragmentShareTransition? = null, ) { val transaction = this.supportFragmentManager.beginTransaction() val fragment = fragmentFactory(screen) + fragment.arguments = bundle fgShareTransition?.apply { fragment.sharedElementEnterTransition = enterTransitionSet fragment.sharedElementReturnTransition = exitTransitionSet @@ -117,6 +121,7 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.WalletConnectSessions -> WalletConnectFragment() AppScreen.QrScan -> QrScanFragment() AppScreen.ReferralProgram -> ReferralFragment() + AppScreen.Swap -> SwapFragment() AppScreen.Welcome -> WelcomeFragment() AppScreen.SaveWallet -> SaveWalletBottomSheetFragment() AppScreen.WalletSelector -> WalletSelectorBottomSheetFragment() diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt index 3e4f3a9702..71f3274e4c 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt @@ -1,6 +1,7 @@ package com.tangem.tap.common.redux.navigation import android.net.Uri +import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import org.rekotlin.Action import java.lang.ref.WeakReference @@ -10,6 +11,7 @@ sealed class NavigationAction : Action { val screen: AppScreen, val fragmentShareTransition: FragmentShareTransition? = null, val addToBackstack: Boolean = true, + val bundle: Bundle? = null, ) : NavigationAction() data class PopBackTo( diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt index cf60dc79f6..0b40b7bd8e 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt @@ -23,6 +23,7 @@ enum class AppScreen( WalletConnectSessions, QrScan, ReferralProgram, + Swap, Welcome, SaveWallet(isDialogFragment = true), WalletSelector(isDialogFragment = true), diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index 0cc8039e96..44872ab399 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -161,7 +161,12 @@ sealed class WalletAction : Action { ) : DialogAction() object SignedHashesMultiWalletDialog : DialogAction() - object ChooseTradeActionDialog : DialogAction() + data class ChooseTradeActionDialog( + val buyAllowed: Boolean, + val sellAllowed: Boolean, + val swapAllowed: Boolean, + ) : DialogAction() + data class ChooseCurrency(val amounts: List?) : DialogAction() data class RussianCardholdersWarningDialog( val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data? = null, @@ -190,6 +195,8 @@ sealed class WalletAction : Action { val destinationAddress: String, val transactionId: String, ) : TradeCryptoAction() + + object Swap : TradeCryptoAction() } data class ChangeSelectedAddress(val type: AddressType) : WalletAction() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index 49773a9854..b803175cf8 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -329,6 +329,9 @@ data class WalletData( val isAvailableToSell: Boolean get() = store.state.globalState.exchangeManager.availableForSell(currency) + val isAvailableToSwap: Boolean + get() = currency.blockchain.isEvm() && currency.coinId != null + fun shouldShowMultipleAddress(): Boolean { val listOfAddresses = walletAddresses?.list ?: return false return listOfAddresses.size > 1 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 df69426a2c..0cb3293ea0 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 @@ -1,7 +1,10 @@ package com.tangem.tap.features.wallet.redux.middlewares +import androidx.core.os.bundleOf import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager import com.tangem.blockchain.common.AmountType +import com.tangem.domain.common.extensions.toNetworkId +import com.tangem.feature.swap.presentation.SwapFragment import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Token @@ -11,6 +14,7 @@ import com.tangem.tap.common.extensions.dispatchOpenUrl import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.tokens.getIconUrl import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE import com.tangem.tap.features.send.redux.PrepareSendScreen @@ -22,6 +26,9 @@ import com.tangem.tap.network.exchangeServices.buyErc20TestnetTokens import com.tangem.tap.scope import com.tangem.tap.store import kotlinx.coroutines.launch +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import com.tangem.feature.swap.domain.models.data.Currency as SwapCurrency class TradeCryptoMiddleware { fun handle(state: () -> AppState?, action: WalletAction.TradeCryptoAction) { @@ -32,6 +39,7 @@ class TradeCryptoMiddleware { is WalletAction.TradeCryptoAction.Sell -> proceedSellAction() is WalletAction.TradeCryptoAction.SendCrypto -> preconfigureAndOpenSendScreen(action) is WalletAction.TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId) + is WalletAction.TradeCryptoAction.Swap -> openSwap() } } @@ -136,4 +144,33 @@ class TradeCryptoMiddleware { transactionId = transactionId, )?.let { store.dispatchOpenUrl(it) } } + + private fun openSwap() { + val currency = store.state.walletState.selectedWalletData?.currency?.toSwapCurrency() + val bundle = bundleOf(SwapFragment.CURRENCY_BUNDLE_KEY to Json.encodeToString(currency)) + store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Swap, bundle = bundle)) + } + + private fun Currency.toSwapCurrency(): SwapCurrency { + return when (this) { + is Currency.Blockchain -> { + SwapCurrency.NativeToken( + id = blockchain.id, + name = this.currencyName, + symbol = this.currencySymbol, + networkId = this.blockchain.toNetworkId(), + logoUrl = getIconUrl(this.blockchain.toNetworkId()), + ) + } + is Currency.Token -> SwapCurrency.NonNativeToken( + id = this.token.id ?: "", + name = this.currencyName, + symbol = this.currencySymbol, + networkId = this.blockchain.toNetworkId(), + logoUrl = getIconUrl(this.token.id ?: ""), + contractAddress = this.token.contractAddress, + decimalCount = decimals, + ) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt index fb6ff1dd3f..5f8e7c5ae2 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt @@ -20,7 +20,13 @@ class WalletDialogsMiddleware { store.state.walletState.selectedWalletData?.let { Analytics.send(Token.ButtonExchange(AnalyticsParam.CurrencyType.Currency(it.currency))) } - store.dispatchDialogShow(WalletDialog.ChooseTradeActionDialog) + store.dispatchDialogShow( + WalletDialog.ChooseTradeActionDialog( + buyAllowed = action.buyAllowed, + sellAllowed = action.sellAllowed, + swapAllowed = action.swapAllowed, + ), + ) } is WalletAction.DialogAction.QrCode -> { store.dispatchDialogShow( diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt index fc182e2c4f..d8ffb0df63 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt @@ -9,7 +9,12 @@ import com.tangem.wallet.R sealed interface WalletDialog : StateDialog { data class SelectAmountToSendDialog(val amounts: List?) : WalletDialog object SignedHashesMultiWalletDialog : WalletDialog - object ChooseTradeActionDialog : WalletDialog + data class ChooseTradeActionDialog( + val buyAllowed: Boolean, + val sellAllowed: Boolean, + val swapAllowed: Boolean, + ) : WalletDialog + data class CurrencySelectionDialog( val currenciesList: List, val currentAppCurrency: FiatCurrency, @@ -17,7 +22,7 @@ sealed interface WalletDialog : StateDialog { data class RemoveWalletDialog( val currencyTitle: String, - val onOk: () -> Unit + val onOk: () -> Unit, ) : WalletDialog { val messageRes: Int = R.string.token_details_hide_alert_message val titleRes: Int = R.string.token_details_hide_alert_title @@ -26,7 +31,7 @@ sealed interface WalletDialog : StateDialog { data class TokensAreLinkedDialog( val currencyTitle: String, - val currencySymbol: String + val currencySymbol: String, ) : WalletDialog { val messageRes: Int = R.string.token_details_unable_hide_alert_message val titleRes: Int = R.string.token_details_unable_hide_alert_title diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index 8083e9ba2b..0795efcea9 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -165,15 +165,6 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), } private fun setupButtons() = with(binding) { - rowButtons.onBuyClick = { - store.dispatch(WalletAction.TradeCryptoAction.Buy()) - } - rowButtons.onSellClick = { - store.dispatch(WalletAction.TradeCryptoAction.Sell) - } - rowButtons.onTradeClick = { - store.dispatch(WalletAction.DialogAction.ChooseTradeActionDialog) - } rowButtons.onSendClick = { store.dispatch(WalletAction.Send()) } @@ -273,6 +264,15 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), sellAllowed = selectedWallet.isAvailableToSell, sendAllowed = selectedWallet.mainButton.enabled, ) + rowButtons.onTradeClick = { + store.dispatch( + WalletAction.DialogAction.ChooseTradeActionDialog( + buyAllowed = selectedWallet.isAvailableToBuy, + sellAllowed = selectedWallet.isAvailableToSell, + swapAllowed = selectedWallet.isAvailableToSwap, + ), + ) + } } private fun handleWarnings(selectedWallet: WalletData) = with(binding) { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionBottomSheetDialog.kt index 3fc5905990..cb940267b1 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionBottomSheetDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionBottomSheetDialog.kt @@ -4,11 +4,16 @@ import android.content.Context import android.os.Bundle import android.view.LayoutInflater import com.google.android.material.bottomsheet.BottomSheetDialog +import com.tangem.tap.common.extensions.show import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.features.wallet.redux.models.WalletDialog import com.tangem.tap.store import com.tangem.wallet.databinding.DialogWalletTradeBinding -class ChooseTradeActionBottomSheetDialog(context: Context) : BottomSheetDialog(context) { +class ChooseTradeActionBottomSheetDialog( + context: Context, + private val dialogData: WalletDialog.ChooseTradeActionDialog, +) : BottomSheetDialog(context) { var binding: DialogWalletTradeBinding? = null @@ -26,13 +31,26 @@ class ChooseTradeActionBottomSheetDialog(context: Context) : BottomSheetDialog(c store.dispatch(WalletAction.DialogAction.Hide) } - binding!!.dialogBtnBuy.setOnClickListener { - dismiss() - store.dispatch(WalletAction.TradeCryptoAction.Buy()) - } - binding!!.dialogBtnSell.setOnClickListener { - dismiss() - store.dispatch(WalletAction.TradeCryptoAction.Sell) + binding?.let { + with(it) + { + dialogBtnBuy.show(dialogData.buyAllowed) + dialogBtnSell.show(dialogData.sellAllowed) + dialogBtnSwap.show(dialogData.swapAllowed) + + dialogBtnBuy.setOnClickListener { + dismiss() + store.dispatch(WalletAction.TradeCryptoAction.Buy()) + } + dialogBtnSell.setOnClickListener { + dismiss() + store.dispatch(WalletAction.TradeCryptoAction.Sell) + } + dialogBtnSwap.setOnClickListener { + dismiss() + store.dispatch(WalletAction.TradeCryptoAction.Swap) + } + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/view/WalletDetailsButtonsRow.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/view/WalletDetailsButtonsRow.kt index 582b5f178c..5c73fa7531 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/view/WalletDetailsButtonsRow.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/view/WalletDetailsButtonsRow.kt @@ -5,6 +5,7 @@ import android.util.AttributeSet import android.view.LayoutInflater import android.widget.LinearLayout import androidx.core.view.isVisible +import com.tangem.tap.common.extensions.hide import com.tangem.wallet.databinding.ViewWalletDetailsButtonsRowBinding internal class WalletDetailsButtonsRow @JvmOverloads constructor( @@ -39,11 +40,9 @@ internal class WalletDetailsButtonsRow @JvmOverloads constructor( sendAllowed: Boolean, ) = with(binding) { containerExchangeButtons.isVisible = exchangeServiceFeatureOn - - btnBuy.isVisible = (buyAllowed && !sellAllowed) || (!buyAllowed && !sellAllowed) - btnBuy.isEnabled = buyAllowed - btnSell.isVisible = !buyAllowed && sellAllowed - btnTrade.isVisible = buyAllowed && sellAllowed + btnBuy.hide() + btnSell.hide() + btnTrade.isVisible = exchangeServiceFeatureOn btnSend.isEnabled = sendAllowed } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index e8fc90a242..ed9576d384 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -134,9 +134,15 @@ class SingleWalletView : WalletView() { sendAllowed = walletData.mainButton.enabled, ) - rowButtons.onBuyClick = { store.dispatch(WalletAction.TradeCryptoAction.Buy()) } - rowButtons.onSendClick = { store.dispatch(WalletAction.TradeCryptoAction.Sell) } - rowButtons.onTradeClick = { store.dispatch(WalletAction.DialogAction.ChooseTradeActionDialog) } + rowButtons.onTradeClick = { + store.dispatch( + WalletAction.DialogAction.ChooseTradeActionDialog( + buyAllowed = walletData.isAvailableToBuy, + sellAllowed = walletData.isAvailableToSell, + swapAllowed = walletData.isAvailableToSwap, + ), + ) + } rowButtons.onSendClick = { when (walletData.mainButton) { diff --git a/app/src/main/res/layout/dialog_wallet_trade.xml b/app/src/main/res/layout/dialog_wallet_trade.xml index cb4e47fecd..cc9299027f 100644 --- a/app/src/main/res/layout/dialog_wallet_trade.xml +++ b/app/src/main/res/layout/dialog_wallet_trade.xml @@ -27,7 +27,7 @@ android:textColor="@color/darkGray3" android:textSize="14sp" android:textStyle="bold" - app:drawableStartCompat="@drawable/ic_arrow_up" /> + app:drawableStartCompat="@drawable/ic_arrow_up_24" /> + app:drawableStartCompat="@drawable/ic_arrow_down_24" /> + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 75b1a4e453..f772f94772 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,6 +16,7 @@ buildscript { plugins { id("com.google.dagger.hilt.android") version "2.44" apply false + kotlin("plugin.serialization") version Versions.kotlin apply false } allprojects { diff --git a/buildSrc/src/main/java/Dependency.kt b/buildSrc/src/main/java/Dependency.kt index 54ee97cd6d..3f2a1883da 100644 --- a/buildSrc/src/main/java/Dependency.kt +++ b/buildSrc/src/main/java/Dependency.kt @@ -67,6 +67,7 @@ object Library { const val zxingQrBarcodeScanner = "me.dm7.barcodescanner:zxing:" + Versions.zxingQrBarcodeScanner const val zxingQrCore = "com.google.zxing:core:" + Versions.zxingQrCode const val mviCoreWatcher = "com.github.badoo.mvicore:mvicore-diff:" + Versions.mviCore + const val kotlinSerialization = "org.jetbrains.kotlinx:kotlinx-serialization-json:" + Versions.kotlinSerialization } object Tangem { diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index 1ad00eea91..524e5e5277 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -56,6 +56,7 @@ object Versions { const val zxingQrBarcodeScanner = "1.9.8" const val zxingQrCode = "3.3.3" const val mviCore = "1.3.1" + const val kotlinSerialization = "1.4.1" // endregion Other libraries // region Tangem diff --git a/core/ui/src/main/res/drawable/ic_arrow_down_24.xml b/core/ui/src/main/res/drawable/ic_arrow_down_24.xml new file mode 100644 index 0000000000..90afd396ed --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_arrow_down_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/ui/src/main/res/drawable/ic_arrow_up_24.xml b/core/ui/src/main/res/drawable/ic_arrow_up_24.xml new file mode 100644 index 0000000000..57f3613ff6 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_arrow_up_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/features/swap/domain/build.gradle.kts b/features/swap/domain/build.gradle.kts index 0d5065fe21..87fa664521 100644 --- a/features/swap/domain/build.gradle.kts +++ b/features/swap/domain/build.gradle.kts @@ -2,10 +2,10 @@ plugins { id("java-library") id("org.jetbrains.kotlin.jvm") kotlin("kapt") + kotlin("plugin.serialization") } dependencies { - /** Libs */ implementation(project(":libs:crypto")) implementation(project(":core:utils")) @@ -13,6 +13,9 @@ dependencies { /** DI */ implementation(Library.hiltCore) kapt(Library.hiltKapt) + + /** Other Libraries **/ + implementation(Library.kotlinSerialization) } java { diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/Currency.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/Currency.kt index cc69440af7..947c62e7bc 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/Currency.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/Currency.kt @@ -1,21 +1,25 @@ package com.tangem.feature.swap.domain.models.data -sealed class Currency( - open val id: String, - open val name: String, - open val symbol: String, - open val networkId: String, - open val logoUrl: String, -) { +import kotlinx.serialization.Serializable +@Serializable +sealed class Currency { + abstract val id: String + abstract val name: String + abstract val symbol: String + abstract val networkId: String + abstract val logoUrl: String + + @Serializable data class NativeToken( override val id: String, override val name: String, override val symbol: String, override val networkId: String, override val logoUrl: String, - ) : Currency(id, name, symbol, networkId, logoUrl) + ) : Currency() + @Serializable data class NonNativeToken( override val id: String, override val name: String, @@ -24,5 +28,5 @@ sealed class Currency( override val logoUrl: String, val contractAddress: String, val decimalCount: Int, - ) : Currency(id, name, symbol, networkId, logoUrl) + ) : Currency() } \ No newline at end of file diff --git a/features/swap/presentation/build.gradle.kts b/features/swap/presentation/build.gradle.kts index be71e61c6d..747a8690a8 100644 --- a/features/swap/presentation/build.gradle.kts +++ b/features/swap/presentation/build.gradle.kts @@ -2,6 +2,7 @@ plugins { id("com.android.library") kotlin("android") kotlin("kapt") + kotlin("plugin.serialization") id("com.google.dagger.hilt.android") } @@ -68,6 +69,7 @@ dependencies { /** Other libraries */ implementation(Library.composeShimmer) + implementation(Library.kotlinSerialization) /** DI */ implementation(Library.hilt) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt index 118e2b6e34..71f56935ef 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt @@ -57,4 +57,8 @@ class SwapFragment : Fragment() { } } } + + companion object { + const val CURRENCY_BUNDLE_KEY = "swap_currency" + } } \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheetContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheetContent.kt index 90082105f4..0d8f96198f 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheetContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheetContent.kt @@ -31,7 +31,7 @@ import com.tangem.feature.swap.presentation.R @Composable fun SwapPermissionBottomSheetContent( - data: SwapPermissionStateHolder, + data: SwapPermissionStateHolder?, onCancel: () -> Unit, ) { Column( @@ -54,7 +54,10 @@ fun SwapPermissionBottomSheetContent( SpacerH10() Text( - text = stringResource(id = R.string.swapping_permission_subheader, data.currency), + text = stringResource( + id = R.string.swapping_permission_subheader, + data?.currency ?: "", + ), color = TangemTheme.colors.text.secondary, style = TangemTheme.typography.body2, textAlign = TextAlign.Center, @@ -64,11 +67,11 @@ fun SwapPermissionBottomSheetContent( SpacerH16() ApprovalBottomSheetInfo( - currency = data.currency, - amount = data.amount, - walletAddress = data.walletAddress, - spenderAddress = data.spenderAddress, - fee = data.fee, + currency = data?.currency ?: "", + amount = data?.amount ?: "", + walletAddress = data?.walletAddress ?: "", + spenderAddress = data?.spenderAddress ?: "", + fee = data?.fee ?: "", ) SpacerH28() @@ -77,7 +80,7 @@ fun SwapPermissionBottomSheetContent( text = stringResource(id = R.string.swapping_permission_buttons_approve), icon = painterResource(id = R.drawable.ic_tangem_24), modifier = Modifier.fillMaxWidth(), - onClick = data.approveButton.onClick, + onClick = { data?.approveButton?.onClick?.invoke() }, ) SpacerH12() @@ -86,7 +89,7 @@ fun SwapPermissionBottomSheetContent( text = stringResource(id = R.string.common_cancel), modifier = Modifier.fillMaxWidth(), ) { - data.cancelButton.onClick() + data?.cancelButton?.onClick?.invoke() onCancel() } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt index 5011b9ec33..e49409bd4e 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt @@ -33,12 +33,10 @@ internal fun SwapScreen(stateHolder: SwapStateHolder) { ) }, sheetContent = { - if (stateHolder.permissionState != null) { SwapPermissionBottomSheetContent( data = stateHolder.permissionState, onCancel = { coroutineScope.launch { bottomSheetScaffoldState.bottomSheetState.collapse() } }, ) - } }, scaffoldState = bottomSheetScaffoldState, sheetShape = RoundedCornerShape( @@ -52,7 +50,9 @@ internal fun SwapScreen(stateHolder: SwapStateHolder) { state = stateHolder, onPermissionWarningClick = { coroutineScope.launch { - if (bottomSheetScaffoldState.bottomSheetState.isCollapsed) { + if (bottomSheetScaffoldState.bottomSheetState.isCollapsed + && stateHolder.permissionState != null + ) { bottomSheetScaffoldState.bottomSheetState.expand() } else { bottomSheetScaffoldState.bottomSheetState.collapse() From 4a47c5ab3a308059277f26e5d592ae26bf0a1349 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 30 Dec 2022 16:24:42 +0300 Subject: [PATCH 036/145] Updated on 2026-08-14 --- core/datasource/build.gradle.kts | 2 - .../datasource/api/AuthHeaderInterceptor.kt | 25 ------ .../{di => api/oneinch}/OneInchApiFactory.kt | 4 +- .../com/tangem/datasource/di/NetworkModule.kt | 83 +++++++++---------- .../tangem/datasource/di/OneInchApisModule.kt | 7 +- .../tangem/datasource/utils/HttpClientExt.kt | 30 +++++++ .../tangem/datasource/utils/RequestHeader.kt | 21 +++++ .../tangem/feature/swap/SwapRepositoryImpl.kt | 2 +- .../tangem/feature/swap/di/SwapDataModule.kt | 2 +- 9 files changed, 98 insertions(+), 78 deletions(-) delete mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/AuthHeaderInterceptor.kt rename core/datasource/src/main/java/com/tangem/datasource/{di => api/oneinch}/OneInchApiFactory.kt (79%) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/utils/HttpClientExt.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/utils/RequestHeader.kt diff --git a/core/datasource/build.gradle.kts b/core/datasource/build.gradle.kts index 1359125527..082885c1d7 100644 --- a/core/datasource/build.gradle.kts +++ b/core/datasource/build.gradle.kts @@ -63,8 +63,6 @@ dependencies { implementation(Library.okHttp) implementation(Library.okHttpLogging) - // kapt("com.squareup.moshi:moshi-kotlin-codegen:1.14.0") - /** Time */ implementation(Library.jodatime) } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/AuthHeaderInterceptor.kt b/core/datasource/src/main/java/com/tangem/datasource/api/AuthHeaderInterceptor.kt deleted file mode 100644 index 5a50c5215e..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/api/AuthHeaderInterceptor.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.datasource.api - -import com.tangem.lib.auth.AuthProvider -import okhttp3.Interceptor -import okhttp3.Response - -class AuthHeaderInterceptor( - private val authProvider: AuthProvider, -) : Interceptor { - - override fun intercept(chain: Interceptor.Chain): Response { - val request = chain.request() - .newBuilder() - .addHeader(HEADER_CARD_KEY, authProvider.getCardPublicKey()) - .addHeader(HEADER_CARD_ID, authProvider.getCardId()) - .build() - - return chain.proceed(request) - } - - companion object { - const val HEADER_CARD_KEY = "card_public_key" - const val HEADER_CARD_ID = "card_id" - } -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/OneInchApiFactory.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApiFactory.kt similarity index 79% rename from core/datasource/src/main/java/com/tangem/datasource/di/OneInchApiFactory.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApiFactory.kt index e9493488d1..da21f86558 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/OneInchApiFactory.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApiFactory.kt @@ -1,6 +1,4 @@ -package com.tangem.datasource.di - -import com.tangem.datasource.api.oneinch.OneInchApi +package com.tangem.datasource.api.oneinch class OneInchApiFactory { diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index 51dabf7253..fc33504a48 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -2,17 +2,19 @@ package com.tangem.datasource.di import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory -import com.tangem.datasource.api.AuthHeaderInterceptor import com.tangem.datasource.api.common.BigDecimalAdapter import com.tangem.datasource.api.referral.ReferralApi import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.utils.RequestHeader.AuthenticationHeader +import com.tangem.datasource.utils.RequestHeader.CacheControlHeader +import com.tangem.datasource.utils.addHeaders +import com.tangem.datasource.utils.allowLogging import com.tangem.lib.auth.AuthProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import okhttp3.OkHttpClient -import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory import javax.inject.Singleton @@ -21,46 +23,6 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) class NetworkModule { - @Provides - @Singleton - fun provideTangemTechApi( - okHttpClient: OkHttpClient, - moshi: Moshi, - ): TangemTechApi { - return Retrofit.Builder() - .addConverterFactory( - MoshiConverterFactory.create(moshi), - ) - .baseUrl(DEV_TANGEM_TECH_BASE_URL) - .client(okHttpClient) - .build() - .create(TangemTechApi::class.java) - } - - @Provides - @Singleton - fun provideReferralApi(okHttpClient: OkHttpClient, moshi: Moshi): ReferralApi { - return Retrofit.Builder() - .addConverterFactory( - MoshiConverterFactory.create(moshi), - ) - .baseUrl(DEV_TANGEM_TECH_BASE_URL) - .client(okHttpClient) - .build() - .create(ReferralApi::class.java) - } - - @Provides - @Singleton - fun provideOkHttpClient(authProvider: AuthProvider): OkHttpClient { - return OkHttpClient.Builder() - .addInterceptor(AuthHeaderInterceptor(authProvider)) - .addInterceptor( - HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY), - ) - .build() - } - @Provides @Singleton fun provideMoshi(): Moshi { @@ -70,6 +32,43 @@ class NetworkModule { .build() } + @Provides + @Singleton + fun provideTangemTechApi(authProvider: AuthProvider, moshi: Moshi): TangemTechApi { + return Retrofit.Builder() + .addConverterFactory(MoshiConverterFactory.create(moshi)) + .baseUrl(DEV_TANGEM_TECH_BASE_URL) + .client( + OkHttpClient.Builder() + .addHeaders( + CacheControlHeader, + AuthenticationHeader(authProvider) + ) + .allowLogging() + .build() + ) + .build() + .create(TangemTechApi::class.java) + } + + @Provides + @Singleton + fun provideReferralApi(authProvider: AuthProvider, moshi: Moshi): ReferralApi { + return Retrofit.Builder() + .addConverterFactory( + MoshiConverterFactory.create(moshi), + ) + .baseUrl(DEV_TANGEM_TECH_BASE_URL) + .client( + OkHttpClient.Builder() + .addHeaders(AuthenticationHeader(authProvider)) + .allowLogging() + .build() + ) + .build() + .create(ReferralApi::class.java) + } + private companion object { const val PROD_TANGEM_TECH_BASE_URL = "https://api.tangem-tech.com/v1/" const val DEV_TANGEM_TECH_BASE_URL = "https://devapi.tangem-tech.com/v1/" diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/OneInchApisModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/OneInchApisModule.kt index e23d2849c2..55ee683715 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/OneInchApisModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/OneInchApisModule.kt @@ -2,12 +2,13 @@ package com.tangem.datasource.di import com.squareup.moshi.Moshi import com.tangem.datasource.api.oneinch.OneInchApi +import com.tangem.datasource.api.oneinch.OneInchApiFactory +import com.tangem.datasource.utils.allowLogging import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import okhttp3.OkHttpClient -import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory import javax.inject.Singleton @@ -33,9 +34,7 @@ class OneInchApisModule { .baseUrl(url) .client( OkHttpClient.Builder() - .addInterceptor( - HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY), - ) + .allowLogging() .build(), ) .build() diff --git a/core/datasource/src/main/java/com/tangem/datasource/utils/HttpClientExt.kt b/core/datasource/src/main/java/com/tangem/datasource/utils/HttpClientExt.kt new file mode 100644 index 0000000000..dffc98c05a --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/utils/HttpClientExt.kt @@ -0,0 +1,30 @@ +package com.tangem.datasource.utils + +import com.tangem.datasource.BuildConfig +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import okhttp3.logging.HttpLoggingInterceptor.Level + +/** Extension for adding headers [requestHeaders] to every [OkHttpClient] request */ +internal fun OkHttpClient.Builder.addHeaders(vararg requestHeaders: RequestHeader): OkHttpClient.Builder { + return addInterceptor( + Interceptor { chain -> + val request = chain.request().newBuilder().apply { + requestHeaders + .flatMap(RequestHeader::values) + .forEach { addHeader(it.first, it.second) } + }.build() + + chain.proceed(request) + }, + ) +} + +/** + * Extension for logging each [OkHttpClient] request + * + * @param level logging level. By default, only the request body. + */ +internal fun OkHttpClient.Builder.allowLogging(level: Level = Level.BODY): OkHttpClient.Builder = + if (BuildConfig.DEBUG) addInterceptor(interceptor = HttpLoggingInterceptor().setLevel(level)) else this \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/utils/RequestHeader.kt b/core/datasource/src/main/java/com/tangem/datasource/utils/RequestHeader.kt new file mode 100644 index 0000000000..9e9e143cf2 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/utils/RequestHeader.kt @@ -0,0 +1,21 @@ +package com.tangem.datasource.utils + +import com.tangem.lib.auth.AuthProvider + +/** + * Presentation of request header + * + * @param pairs header name and header value pairs + */ +sealed class RequestHeader(vararg pairs: Pair) { + + /** Header list */ + val values: List> = pairs.toList() + + object CacheControlHeader : RequestHeader("Cache-Control" to "max-age=600") + + class AuthenticationHeader(authProvider: AuthProvider) : RequestHeader( + "card_public_key" to authProvider.getCardPublicKey(), + "card_id" to authProvider.getCardId() + ) +} \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt index 25ffc2bea2..088a47045f 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt @@ -1,10 +1,10 @@ package com.tangem.feature.swap import com.tangem.datasource.api.oneinch.OneInchApi +import com.tangem.datasource.api.oneinch.OneInchApiFactory import com.tangem.datasource.api.oneinch.OneInchErrorsHandler import com.tangem.datasource.api.oneinch.errors.OneIncResponseException import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.datasource.di.OneInchApiFactory import com.tangem.feature.swap.converters.ApproveConverter import com.tangem.feature.swap.converters.QuotesConverter import com.tangem.feature.swap.converters.SwapConverter diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt index bb35020942..6cc1d6c846 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt @@ -1,8 +1,8 @@ package com.tangem.feature.swap.di +import com.tangem.datasource.api.oneinch.OneInchApiFactory import com.tangem.datasource.api.oneinch.OneInchErrorsHandler import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.datasource.di.OneInchApiFactory import com.tangem.feature.swap.SwapRepositoryImpl import com.tangem.feature.swap.converters.ApproveConverter import com.tangem.feature.swap.converters.QuotesConverter From ad0a6c81bf4f140d103b46f1ddb6aba00d59705e Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 30 Dec 2022 17:39:37 +0300 Subject: [PATCH 037/145] Updated on 2026-08-14 --- .../com/tangem/tap/common/redux/AppState.kt | 7 ++++++ .../common/redux/FeatureRepositoryStore.kt | 15 ++++++++++++ .../common/redux/global/GlobalMidlleware.kt | 14 ++++------- .../features/home/data/HomeRepositoryImpl.kt | 23 +++++++++++++++++++ .../features/home/domain/HomeRepository.kt | 10 ++++++++ .../wallet/data/WalletRepositoryImpl.kt | 15 +++++++----- .../redux/middlewares/WalletMiddleware.kt | 3 +-- .../datasource/api/tangemTech/Responses.kt | 4 ---- .../api/tangemTech/TangemTechApi.kt | 3 ++- .../api/tangemTech/TangemTechService.kt | 4 ---- .../api/tangemTech/models/GeoResponse.kt | 7 ++++++ 11 files changed, 79 insertions(+), 26 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/redux/FeatureRepositoryStore.kt create mode 100644 app/src/main/java/com/tangem/tap/features/home/data/HomeRepositoryImpl.kt create mode 100644 app/src/main/java/com/tangem/tap/features/home/domain/HomeRepository.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/GeoResponse.kt 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 9f7c34b90f..820f5b3d3f 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 @@ -39,6 +39,7 @@ import com.tangem.tap.features.walletSelector.redux.WalletSelectorMiddleware import com.tangem.tap.features.walletSelector.redux.WalletSelectorState import com.tangem.tap.features.welcome.redux.WelcomeMiddleware import com.tangem.tap.features.welcome.redux.WelcomeState +import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider import org.rekotlin.Middleware import org.rekotlin.StateType @@ -68,6 +69,12 @@ data class AppState( val domainNetworks: NetworkServices get() = domainState.globalState.networkServices + val featureRepositoryProvider: FeatureRepositoryProvider + get() = FeatureRepositoryProvider( + tangemTechApi = domainNetworks.tangemTechService.api, + dispatchers = AppCoroutineDispatcherProvider(), + ) + companion object { fun getMiddleware(): List> { return listOf( diff --git a/app/src/main/java/com/tangem/tap/common/redux/FeatureRepositoryStore.kt b/app/src/main/java/com/tangem/tap/common/redux/FeatureRepositoryStore.kt new file mode 100644 index 0000000000..5794edffb9 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/redux/FeatureRepositoryStore.kt @@ -0,0 +1,15 @@ +package com.tangem.tap.common.redux + +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.tap.features.home.data.HomeRepositoryImpl +import com.tangem.tap.features.home.domain.HomeRepository +import com.tangem.tap.features.wallet.data.WalletRepositoryImpl +import com.tangem.tap.features.wallet.domain.WalletRepository +import com.tangem.utils.coroutines.CoroutineDispatcherProvider + +class FeatureRepositoryProvider(tangemTechApi: TangemTechApi, dispatchers: CoroutineDispatcherProvider) { + + val walletRepository: WalletRepository = WalletRepositoryImpl(tangemTechApi, dispatchers) + + val homeRepository: HomeRepository = HomeRepositoryImpl(tangemTechApi, dispatchers) +} \ No newline at end of file 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 dfcdf942bf..46ea27926e 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,7 +4,6 @@ import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.guard import com.tangem.common.extensions.ifNotNull -import com.tangem.common.services.Result import com.tangem.domain.common.LogConfig import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.common.extensions.dispatchDebugErrorNotification @@ -187,23 +186,20 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di } is GlobalAction.FetchUserCountry -> { scope.launch { - val techService = store.state.domainNetworks.tangemTechService - when (val result = techService.userCountry()) { - is Result.Success -> { + // TODO("After adding DI") get dependencies by DI + runCatching { store.state.featureRepositoryProvider.homeRepository.getUserCountryCode() } + .onSuccess { store.dispatchOnMain( - GlobalAction.FetchUserCountry.Success( - countryCode = result.data.code.lowercase(), - ), + GlobalAction.FetchUserCountry.Success(countryCode = it.code.lowercase()), ) } - is Result.Failure -> { + .onFailure { store.dispatchOnMain( GlobalAction.FetchUserCountry.Success( countryCode = Locale.getDefault().country.lowercase(), ), ) } - } } } } diff --git a/app/src/main/java/com/tangem/tap/features/home/data/HomeRepositoryImpl.kt b/app/src/main/java/com/tangem/tap/features/home/data/HomeRepositoryImpl.kt new file mode 100644 index 0000000000..fed3f2e394 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/data/HomeRepositoryImpl.kt @@ -0,0 +1,23 @@ +package com.tangem.tap.features.home.data + +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.api.tangemTech.models.GeoResponse +import com.tangem.tap.features.home.domain.HomeRepository +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext + +/** + * Implementation of repository for Home feature + * + * @property tangemTechApi API for server requests + * @property dispatchers coroutine dispatcher provider + */ +class HomeRepositoryImpl( + private val tangemTechApi: TangemTechApi, + private val dispatchers: CoroutineDispatcherProvider, +) : HomeRepository { + + override suspend fun getUserCountryCode(): GeoResponse = withContext(dispatchers.io) { + tangemTechApi.getUserCountryCode() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/domain/HomeRepository.kt b/app/src/main/java/com/tangem/tap/features/home/domain/HomeRepository.kt new file mode 100644 index 0000000000..ad489ae0fe --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/domain/HomeRepository.kt @@ -0,0 +1,10 @@ +package com.tangem.tap.features.home.domain + +import com.tangem.datasource.api.tangemTech.models.GeoResponse + +/** Repository for Home feature */ +interface HomeRepository { + + /** Get user's country code */ + suspend fun getUserCountryCode(): GeoResponse +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/data/WalletRepositoryImpl.kt b/app/src/main/java/com/tangem/tap/features/wallet/data/WalletRepositoryImpl.kt index a8d6086302..e610e03389 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/data/WalletRepositoryImpl.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/data/WalletRepositoryImpl.kt @@ -3,18 +3,21 @@ package com.tangem.tap.features.wallet.data import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse import com.tangem.tap.features.wallet.domain.WalletRepository -import kotlinx.coroutines.Dispatchers +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext /** * Implementation of repository for Wallet feature * * @property tangemTechApi API for server requests + * @property dispatchers coroutine dispatcher provider */ -class WalletRepositoryImpl(private val tangemTechApi: TangemTechApi) : WalletRepository { +class WalletRepositoryImpl( + private val tangemTechApi: TangemTechApi, + private val dispatchers: CoroutineDispatcherProvider, +) : WalletRepository { - override suspend fun getCurrencyList(): CurrenciesResponse = - withContext(Dispatchers.IO) { // TODO("After adding DI") replace with CoroutineDispatcherProvider - tangemTechApi.getCurrencyList() - } + override suspend fun getCurrencyList(): CurrenciesResponse = withContext(dispatchers.io) { + tangemTechApi.getCurrencyList() + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index 656faa2527..222f7b7fa1 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -36,7 +36,6 @@ import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.send.redux.PrepareSendScreen -import com.tangem.tap.features.wallet.data.WalletRepositoryImpl import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.PendingTransactionType import com.tangem.tap.features.wallet.models.filterByCoin @@ -74,7 +73,7 @@ class WalletMiddleware { private val appCurrencyMiddleware by lazy(mode = LazyThreadSafetyMode.NONE) { AppCurrencyMiddleware( // TODO("After adding DI") get dependencies by DI - walletRepository = WalletRepositoryImpl(store.state.domainNetworks.tangemTechService.api), + walletRepository = store.state.featureRepositoryProvider.walletRepository, tapWalletManager = store.state.globalState.tapWalletManager, fiatCurrenciesPrefStorage = preferencesStorage.fiatCurrenciesPrefStorage, appCurrencyProvider = { store.state.globalState.appCurrency }, diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt index 865685b00a..adcccdac54 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt @@ -34,10 +34,6 @@ data class CoinsResponse( //rates.keys = networkId's data class RatesResponse(val rates: Map) : TangemTechResponse -data class GeoResponse( - val code: String, -) : TangemTechResponse - data class UserTokensResponse( val version: Int = 0, val group: String? = null, diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index d913dc7353..8a46ed2092 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -1,6 +1,7 @@ package com.tangem.datasource.api.tangemTech import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse +import com.tangem.datasource.api.tangemTech.models.GeoResponse import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.PUT @@ -33,7 +34,7 @@ interface TangemTechApi { suspend fun getCurrencyList(): CurrenciesResponse @GET("geo") - suspend fun geo(): GeoResponse + suspend fun getUserCountryCode(): GeoResponse @GET("user-tokens/{user-id}") suspend fun getUserTokens(@Path(value = "user-id") userId: String): UserTokensResponse diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt index e609c1dafc..3827446aec 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt @@ -50,10 +50,6 @@ class TangemTechService( } } - suspend fun userCountry(): Result = withContext(Dispatchers.IO) { - performRequest { api.geo() } - } - suspend fun getUserTokens(userId: String): Result = withContext(Dispatchers.IO) { performRequest { api.getUserTokens(userId) } } diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/GeoResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/GeoResponse.kt new file mode 100644 index 0000000000..e6cdfa4b12 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/GeoResponse.kt @@ -0,0 +1,7 @@ +package com.tangem.datasource.api.tangemTech.models + +import com.squareup.moshi.Json + +data class GeoResponse( + @Json(name = "code") val code: String, +) \ No newline at end of file From ae4c52181326023f591b095c9c3e158c1af3e239 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 9 Jan 2023 12:39:33 +0400 Subject: [PATCH 038/145] Updated on 2026-08-14 --- .../java/com/tangem/tap/domain/TapErrors.kt | 12 +- .../domain/tokens/UserTokensNetworkService.kt | 27 ---- .../tap/domain/tokens/UserTokensRepository.kt | 148 +++++++++--------- .../domain/tokens/UserTokensStorageService.kt | 2 +- .../tokens/converters/CurrencyConverter.kt | 20 +++ .../tap/features/wallet/models/Currency.kt | 34 ++-- .../datasource/api/tangemTech/Responses.kt | 24 +-- .../api/tangemTech/TangemTechApi.kt | 3 +- .../api/tangemTech/TangemTechService.kt | 9 -- .../tangemTech/models/UserTokensResponse.kt | 20 +++ 10 files changed, 133 insertions(+), 166 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/tokens/converters/CurrencyConverter.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensResponse.kt diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt index aae3cf8e9a..72d70b4bb1 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt @@ -2,7 +2,6 @@ package com.tangem.tap.domain import androidx.annotation.StringRes import com.tangem.common.core.TangemError -import com.tangem.datasource.api.tangemTech.TangemTechError import com.tangem.wallet.R interface TapErrors @@ -78,13 +77,4 @@ fun TapErrors.assembleErrors(): MutableList?>> { is TapError -> idList.add(Pair(this.messageResource, this.args)) } return idList -} - -fun TangemTechError.toTapError(): TapError { - return when (this.code) { - 404 -> NoDataError(this.description) - else -> TapError.CustomError(customMessage = this.description) - } -} - -class NoDataError(message: String) : TapError.CustomError(customMessage = message) \ No newline at end of file +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt deleted file mode 100644 index 821a8046ac..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.tangem.tap.domain.tokens - -import com.tangem.common.core.TangemSdkError -import com.tangem.common.services.Result -import com.tangem.datasource.api.tangemTech.TangemTechService -import com.tangem.datasource.api.tangemTech.UserTokensResponse -import com.tangem.tap.domain.NoDataError - -class UserTokensNetworkService(private val tangemTechService: TangemTechService) { - suspend fun getUserTokens(userId: String): Result { - return when (val result = tangemTechService.getUserTokens(userId)) { - is Result.Success -> result - is Result.Failure -> { - val error = result.error - if (error is TangemSdkError.NetworkError && error.customMessage.contains("404")) { - return Result.Failure(NoDataError(error.customMessage)) - } else { - return result - } - } - } - } - - suspend fun saveUserTokens(userId: String, tokens: UserTokensResponse): Result { - return tangemTechService.putUserTokens(userId, tokens) - } -} diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index 6fb7de9ad7..efd7dbefb9 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -2,13 +2,14 @@ package com.tangem.tap.domain.tokens import android.content.Context import com.tangem.blockchain.common.DerivationStyle -import com.tangem.common.services.Result +import com.tangem.common.core.TangemSdkError +import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.TangemTechService -import com.tangem.datasource.api.tangemTech.UserTokensResponse +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.domain.common.CardDTO import com.tangem.domain.common.util.userWalletId import com.tangem.tap.common.AndroidFileReader -import com.tangem.tap.domain.NoDataError +import com.tangem.tap.domain.tokens.converters.CurrencyConverter import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.wallet.models.Currency @@ -16,93 +17,91 @@ import com.tangem.tap.features.wallet.models.toBlockchainNetworks import com.tangem.tap.features.wallet.models.toCurrencies import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.store -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch +import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext class UserTokensRepository( private val storageService: UserTokensStorageService, - private val networkService: UserTokensNetworkService, + private val tangemTechApi: TangemTechApi, + private val dispatchers: CoroutineDispatcherProvider, ) { - suspend fun getUserTokens(card: CardDTO): List { + + // TODO("After adding DI") replace with CoroutineDispatcherProvider + suspend fun getUserTokens(card: CardDTO): List = withContext(dispatchers.io) { val userId = card.userWalletId.stringValue if (DemoHelper.isDemoCardId(card.cardId)) { - return loadTokensOffline(card, userId).ifEmpty { loadDemoCurrencies() } + return@withContext loadTokensOffline(card, userId).ifEmpty(::loadDemoCurrencies) } if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { - return loadTokensOffline(card, userId) + return@withContext loadTokensOffline(card, userId) } - return when (val networkResult = networkService.getUserTokens(userId)) { - is Result.Success -> { - val tokens = networkResult.data.tokens.mapNotNull { Currency.fromTokenResponse(it) } - storageService.saveUserTokens(userId, tokens.toUserTokensResponse()) - tokens.distinct() + runCatching { tangemTechApi.getUserTokens(userId) } + .onSuccess { response -> + return@withContext response.tokens + .mapNotNull(Currency.Companion::fromTokenResponse).also { + storageService.saveUserTokens(userId, it.toUserTokensResponse()) + } + .distinct() + } + .onFailure { + return@withContext handleGetUserTokensFailure(card = card, userId = userId, error = it) } - is Result.Failure -> { - handleGetUserTokensFailure(card = card, userId = userId, error = networkResult.error) - } - } + throw IllegalStateException("Unreachable code because runCatching must return result") } - suspend fun saveUserTokens(card: CardDTO, tokens: List) { + // TODO("After adding DI") replace with CoroutineDispatcherProvider + suspend fun saveUserTokens(card: CardDTO, tokens: List) = withContext(dispatchers.io) { val userId = card.userWalletId.stringValue val userTokens = tokens.toUserTokensResponse() - networkService.saveUserTokens(userId, userTokens) + tangemTechApi.saveUserTokens(userId, userTokens) storageService.saveUserTokens(userId, userTokens) } - suspend fun removeUserTokens(card: CardDTO) { - val userId = card.userWalletId.stringValue - val userTokens = emptyList().toUserTokensResponse() - networkService.saveUserTokens(userId, userTokens) - storageService.saveUserTokens(userId, userTokens) - } - - private fun List.toUserTokensResponse(): UserTokensResponse { - val tokensResponse = this.map { it.toTokenResponse() } - return UserTokensResponse( - tokens = tokensResponse, - group = GROUP_DEFAULT_VALUE, - sort = SORT_DEFAULT_VALUE, - ) - } - - suspend fun loadBlockchainsToDerive(card: CardDTO): List { - val userId = card.userWalletId.stringValue - val blockchainNetworks = loadTokensOffline(card, userId).toBlockchainNetworks() + suspend fun loadBlockchainsToDerive(card: CardDTO): List = withContext(dispatchers.io) { + val blockchainNetworks = loadTokensOffline( + card = card, + userId = card.userWalletId.stringValue, + ).toBlockchainNetworks() if (DemoHelper.isDemoCardId(card.cardId)) { - return blockchainNetworks - .ifEmpty { loadDemoCurrencies().toBlockchainNetworks() } + return@withContext blockchainNetworks.ifEmpty(loadDemoCurrencies()::toBlockchainNetworks) } - return blockchainNetworks + return@withContext blockchainNetworks + } + + private suspend fun loadTokensOffline(card: CardDTO, userId: String): List { + return storageService.getUserTokens(userId) ?: storageService.getUserTokens(card) } private fun loadDemoCurrencies(): List { - return DemoHelper.config.demoBlockchains.map { - BlockchainNetwork( - blockchain = it, - derivationPath = it.derivationPath(DerivationStyle.LEGACY)?.rawPath, - tokens = emptyList(), - ) - }.flatMap { it.toCurrencies() } + return DemoHelper.config.demoBlockchains + .map { blockchain -> + BlockchainNetwork( + blockchain = blockchain, + derivationPath = blockchain.derivationPath(DerivationStyle.LEGACY)?.rawPath, + tokens = emptyList(), + ) + } + .flatMap(BlockchainNetwork::toCurrencies) } - private suspend fun handleGetUserTokensFailure( - card: CardDTO, - userId: String, - error: Throwable, - ): List { - return when (error) { - is NoDataError -> { - val tokens = storageService.getUserTokens(card) - val userTokens = tokens.toUserTokensResponse() - coroutineScope { launch { networkService.saveUserTokens(userId = userId, tokens = userTokens) } } - tokens - } + private fun List.toUserTokensResponse() = UserTokensResponse( + tokens = CurrencyConverter.convertList(this), + group = GROUP_DEFAULT_VALUE, + sort = SORT_DEFAULT_VALUE, + ) + + private suspend fun handleGetUserTokensFailure(card: CardDTO, userId: String, error: Throwable): List { + return when { + error is TangemSdkError.NetworkError && error.customMessage.contains(NOT_FOUND_HTTP_CODE) -> + storageService.getUserTokens(card).also { + tangemTechApi.saveUserTokens(userId = userId, userTokens = it.toUserTokensResponse()) + } else -> { val tokens = storageService.getUserTokens(userId) ?: storageService.getUserTokens(card) tokens.distinct() @@ -110,21 +109,28 @@ class UserTokensRepository( } } - private suspend fun loadTokensOffline(card: CardDTO, userId: String): List { - return storageService.getUserTokens(userId) ?: storageService.getUserTokens(card) - } - companion object { - const val SORT_DEFAULT_VALUE = "manual" - const val GROUP_DEFAULT_VALUE = "none" + private const val GROUP_DEFAULT_VALUE = "none" + private const val SORT_DEFAULT_VALUE = "manual" + private const val NOT_FOUND_HTTP_CODE = "404" + + // TODO("After adding DI") get dependencies by DI fun init(context: Context, tangemTechService: TangemTechService): UserTokensRepository { val fileReader = AndroidFileReader(context) val oldUserTokensRepository = OldUserTokensRepository( - fileReader, store.state.domainNetworks.tangemTechService, + fileReader = fileReader, + tangemNetworkService = store.state.domainNetworks.tangemTechService, + ) + val storageService = UserTokensStorageService( + oldUserTokensRepository = oldUserTokensRepository, + fileReader = fileReader, + ) + + return UserTokensRepository( + storageService = storageService, + tangemTechApi = tangemTechService.api, + dispatchers = AppCoroutineDispatcherProvider(), ) - val storageService = UserTokensStorageService(oldUserTokensRepository, fileReader) - val networkService = UserTokensNetworkService(tangemTechService) - return UserTokensRepository(storageService, networkService) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt index 9e5dac0b68..93d4d909ce 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt @@ -2,8 +2,8 @@ package com.tangem.tap.domain.tokens import com.squareup.moshi.JsonAdapter import com.tangem.Log -import com.tangem.datasource.api.tangemTech.UserTokensResponse import com.tangem.datasource.api.common.MoshiConverter +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.domain.common.CardDTO import com.tangem.tap.common.FileReader import com.tangem.tap.features.wallet.models.Currency diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/converters/CurrencyConverter.kt b/app/src/main/java/com/tangem/tap/domain/tokens/converters/CurrencyConverter.kt new file mode 100644 index 0000000000..48f55ed575 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tokens/converters/CurrencyConverter.kt @@ -0,0 +1,20 @@ +package com.tangem.tap.domain.tokens.converters + +import com.tangem.datasource.api.tangemTech.models.TokenBody +import com.tangem.domain.common.extensions.toNetworkId +import com.tangem.tap.features.wallet.models.Currency +import com.tangem.utils.converter.Converter + +/** Converter from domain model [Currency] to data model [TokenBody] */ +object CurrencyConverter : Converter { + + override fun convert(value: Currency) = TokenBody( + id = value.coinId, + networkId = value.blockchain.toNetworkId(), + derivationPath = value.derivationPath, + name = value.currencyName, + symbol = value.currencySymbol, + decimals = value.decimals, + contractAddress = if (value is Currency.Token) value.token.contractAddress else null + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt index 146d622d16..00ea34457c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt @@ -1,11 +1,10 @@ package com.tangem.tap.features.wallet.models import com.tangem.blockchain.common.DerivationStyle +import com.tangem.datasource.api.tangemTech.models.TokenBody import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.toCoinId -import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.features.addCustomToken.CustomCurrency -import com.tangem.datasource.api.tangemTech.TokenResponse import com.tangem.tap.common.redux.global.CryptoCurrencyName import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.tokens.redux.TokenWithBlockchain @@ -57,17 +56,6 @@ sealed interface Currency { fun isBlockchain(): Boolean = this is Blockchain fun isToken(): Boolean = this is Token - fun toTokenResponse(): TokenResponse { - return TokenResponse( - id = coinId, - networkId = blockchain.toNetworkId(), - derivationPath = derivationPath, - name = currencyName, - symbol = currencySymbol, - decimals = decimals, - contractAddress = if (this is Token) token.contractAddress else null, - ) - } companion object { fun fromBlockchainNetwork( @@ -110,24 +98,24 @@ sealed interface Currency { ) } - fun fromTokenResponse(tokenResponse: TokenResponse): Currency? { - val blockchain = com.tangem.blockchain.common.Blockchain.fromNetworkId(tokenResponse.networkId) + fun fromTokenResponse(tokenBody: TokenBody): Currency? { + val blockchain = com.tangem.blockchain.common.Blockchain.fromNetworkId(tokenBody.networkId) ?: return null return when { - tokenResponse.contractAddress != null -> Token( + tokenBody.contractAddress != null -> Token( token = SdkToken( - name = tokenResponse.name, - symbol = tokenResponse.symbol, - contractAddress = tokenResponse.contractAddress!!, - decimals = tokenResponse.decimals, - id = tokenResponse.id, + name = tokenBody.name, + symbol = tokenBody.symbol, + contractAddress = tokenBody.contractAddress!!, + decimals = tokenBody.decimals, + id = tokenBody.id, ), blockchain = blockchain, - derivationPath = tokenResponse.derivationPath, + derivationPath = tokenBody.derivationPath, ) else -> Blockchain( blockchain = blockchain, - derivationPath = tokenResponse.derivationPath, + derivationPath = tokenBody.derivationPath, ) } } diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt index adcccdac54..64e625c180 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt @@ -32,26 +32,4 @@ data class CoinsResponse( } //rates.keys = networkId's -data class RatesResponse(val rates: Map) : TangemTechResponse - -data class UserTokensResponse( - val version: Int = 0, - val group: String? = null, - val sort: String? = null, - val tokens: List = emptyList(), -) : TangemTechResponse - -data class TokenResponse( - val id: String? = null, - val networkId: String, - val derivationPath: String? = null, - val name: String, - val symbol: String, - val decimals: Int, - val contractAddress: String?, -) : TangemTechResponse - -data class TangemTechError( - val code: Int, - val description: String, -) \ No newline at end of file +data class RatesResponse(val rates: Map) : TangemTechResponse \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index 8a46ed2092..2dfe10dd02 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -2,6 +2,7 @@ package com.tangem.datasource.api.tangemTech import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse import com.tangem.datasource.api.tangemTech.models.GeoResponse +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.PUT @@ -40,5 +41,5 @@ interface TangemTechApi { suspend fun getUserTokens(@Path(value = "user-id") userId: String): UserTokensResponse @PUT("user-tokens/{user-id}") - suspend fun putUserTokens(@Path(value = "user-id") userId: String, @Body userTokens: UserTokensResponse) + suspend fun saveUserTokens(@Path(value = "user-id") userId: String, @Body userTokens: UserTokensResponse) } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt index 3827446aec..136a9abcf7 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt @@ -50,15 +50,6 @@ class TangemTechService( } } - suspend fun getUserTokens(userId: String): Result = withContext(Dispatchers.IO) { - performRequest { api.getUserTokens(userId) } - } - - suspend fun putUserTokens(userId: String, userTokens: UserTokensResponse): Result = - withContext(Dispatchers.IO) { - performRequest { api.putUserTokens(userId, userTokens) } - } - fun addHeaderInterceptors(interceptors: List) { headerInterceptors.removeAll(interceptors) headerInterceptors.addAll(interceptors) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensResponse.kt new file mode 100644 index 0000000000..22c9d2fd4e --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensResponse.kt @@ -0,0 +1,20 @@ +package com.tangem.datasource.api.tangemTech.models + +import com.squareup.moshi.Json + +data class UserTokensResponse( + @Json(name = "version") val version: Int = 0, + @Json(name = "group") val group: String? = null, + @Json(name = "sort") val sort: String? = null, + @Json(name = "tokens") val tokens: List = emptyList(), +) + +data class TokenBody( + @Json(name = "id") val id: String? = null, + @Json(name = "networkId") val networkId: String, + @Json(name = "derivationPath") val derivationPath: String? = null, + @Json(name = "name") val name: String, + @Json(name = "symbol") val symbol: String, + @Json(name = "decimals") val decimals: Int, + @Json(name = "contractAddress") val contractAddress: String?, +) \ No newline at end of file From 01e5b6d95a793ba7702966f377cb97ed94b44fa1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 9 Jan 2023 13:56:57 +0300 Subject: [PATCH 039/145] Updated on 2026-08-14 --- .../tangem/tap/common/extensions/Specific.kt | 1 + .../redux/navigation/NavigationMiddleware.kt | 1 + .../tap/proxy/TransactionManagerImpl.kt | 5 +- .../java/com/tangem/utils/FormatExtensions.kt | 21 ++ .../swap/converters/QuotesConverter.kt | 7 +- .../feature/swap/domain/SwapInteractor.kt | 11 +- .../feature/swap/domain/SwapInteractorImpl.kt | 40 ++-- .../swap/domain/cache/SwapDataCache.kt | 6 +- .../swap/domain/cache/SwapDataCacheImpl.kt | 6 +- .../swap/domain/models/SwapDataHolder.kt | 3 +- .../swap/domain/models/data/SwapAmount.kt | 41 ++++ .../swap/domain/models/data/SwapState.kt | 10 +- .../tangem/feature/swap/ui/StateBuilder.kt | 110 +++++++++++ .../feature/swap/viewmodels/SwapViewModel.kt | 179 ++++++++++-------- .../tangem/lib/crypto/TransactionManager.kt | 5 + 15 files changed, 322 insertions(+), 124 deletions(-) create mode 100644 core/utils/src/main/java/com/tangem/utils/FormatExtensions.kt create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapAmount.kt create mode 100644 features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.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 795bc8e268..3f8e39c384 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 @@ -11,6 +11,7 @@ import java.text.DecimalFormat import java.text.DecimalFormatSymbols import java.util.* +//todo move extensions to utils fun BigDecimal.toFormattedString( decimals: Int, roundingMode: RoundingMode = RoundingMode.DOWN, locale: Locale = Locale.US ): String { diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt index 871ef1ea5c..d5712f07f5 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt @@ -25,6 +25,7 @@ val navigationMiddleware: Middleware = { _, state -> screen = action.screen, addToBackstack = action.addToBackstack, fgShareTransition = action.fragmentShareTransition, + bundle = action.bundle, ) } is NavigationAction.PopBackTo -> { diff --git a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt index 11b7525e9a..bec8c6eebe 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -33,7 +33,6 @@ class TransactionManagerImpl( private val appStateHolder: AppStateHolder, ) : TransactionManager { - @Throws(IllegalStateException::class) override suspend fun sendTransaction( networkId: String, amountToSend: BigDecimal, @@ -63,6 +62,10 @@ class TransactionManagerImpl( return handleSendResult(sendResult) } + override fun getNativeTokenDecimals(networkId: String): Int { + return Blockchain.fromNetworkId(networkId)?.decimals() ?: error("blockchain not found") + } + @Throws(IllegalStateException::class) override suspend fun getFee( networkId: String, diff --git a/core/utils/src/main/java/com/tangem/utils/FormatExtensions.kt b/core/utils/src/main/java/com/tangem/utils/FormatExtensions.kt new file mode 100644 index 0000000000..218d035d61 --- /dev/null +++ b/core/utils/src/main/java/com/tangem/utils/FormatExtensions.kt @@ -0,0 +1,21 @@ +package com.tangem.utils + +import java.math.BigDecimal +import java.math.RoundingMode +import java.text.DecimalFormat +import java.text.DecimalFormatSymbols +import java.util.* + +fun BigDecimal.toFormattedString( + decimals: Int, roundingMode: RoundingMode = RoundingMode.DOWN, locale: Locale = Locale.US, +): String { + val symbols = DecimalFormatSymbols(locale) + val df = DecimalFormat().apply { + decimalFormatSymbols = symbols + maximumFractionDigits = decimals + minimumFractionDigits = 0 + isGroupingUsed = false + this.roundingMode = roundingMode + } + return df.format(this) +} \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt index d6566ef6e3..4cb95d4c03 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt @@ -2,6 +2,7 @@ package com.tangem.feature.swap.converters import com.tangem.datasource.api.oneinch.models.QuoteResponse import com.tangem.feature.swap.domain.models.data.SwapState.QuoteModel +import com.tangem.feature.swap.domain.models.data.createFromAmountWithOffset import com.tangem.utils.converter.Converter import javax.inject.Inject @@ -9,8 +10,10 @@ class QuotesConverter @Inject constructor() : Converter if (tokenToApprove is Currency.NonNativeToken) { @@ -71,11 +56,10 @@ internal class SwapInteractorImpl @Inject constructor( } } - override suspend fun findBestQuote(fromToken: Currency, toToken: Currency, amount: String): SwapState { + override suspend fun findBestQuote(fromToken: Currency, toToken: Currency, amount: SwapAmount): SwapState { val networkId = cache.getNetworkId() val fromTokenAddress = getTokenAddress(fromToken) val toTokenAddress = getTokenAddress(toToken) - val bigDecimalAmount = amount.toBigDecimalOrNull() ?: error("wrong amount format, use only digits") val isAllowedToSpend = if (networkId.isNullOrEmpty()) { error("no networkId found, please call getTokensToSwap first") } else { @@ -91,13 +75,13 @@ internal class SwapInteractorImpl @Inject constructor( networkId = networkId, fromTokenAddress = fromTokenAddress, toTokenAddress = toTokenAddress, - amount = amount, + amount = amount.toStringWithRightOffset(), ).let { quotes -> val quoteDataModel = quotes.dataModel if (quoteDataModel != null) { cache.cacheSwapParams( quoteModel = quoteDataModel.copy(isAllowedToSpend = isAllowedToSpend), - amount = bigDecimalAmount, + amount = amount, fromCurrency = fromToken, toCurrency = toToken, ) @@ -115,9 +99,9 @@ internal class SwapInteractorImpl @Inject constructor( if (quoteModel != null && amountToSwap != null && networkId != null) { repository.prepareSwapTransaction( networkId = networkId, - fromTokenAddress = quoteModel.fromTokenAmount, - toTokenAddress = quoteModel.toTokenAmount, - amount = amountToSwap.toPlainString(), + fromTokenAddress = quoteModel.fromTokenAddress, + toTokenAddress = quoteModel.toTokenAddress, + amount = amountToSwap.toStringWithRightOffset(), slippage = DEFAULT_SLIPPAGE, fromWalletAddress = getWalletAddress(networkId), ).let { @@ -137,6 +121,14 @@ internal class SwapInteractorImpl @Inject constructor( } } + override fun getTokenDecimals(token: Currency): Int { + return if (token is Currency.NonNativeToken) { + token.decimalCount + } else { + transactionManager.getNativeTokenDecimals(token.networkId) + } + } + private fun getWalletAddress(networkId: String): String { return userWalletManager.getWalletAddress(networkId) } diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt index 99002c3de9..4318e5dbfc 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt @@ -2,17 +2,17 @@ package com.tangem.feature.swap.domain.cache import com.tangem.feature.swap.domain.models.ExchangeCurrencies import com.tangem.feature.swap.domain.models.data.Currency +import com.tangem.feature.swap.domain.models.data.SwapAmount import com.tangem.feature.swap.domain.models.data.SwapState.QuoteModel -import java.math.BigDecimal interface SwapDataCache { fun cacheNetworkId(networkId: String) - fun cacheSwapParams(quoteModel: QuoteModel, amount: BigDecimal, fromCurrency: Currency, toCurrency: Currency) + fun cacheSwapParams(quoteModel: QuoteModel, amount: SwapAmount, fromCurrency: Currency, toCurrency: Currency) fun cacheAvailableToSwapTokens(networkId: String, tokens: List) fun getAvailableTokens(networkId: String): List fun getLastQuote(): QuoteModel? - fun getAmountToSwap(): BigDecimal? + fun getAmountToSwap(): SwapAmount? fun getNetworkId(): String? fun getExchangeCurrencies(): ExchangeCurrencies? } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt index 953041e139..2ee0425726 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt @@ -3,8 +3,8 @@ package com.tangem.feature.swap.domain.cache import com.tangem.feature.swap.domain.models.ExchangeCurrencies import com.tangem.feature.swap.domain.models.SwapDataHolder import com.tangem.feature.swap.domain.models.data.Currency +import com.tangem.feature.swap.domain.models.data.SwapAmount import com.tangem.feature.swap.domain.models.data.SwapState.QuoteModel -import java.math.BigDecimal class SwapDataCacheImpl : SwapDataCache { @@ -13,7 +13,7 @@ class SwapDataCacheImpl : SwapDataCache { override fun cacheSwapParams( quoteModel: QuoteModel, - amount: BigDecimal, + amount: SwapAmount, fromCurrency: Currency, toCurrency: Currency, ) { @@ -52,7 +52,7 @@ class SwapDataCacheImpl : SwapDataCache { return lastDataForSwap.quoteModel } - override fun getAmountToSwap(): BigDecimal? { + override fun getAmountToSwap(): SwapAmount? { return lastDataForSwap.amountToSwap } } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataHolder.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataHolder.kt index c2534d20fe..e974b51f15 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataHolder.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataHolder.kt @@ -1,12 +1,13 @@ package com.tangem.feature.swap.domain.models import com.tangem.feature.swap.domain.models.data.Currency +import com.tangem.feature.swap.domain.models.data.SwapAmount import com.tangem.feature.swap.domain.models.data.SwapState.QuoteModel import java.math.BigDecimal data class SwapDataHolder( val quoteModel: QuoteModel? = null, - val amountToSwap: BigDecimal? = null, + val amountToSwap: SwapAmount? = null, val networkId: String? = null, val exchangeCurrencies: ExchangeCurrencies? = null, ) diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapAmount.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapAmount.kt new file mode 100644 index 0000000000..9b5738306a --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapAmount.kt @@ -0,0 +1,41 @@ +package com.tangem.feature.swap.domain.models.data + +import com.tangem.utils.toFormattedString +import java.math.BigDecimal + +data class SwapAmount( + val value: BigDecimal, + val decimals: Int, +) + +fun SwapAmount.formatToUIRepresentation(): String { + return value.toFormattedString(decimals = decimals) +} + +fun createFromAmountWithoutOffset(amountWithoutOffset: String, decimals: Int): SwapAmount { + return SwapAmount( + value = requireNotNull( + amountWithoutOffset.toBigDecimalOrNull(), + ) { "wrong amount format, use only digits" }, + decimals = decimals, + ) +} + +fun createFromAmountWithOffset(amountWithOffset: String, decimals: Int): SwapAmount { + return SwapAmount( + value = requireNotNull( + amountWithOffset.toBigDecimalOrNull()?.movePointLeft(decimals), + ) { "wrong amount format, use only digits" }, + decimals = decimals, + ) +} + +@Throws(IllegalStateException::class) +fun SwapAmount.toStringWithRightOffset(): String { + return value.movePointRight(decimals).toPlainString() +} + +@Throws(IllegalStateException::class) +fun SwapAmount.toStringWithLeftOffset(): String { + return value.movePointLeft(decimals).toPlainString() +} \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapState.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapState.kt index 7c269ea158..6b00f55db1 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapState.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapState.kt @@ -3,15 +3,17 @@ package com.tangem.feature.swap.domain.models.data sealed interface SwapState { data class QuoteModel( - val fromTokenAmount: String, - val toTokenAmount: String, + val fromTokenAmount: SwapAmount, + val toTokenAmount: SwapAmount, + val fromTokenAddress: String, + val toTokenAddress: String, val estimatedGas: Int, val isAllowedToSpend: Boolean = false, ) : SwapState data class SwapSuccess( - val fromTokenAmount: String, - val toTokenAmount: String, + val fromTokenAmount: SwapAmount, + val toTokenAmount: SwapAmount, ) : SwapState data class SwapError( diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt new file mode 100644 index 0000000000..65a6894659 --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -0,0 +1,110 @@ +package com.tangem.feature.swap.ui + +import com.tangem.feature.swap.domain.models.data.Currency +import com.tangem.feature.swap.domain.models.data.SwapState +import com.tangem.feature.swap.domain.models.data.formatToUIRepresentation +import com.tangem.feature.swap.models.FeeState +import com.tangem.feature.swap.models.SwapButton +import com.tangem.feature.swap.models.SwapCardData +import com.tangem.feature.swap.models.SwapStateHolder +import com.tangem.feature.swap.models.SwapWarning +import com.tangem.feature.swap.models.TransactionCardType + +/** + * State builder creates a specific states for SwapScreen + */ +class StateBuilder { + + fun createInitialLoadingState(networkCurrency: String, onAmountChanged: (String) -> Unit): SwapStateHolder { + return SwapStateHolder( + sendCardData = SwapCardData( + type = TransactionCardType.SendCard("", true, onAmountChanged), + amount = null, + amountEquivalent = null, + tokenIconUrl = "", + tokenCurrency = "", + canSelectAnotherToken = false, + ), + receiveCardData = SwapCardData( + type = TransactionCardType.ReceiveCard(), + amount = null, + amountEquivalent = null, + tokenIconUrl = "", + tokenCurrency = "", + canSelectAnotherToken = false, + ), + fee = FeeState.Loading, + networkCurrency = networkCurrency, + swapButton = SwapButton(enabled = false, loading = true, onClick = {}), + onRefresh = {}, + onBackClicked = {}, + onChangeCardsClicked = {}, + ) + } + + fun createQuotesLoadingState( + uiStateHolder: SwapStateHolder, + fromToken: Currency, + toToken: Currency, + mainTokenId: String, + ): SwapStateHolder { + return uiStateHolder.copy( + sendCardData = SwapCardData( + type = (uiStateHolder.sendCardData.type as TransactionCardType.SendCard), + amount = uiStateHolder.sendCardData.amount, + amountEquivalent = uiStateHolder.sendCardData.amountEquivalent, + tokenIconUrl = fromToken.logoUrl, + tokenCurrency = fromToken.symbol, + canSelectAnotherToken = mainTokenId != fromToken.id, + ), + receiveCardData = SwapCardData( + type = TransactionCardType.ReceiveCard(), + amount = uiStateHolder.receiveCardData.amount, + amountEquivalent = uiStateHolder.receiveCardData.amountEquivalent, + tokenIconUrl = toToken.logoUrl, + tokenCurrency = toToken.symbol, + canSelectAnotherToken = mainTokenId != toToken.id, + ), + fee = FeeState.Loading, + swapButton = SwapButton(enabled = false, loading = true, onClick = {}), + ) + } + + fun createQuotesLoadedState( + uiStateHolder: SwapStateHolder, + quoteModel: SwapState.QuoteModel, + fromToken: Currency, + onSwapClick: () -> Unit, + ): SwapStateHolder { + return uiStateHolder.copy( + sendCardData = SwapCardData( + type = (uiStateHolder.sendCardData.type as TransactionCardType.SendCard) + .copy(permissionIsGiven = quoteModel.isAllowedToSpend), + amount = quoteModel.fromTokenAmount.formatToUIRepresentation(), + amountEquivalent = uiStateHolder.sendCardData.amountEquivalent, + tokenIconUrl = uiStateHolder.sendCardData.tokenIconUrl, + tokenCurrency = uiStateHolder.sendCardData.tokenCurrency, + canSelectAnotherToken = uiStateHolder.sendCardData.canSelectAnotherToken, + ), + receiveCardData = SwapCardData( + type = TransactionCardType.ReceiveCard(), + amount = quoteModel.toTokenAmount.formatToUIRepresentation(), + amountEquivalent = uiStateHolder.receiveCardData.amountEquivalent, + tokenIconUrl = uiStateHolder.receiveCardData.tokenIconUrl, + tokenCurrency = uiStateHolder.receiveCardData.tokenCurrency, + canSelectAnotherToken = uiStateHolder.receiveCardData.canSelectAnotherToken, + ), + warnings = if (!quoteModel.isAllowedToSpend) { + listOf(SwapWarning.PermissionNeeded(fromToken.symbol)) + } else { + emptyList() + }, + fee = FeeState.Loaded(quoteModel.estimatedGas.toString()), + swapButton = SwapButton( + enabled = quoteModel.isAllowedToSpend, + loading = false, + onClick = onSwapClick, + ), + ) + } +} \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index 30004ba1ec..0609a4e8cf 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -4,38 +4,44 @@ import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.tangem.feature.swap.domain.SwapInteractor -import com.tangem.feature.swap.models.ApprovePermissionButton -import com.tangem.feature.swap.models.CancelPermissionButton -import com.tangem.feature.swap.models.FeeState -import com.tangem.feature.swap.models.SwapButton -import com.tangem.feature.swap.models.SwapCardData -import com.tangem.feature.swap.models.SwapPermissionStateHolder -import com.tangem.feature.swap.models.SwapSelectTokenStateHolder +import com.tangem.feature.swap.domain.models.data.Currency +import com.tangem.feature.swap.domain.models.data.SwapState +import com.tangem.feature.swap.domain.models.data.createFromAmountWithoutOffset import com.tangem.feature.swap.models.SwapStateHolder -import com.tangem.feature.swap.models.SwapSuccessStateHolder -import com.tangem.feature.swap.models.SwapWarning -import com.tangem.feature.swap.models.TokenBalanceData -import com.tangem.feature.swap.models.TokenToSelect -import com.tangem.feature.swap.models.TransactionCardType -import com.tangem.feature.swap.presentation.R +import com.tangem.feature.swap.presentation.SwapFragment import com.tangem.feature.swap.router.SwapRouter import com.tangem.feature.swap.router.SwapScreen +import com.tangem.feature.swap.ui.StateBuilder import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json import javax.inject.Inject import kotlin.properties.Delegates @HiltViewModel internal class SwapViewModel @Inject constructor( - private val referralInteractor: SwapInteractor, + private val swapInteractor: SwapInteractor, private val dispatchers: CoroutineDispatcherProvider, + savedStateHandle: SavedStateHandle, ) : ViewModel() { - var uiState: SwapStateHolder by mutableStateOf(createInitialUiState()) + private val stateBuilder = StateBuilder() + private val currency = Json.decodeFromString( + savedStateHandle[SwapFragment.CURRENCY_BUNDLE_KEY] + ?: error("no expected parameter Currency found"), + ) + + var uiState: SwapStateHolder by mutableStateOf( + stateBuilder.createInitialLoadingState(currency.symbol) + { /** [REDACTED_TODO_COMMENT]*/ }, + ) private set private var swapRouter: SwapRouter by Delegates.notNull() @@ -52,78 +58,87 @@ internal class SwapViewModel @Inject constructor( ) } - private fun createInitialUiState(): SwapStateHolder { // TODO: create using real state - return createTestUiState() - } - - // region temp for test init { - viewModelScope.launch { + viewModelScope.launch(dispatchers.main) { runCatching { - val tokens = referralInteractor.getTokensToSwap("binance-smart-chain") - val token = tokens[1] - val token2 = tokens[2] - }.onFailure { - Log.e("SwapViewModel", it.message ?: it.cause.toString()) + withContext(dispatchers.io) { + swapInteractor.getTokensToSwap(currency.networkId) + } } + .onSuccess { tokens -> + if (tokens.size > MIN_TOKENS_IN_LIST) { + tokens.firstOrNull { token -> + token.id == currency.id + }?.let { + loadQuotes(it, tokens[1], INITIAL_AMOUNT) + } + } + } + .onFailure { + Log.e("SwapViewModel", it.message ?: it.cause.toString()) + } } } - private fun createTestUiState(): SwapStateHolder { - val sendCard = SwapCardData( - type = TransactionCardType.SendCard("123", false, {}), - amount = "1 000 000 000 000 000 000", - amountEquivalent = "1 000 000 000 000 000 000", - tokenIconUrl = "", - tokenCurrency = "DAI", - networkIconRes = R.drawable.img_polygon_22, - ) - - val receiveCard = SwapCardData( - type = TransactionCardType.ReceiveCard(), - amount = "1 000 000", - amountEquivalent = "1 000 000", - tokenIconUrl = "", - tokenCurrency = "DAI", - networkIconRes = R.drawable.img_polygon_22, - canSelectAnotherToken = true, - ) - - val permissionState = SwapPermissionStateHolder( - currency = "DAI", - amount = "∞", - walletAddress = "", - spenderAddress = "", - fee = "2,14$", - approveButton = ApprovePermissionButton(true) {}, - cancelButton = CancelPermissionButton(true) {}, - ) - - val token = TokenToSelect( - id = "USDC", - name = "USDC", - symbol = "USDC", - iconUrl = "", - addedTokenBalanceData = TokenBalanceData(amount = "15 000 $", amountEquivalent = "15 000 USDT"), - ) - - val selectTokenState = SwapSelectTokenStateHolder( - listOf(token, token, token, token, token, token, token, token, token), - listOf(token, token, token, token, token, token, token, token, token), {}, {}, - ) - - return SwapStateHolder( - sendCardData = sendCard, - receiveCardData = receiveCard, - fee = FeeState.Loaded(fee = "0.155 MATIC (0.14 $)"), - warnings = listOf(SwapWarning.PermissionNeeded("DAI")), - networkCurrency = "MATIC", - swapButton = SwapButton(enabled = true, loading = false, onClick = {}), - onRefresh = {}, onBackClicked = {}, onChangeCardsClicked = {}, - permissionState = permissionState, - successState = SwapSuccessStateHolder("Success"), - selectTokenState = selectTokenState, - ) + private fun loadQuotes(fromToken: Currency, toToken: Currency, amount: String) { + uiState = stateBuilder.createQuotesLoadingState(uiState, fromToken, toToken, currency.id) + viewModelScope.launch(dispatchers.main) { + runCatching { + withContext(dispatchers.io) { + swapInteractor.findBestQuote( + fromToken = fromToken, + toToken = toToken, + amount = createFromAmountWithoutOffset( + amountWithoutOffset = amount, + decimals = swapInteractor.getTokenDecimals(fromToken), + ), + ) + } + } + .onSuccess { swapState -> + when (swapState) { + is SwapState.QuoteModel -> { + uiState = stateBuilder.createQuotesLoadedState(uiState, swapState, fromToken) { + onSwapClick(toToken, swapState) + } + } + is SwapState.SwapSuccess -> { + //todo implement + } + is SwapState.SwapError -> { + } + } + } + .onFailure { } + } + } + + private fun onSwapClick(toToken: Currency, quoteModel: SwapState.QuoteModel) { + viewModelScope.launch(dispatchers.main) { + runCatching { + if (!quoteModel.isAllowedToSpend) { + swapInteractor.givePermissionToSwap(toToken) + } else { + swapInteractor.onSwap() + } + } + .onSuccess { + if (it is SwapState) { + when (it) { + is SwapState.SwapSuccess -> { + } + is SwapState.SwapError -> { + } + else -> {} + } + } + } + .onFailure { } + } + } + + companion object { + private const val MIN_TOKENS_IN_LIST = 2 + private const val INITIAL_AMOUNT = "1" } - // endregion temp for test } \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt index a1c8bd3631..6da184fb5a 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt @@ -7,6 +7,7 @@ import java.math.BigDecimal interface TransactionManager { + @Throws(IllegalStateException::class) suspend fun sendTransaction( networkId: String, amountToSend: BigDecimal, @@ -16,6 +17,7 @@ interface TransactionManager { dataToSign: String, ): SendTxResult + @Throws(IllegalStateException::class) suspend fun getFee( networkId: String, amountToSend: BigDecimal, @@ -24,4 +26,7 @@ interface TransactionManager { ): ProxyAmount fun getNativeAddress(networkId: String): String + + @Throws(IllegalStateException::class) + fun getNativeTokenDecimals(networkId: String): Int } \ No newline at end of file From e56e411930f729ed07798a25a57a1791cd3d2e45 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 9 Jan 2023 15:20:29 +0400 Subject: [PATCH 040/145] Updated on 2026-08-14 --- .../com/tangem/tap/domain/RatesRepository.kt | 69 ++++++++++--------- .../com/tangem/tap/domain/TapWalletManager.kt | 10 ++- .../repository/di/RepositoryProvider.kt | 7 +- .../DefaultWalletAmountsRepository.kt | 54 +++++++-------- .../api/tangemTech/TangemTechApi.kt | 2 +- .../api/tangemTech/TangemTechService.kt | 9 --- 6 files changed, 75 insertions(+), 76 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt b/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt index 7cee3bcfd9..10c14744b2 100644 --- a/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt @@ -1,48 +1,49 @@ package com.tangem.tap.domain import com.tangem.common.services.Result +import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.domain.common.ThrottlerWithValues -import com.tangem.datasource.api.tangemTech.TangemTechService import com.tangem.tap.features.wallet.models.Currency -import com.tangem.tap.store +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext import java.math.BigDecimal -//TODO: refactoring: move to domain -class RatesRepository { - - private val tangemTechService: TangemTechService - get() = store.state.domainNetworks.tangemTechService - +class RatesRepository( + private val tangemTechApi: TangemTechApi, + private val dispatchers: CoroutineDispatcherProvider, +) { private val throttler = ThrottlerWithValues?>(60000) - suspend fun loadFiatRate(currencyId: String, coinsList: List): Result { -// get and submit previous result of equivalents. - val throttledResult = coinsList.filter { throttler.isStillThrottled(it) }.map { - Pair(it, throttler.geValue(it)) - } - - val currenciesToUpdate = coinsList.filter { !throttler.isStillThrottled(it) } - val coinIds = currenciesToUpdate.mapNotNull { it.coinId }.distinct() - if (coinIds.isEmpty()) return handleFiatRatesResult(throttledResult.toMap()) - - return when (val result = tangemTechService.rates(currencyId, coinIds)) { - is Result.Success -> { - val ratesResultList: Map> = result.data.rates.mapValues { - Result.Success(it.value.toBigDecimal()) - } - val updatedCurrencies = throttledResult.toMap().toMutableMap() - coinsList.forEach { currency -> - ratesResultList[currency.coinId]?.let { - updatedCurrencies[currency] = it - throttler.updateThrottlingTo(currency) - throttler.setValue(currency, it) - } - } - handleFiatRatesResult(updatedCurrencies) + suspend fun loadFiatRate(currencyId: String, coinsList: List): Result = + withContext(dispatchers.io) { + // get and submit previous result of equivalents. + val throttledResult = coinsList.filter { throttler.isStillThrottled(it) }.map { + Pair(it, throttler.geValue(it)) } - is Result.Failure -> Result.Failure(result.error) + + val currenciesToUpdate = coinsList.filter { !throttler.isStillThrottled(it) } + val coinIds = currenciesToUpdate.mapNotNull { it.coinId }.distinct() + if (coinIds.isEmpty()) return@withContext handleFiatRatesResult(throttledResult.toMap()) + + runCatching { tangemTechApi.getRates(currencyId.lowercase(), coinIds.joinToString(",")) } + .onSuccess { response -> + val ratesResultList: Map> = response.rates.mapValues { + Result.Success(it.value.toBigDecimal()) + } + val updatedCurrencies = throttledResult.toMap().toMutableMap() + coinsList.forEach { currency -> + ratesResultList[currency.coinId]?.let { + updatedCurrencies[currency] = it + throttler.updateThrottlingTo(currency) + throttler.setValue(currency, it) + } + } + return@withContext handleFiatRatesResult(updatedCurrencies) + } + .onFailure { Result.Failure(it) } + + throw IllegalStateException("Unreachable code because runCatching must return result") } - } private fun handleFiatRatesResult(rates: Map?>): Result.Success { val success = mutableMapOf() 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 ea26d15848..12ee35a3a9 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -36,6 +36,7 @@ import com.tangem.tap.store import com.tangem.tap.tangemSdkManager import com.tangem.tap.userTokensRepository import com.tangem.tap.walletStoresManager +import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import timber.log.Timber @@ -43,7 +44,14 @@ import timber.log.Timber class TapWalletManager { val walletManagerFactory: WalletManagerFactory by lazy { WalletManagerFactory(blockchainSdkConfig) } - val rates: RatesRepository = RatesRepository() + + // TODO("After adding DI") get dependencies by DI + val rates: RatesRepository by lazy { + RatesRepository( + tangemTechApi = store.state.domainNetworks.tangemTechService.api, + dispatchers = AppCoroutineDispatcherProvider(), + ) + } private val blockchainSdkConfig by lazy { store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig() diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/di/RepositoryProvider.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/di/RepositoryProvider.kt index 7fc7ba38e7..d89d4ea6aa 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/di/RepositoryProvider.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/di/RepositoryProvider.kt @@ -8,6 +8,7 @@ import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository import com.tangem.tap.domain.walletStores.repository.implementation.DefaultWalletAmountsRepository import com.tangem.tap.domain.walletStores.repository.implementation.DefaultWalletManagersRepository import com.tangem.tap.domain.walletStores.repository.implementation.DefaultWalletStoresRepository +import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider fun WalletStoresRepository.Companion.provideDefaultImplementation(): WalletStoresRepository { return DefaultWalletStoresRepository() @@ -22,5 +23,9 @@ fun WalletManagersRepository.Companion.provideDefaultImplementation( fun WalletAmountsRepository.Companion.provideDefaultImplementation( tangemTechService: TangemTechService, ): WalletAmountsRepository { - return DefaultWalletAmountsRepository(tangemTechService) + // TODO("After adding DI") get dependencies by DI + return DefaultWalletAmountsRepository( + tangemTechApi = tangemTechService.api, + dispatchers = AppCoroutineDispatcherProvider(), + ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt index 640c08fe67..0c7b19fb4b 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt @@ -14,8 +14,7 @@ import com.tangem.common.flatMap import com.tangem.common.flatMapOnFailure import com.tangem.common.fold import com.tangem.common.map -import com.tangem.common.services.Result -import com.tangem.datasource.api.tangemTech.TangemTechService +import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.common.entities.FiatCurrency @@ -34,10 +33,12 @@ import com.tangem.tap.domain.walletStores.repository.implementation.utils.update import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithUnreachable import com.tangem.tap.domain.walletStores.storage.WalletManagerStorage import com.tangem.tap.domain.walletStores.storage.WalletStoresStorage +import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.PendingTransactionType import com.tangem.tap.features.wallet.models.filterByCoin import com.tangem.tap.features.wallet.models.getPendingTransactions import com.tangem.tap.network.NetworkConnectivity +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -47,7 +48,8 @@ import timber.log.Timber import java.math.BigDecimal internal class DefaultWalletAmountsRepository( - private val tangemTechService: TangemTechService, + private val tangemTechApi: TangemTechApi, + private val dispatchers: CoroutineDispatcherProvider, ) : WalletAmountsRepository { private val walletStoresStorage = WalletStoresStorage private val walletManagersStorage = WalletManagerStorage @@ -119,39 +121,31 @@ internal class DefaultWalletAmountsRepository( val coinsIds = currencies.mapNotNull { it.coinId }.distinct().toList() - val fiatRatesResult = withContext(Dispatchers.IO) { - tangemTechService.rates( - currency = fiatCurrency.code, - ids = coinsIds, - ) - } + return withContext(dispatchers.io) { + runCatching { tangemTechApi.getRates(fiatCurrency.code.lowercase(), coinsIds.joinToString(",")) } + .onSuccess { + updateWalletStoresWithFiatRates(walletStores = walletStores, fiatRates = it.rates) + return@withContext CompletionResult.Success(Unit) + } + .onFailure { + val error = WalletStoresError.FetchFiatRatesError( + currencies = currencies.map(Currency::currencySymbol).toList(), + cause = it, + ) - return when (fiatRatesResult) { - is Result.Success -> { - updateWalletStoresWithFiatRates( - walletStores = walletStores, - fiatRates = fiatRatesResult.data.rates, - ) - - CompletionResult.Success(Unit) - } - is Result.Failure -> { - val error = WalletStoresError.FetchFiatRatesError( - currencies = currencies.map { it.currencySymbol }.toList(), - cause = fiatRatesResult.error, - ) - - Timber.e( - error, - """ + Timber.e( + error, + """ Unable to fetch fiat rates |- User wallets ids: $walletsIds |- Coins ids: $coinsIds """.trimIndent(), - ) + ) - CompletionResult.Failure(error) - } + return@withContext CompletionResult.Failure(error) + } + + throw IllegalStateException("Unreachable code because runCatching must return result") } } diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index 2dfe10dd02..e42f069cac 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -26,7 +26,7 @@ interface TangemTechApi { ): CoinsResponse @GET("rates") - suspend fun rates( + suspend fun getRates( @Query("currencyId") currencyId: String, @Query("coinIds") coinIds: String, ): RatesResponse diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt index 136a9abcf7..f1d66ce518 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt @@ -41,15 +41,6 @@ class TangemTechService( } } - suspend fun rates( - currency: String, - ids: List, - ): Result = withContext(Dispatchers.IO) { - performRequest { - api.rates(currency.lowercase(), ids.joinToString(",")) - } - } - fun addHeaderInterceptors(interceptors: List) { headerInterceptors.removeAll(interceptors) headerInterceptors.addAll(interceptors) From 5352f6e6f476c6b5aee9ce0a0775cb526ccbfdc7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 23 Dec 2022 20:34:08 +0400 Subject: [PATCH 041/145] Updated on 2026-08-14 --- .../DefaultWalletAmountsRepository.kt | 20 ++ .../redux/reducers/MultiWalletReducer.kt | 2 +- .../src/main/res/values-de/strings_final.xml | 233 ++++++++++++++++++ .../src/main/res/values-fr/strings_final.xml | 233 ++++++++++++++++++ .../src/main/res/values-it/strings_final.xml | 233 ++++++++++++++++++ .../src/main/res/values-ru/strings_final.xml | 233 ++++++++++++++++++ .../res/src/main/res/values/strings_final.xml | 233 ++++++++++++++++++ 7 files changed, 1186 insertions(+), 1 deletion(-) create mode 100644 core/res/src/main/res/values-de/strings_final.xml create mode 100644 core/res/src/main/res/values-fr/strings_final.xml create mode 100644 core/res/src/main/res/values-it/strings_final.xml create mode 100644 core/res/src/main/res/values-ru/strings_final.xml create mode 100644 core/res/src/main/res/values/strings_final.xml diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt index 0c7b19fb4b..5a59ae8c30 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt @@ -169,6 +169,26 @@ internal class DefaultWalletAmountsRepository( .fold() } + private suspend fun fetchAmountsForUserWallet( + userWallet: UserWallet, + ): CompletionResult = withContext(Dispatchers.Default) { + val walletId = userWallet.walletId + val scanResponse = userWallet.scanResponse + val walletStores = walletStoresStorage.getAll() + .first() + .getOrElse(walletId) { emptyList() } + + walletStores.map { walletStore -> + async { + // TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository] + val walletManager = walletStore.walletManager + fetchAmountsForWalletStore(walletId, scanResponse, walletStore, walletManager) + } + } + .awaitAll() + .fold() + } + private suspend fun fetchAmountsForWalletStore( walletId: UserWalletId, scanResponse: ScanResponse, 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 729d99f960..8ed45a3a6f 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 @@ -73,7 +73,7 @@ class MultiWalletReducer { val selectedCurrency = if (!state.isMultiwalletAllowed) { walletStores.firstOrNull()?.walletsData?.firstOrNull()?.currency } else { - state.selectedCurrency + state.selectedWalletData?.currency } state.copy( walletsStores = walletStores, diff --git a/core/res/src/main/res/values-de/strings_final.xml b/core/res/src/main/res/values-de/strings_final.xml new file mode 100644 index 0000000000..046904ddaf --- /dev/null +++ b/core/res/src/main/res/values-de/strings_final.xml @@ -0,0 +1,233 @@ + + + Tangem Wallet + 3 cards + 2 cards + Shipping + Free + I have a promo code… + Total + Other payment methods + Buy now + Meet\nTangem + Buy + Store + Send + Pay + Exchange + Lend + Borrow + Revolutionary Hardware Wallet + Store your crypto assets secure while keeping private keys contained in your card + Ultra Secure Backup + Up to + 3 physical cards + to one wallet + Thousands of Currencies + A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card + DeFi Compatible + Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services + The Wallet for Everyone + Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. + Order + Search tokens + You are currently running in Demo mode. All funds are not real. + This feature is disabled in Demo mode + Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. + Available networks + %s network not found. Please, add it first and try again. + Attention + Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. + Tokens in Solana network are not supported by this card due to firmware limitation. + Contract address copied! + Contract address + Required field + Please select the network + Decimal number must be a valid integer, no higher than %d + Contract address is invalid + Derivation path is invalid + Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. + This token/network has already been added to your list + Decimals + Network + Not selected + E.g. USD Coin + Name + E.g. USDC + Token symbol + BIP44 coin type + Default + The server is not available, please try again later + Total balance + The amount does not include some of your funds + Tokens + Manage tokens + No rate + Network is unreachable + Hide token + Hide %s + Hide + You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. + Unable to hide %s + The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. + Your wallet has not been backed up + To protect your assets, we advise you to carry out this procedure + Erneut versuchen + Chat + %s network + I understand + Yes + No + Russian bank cards are not accepted at the moment + Do you have a bank card of another country or a UnionPay card? + This network is not supported. Please select another network. + Card Settings + Security Mode + Change access code + Access code will be changed on this card only + Continue + Tangem Bot + Get your card ready! + Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. + Scan Card + App Settings + Keep the wallet in the app + Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. + Save Access Code + Biometric authentication will be requested instead of the access code for interactions with your card. + Removing the saved card deletes all the saved wallets and their access codes. + This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + WalletConnect + Connect to Dapps + This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + I understand that after performing this action, I will no longer have access to the current wallet + Reset the card + Reset to Factory Settings + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Select network + Scan your card + To access all the networks you need to scan the card + Connection with this Dapp cannot be established due to its technical implementation. + Welcome back! + Use %s or scan a card to access the app + Log in with %s + Karte scannen + Save your Wallet + Would you like to use %s? + Would you like to use biometrics? + Access the app + Log into the app and check your balance without scanning the card + Access code + %s will be requested instead of the access code for interactions with your wallet + Biometrics will be requested instead of the access code for interactions with your wallet + Note that making a transaction with your funds will still require your card + Allow to use %s + Allow to use biometrics + New feature + My Wallets + Multi-currency + Single-currency + Add new wallet + Rename Wallet + Wallet name + Unlock all with %s + Invalid Tag. It won\'t be added to the transaction. + Invalid Memo. It won\'t be added to the transaction. + Tag + Memo + Attention + Tap the card with the visa logo + No funds for activation + Please contact support + Four identical digits isn\'t safe + Such a PIN can be brute-forced easily + Pin code + Connect + KYC + Verify your identity + Set PIN code + Register + Verify via Utorg + Refresh + Connect your card + Verify your identity + KYC is in progress + Connect your card to the decentralized payment system + To start using your card you have to pass the KYC process + Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. + PIN Code + Set PIN code for your SaltPay card + Chat with support + Please hold the card until the operation complete + To start the backup process you have to add Tangem card as your backup + No backup card + Backup card ready + Finalize the backup process by creating an access code + Prepare the SaltPay card + Tap the SaltPay card + Tap the Tangem card + Support + Claim %s + To get started, simply claim wxDAI to your wallet + Claim + Congratulations! Your first payment crypto card has been activated + Claiming + It will take a few seconds + Something went wrong + Please check you email for further instructions + Do you want to exit the activation process? + In this case, you will need to start from the beginning. + You have used a card from another wallet. Tap the card associated with this wallet + Referral program + Refer your friends to Tangem + You + Will get + for each wallet bought by your friend on your %s network address%s + Your friend + Will get a + %s discount + when buying a card on tangem.com + Your friends bought + Your personal code + Participate + terms and conditions + By tapping this button you accept + You\'ve accepted + of the referral program + Internal error: wallet manager not found + Bilanz: %s + Share + Copy + Erfolg + Give Permission + To continue you need to allow 1inch smart contracts to use your %s + Amount %s + Your Wallet + Spender + Approve + Swap of %s to + Swap + Insufficient funds + Give Permission + Permit and Swap + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Reason: %s. Please try again later. + Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Personal code copied! + Buy Tangem Wallet with discount!\n%s + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. + Ungültige Adresse + This wallet has already been saved, you can add another one + Entfernen + Privacy policy + Enable biometric authorization + It looks like you have biometric authentication disabled, it is necessary to save wallets + Enable + Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. + %d selected + biometric authentication + biometrics + Enable biometric authentication + Go to settings to enable biometric authentication in the Tangem App + Scan the card + diff --git a/core/res/src/main/res/values-fr/strings_final.xml b/core/res/src/main/res/values-fr/strings_final.xml new file mode 100644 index 0000000000..4b3d386d9b --- /dev/null +++ b/core/res/src/main/res/values-fr/strings_final.xml @@ -0,0 +1,233 @@ + + + Tangem Wallet + 3 cards + 2 cards + Shipping + Free + I have a promo code… + Total + Other payment methods + Buy now + Meet\nTangem + Buy + Store + Send + Pay + Exchange + Lend + Borrow + Revolutionary Hardware Wallet + Store your crypto assets secure while keeping private keys contained in your card + Ultra Secure Backup + Up to + 3 physical cards + to one wallet + Thousands of Currencies + A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card + DeFi Compatible + Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services + The Wallet for Everyone + Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. + Order + Search tokens + You are currently running in Demo mode. All funds are not real. + This feature is disabled in Demo mode + Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. + Available networks + %s network not found. Please, add it first and try again. + Attention + Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. + Tokens in Solana network are not supported by this card due to firmware limitation. + Contract address copied! + Contract address + Required field + Please select the network + Decimal number must be a valid integer, no higher than %d + Contract address is invalid + Derivation path is invalid + Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. + This token/network has already been added to your list + Decimals + Network + Not selected + E.g. USD Coin + Name + E.g. USDC + Token symbol + BIP44 coin type + Default + The server is not available, please try again later + Total balance + The amount does not include some of your funds + Tokens + Manage tokens + No rate + Network is unreachable + Hide token + Hide %s + Hide + You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. + Unable to hide %s + The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. + Your wallet has not been backed up + To protect your assets, we advise you to carry out this procedure + Réessayer + Chat + %s network + I understand + Yes + No + Russian bank cards are not accepted at the moment + Do you have a bank card of another country or a UnionPay card? + This network is not supported. Please select another network. + Card Settings + Security Mode + Change access code + Access code will be changed on this card only + Continue + Tangem Bot + Get your card ready! + Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. + Scan Card + App Settings + Keep the wallet in the app + Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. + Save Access Code + Biometric authentication will be requested instead of the access code for interactions with your card. + Removing the saved card deletes all the saved wallets and their access codes. + This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + WalletConnect + Connect to Dapps + This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + I understand that after performing this action, I will no longer have access to the current wallet + Reset the card + Reset to Factory Settings + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Select network + Scan your card + To access all the networks you need to scan the card + Connection with this Dapp cannot be established due to its technical implementation. + Welcome back! + Use %s or scan a card to access the app + Log in with %s + Scannez la carte + Save your Wallet + Would you like to use %s? + Would you like to use biometrics? + Access the app + Log into the app and check your balance without scanning the card + Access code + %s will be requested instead of the access code for interactions with your wallet + Biometrics will be requested instead of the access code for interactions with your wallet + Note that making a transaction with your funds will still require your card + Allow to use %s + Allow to use biometrics + New feature + My Wallets + Multi-currency + Single-currency + Add new wallet + Rename Wallet + Wallet name + Unlock all with %s + Invalid Tag. It won\'t be added to the transaction. + Invalid Memo. It won\'t be added to the transaction. + Tag + Memo + Attention + Tap the card with the visa logo + No funds for activation + Please contact support + Four identical digits isn\'t safe + Such a PIN can be brute-forced easily + Pin code + Connect + KYC + Verify your identity + Set PIN code + Register + Verify via Utorg + Refresh + Connect your card + Verify your identity + KYC is in progress + Connect your card to the decentralized payment system + To start using your card you have to pass the KYC process + Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. + PIN Code + Set PIN code for your SaltPay card + Chat with support + Please hold the card until the operation complete + To start the backup process you have to add Tangem card as your backup + No backup card + Backup card ready + Finalize the backup process by creating an access code + Prepare the SaltPay card + Tap the SaltPay card + Tap the Tangem card + Support + Claim %s + To get started, simply claim wxDAI to your wallet + Claim + Congratulations! Your first payment crypto card has been activated + Claiming + It will take a few seconds + Something went wrong + Please check you email for further instructions + Do you want to exit the activation process? + In this case, you will need to start from the beginning. + You have used a card from another wallet. Tap the card associated with this wallet + Referral program + Refer your friends to Tangem + You + Will get + for each wallet bought by your friend on your %s network address%s + Your friend + Will get a + %s discount + when buying a card on tangem.com + Your friends bought + Your personal code + Participate + terms and conditions + By tapping this button you accept + You\'ve accepted + of the referral program + Internal error: wallet manager not found + Solde : %s + Share + Copy + Avec succès + Give Permission + To continue you need to allow 1inch smart contracts to use your %s + Amount %s + Your Wallet + Spender + Approve + Swap of %s to + Swap + Insufficient funds + Give Permission + Permit and Swap + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Reason: %s. Please try again later. + Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Personal code copied! + Buy Tangem Wallet with discount!\n%s + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. + Adresse incorrecte + This wallet has already been saved, you can add another one + Supprimer + Privacy policy + Enable biometric authorization + It looks like you have biometric authentication disabled, it is necessary to save wallets + Enable + Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. + %d selected + biometric authentication + biometrics + Enable biometric authentication + Go to settings to enable biometric authentication in the Tangem App + Scan the card + diff --git a/core/res/src/main/res/values-it/strings_final.xml b/core/res/src/main/res/values-it/strings_final.xml new file mode 100644 index 0000000000..53bd5f260c --- /dev/null +++ b/core/res/src/main/res/values-it/strings_final.xml @@ -0,0 +1,233 @@ + + + Tangem Wallet + 3 cards + 2 cards + Shipping + Free + I have a promo code… + Total + Other payment methods + Buy now + Meet\nTangem + Buy + Store + Send + Pay + Exchange + Lend + Borrow + Revolutionary Hardware Wallet + Store your crypto assets secure while keeping private keys contained in your card + Ultra Secure Backup + Up to + 3 physical cards + to one wallet + Thousands of Currencies + A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card + DeFi Compatible + Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services + The Wallet for Everyone + Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. + Order + Search tokens + You are currently running in Demo mode. All funds are not real. + This feature is disabled in Demo mode + Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. + Available networks + %s network not found. Please, add it first and try again. + Attention + Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. + Tokens in Solana network are not supported by this card due to firmware limitation. + Contract address copied! + Contract address + Required field + Please select the network + Decimal number must be a valid integer, no higher than %d + Contract address is invalid + Derivation path is invalid + Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. + This token/network has already been added to your list + Decimals + Network + Not selected + E.g. USD Coin + Name + E.g. USDC + Token symbol + BIP44 coin type + Default + The server is not available, please try again later + Total balance + The amount does not include some of your funds + Tokens + Manage tokens + No rate + Network is unreachable + Hide token + Hide %s + Hide + You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. + Unable to hide %s + The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. + Your wallet has not been backed up + To protect your assets, we advise you to carry out this procedure + Riprova + Chat + %s network + I understand + Yes + No + Russian bank cards are not accepted at the moment + Do you have a bank card of another country or a UnionPay card? + This network is not supported. Please select another network. + Card Settings + Security Mode + Change access code + Access code will be changed on this card only + Continue + Tangem Bot + Get your card ready! + Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. + Scan Card + App Settings + Keep the wallet in the app + Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. + Save Access Code + Biometric authentication will be requested instead of the access code for interactions with your card. + Removing the saved card deletes all the saved wallets and their access codes. + This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + WalletConnect + Connect to Dapps + This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + I understand that after performing this action, I will no longer have access to the current wallet + Reset the card + Reset to Factory Settings + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Select network + Scan your card + To access all the networks you need to scan the card + Connection with this Dapp cannot be established due to its technical implementation. + Welcome back! + Use %s or scan a card to access the app + Log in with %s + Scansiona carta + Save your Wallet + Would you like to use %s? + Would you like to use biometrics? + Access the app + Log into the app and check your balance without scanning the card + Access code + %s will be requested instead of the access code for interactions with your wallet + Biometrics will be requested instead of the access code for interactions with your wallet + Note that making a transaction with your funds will still require your card + Allow to use %s + Allow to use biometrics + New feature + My Wallets + Multi-currency + Single-currency + Add new wallet + Rename Wallet + Wallet name + Unlock all with %s + Invalid Tag. It won\'t be added to the transaction. + Invalid Memo. It won\'t be added to the transaction. + Tag + Memo + Attention + Tap the card with the visa logo + No funds for activation + Please contact support + Four identical digits isn\'t safe + Such a PIN can be brute-forced easily + Pin code + Connect + KYC + Verify your identity + Set PIN code + Register + Verify via Utorg + Refresh + Connect your card + Verify your identity + KYC is in progress + Connect your card to the decentralized payment system + To start using your card you have to pass the KYC process + Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. + PIN Code + Set PIN code for your SaltPay card + Chat with support + Please hold the card until the operation complete + To start the backup process you have to add Tangem card as your backup + No backup card + Backup card ready + Finalize the backup process by creating an access code + Prepare the SaltPay card + Tap the SaltPay card + Tap the Tangem card + Support + Claim %s + To get started, simply claim wxDAI to your wallet + Claim + Congratulations! Your first payment crypto card has been activated + Claiming + It will take a few seconds + Something went wrong + Please check you email for further instructions + Do you want to exit the activation process? + In this case, you will need to start from the beginning. + You have used a card from another wallet. Tap the card associated with this wallet + Referral program + Refer your friends to Tangem + You + Will get + for each wallet bought by your friend on your %s network address%s + Your friend + Will get a + %s discount + when buying a card on tangem.com + Your friends bought + Your personal code + Participate + terms and conditions + By tapping this button you accept + You\'ve accepted + of the referral program + Internal error: wallet manager not found + Saldo: %s + Share + Copy + Con successo + Give Permission + To continue you need to allow 1inch smart contracts to use your %s + Amount %s + Your Wallet + Spender + Approve + Swap of %s to + Swap + Insufficient funds + Give Permission + Permit and Swap + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Reason: %s. Please try again later. + Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Personal code copied! + Buy Tangem Wallet with discount!\n%s + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. + Indirizzo non valido + This wallet has already been saved, you can add another one + Rimuovere + Privacy policy + Enable biometric authorization + It looks like you have biometric authentication disabled, it is necessary to save wallets + Enable + Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. + %d selected + biometric authentication + biometrics + Enable biometric authentication + Go to settings to enable biometric authentication in the Tangem App + Scan the card + diff --git a/core/res/src/main/res/values-ru/strings_final.xml b/core/res/src/main/res/values-ru/strings_final.xml new file mode 100644 index 0000000000..c56fea1dcc --- /dev/null +++ b/core/res/src/main/res/values-ru/strings_final.xml @@ -0,0 +1,233 @@ + + + Tangem Wallet + 3 карты + 2 карты + Доставка + Бесплатно + У меня есть промо-код… + Итого + Другие способы оплаты + Купить сейчас + Встречайте\nTangem + Покупайте + Храните + Отправляйте + Расплачивайтесь + Обменивайте + Вкладывайте + Занимайте + Революционный аппаратный кошелек + Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. + Все ключи в безопасности + До + трех карт + с одним кошельком + Тысячи криптовалют + Аппаратный кошелек для ваших биткоинов, эфира и многих других валют одновременно — все в одной карте + Поддержка DeFi + Обменивайте, покупайте NFT, получайте займы и делайте вклады в более чем 100 различных децентрализованных сервисах + Кошелек для каждого + Используйте его на ходу, в любом месте, в любое время. Без проводов и батареек. Как только понадобится крипта, просто приложите карту к телефону. + Купить + Поиск токенов + Приложение работает в демонстрационном режиме. Средства на всех счетах ненастоящие. + Эта функция недоступна в демонстрационном режиме + Недостаточно средств для комиссии на вашем %s кошельке для отправки транзакции. Сначала пополните свой %s кошелек. + Доступные сети + Сеть %s не найдена. Пожалуйста, добавьте её и попробуйте заново. + Внимание + Внимание! Валюты на разных сетях имеют разные адреса. Убедитесь, что адрес соответствует сети, в которой вы отправляете средства. + Токены в сети Solana не поддерживаются этой картой из-за ограничений прошивки. + Адрес контракта скопирован! + Адрес контракта + Обязательное поле + Пожалуйста, выберите сеть + Количество знаков после запятой должно быть корректным числом не больше %d + Адрес контракта некорректен + Путь деривации некорректен + Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить + Этот токен/сеть уже находится в вашем списке + Знаков после запятой + Сеть + Не выбрано + Например, USD Coin + Название токена + Например, USDC + Символ токена + Деривация по BIP44 + По-умолчанию + Сервер недоступен, повторите попытку позднее + Баланс + В сумме учтены не все монеты + Токены + Управление токенами + Нет цены + Сеть недоступна + Скрыть токен + Скрыть %s + Скрыть + Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. + Невозможно скрыть %s + Токен %s является основной валютой в сети %s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети. + Бэкап кошелька не был произведен + Чтобы защитить свои активы, мы советуем вам выполнить эту процедуру + Повторить + Чат + Сеть %s + Я понял + Да + Нет + Карты банков РФ в данный момент не принимаются + У вас есть карта банка другой страны или платежной системы UnionPay? + Сеть не поддерживается. Пожалуйста, выберите другую сеть. + Настройки карты + Тип безопасности + Смена кода доступа + Код доступа будет изменен только на данной карте + Продолжить + Tangem Bot + Приготовьте свою карту + Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку. + Сканировать + Настройки приложения + Cохранение кошелька + Подключите функцию привязки карты в приложении, а также возможность биометрической аутентификации. Подпись транзакции все так же потребует карту. + Сохранение кода доступа + Подключите функцию хранения кодов доступа от карт на телефоне в зашифрованном виде, и при работе с картой вместо кода доступа будет запрашиваться биометрическая аутентификация. + При отключении функции сохранения кошелька все ранее сохраненные кошельки будут удалены. + Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком. + WalletConnect + Подключение к Dapps + Это действие приведет к полному удалению кошелька на этой карте. Кошелек невозможно будет восстановить или использовать данную карту для восстановления кода доступа + Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку + Сбросить карту + Сброс к заводским настройкам + Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать эту карту для восстановления кода доступа. + Выберите сеть + Отсканируйте карту + Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту + Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации. + C возвращением! + Используйте %s или код доступа для входа в приложение + Войти с %s + Сканировать карту + Сохраните ваш кошелек + Вы хотите использовать %s? + Вы хотите использовать биометрию? + Доступ в приложение + Войдите в приложение и следите за своим балансом без сканирования карты + Код доступа + Для операций с вашим кошельком будет запрашиваться %s вместо кода доступа карты + Для операций с вашим кошельком будет запрашиваться биометрия вместо кода доступа карты + Обратите внимание, что для совершения транзакции с вашими средствами по-прежнему потребуется ваша карта + Использовать %s + Использовать биометрию + Новый функционал + Мои кошельки + Мультивалютные + Одновалютные + Добавить новый кошелек + Переименование кошелька + Имя кошелька + Разблокировать все с %s + Недопустимый Tag. Он не будет добавлен в транзакцию. + Недопустимый Memo. Он не будет добавлен в транзакцию. + Tag + Memo + Внимание + Приложите карту с логотипом Visa + Недостаточно средств для активации + Пожалуйста обратитесь в службу поддержки + Ввод одинаковых цифр является не безопасным + Данный Код доступа может быть легко взломан + Код доступа + Подключиться + Верификация клиента + Подтвердите свою личность + Установить Код доступа + Зарегистрироваться + Верифицировать (Utorg) + Обновить + Подключите свою карту + Подтвердите свою личность + Подтверждение личности в процессе + Подключите вашу карту к децентрализованной платежной системе + Для начала работы с картой вам необходимо завершить процесс подтверждения личности + Пожалуйста дождитесь завершения процесса подтверждения личности. Вы будете уведомлены через e-mail. Обычно это занимает не более часа. Вы можете закрыть приложение и вернуться позже. + Код доступа + Установите Код доступа для вашей SaltPay карты + Чат поддержки + Пожалуйста, удерживайте карту до завершения операции + Для начала процесса бэкапа вам необходимо добавить Tangem карту + Бэкап карта не добавлена + Бэкап карта создана + Завершите процесс бэкапа создав код доступа + Приготовьте SaltPay карту + Приложите SaltPay карту + Приложите Tangem карту + Чат + Запросить %s + Для начала работы просто запросите начисление wxDai на свой кошелек + Запросить + Поздравляем! Ваша платежная крипто карта теперь активирована! + Запрашивается + Это займет несколько секунд + Что-то пошло не так + Более подробная информация отправлена на ваш адрес электронной почты. + Вы хотите выйти из процесса активации? + В этом случае вам будет необходимо начать процесс заново. + Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком. + Реферальная программа + Приведи друга в Tangem + Вы + Получите + на ваш адрес в сети %s%s за каждый кошелек, который купит ваш друг + Ваш друг + Получит + %s скидку + при покупке карточки на сайте tangem.com + Ваши друзья купили + Ваш персональный код + Участвовать + условия участия + Нажимая на эту кнопку вы принимаете + Вы приняли + в реферальной программе + Внутренняя ошибка: не удается найти менеджер кошельков + Баланс: %s + Поделиться + Копировать + Успешно + Дать разрешение + Чтобы продолжить, вам нужно разрешить смарт-контракту 1inch использовать ваш %s + Количество %s + Ваш кошелек + Отправитель + Подтвердить + Обмен %s на + Обмен + Недостаточно средств + Дать разрешение + Разрешить и обменять + Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. + Не удалось загрузить информацию по реферальной программе. Причина: %s. Пожалуйста, попробуйте позже. + Не удалось обработать вашу заявку на участие. Причина: %s. Пожалуйста, попробуйте позже. Если проблема сохранится, вы можете обратиться в техподдержку. + Персональный код скопирован! + Купи Tangem Wallet со скидкой!\n%s + Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s, он будет деактивирован, а все оставшиеся средства будут уничтожены. + Неверный адрес + Этот кошелек уже был сохранен, вы можете добавить другой + Удалить + Privacy policy + Включите биометрическую аутентификацию + Похоже, что у вас отключена биометрическая аутентификация, она необходима для сохранения кошельков + Включить + Функция сохранения кошелька позволяет вам использовать свой кошелек с биометрической аутентификацией, не прикладывая карту к телефону для получения доступа. + %d выбрано + биометрическую аутентификацию + биометрией + Включите биометрическую аутентификацию + Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem App + Отсканируйте карту + diff --git a/core/res/src/main/res/values/strings_final.xml b/core/res/src/main/res/values/strings_final.xml new file mode 100644 index 0000000000..673ced5d90 --- /dev/null +++ b/core/res/src/main/res/values/strings_final.xml @@ -0,0 +1,233 @@ + + + Tangem Wallet + 3 cards + 2 cards + Shipping + Free + I have a promo code… + Total + Other payment methods + Buy now + Meet\nTangem + Buy + Store + Send + Pay + Exchange + Lend + Borrow + Revolutionary Hardware Wallet + Store your crypto assets secure while keeping private keys contained in your card + Ultra Secure Backup + Up to + 3 physical cards + to one wallet + Thousands of Currencies + A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card + DeFi Compatible + Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services + The Wallet for Everyone + Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. + Order + Search tokens + You are currently running in Demo mode. All funds are not real. + This feature is disabled in Demo mode + Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. + Available networks + %s network not found. Please, add it first and try again. + Attention + Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. + Tokens in Solana network are not supported by this card due to firmware limitation. + Contract address copied! + Contract address + Required field + Please select the network + Decimal number must be a valid integer, no higher than %d + Contract address is invalid + Derivation path is invalid + Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. + This token/network has already been added to your list + Decimals + Network + Not selected + E.g. USD Coin + Name + E.g. USDC + Token symbol + BIP44 coin type + Default + The server is not available, please try again later + Total balance + The amount does not include some of your funds + Tokens + Manage tokens + No rate + Network is unreachable + Hide token + Hide %s + Hide + You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. + Unable to hide %s + The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. + Your wallet has not been backed up + To protect your assets, we advise you to carry out this procedure + Retry + Chat + %s network + I understand + Yes + No + Russian bank cards are not accepted at the moment + Do you have a bank card of another country or a UnionPay card? + This network is not supported. Please select another network. + Card Settings + Security Mode + Change Access Code + Access code will be changed on this card only + Continue + Tangem Bot + Get your card ready! + Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. + Scan Card + App Settings + Keep the wallet in the app + Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. + Save Access Code + Biometric authentication will be requested instead of the access code for interactions with your card. + Removing the saved card deletes all the saved wallets and their access codes. + This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + WalletConnect + Connect to Dapps + This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + I understand that after performing this action, I will no longer have access to the current wallet + Reset the Card + Reset to Factory Settings + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Select network + Scan your card + To access all the networks you need to scan the card + Connection with this Dapp cannot be established due to its technical implementation. + Welcome back! + Use %s or scan a card to access the app + Log in with %s + Scan card + Save your wallet + Would you like to use %s? + Would you like to use biometrics? + Access the app + Log into the app and check your balance without scanning the card + Access code + %s will be requested instead of the access code for interactions with your wallet + Biometrics will be requested instead of the access code for interactions with your wallet + Note that making a transaction with your funds will still require your card + Allow to use %s + Allow to use biometrics + New feature + My Wallets + Multi-currency + Single-currency + Add new wallet + Rename Wallet + Wallet name + Unlock all with %s + Invalid Tag. It won\'t be added to the transaction. + Invalid Memo. It won\'t be added to the transaction. + Tag + Memo + Attention + Tap the card with the visa logo + No funds for activation + Please contact support + Four identical digits isn\'t safe + Such a PIN can be brute-forced easily + Pin code + Connect + KYC + Verify your identity + Set PIN code + Register + Verify via Utorg + Refresh + Connect your card + Verify your identity + KYC is in progress + Connect your card to the decentralized payment system + To start using your card you have to pass the KYC process + Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. + PIN Code + Set PIN code for your SaltPay card + Chat with support + Please hold the card until the operation complete + To start the backup process you have to add Tangem card as your backup + No backup card + Backup card ready + Finalize the backup process by creating an access code + Prepare the SaltPay card + Tap the SaltPay card + Tap the Tangem card + Support + Claim %s + To get started, simply claim wxDAI to your wallet + Claim + Congratulations! Your first payment crypto card has been activated + Claiming + It will take a few seconds + Something went wrong + Please check your email for further instructions + Do you want to exit the activation process? + In this case, you will need to start from the beginning. + You have used a card from another wallet. Tap the card associated with this wallet + Referral program + Refer your friends to Tangem + You + Will get + for each wallet bought by your friend on your %s network address%s + Your friend + Will get a + %s discount + when buying a card on tangem.com + Your friends bought + Your personal code + Participate + terms and conditions + By tapping this button you accept + You\'ve accepted + of the referral program + Internal error: wallet manager not found + Balance: %s + Share + Copy + Success + Give Permission + To continue you need to allow 1inch smart contracts to use your %s + Amount %s + Your Wallet + Spender + Approve + Swap of %s to + Swap + Insufficient funds + Give Permission + Permit and Swap + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Reason: %s. Please try again later. + Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Personal code copied! + Buy Tangem Wallet with discount!\n%s + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. + Invalid address + This wallet has already been saved, you can add another one + Delete + Privacy policy + Enable biometric authorization + It looks like you have biometric authentication disabled, it is necessary to save wallets + Enable + Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. + %d selected + biometric authentication + biometrics + Enable biometric authentication + Go to settings to enable biometric authentication in the Tangem App + Scan the card + From 1ef5e450d84a02b6e0030f77bbf402b1fdc5779a Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 26 Dec 2022 18:11:01 +0300 Subject: [PATCH 042/145] Updated on 2026-08-14 --- .../tap/features/wallet/ui/WalletFragment.kt | 7 +++--- .../core/ui/utils/OneTouchClickListener.kt | 25 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/utils/OneTouchClickListener.kt 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 c0bde194bf..8a67196de1 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 @@ -16,6 +16,7 @@ import by.kirich1409.viewbindingdelegate.viewBinding import coil.load import coil.size.Scale import com.tangem.core.ui.fragments.setStatusBarColor +import com.tangem.core.ui.utils.OneTouchClickListener import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.tap.MainActivity import com.tangem.tap.common.analytics.Analytics @@ -110,9 +111,9 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber Unit) : View.OnClickListener { + + private var lastClickTimeMs: Long = 0L + + override fun onClick(v: View?) { + if (SystemClock.elapsedRealtime() - lastClickTimeMs > CLICK_DELAY_MS) { + lastClickTimeMs = SystemClock.elapsedRealtime() + action() + } + } + + companion object { + private const val CLICK_DELAY_MS = 500L + } +} \ No newline at end of file From a7ec12b6dc7c8a30d545f32db5716ce479028268 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 26 Dec 2022 14:11:02 +0300 Subject: [PATCH 043/145] Updated on 2026-08-14 --- .../com/tangem/tap/domain/TangemSdkManager.kt | 5 ++ .../redux/WalletSelectorMiddleware.kt | 68 +++++++++++-------- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 01350c6c63..be8e4f65b2 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -234,6 +234,11 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co } } + fun useBiometricsForAccessCode(): Boolean { + val policy = tangemSdk.config.userCodeRequestPolicy + return policy is UserCodeRequestPolicy.AlwaysWithBiometrics && policy.codeType == UserCodeType.AccessCode + } + companion object { val config = Config( linkedTerminal = true, diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index b6b31419de..c7d8495f6a 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -19,6 +19,7 @@ import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.scanCard.ScanCardProcessor +import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager @@ -131,21 +132,45 @@ internal class WalletSelectorMiddleware { private fun addWallet() = scope.launch { Analytics.send(MyWallets.Button.ScanNewCard) - scanCardInternal { scanResponse -> - val userWallet = UserWalletBuilder(scanResponse).build() + val prevUseBiometricsForAccessCode = tangemSdkManager.useBiometricsForAccessCode() - userWalletsListManager.save(userWallet) - .doOnFailure { error -> - store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) - } - .doOnSuccess { - Analytics.send(MyWallets.CardWasScanned) + // Update access code policy for access code saving when a card was scanned + tangemSdkManager.setAccessCodeRequestPolicy( + useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, + ) + ScanCardProcessor.scan( + onSuccess = { scanResponse -> + saveUserWalletAndPopBackToWalletScreen(scanResponse) + .doOnFailure { error -> + // Rollback policy if card saving was failed + tangemSdkManager.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode) + store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) + } + }, + onFailure = { error -> + // Rollback policy if card scanning was failed + tangemSdkManager.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode) + store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) + }, + onWalletNotCreated = { + // No need to rollback policy, continue with the policy set before the card scan + store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + }, + ) + } - store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) - store.onUserWalletSelected(userWallet) - } - } + private suspend fun saveUserWalletAndPopBackToWalletScreen(scanResponse: ScanResponse): CompletionResult { + val userWallet = UserWalletBuilder(scanResponse).build() + + return userWalletsListManager.save(userWallet) + .doOnSuccess { + Analytics.send(MyWallets.CardWasScanned) + + store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + store.onUserWalletSelected(userWallet) + } } private fun selectWallet(userWalletId: UserWalletId) { @@ -265,23 +290,6 @@ internal class WalletSelectorMiddleware { } } - private suspend inline fun scanCardInternal( - crossinline onCardScanned: suspend (ScanResponse) -> Unit, - ) { - ScanCardProcessor.scan( - onSuccess = { - onCardScanned(it) - }, - onFailure = { error -> - store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) - }, - onWalletNotCreated = { - store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) - store.dispatchOnMain(NavigationAction.PopBackTo()) - }, - ) - } - private suspend fun deleteAccessCodes(userWalletsIds: List): CompletionResult { val cardsIds = userWalletsListManager.userWallets.firstOrNull().orEmpty() .asSequence() From 5dc6145d634009a4e7a892b68c2ad913f750d791 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Dec 2022 11:30:21 +0300 Subject: [PATCH 044/145] Updated on 2026-08-14 --- .../com/tangem/tap/features/onboarding/OnboardingHelper.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 9b5c5e9958..775dca1ed3 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -70,7 +70,10 @@ class OnboardingHelper { // then open save wallet screen tangemSdkManager.canUseBiometry && preferencesStorage.shouldShowSaveUserWalletScreen -> scope.launch { + store.onCardScanned(scanResponse) + delay(timeMillis = 1_200) + store.dispatchOnMain( SaveWalletAction.ProvideBackupInfo( scanResponse = scanResponse, From 5ddd0600796cfcffeb156380464deb8405cdcacd Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Dec 2022 17:59:57 +0300 Subject: [PATCH 045/145] Updated on 2026-08-14 --- .../DefaultWalletManagersRepository.kt | 6 +---- .../redux/middlewares/WalletMiddleware.kt | 2 +- .../redux/reducers/MultiWalletReducer.kt | 8 +++---- .../wallet/redux/reducers/WalletReducer.kt | 6 ++--- .../tap/features/wallet/ui/WalletViewModel.kt | 8 +++++-- .../redux/WalletSelectorMiddleware.kt | 24 ++++++++++++------- 6 files changed, 30 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt index 3de7a3f6b1..6355f2f6d6 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt @@ -106,11 +106,7 @@ internal class DefaultWalletManagersRepository( override suspend fun delete(userWalletIds: List): CompletionResult = catching { walletManagersStorage.update { prevManagers -> - prevManagers.apply { - userWalletIds.forEach { userWalletId -> - remove(userWalletId) - } - } + prevManagers.filterKeys { it !in userWalletIds } as HashMap> } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index 222f7b7fa1..88916c3e2b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -306,7 +306,7 @@ class WalletMiddleware { val reduxWalletStores = wallStores.mapToReduxModels(state.isMultiwalletAllowed) store.dispatchOnMain( WalletAction.WalletStoresChanged.UpdateWalletStores( - reduxWalletStores = reduxWalletStores.toList(), + reduxWalletStores = reduxWalletStores, ), ) } 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 8ed45a3a6f..afb523a5ca 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 @@ -36,7 +36,7 @@ class MultiWalletReducer { fun reduce(action: WalletAction.MultiWallet, state: WalletState): WalletState { return when (action) { is WalletAction.MultiWallet.AddBlockchains -> { - val walletStores: List = action.blockchains.mapNotNull { blockchain -> + val walletStores: List = action.blockchains.map { blockchain -> val walletManager = action.walletManagers.firstOrNull { it.wallet.blockchain == blockchain.blockchain && (it.wallet.publicKey.derivationPath?.rawPath == blockchain.derivationPath) @@ -70,10 +70,10 @@ class MultiWalletReducer { ) } - val selectedCurrency = if (!state.isMultiwalletAllowed) { - walletStores.firstOrNull()?.walletsData?.firstOrNull()?.currency + val selectedCurrency = if (state.isMultiwalletAllowed) { + state.selectedCurrency } else { - state.selectedWalletData?.currency + walletStores.firstOrNull()?.walletsData?.firstOrNull()?.currency } state.copy( walletsStores = walletStores, 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 254fe48e3b..d71909cfec 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 @@ -362,13 +362,13 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS ) } is WalletAction.LoadData.Success -> { - val selectedCurrency = if (!newState.isMultiwalletAllowed) { + val selectedCurrency = if (newState.isMultiwalletAllowed) { + newState.selectedCurrency + } else { newState.walletsStores.firstOrNull() ?.walletsData ?.firstOrNull() ?.currency - } else { - newState.selectedWalletData?.currency } newState = newState.copy( diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt index df0b0537ed..a9fc0ad8cf 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt @@ -8,8 +8,10 @@ import com.tangem.tap.store import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletStoresManager import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -21,8 +23,10 @@ internal class WalletViewModel : ViewModel() { private fun bootstrapSelectedWalletStoresChanges() { userWalletsListManager.selectedUserWallet - .flatMapLatest { selectedWallet -> - walletStoresManager.get(selectedWallet.walletId) + .map { it.walletId } + .distinctUntilChanged() + .flatMapLatest { selectedUserWalletId -> + walletStoresManager.get(selectedUserWalletId) } .onEach { walletStores -> store.dispatch(WalletAction.WalletStoresChanged(walletStores)) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index c7d8495f6a..cc6642743f 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -277,26 +277,32 @@ internal class WalletSelectorMiddleware { .flatMap { walletStoresManager.delete(userWalletsIds) } .flatMap { deleteAccessCodes(userWalletsIds) } .doOnSuccess { - val selectedWallet = userWalletsListManager.selectedUserWalletSync + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync when { - selectedWallet == null -> { + selectedUserWallet == null -> { store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) } - currentSelectedWalletId != selectedWallet.walletId -> { - store.onUserWalletSelected(selectedWallet) + + currentSelectedWalletId != selectedUserWallet.walletId -> { + store.onUserWalletSelected(selectedUserWallet) } } } } private suspend fun deleteAccessCodes(userWalletsIds: List): CompletionResult { - val cardsIds = userWalletsListManager.userWallets.firstOrNull().orEmpty() - .asSequence() - .filter { it.walletId in userWalletsIds } - .flatMap { it.cardsInWallet } + val cardsIds = userWalletsListManager.userWallets.firstOrNull() + ?.asSequence() + ?.filter { it.walletId in userWalletsIds } + ?.flatMap { it.cardsInWallet } + ?.toSet() - return tangemSdkManager.deleteSavedUserCodes(cardsIds.toSet()) + return if (cardsIds.isNullOrEmpty()) { + CompletionResult.Success(Unit) + } else { + tangemSdkManager.deleteSavedUserCodes(cardsIds.toSet()) + } } private suspend fun UserWalletModel.updateWalletStoresAndCalculateFiatBalance( From 343f6de7cdd3d32ccfb9d18b5515b7589ef9f181 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Dec 2022 19:01:21 +0300 Subject: [PATCH 046/145] Updated on 2026-08-14 --- .../tap/features/wallet/redux/reducers/WalletReducer.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 d71909cfec..2505702c20 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 @@ -20,7 +20,6 @@ import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency -import com.tangem.tap.features.wallet.models.TotalBalance import com.tangem.tap.features.wallet.models.WalletRent import com.tangem.tap.features.wallet.redux.AddressData import com.tangem.tap.features.wallet.redux.Artwork @@ -36,7 +35,6 @@ import com.tangem.tap.features.wallet.redux.replaceSomeWalletsData import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.proxy.AppStateHolder -import com.tangem.tap.store import org.rekotlin.Action import timber.log.Timber import java.math.BigDecimal @@ -344,8 +342,9 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS card.settings.isBackupAllowed && card.backupStatus == CardDTO.BackupStatus.NoBackup, walletCardsCount = card.findCardsCount(), + walletsStores = newState.walletsStores, totalBalance = if (isMultiCurrency) { - TotalBalance(ProgressState.Loading, BigDecimal.ZERO, store.state.globalState.appCurrency) + newState.totalBalance } else { null }, From 87ba93d4d67c06276d351ea82e5c2a4898e6a361 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 17:15:32 +0300 Subject: [PATCH 047/145] Updated on 2026-08-14 --- .../tangem/tap/features/wallet/ui/WalletDetailsFragment.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index 0795efcea9..9afdb2071b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -132,12 +132,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), override fun onStop() { super.onStop() store.unsubscribe(this) - } - - override fun onDestroy() { walletDataWatcher.clear() walletStateWatcher.clear() - super.onDestroy() } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { From 1084c9ff697a679548e39f0fe2db8e736bc79b8e Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 18:40:03 +0300 Subject: [PATCH 048/145] Updated on 2026-08-14 --- .../AddressInfoBottomSheetDialog.kt | 30 +++++------------ .../wallet/ui/WalletDetailsFragment.kt | 18 +++++++++-- .../wallet/ui/wallet/SingleWalletView.kt | 32 ++++++++++++++----- .../layout/dialog_onboarding_address_info.xml | 2 +- app/src/main/res/layout/layout_address.xml | 11 +++++++ .../main/res/layout/layout_wallet_details.xml | 2 +- core/res/src/main/res/values-de/strings.xml | 3 +- core/res/src/main/res/values-fr/strings.xml | 3 +- core/res/src/main/res/values-it/strings.xml | 3 +- core/res/src/main/res/values-ru/strings.xml | 3 +- core/res/src/main/res/values/strings.xml | 3 +- 11 files changed, 65 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt index 4635716436..b56ddaf6cd 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt @@ -11,8 +11,8 @@ import com.tangem.tap.common.extensions.copyToClipboard import com.tangem.tap.common.extensions.dispatchDialogHide import com.tangem.tap.common.extensions.dispatchShare import com.tangem.tap.common.extensions.dispatchToastNotification +import com.tangem.tap.common.extensions.getString import com.tangem.tap.common.redux.AppDialog -import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.redux.AddressData import com.tangem.tap.store import com.tangem.wallet.R @@ -62,26 +62,12 @@ class AddressInfoBottomSheetDialog( Analytics.send(Token.Recieve.ButtonShareAddress()) store.dispatchShare(data.shareUrl) } - tvReceiveMessage.text = getQRReceiveMessage(tvReceiveMessage.context, stateDialog.currency) - } -} - -fun getQRReceiveMessage(context: Context, currency: Currency): String { - return when (currency) { - is Currency.Blockchain -> { - context.getString( - R.string.address_qr_code_message_format, - currency.blockchain.fullName, - currency.currencySymbol, - ) - } - is Currency.Token -> { - context.getString( - R.string.address_qr_code_message_token_format, - currency.token.name, - currency.currencySymbol, - currency.blockchain.fullName, - ) - } + val blockchain = stateDialog.currency.blockchain + tvReceiveMessage.text = tvReceiveMessage.getString( + id = R.string.address_qr_code_message_format, + blockchain.fullName, + blockchain.currency, + blockchain.fullName, + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index 9afdb2071b..fcbe06954f 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -36,7 +36,6 @@ import com.tangem.tap.common.extensions.toQrCode import com.tangem.tap.common.recyclerView.SpaceItemDecoration import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.tokens.models.BlockchainNetwork -import com.tangem.tap.features.onboarding.getQRReceiveMessage import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.PendingTransaction import com.tangem.tap.features.wallet.redux.ErrorType @@ -324,8 +323,21 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), ) } ivQrCode.setImageBitmap(state.walletAddresses.selectedAddress.shareUrl.toQrCode()) - tvReceiveMessage.text = - getQRReceiveMessage(tvReceiveMessage.context, state.currency) + + tvReceiveMessage.text = when (val currency = state.currency) { + is Currency.Blockchain -> tvReceiveMessage.getString( + id = R.string.address_qr_code_message_format, + currency.blockchain.fullName, + currency.currencySymbol, + currency.blockchain.fullName, + ) + is Currency.Token -> tvReceiveMessage.getString( + id = R.string.address_qr_code_message_format, + currency.token.name, + currency.currencySymbol, + currency.blockchain.fullName, + ) + } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index ed9576d384..1e96cb230b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -6,6 +6,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.tangem.tap.common.extensions.beginDelayedTransition import com.tangem.tap.common.extensions.fitChipsByGroupWidth import com.tangem.tap.common.extensions.getQuantityString +import com.tangem.tap.common.extensions.getString import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState @@ -65,7 +66,7 @@ class SingleWalletView : WalletView() { setupTwinCards(state.twinCardsState, binding) setupButtons(state.primaryWallet, binding, state.isExchangeServiceFeatureOn) - setupAddressCard(state.primaryWallet, binding) + setupAddressCard(state, binding) showPendingTransactionsIfPresent(state.primaryWallet.pendingTransactions) setupBalance(state, state.primaryWallet) } @@ -152,35 +153,50 @@ class SingleWalletView : WalletView() { } } - private fun setupAddressCard(state: WalletData, binding: FragmentWalletBinding) = with(binding.lAddress) { - if (state.walletAddresses != null && state.currency is Currency.Blockchain) { + private fun setupAddressCard(state: WalletState, binding: FragmentWalletBinding) = with(binding.lAddress) { + val primaryWallet = state.primaryWallet + if (primaryWallet?.walletAddresses != null && primaryWallet.currency is Currency.Blockchain) { binding.lAddress.root.show() - if (state.shouldShowMultipleAddress()) { + if (primaryWallet.shouldShowMultipleAddress()) { (binding.lAddress.root as? ViewGroup)?.beginDelayedTransition() chipGroupAddressType.show() chipGroupAddressType.fitChipsByGroupWidth() - val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type) + val checkedId = MultipleAddressUiHelper.typeToId(primaryWallet.walletAddresses.selectedAddress.type) if (checkedId != View.NO_ID) chipGroupAddressType.check(checkedId) chipGroupAddressType.setOnCheckedChangeListener { group, checkedId -> if (checkedId == -1) return@setOnCheckedChangeListener - val type = MultipleAddressUiHelper.idToType(checkedId, state.currency.blockchain) + val type = MultipleAddressUiHelper.idToType(checkedId, primaryWallet.currency.blockchain) type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) } } } else { chipGroupAddressType.hide() } - tvAddress.text = state.walletAddresses.selectedAddress.address + tvAddress.text = primaryWallet.walletAddresses.selectedAddress.address tvExplore.setOnClickListener { store.dispatch( WalletAction.ExploreAddress( - state.walletAddresses.selectedAddress.exploreUrl, + primaryWallet.walletAddresses.selectedAddress.exploreUrl, fragment!!.requireContext(), ), ) } + setupCardInfo(state) } else { binding.lAddress.root.hide() } } + + private fun setupCardInfo(state: WalletState) { + val textView = binding?.lAddress?.tvInfo + val blockchain = state.primaryWallet?.currency?.blockchain + if (textView != null && blockchain != null) { + textView.text = textView.getString( + id = R.string.address_qr_code_message_format, + blockchain.fullName, + blockchain.currency, + blockchain.fullName, + ) + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_onboarding_address_info.xml b/app/src/main/res/layout/dialog_onboarding_address_info.xml index ca04440067..eadb8f7f36 100644 --- a/app/src/main/res/layout/dialog_onboarding_address_info.xml +++ b/app/src/main/res/layout/dialog_onboarding_address_info.xml @@ -36,7 +36,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/imv_qr_code" - tools:text="@string/address_qr_code_message_token_format" /> + tools:text="Send only Ethereum (ETH) from Ethereum network to this address. Using other tokens and networks may result in loss of funds." /> + + + tools:text="Send only Ethereum (ETH) from Ethereum network to this address. Using other tokens and networks may result in loss of funds." /> diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 37d63b9b52..80ac6ebe7b 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -355,8 +355,7 @@ Success! Your card is activated and ready to be used Balance - Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. - Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. + Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. The twinning process is partly complete. You can\'t exit it now. OK, ich hab\'s! diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index f65d8b25c1..e657f386a8 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -355,8 +355,7 @@ Success! Your card is activated and ready to be used Balance - Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. - Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. + Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. The twinning process is partly complete. You can\'t exit it now. Ok, je l\'ai! diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml index f92dde614e..698174469b 100644 --- a/core/res/src/main/res/values-it/strings.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -355,8 +355,7 @@ Success! Your card is activated and ready to be used Balance - Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. - Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. + Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. The twinning process is partly complete. You can\'t exit it now. Ok, ho capito! diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 73375dc62d..d015da467f 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -355,8 +355,7 @@ Успешно! Ваша карта активирована и готова к использованию Баланс - Отправляйте только %s (%s) на этот адрес. Иначе это может привести к утрате средств. - Отправляйте только %s (%s) из сети %s на этот адрес. Иначе это может привести к утрате средств. + Отправляйте только %s (%s) в сети %s на этот адрес. Использование другой сети может привести к утрате средств. Если процесc создания кошелька каким-либо образом прервется, вам придется начинать сначала. Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас. Понятно! diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index e7edae9700..1c84b9004d 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -355,8 +355,7 @@ Success! Your card is activated and ready to be used Balance - Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. - Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. + Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. The twinning process is partly complete. You can\'t exit it now. Ok, Got it! From c0526b7ceb288e535a79c74ba3932b6dc08f6f99 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 17:00:44 +0300 Subject: [PATCH 049/145] Updated on 2026-08-14 --- .../tap/domain/model/builders/UserWalletBuilder.kt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt index 6e4d834d38..ed8eb97623 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt @@ -1,5 +1,7 @@ package com.tangem.tap.domain.model.builders +import com.tangem.common.card.EllipticCurve +import com.tangem.common.card.FirmwareVersion import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result import com.tangem.domain.common.CardDTO @@ -40,7 +42,16 @@ class UserWalletBuilder( ProductType.Note -> false ProductType.Twins -> false ProductType.SaltPay -> false - ProductType.Wallet -> !card.isStart2Coin + ProductType.Wallet -> when { + card.isStart2Coin -> false + card.firmwareVersion >= FirmwareVersion.MultiWalletAvailable -> true + else -> { + val cardWallets = card.wallets + require(cardWallets.isNotEmpty()) { "Card wallets must not be empty" } + + cardWallets.first().curve == EllipticCurve.Secp256k1 + } + } } fun backupCardsIds(backupCardsIds: Set?) = this.apply { From 9143789880bcd5f911df50f4f748cbf81ad3a82d Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 18:25:38 +0300 Subject: [PATCH 050/145] Updated on 2026-08-14 --- .../BiometricUserWalletsListManager.kt | 2 +- .../repository/UserWalletsKeysRepository.kt | 6 ++++++ .../UserWalletsPublicInformationRepository.kt | 2 -- .../BiometricUserWalletsKeysRepository.kt | 7 +++++++ .../DefaultUserWalletsPublicInformationRepository.kt | 4 ---- .../tap/features/details/redux/DetailsMiddleware.kt | 12 +++++++++--- .../tap/features/details/redux/DetailsReducer.kt | 3 +-- 7 files changed, 24 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index f7781ca770..fdd95f5f63 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -51,7 +51,7 @@ internal class BiometricUserWalletsListManager( get() = state.value.isLocked override val hasSavedUserWallets: Boolean - get() = publicInformationRepository.isNotEmpty() + get() = keysRepository.hasSavedEncryptionKeys() override suspend fun unlockWithBiometry(): CompletionResult { return unlockWithBiometryInternal() diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt index f10e0beeb5..bf96081cd4 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt @@ -32,4 +32,10 @@ internal interface UserWalletsKeysRepository { * @return [CompletionResult] of operation * */ suspend fun clear(): CompletionResult + + /** + * Determine if the user has saved user wallets + * @return [Boolean] true if user has saved wallets + * */ + fun hasSavedEncryptionKeys(): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt index f0387d3f0e..8765a6b804 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt @@ -12,6 +12,4 @@ internal interface UserWalletsPublicInformationRepository { suspend fun delete(walletIds: List): CompletionResult suspend fun clear(): CompletionResult - - fun isNotEmpty(): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt index 5e57d10474..8de5a59457 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt @@ -17,6 +17,7 @@ import com.tangem.tap.domain.userWalletList.UserWalletListError import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext internal class BiometricUserWalletsKeysRepository( @@ -77,6 +78,12 @@ internal class BiometricUserWalletsKeysRepository( } } + override fun hasSavedEncryptionKeys(): Boolean { + return runBlocking { + getUserWalletsIds().isNotEmpty() + } + } + private suspend fun getAllInternal(): CompletionResult> { return getUserWalletsIds() .map { userWalletId -> diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt index 46a016f8c0..7d1bd9333b 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt @@ -72,10 +72,6 @@ internal class DefaultUserWalletsPublicInformationRepository( } } - override fun isNotEmpty(): Boolean { - return secureStorage.get(StorageKey.UserWalletPublicInformation.name)?.isNotEmpty() == true - } - @JvmName("saveWithPublicInformation") private suspend fun save( publicInformation: List, 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 319071642c..a170aebdfd 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 @@ -278,9 +278,6 @@ class DetailsMiddleware { val userWallet = UserWalletBuilder(scanResponse).build() userWalletsListManager.save(userWallet) - .doOnFailure { error -> - Timber.e(error, "Wallet saving failed") - } .doOnSuccess { Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.On)) @@ -296,6 +293,9 @@ class DetailsMiddleware { store.onUserWalletSelected(userWallet) } + .doOnFailure { error -> + Timber.e(error, "Unable to save user wallet") + } } private suspend fun deleteSavedWallets() { @@ -315,6 +315,9 @@ class DetailsMiddleware { store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) } + .doOnFailure { error -> + Timber.e(error, "Unable to delete saved wallets") + } } private fun saveAccessCodes(state: DetailsState) { @@ -353,6 +356,9 @@ class DetailsMiddleware { ) } } + .doOnFailure { error -> + Timber.e(error, "Unable to delete saved access codes") + } } } } \ No newline at end of file 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 d040111cdf..d6c2454283 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 @@ -12,7 +12,6 @@ import com.tangem.tap.domain.extensions.signedHashesCount import com.tangem.tap.preferencesStorage import com.tangem.tap.store import com.tangem.tap.tangemSdkManager -import com.tangem.tap.userWalletsListManager import org.rekotlin.Action import java.util.* @@ -58,7 +57,7 @@ private fun handlePrepareScreen( createBackupAllowed = action.scanResponse.card.backupStatus == CardDTO.BackupStatus.NoBackup, appCurrency = store.state.globalState.appCurrency, isBiometricsAvailable = tangemSdkManager.canUseBiometry, - saveWallets = userWalletsListManager.hasSavedUserWallets, + saveWallets = preferencesStorage.shouldSaveUserWallets, saveAccessCodes = preferencesStorage.shouldSaveAccessCodes, ) } From 4025b5536d6a3d8f59d8608cc737d3742ec5663c Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Dec 2022 11:10:34 +0300 Subject: [PATCH 051/145] Updated on 2026-08-14 --- .../converters/BasicEventConverter.kt | 8 ++- .../common/feedback/AdditionalFeedbackInfo.kt | 4 +- .../com/tangem/tap/domain/model/UserWallet.kt | 20 ------ .../model/builders/UserWalletBuilder.kt | 23 +++--- .../model/builders/UserWalletIdBuilder.kt | 70 +++++++++++++++++++ .../tap/domain/tokens/UserTokensRepository.kt | 24 +++++-- .../BiometricUserWalletsListManager.kt | 4 +- .../model/UserWalletEncryptionKey.kt | 2 +- ...erWalletsSensitiveInformationRepository.kt | 2 +- .../utils/UserWalletEncyptionKeyCalculator.kt | 19 ++--- .../details/redux/DetailsMiddleware.kt | 32 ++++----- .../tap/features/home/redux/HomeMiddleware.kt | 2 +- .../twins/redux/TwinCardsMiddleware.kt | 4 +- .../saveWallet/redux/SaveWalletMiddleware.kt | 2 +- .../redux/WalletSelectorMiddleware.kt | 2 + .../welcome/redux/WelcomeMiddleware.kt | 2 +- .../tangem/tap/proxy/UserWalletManagerImpl.kt | 9 ++- .../tangem/domain/common/util/UserWalletId.kt | 16 +---- 18 files changed, 153 insertions(+), 92 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt rename domain/src/main/java/com/tangem/domain/common/util/CardExtensions.kt => app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt (55%) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt index 45d55aac2a..52fa450434 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt @@ -3,11 +3,11 @@ package com.tangem.tap.common.analytics.converters import com.tangem.common.Converter import com.tangem.common.extensions.isZero import com.tangem.domain.common.ScanResponse -import com.tangem.domain.common.util.userWalletId import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.common.analytics.filters.BasicTopUpFilter import com.tangem.tap.domain.extensions.isMultiwalletAllowed +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.features.wallet.redux.WalletData import com.tangem.tap.features.wallet.redux.WalletState @@ -29,7 +29,9 @@ class BasicSignInEventConverter( currency = cardCurrency, batch = scanResponse.card.batchId, ).apply { - filterData = scanResponse.card.userWalletId.stringValue + filterData = UserWalletIdBuilder.scanResponse(scanResponse) + .build() + ?.stringValue } } } @@ -43,7 +45,7 @@ class BasicTopUpEventConverter( val cardCurrency = ParamCardCurrencyConverter().convert(scanResponse) ?: return null val data = BasicTopUpFilter.Data( - walletId = scanResponse.card.userWalletId.stringValue, + walletId = UserWalletIdBuilder.scanResponse(scanResponse).build()?.stringValue ?: "", cardBalanceState = AnalyticsParam.CardBalanceState.from(value.walletsDataFromStores), ) diff --git a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt index 16e1bb559a..589597238f 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt @@ -10,8 +10,8 @@ import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.address.Address import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse -import com.tangem.domain.common.util.userWalletId import com.tangem.tap.common.extensions.stripZeroPlainString +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder class AdditionalFeedbackInfo { class EmailWalletInfo( @@ -54,7 +54,7 @@ class AdditionalFeedbackInfo { cardFirmwareVersion = data.card.firmwareVersion.stringValue cardIssuer = data.card.issuer.name signedHashesCount = formatSignedHashes(data.card.wallets) - userWalletId = data.card.userWalletId.stringValue + userWalletId = UserWalletIdBuilder.scanResponse(data).build()?.stringValue ?: "" } fun setWalletsInfo(walletManagers: List) { diff --git a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt index 4dec0df7d1..a76fbb9d1a 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt @@ -1,6 +1,5 @@ package com.tangem.tap.domain.model -import com.tangem.common.extensions.toHexString import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.util.UserWalletId @@ -33,23 +32,4 @@ data class UserWallet( val isLocked: Boolean get() = scanResponse.card.wallets.isEmpty() -} - -/** - * !!! Workaround !!! - * - * Calculate same [UserWalletId] for twins instead - * - * TODO: Remove after [REDACTED_JIRA] - * */ -fun UserWallet.isTwinnedWith(other: UserWallet): Boolean { - if (!scanResponse.isTangemTwins() || !other.scanResponse.isTangemTwins()) return false - if (scanResponse.secondTwinPublicKey == null || other.scanResponse.secondTwinPublicKey == null) return false - if (other.scanResponse.secondTwinPublicKey == scanResponse.card.wallets.firstOrNull()?.publicKey?.toHexString()) { - return true - } - if (scanResponse.secondTwinPublicKey == other.scanResponse.card.wallets.firstOrNull()?.publicKey?.toHexString()) { - return true - } - return false } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt index ed8eb97623..9148486bc6 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt @@ -10,7 +10,6 @@ import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.TwinCardNumber import com.tangem.domain.common.TwinsHelper -import com.tangem.domain.common.util.userWalletId import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.operations.attestation.TangemApi import com.tangem.tap.domain.model.UserWallet @@ -60,16 +59,20 @@ class UserWalletBuilder( } } - suspend fun build(): UserWallet { + suspend fun build(): UserWallet? { return with(scanResponse) { - UserWallet( - walletId = card.userWalletId, - name = userWalletName, - artworkUrl = loadArtworkUrl(card.cardId, card.cardPublicKey), - cardsInWallet = backupCardsIds.plus(card.cardId), - scanResponse = this, - isMultiCurrency = isMultiCurrency, - ) + UserWalletIdBuilder.scanResponse(scanResponse) + .build() + ?.let { + UserWallet( + walletId = it, + name = userWalletName, + artworkUrl = loadArtworkUrl(card.cardId, card.cardPublicKey), + cardsInWallet = backupCardsIds.plus(card.cardId), + scanResponse = this, + isMultiCurrency = isMultiCurrency, + ) + } } } diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt new file mode 100644 index 0000000000..1fdef7c37c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt @@ -0,0 +1,70 @@ +package com.tangem.tap.domain.model.builders + +import com.tangem.common.extensions.calculateSha256 +import com.tangem.common.extensions.hexToBytes +import com.tangem.crypto.Secp256k1 +import com.tangem.domain.common.CardDTO +import com.tangem.domain.common.ProductType +import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.TapWorkarounds.isTangemTwins +import com.tangem.domain.common.extensions.calculateHmacSha256 +import com.tangem.domain.common.util.UserWalletId + +class UserWalletIdBuilder private constructor( + private val publicKey: ByteArray?, + private val pairTwinPublicKey: ByteArray? = null, +) { + fun build(): UserWalletId? { + val seed = if (publicKey != null) { + if (pairTwinPublicKey != null) { + Secp256k1.sum(publicKey, pairTwinPublicKey) + } else { + publicKey + } + } else null + + return seed?.let { + UserWalletId(value = calculateUserWalletId(it)) + } + } + + private fun calculateUserWalletId(seed: ByteArray?): ByteArray? { + val message = MESSAGE_FOR_WALLET_ID.toByteArray() + val keyHash = seed?.calculateSha256() + + return if (keyHash != null) { + message.calculateHmacSha256(keyHash) + } else null + } + + companion object { + private const val MESSAGE_FOR_WALLET_ID = "UserWalletID" + + @Throws(IllegalArgumentException::class) + fun card(card: CardDTO): UserWalletIdBuilder { + require(!card.isTangemTwins) { + "For twin cards use scanResponse to ID calculation" + } + + return UserWalletIdBuilder(findPublicKey(card.wallets)) + } + + fun scanResponse(scanResponse: ScanResponse): UserWalletIdBuilder { + return UserWalletIdBuilder( + publicKey = findPublicKey(scanResponse.card.wallets), + pairTwinPublicKey = when (scanResponse.productType) { + ProductType.Twins -> scanResponse.secondTwinPublicKey?.hexToBytes() + ProductType.Note, + ProductType.Wallet, + ProductType.SaltPay, + -> null + }, + ) + } + + private fun findPublicKey(wallets: List): ByteArray? { + return wallets.firstOrNull() + ?.publicKey + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index efd7dbefb9..88b71c052e 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -7,8 +7,8 @@ import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.TangemTechService import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.domain.common.CardDTO -import com.tangem.domain.common.util.userWalletId import com.tangem.tap.common.AndroidFileReader +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.tokens.converters.CurrencyConverter import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.demo.DemoHelper @@ -29,7 +29,7 @@ class UserTokensRepository( // TODO("After adding DI") replace with CoroutineDispatcherProvider suspend fun getUserTokens(card: CardDTO): List = withContext(dispatchers.io) { - val userId = card.userWalletId.stringValue + val userId = getUserWalletId(card) ?: return emptyList() if (DemoHelper.isDemoCardId(card.cardId)) { return@withContext loadTokensOffline(card, userId).ifEmpty(::loadDemoCurrencies) } @@ -55,17 +55,22 @@ class UserTokensRepository( // TODO("After adding DI") replace with CoroutineDispatcherProvider suspend fun saveUserTokens(card: CardDTO, tokens: List) = withContext(dispatchers.io) { - val userId = card.userWalletId.stringValue + val userId = getUserWalletId(card) ?: return val userTokens = tokens.toUserTokensResponse() tangemTechApi.saveUserTokens(userId, userTokens) storageService.saveUserTokens(userId, userTokens) } + suspend fun removeUserTokens(card: CardDTO) { + val userId = getUserWalletId(card) ?: return + val userTokens = emptyList().toUserTokensResponse() + networkService.saveUserTokens(userId, userTokens) + storageService.saveUserTokens(userId, userTokens) + } + suspend fun loadBlockchainsToDerive(card: CardDTO): List = withContext(dispatchers.io) { - val blockchainNetworks = loadTokensOffline( - card = card, - userId = card.userWalletId.stringValue, - ).toBlockchainNetworks() + val userId = getUserWalletId(card) ?: return emptyList() + val blockchainNetworks = loadTokensOffline(card = card, userId = userId).toBlockchainNetworks() if (DemoHelper.isDemoCardId(card.cardId)) { return@withContext blockchainNetworks.ifEmpty(loadDemoCurrencies()::toBlockchainNetworks) @@ -109,6 +114,11 @@ class UserTokensRepository( } } + private fun getUserWalletId(card: CardDTO): String? { + return UserWalletIdBuilder.card(card).build() + ?.stringValue + } + companion object { private const val GROUP_DEFAULT_VALUE = "none" private const val SORT_DEFAULT_VALUE = "manual" diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index fdd95f5f63..4eef3db4b7 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -3,7 +3,6 @@ package com.tangem.tap.domain.userWalletList.implementation import com.tangem.common.* import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.model.UserWallet -import com.tangem.tap.domain.model.isTwinnedWith import com.tangem.tap.domain.userWalletList.UserWalletListError import com.tangem.tap.domain.userWalletList.UserWalletsListManager import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey @@ -89,8 +88,7 @@ internal class BiometricUserWalletsListManager( } else { val isWalletSaved = state.value.userWallets .any { - // Workaround, check [UserWallet.isTwinnedWith] - it.cardsInWallet.contains(userWallet.cardId) || it.isTwinnedWith(userWallet) + it.walletId == userWallet.walletId || it.cardsInWallet.contains(userWallet.cardId) } if (isWalletSaved) { diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt index cdf1edbdfb..a5c20a7790 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt @@ -2,8 +2,8 @@ package com.tangem.tap.domain.userWalletList.model import com.squareup.moshi.JsonClass import com.tangem.domain.common.util.UserWalletId -import com.tangem.domain.common.util.encryptionKey import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.userWalletList.utils.encryptionKey @JsonClass(generateAdapter = true) internal data class UserWalletEncryptionKey( diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt index 5918312b6e..158dc0db2b 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt @@ -9,13 +9,13 @@ import com.tangem.common.catching import com.tangem.common.mapFailure import com.tangem.common.services.secure.SecureStorage import com.tangem.domain.common.util.UserWalletId -import com.tangem.domain.common.util.encryptionKey import com.tangem.tap.common.extensions.filterNotNull import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.userWalletList.UserWalletListError import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation import com.tangem.tap.domain.userWalletList.repository.UserWalletsSensitiveInformationRepository +import com.tangem.tap.domain.userWalletList.utils.encryptionKey import com.tangem.tap.domain.userWalletList.utils.sensitiveInformation import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext diff --git a/domain/src/main/java/com/tangem/domain/common/util/CardExtensions.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt similarity index 55% rename from domain/src/main/java/com/tangem/domain/common/util/CardExtensions.kt rename to app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt index 4dc70fd2bd..3ecfb70521 100644 --- a/domain/src/main/java/com/tangem/domain/common/util/CardExtensions.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt @@ -1,24 +1,27 @@ -package com.tangem.domain.common.util +package com.tangem.tap.domain.userWalletList.utils import com.tangem.common.extensions.calculateSha256 import com.tangem.domain.common.CardDTO import com.tangem.domain.common.extensions.calculateHmacSha256 -val CardDTO.userWalletId: UserWalletId - get() = UserWalletId(findWalletPublicKey(wallets)) +internal val CardDTO.encryptionKey: ByteArray + @Throws(IllegalArgumentException::class) + get() { + val walletPublicKey = requireNotNull(findPublicKey(wallets)) { + "Wallet public key must not be null" + } -val CardDTO.encryptionKey: ByteArray - get() = findWalletPublicKey(wallets) - ?.let { calculateEncryptionKey(it) } - ?: error("Wallet ID not found") + return calculateEncryptionKey(walletPublicKey) + } private fun calculateEncryptionKey(publicKey: ByteArray): ByteArray { val message = MESSAGE_FOR_ENCRYPTION_KEY.toByteArray() val keyHash = publicKey.calculateSha256() + return message.calculateHmacSha256(keyHash) } -private fun findWalletPublicKey(wallets: List): ByteArray? { +private fun findPublicKey(wallets: List): ByteArray? { return wallets.firstOrNull() ?.publicKey } 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 a170aebdfd..9fe984d9c9 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,10 +4,8 @@ import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess -import com.tangem.common.extensions.toHexString import com.tangem.common.flatMap import com.tangem.domain.common.TapWorkarounds.isTangemTwins -import com.tangem.domain.common.util.userWalletId import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings @@ -20,6 +18,7 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.model.builders.UserWalletBuilder +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction @@ -27,6 +26,7 @@ import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager +import com.tangem.tap.userTokensRepository import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletStoresManager import com.tangem.wallet.R @@ -79,20 +79,16 @@ class DetailsMiddleware { } DetailsAction.ScanCard -> { scope.launch { - tangemSdkManager.scanCard(allowRequestAccessCodeFromRepository = true) - .doOnSuccess { card -> - val isSameWallet = state.scanResponse?.card?.userWalletId - ?.equals(card.userWalletId) - ?: false + tangemSdkManager.scanProduct(userTokensRepository) + .doOnSuccess { scanResponse -> + val currentUserWalletId = state.scanResponse + ?.let { UserWalletIdBuilder.scanResponse(it).build() } + val scannedUserWalletId = UserWalletIdBuilder.scanResponse(scanResponse) + .build() + val isSameWallet = currentUserWalletId == scannedUserWalletId - // !!! Workaround !!! - // TODO: Remove after [REDACTED_JIRA] - val isTwinned = card.wallets.firstOrNull()?.publicKey?.toHexString() - ?.equals(state.scanResponse?.secondTwinPublicKey) - ?: false - - if (isSameWallet || isTwinned) { - store.dispatchOnMain(DetailsAction.PrepareCardSettingsData(card)) + if (isSameWallet) { + store.dispatchOnMain(DetailsAction.PrepareCardSettingsData(scanResponse.card)) } else { store.dispatchDialogShow( AppDialog.SimpleOkDialogRes( @@ -122,8 +118,10 @@ class DetailsMiddleware { is DetailsAction.ResetToFactory.Proceed -> { val card = store.state.detailsState.cardSettingsState?.card ?: return scope.launch { + val userWalletId = UserWalletIdBuilder.card(card).build() + tangemSdkManager.resetToFactorySettings(card.cardId) - .flatMap { userWalletsListManager.delete(listOf(card.userWalletId)) } + .flatMap { userWalletsListManager.delete(listOfNotNull(userWalletId)) } .flatMap { tangemSdkManager.deleteSavedUserCodes(setOf(card.cardId)) } .doOnSuccess { Analytics.send(Settings.CardSettings.FactoryResetFinished()) @@ -275,7 +273,7 @@ class DetailsMiddleware { private suspend fun saveCurrentWallet(state: DetailsState) { val scanResponse = state.scanResponse ?: return - val userWallet = UserWalletBuilder(scanResponse).build() + val userWallet = UserWalletBuilder(scanResponse).build() ?: return userWalletsListManager.save(userWallet) .doOnSuccess { diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 0c5b990b06..fdacaa5bd1 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -93,7 +93,7 @@ private fun readCard() = scope.launch { onSuccess = { scanResponse -> scope.launch { if (preferencesStorage.shouldSaveUserWallets) { - val userWallet = UserWalletBuilder(scanResponse).build() + val userWallet = UserWalletBuilder(scanResponse).build() ?: return@launch userWalletsListManager.save(userWallet) .doOnFailure { error -> Timber.e(error, "Unable to save user wallet") diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index bc94264e05..1a5b2840d3 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -6,7 +6,6 @@ import com.tangem.common.CompletionResult import com.tangem.common.extensions.guard import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext -import com.tangem.domain.common.util.userWalletId import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -25,6 +24,7 @@ import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.makePrimaryWalletManager +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.twins.TwinCardsManager import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.features.wallet.models.Currency @@ -156,7 +156,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { TwinCardsStep.CreateFirstWallet -> { scope.launch { userWalletsListManager.delete( - listOf(getScanResponse().card.userWalletId), + listOfNotNull(UserWalletIdBuilder.scanResponse(getScanResponse()).build()), ) } } diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index 106ebfa9af..60bc2e076e 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -88,7 +88,7 @@ internal class SaveWalletMiddleware { scope.launch { val userWallet = UserWalletBuilder(scanResponse) .backupCardsIds(state.backupInfo?.backupCardsIds) - .build() + .build() ?: return@launch val isFirstSavedWallet = !userWalletsListManager.hasSavedUserWallets diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index cc6642743f..c1f24294f4 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.walletSelector.redux import com.tangem.common.CompletionResult +import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.common.flatMap @@ -162,6 +163,7 @@ internal class WalletSelectorMiddleware { private suspend fun saveUserWalletAndPopBackToWalletScreen(scanResponse: ScanResponse): CompletionResult { val userWallet = UserWalletBuilder(scanResponse).build() + ?: return CompletionResult.Failure(TangemSdkError.WalletIsNotCreated()) return userWalletsListManager.save(userWallet) .doOnSuccess { diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index 7b0f37e36e..e7cb2e01e7 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -71,7 +71,7 @@ internal class WelcomeMiddleware { private fun proceedWithCard(state: WelcomeState) = scope.launch { scanCardInternal { scanResponse -> - val userWallet = UserWalletBuilder(scanResponse).build() + val userWallet = UserWalletBuilder(scanResponse).build() ?: return@scanCardInternal userWalletsListManager.save(userWallet, canOverride = true) .doOnFailure { error -> diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt index 5d53a5dadd..926eeb68b5 100644 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -8,12 +8,12 @@ import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.toNetworkId -import com.tangem.domain.common.util.userWalletId import com.tangem.lib.crypto.UserWalletManager import com.tangem.lib.crypto.models.Currency import com.tangem.lib.crypto.models.Currency.NativeToken import com.tangem.lib.crypto.models.Currency.NonNativeToken import com.tangem.tap.domain.extensions.makeWalletManagerForApp +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.redux.WalletAction import org.rekotlin.Action @@ -54,7 +54,12 @@ class UserWalletManagerImpl( } override fun getWalletId(): String { - return appStateHolder.getActualCard()?.userWalletId?.stringValue ?: "" + return appStateHolder.getActualCard()?.let { + UserWalletIdBuilder.card(it) + .build() + ?.stringValue + } + ?: "" } override suspend fun isTokenAdded(currency: Currency): Boolean { diff --git a/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt b/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt index 4e83c913ca..abf878da69 100644 --- a/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt +++ b/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt @@ -1,17 +1,15 @@ package com.tangem.domain.common.util -import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString -import com.tangem.domain.common.extensions.calculateHmacSha256 class UserWalletId( val stringValue: String, ) { val value = stringValue.hexToBytes() - constructor(walletPublicKey: ByteArray?) : this( - stringValue = walletPublicKey?.let { calculateUserWalletId(it).toHexString() } ?: "", + constructor(value: ByteArray?) : this( + stringValue = value?.toHexString() ?: "", ) override fun equals(other: Any?): Boolean { @@ -32,12 +30,4 @@ class UserWalletId( "UserWalletId(${take(3)}...${takeLast(3)})" } } -} - -private fun calculateUserWalletId(publicKey: ByteArray): ByteArray { - val message = MESSAGE_FOR_WALLET_ID.toByteArray() - val keyHash = publicKey.calculateSha256() - return message.calculateHmacSha256(keyHash) -} - -private const val MESSAGE_FOR_WALLET_ID = "UserWalletID" \ No newline at end of file +} \ No newline at end of file From 696eea5fb6461644533f6def26b3e376aa7fd9ce Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Dec 2022 11:10:51 +0300 Subject: [PATCH 052/145] Updated on 2026-08-14 --- buildSrc/src/main/java/Versions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index 524e5e5277..2f14017e3e 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -63,7 +63,7 @@ object Versions { const val tangemBlockchainSdk = "develop-142" // const val tangemBlockchainSdk = "0.0.1" - const val tangemCardSgk = "develop-178" + const val tangemCardSgk = "develop-179" // endregion Tangem // region Testing From f65b631c3aabd389522e60cee51173603fedbbb6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 29 Dec 2022 14:00:54 +0300 Subject: [PATCH 053/145] Updated on 2026-08-14 --- .../userWalletList/UserWalletsListManager.kt | 13 +++- .../BiometricUserWalletsListManager.kt | 71 ++++++++++++++----- .../DummyUserWalletsListManager.kt | 56 --------------- .../model/UserWalletEncryptionKey.kt | 7 -- .../repository/UserWalletsKeysRepository.kt | 6 +- ...erWalletsSensitiveInformationRepository.kt | 2 +- .../BiometricUserWalletsKeysRepository.kt | 5 +- ...erWalletsSensitiveInformationRepository.kt | 5 +- .../utils/UserWalletEncyptionKeyCalculator.kt | 11 +-- .../features/tokens/redux/TokensMiddleware.kt | 15 ++-- .../middlewares/MultiWalletMiddleware.kt | 19 ++--- .../redux/WalletSelectorMiddleware.kt | 4 +- .../ui/WalletSelectorViewModel.kt | 20 ++---- 13 files changed, 104 insertions(+), 130 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt index dfd62c05f5..9e7619e7c5 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt @@ -19,7 +19,7 @@ interface UserWalletsListManager { suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult /** - * Save user wallet + * Save provided user wallet and set it as selected * @param userWallet [UserWallet] to save * @param canOverride If false, then terminate with [UserWalletListError.WalletAlreadySaved] when user tries to save an * already saved card @@ -27,6 +27,17 @@ interface UserWalletsListManager { * */ suspend fun save(userWallet: UserWallet, canOverride: Boolean = false): CompletionResult + /** + * Same as [save] but not change selected user wallet ID + * and not terminate with [UserWalletListError.WalletAlreadySaved] if [UserWallet] already saved + * + * Can terminate with [NoSuchElementException] if unable to find [UserWallet] with provided [UserWalletId] + * @param userWalletId update [UserWallet] with that [UserWalletId] + * @param update lambda that receives stored [UserWallet] and returns updated [UserWallet] + * @return [CompletionResult] of operation with updated [UserWallet] + * */ + suspend fun update(userWalletId: UserWalletId, update: (UserWallet) -> UserWallet): CompletionResult + suspend fun delete(userWalletIds: List): CompletionResult suspend fun clear(): CompletionResult diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index 4eef3db4b7..db1db2ebbd 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -10,6 +10,7 @@ import com.tangem.tap.domain.userWalletList.repository.SelectedUserWalletReposit import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository import com.tangem.tap.domain.userWalletList.repository.UserWalletsPublicInformationRepository import com.tangem.tap.domain.userWalletList.repository.UserWalletsSensitiveInformationRepository +import com.tangem.tap.domain.userWalletList.utils.encryptionKey import com.tangem.tap.domain.userWalletList.utils.toUserWallets import com.tangem.tap.domain.userWalletList.utils.updateWith import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -84,7 +85,7 @@ internal class BiometricUserWalletsListManager( override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult { return if (canOverride) { - saveInternal(userWallet) + saveInternal(userWallet, changeSelectedUserWallet = true) } else { val isWalletSaved = state.value.userWallets .any { @@ -94,11 +95,25 @@ internal class BiometricUserWalletsListManager( if (isWalletSaved) { CompletionResult.Failure(UserWalletListError.WalletAlreadySaved) } else { - saveInternal(userWallet) + saveInternal(userWallet, changeSelectedUserWallet = true) } } } + override suspend fun update( + userWalletId: UserWalletId, + update: (UserWallet) -> UserWallet, + ): CompletionResult { + return get(userWalletId) + .map { storedUserWallet -> + update(storedUserWallet) + } + .flatMap { updatedUserWallet -> + saveInternal(updatedUserWallet, changeSelectedUserWallet = false) + .map { updatedUserWallet } + } + } + override suspend fun delete(userWalletIds: List): CompletionResult { if (userWalletIds.isEmpty()) { return CompletionResult.Success(Unit) @@ -111,9 +126,12 @@ internal class BiometricUserWalletsListManager( .flatMap { keysRepository.delete(userWalletIds) } .map { state.update { prevState -> + val newUserWallets = prevState.userWallets.filter { it.walletId !in userWalletIds } + prevState.copy( encryptionKeys = prevState.encryptionKeys.filter { it.walletId !in userWalletIds }, - userWallets = prevState.userWallets.filter { it.walletId !in userWalletIds }, + userWallets = newUserWallets, + isLocked = newUserWallets.any { it.isLocked }, ) } } @@ -135,27 +153,23 @@ internal class BiometricUserWalletsListManager( } } - private suspend fun saveInternal(userWallet: UserWallet): CompletionResult { - val newEncryptionKeys = state.value.encryptionKeys - .plus(UserWalletEncryptionKey(userWallet)) - .distinctBy { it.walletId } - - return keysRepository.store(newEncryptionKeys) - .doOnSuccess { - state.update { prevState -> - prevState.copy( - encryptionKeys = newEncryptionKeys, - selectedUserWalletId = userWallet.walletId, - ) - } - } + private suspend fun saveInternal( + userWallet: UserWallet, + changeSelectedUserWallet: Boolean, + ): CompletionResult { + return saveEncryptionKeyIfNotNull(userWallet) + .flatMap { sensitiveInformationRepository.save(userWallet, encryptionKey = it) } .flatMap { publicInformationRepository.save(userWallet) } - .flatMap { sensitiveInformationRepository.save(userWallet) } .map { selectedUserWalletRepository.set(userWallet.walletId) } .flatMap { loadModels() } .doOnSuccess { state.update { prevState -> prevState.copy( + selectedUserWalletId = if (changeSelectedUserWallet) { + userWallet.walletId + } else { + prevState.selectedUserWalletId + }, isLocked = prevState.userWallets.any { it.isLocked }, ) } @@ -181,6 +195,27 @@ internal class BiometricUserWalletsListManager( } } + private suspend fun saveEncryptionKeyIfNotNull(userWallet: UserWallet): CompletionResult { + val encryptionKey = userWallet.scanResponse.card.encryptionKey + ?.let { UserWalletEncryptionKey(userWallet.walletId, it) } + + return if (encryptionKey != null) { + keysRepository.save(encryptionKey) + .doOnSuccess { + state.update { prevState -> + prevState.copy( + encryptionKeys = prevState.encryptionKeys + .plus(encryptionKey) + .distinctBy { it.walletId }, + ) + } + } + .map { encryptionKey.encryptionKey } + } else { + CompletionResult.Success(data = null) + } + } + private suspend fun loadModels(): CompletionResult { return getSavedUserWallets() .map { userWallets -> diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt deleted file mode 100644 index e616c48758..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.tangem.tap.domain.userWalletList.implementation - -import com.tangem.common.CompletionResult -import com.tangem.common.catching -import com.tangem.domain.common.util.UserWalletId -import com.tangem.tap.domain.model.UserWallet -import com.tangem.tap.domain.userWalletList.UserWalletsListManager -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf - -class DummyUserWalletsListManager : UserWalletsListManager { - override val userWallets: Flow> - get() = flowOf(emptyList()) - override val selectedUserWallet: Flow - get() = flowOf() - override val selectedUserWalletSync: UserWallet? - get() = null - override val isLocked: Flow - get() = flowOf(true) - override val isLockedSync: Boolean - get() = true - override val hasSavedUserWallets: Boolean - get() = false - - override suspend fun unlockWithBiometry(): CompletionResult { - return CompletionResult.Success(null) - } - - override fun lock() { - /* no-op */ - } - - override suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult { - return catching { - error("Not implemented") - } - } - - override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult { - return CompletionResult.Success(Unit) - } - - override suspend fun delete(userWalletIds: List): CompletionResult { - return CompletionResult.Success(Unit) - } - - override suspend fun clear(): CompletionResult { - return CompletionResult.Success(Unit) - } - - override suspend fun get(userWalletId: UserWalletId): CompletionResult { - return catching { - error("Not implemented") - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt index a5c20a7790..df0f473bae 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt @@ -2,19 +2,12 @@ package com.tangem.tap.domain.userWalletList.model import com.squareup.moshi.JsonClass import com.tangem.domain.common.util.UserWalletId -import com.tangem.tap.domain.model.UserWallet -import com.tangem.tap.domain.userWalletList.utils.encryptionKey @JsonClass(generateAdapter = true) internal data class UserWalletEncryptionKey( val walletId: UserWalletId, val encryptionKey: ByteArray, ) { - constructor(userWallet: UserWallet) : this( - walletId = userWallet.walletId, - encryptionKey = userWallet.scanResponse.card.encryptionKey, - ) - override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is UserWalletEncryptionKey) return false diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt index bf96081cd4..99b62dc99d 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt @@ -14,11 +14,11 @@ internal interface UserWalletsKeysRepository { suspend fun getAll(): CompletionResult> /** - * Store the encryption keys for user wallets. Biometric authentication not required - * @param encryptionKeys List of encryption keys for user wallets + * Save the encryption key for user wallet. Biometric authentication not required + * @param encryptionKey [UserWalletEncryptionKey] to save * @return [CompletionResult] of operation * */ - suspend fun store(encryptionKeys: List): CompletionResult + suspend fun save(encryptionKey: UserWalletEncryptionKey): CompletionResult /** * Delete encryption keys for user wallets. Biometric authentication not required diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt index 7e03055150..fdae3e64a0 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt @@ -7,7 +7,7 @@ import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation internal interface UserWalletsSensitiveInformationRepository { - suspend fun save(userWallet: UserWallet): CompletionResult + suspend fun save(userWallet: UserWallet, encryptionKey: ByteArray?): CompletionResult suspend fun getAll( encryptionKeys: List, ): CompletionResult> diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt index 8de5a59457..11e558535d 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt @@ -45,10 +45,9 @@ internal class BiometricUserWalletsKeysRepository( } } - override suspend fun store(encryptionKeys: List): CompletionResult { + override suspend fun save(encryptionKey: UserWalletEncryptionKey): CompletionResult { return withContext(Dispatchers.IO) { - encryptionKeys.map { storeEncryptionKey(it) } - .fold() + storeEncryptionKey(encryptionKey) .mapFailure { error -> UserWalletListError.SaveEncryptionKeysError(error.cause ?: error) } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt index 158dc0db2b..673ec51a10 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt @@ -38,11 +38,12 @@ internal class DefaultUserWalletsSensitiveInformationRepository( Cipher.getInstance("$algorithm/$blockMode/$encryptionPadding") } - override suspend fun save(userWallet: UserWallet): CompletionResult { + override suspend fun save(userWallet: UserWallet, encryptionKey: ByteArray?): CompletionResult { + if (encryptionKey == null) return CompletionResult.Success(Unit) // Encryption key is null, do nothing return catching { val encryptedSensitiveInformation = userWallet.sensitiveInformation .encode() - .encryptAndStoreIv(userWallet.walletId.stringValue, userWallet.scanResponse.card.encryptionKey) + .encryptAndStoreIv(userWallet.walletId.stringValue, encryptionKey) getAllEncrypted().toMutableMap() .apply { set(userWallet.walletId.stringValue, encryptedSensitiveInformation) } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt index 3ecfb70521..d97af026cc 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt @@ -4,15 +4,8 @@ import com.tangem.common.extensions.calculateSha256 import com.tangem.domain.common.CardDTO import com.tangem.domain.common.extensions.calculateHmacSha256 -internal val CardDTO.encryptionKey: ByteArray - @Throws(IllegalArgumentException::class) - get() { - val walletPublicKey = requireNotNull(findPublicKey(wallets)) { - "Wallet public key must not be null" - } - - return calculateEncryptionKey(walletPublicKey) - } +internal val CardDTO.encryptionKey: ByteArray? + get() = findPublicKey(wallets)?.let { calculateEncryptionKey(it) } private fun calculateEncryptionKey(publicKey: ByteArray): ByteArray { val message = MESSAGE_FOR_ENCRYPTION_KEY.toByteArray() 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 b07a4fd3ca..10a7d4e03a 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 @@ -281,13 +281,16 @@ class TokensMiddleware { ) { val selectedUserWallet = userWalletsListManager.selectedUserWalletSync if (selectedUserWallet != null) { - val updatedUserWallet = selectedUserWallet.copy( - scanResponse = scanResponse, - ) - scope.launch { - userWalletsListManager.save(updatedUserWallet, canOverride = true) - .flatMap { + userWalletsListManager.update( + userWalletId = selectedUserWallet.walletId, + update = { userWallet -> + userWallet.copy( + scanResponse = scanResponse, + ) + }, + ) + .flatMap { updatedUserWallet -> walletCurrenciesManager.addCurrencies( userWallet = updatedUserWallet, currenciesToAdd = currencyList, 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 44f184a4c1..9f587757a7 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 @@ -180,21 +180,24 @@ class MultiWalletMiddleware { } private fun scanAndUpdateCard( - selectedWallet: UserWallet, + selectedUserWallet: UserWallet, state: WalletState?, ) = scope.launch { Analytics.send(MainScreen.CardWasScanned()) ScanCardProcessor.scan( - cardId = selectedWallet.cardId, + cardId = selectedUserWallet.cardId, additionalBlockchainsToDerive = state?.missingDerivations?.map { it.blockchain }, ) { scanResponse -> - val userWallet = selectedWallet.copy( - scanResponse = scanResponse, + userWalletsListManager.update( + userWalletId = selectedUserWallet.walletId, + update = { userWallet -> + userWallet.copy( + scanResponse = scanResponse, + ) + }, ) - - userWalletsListManager.save(userWallet, canOverride = true) - .doOnSuccess { - store.state.globalState.tapWalletManager.loadData(userWallet, refresh = true) + .doOnSuccess { updatedUserWallet -> + store.state.globalState.tapWalletManager.loadData(updatedUserWallet, refresh = true) } } } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index c1f24294f4..4772b501d7 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -239,9 +239,7 @@ internal class WalletSelectorMiddleware { Analytics.send(MyWallets.Button.EditWalletTapped) scope.launch { - userWalletsListManager.get(userWalletId) - .map { it.copy(name = newName) } - .flatMap { userWalletsListManager.save(it, canOverride = true) } + userWalletsListManager.update(userWalletId) { it.copy(name = newName) } .doOnFailure { error -> store.dispatchOnMain(WalletSelectorAction.HandleError(error)) } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt index 1192b37c4a..52a99013e3 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt @@ -38,7 +38,6 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber Unit editingUserWalletsIds.isNotEmpty() && !editingUserWalletsIds.contains(userWalletId) -> { editWallet(userWalletId) } @@ -52,7 +51,7 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber - store.dispatch(WalletSelectorAction.RenameWallet(editedWalletId, newName)) + store.dispatch(WalletSelectorAction.RenameWallet(editedUserWalletId, newName)) stateInternal.update { prevState -> prevState.copy( renameWalletDialog = null, @@ -137,11 +136,6 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber appState.skip { old, new -> old.walletSelectorState == new.walletSelectorState } From 369f3aaa9bb2321f8de6860988b02db4d1912480 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 21:19:13 +0300 Subject: [PATCH 054/145] Updated on 2026-08-14 --- .../DefaultWalletCurrenciesManager.kt | 11 +- .../repository/WalletAmountsRepository.kt | 6 + .../DefaultWalletAmountsRepository.kt | 165 ++++++++++-------- .../utils/WalletStoreOperations.kt | 6 +- .../middlewares/MultiWalletMiddleware.kt | 4 +- 5 files changed, 117 insertions(+), 75 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt index 5fd2e25234..e000fe1c6d 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt @@ -20,6 +20,7 @@ import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.toBlockchainNetworks import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.withContext internal class DefaultWalletCurrenciesManager( @@ -64,7 +65,15 @@ internal class DefaultWalletCurrenciesManager( saveUserCurrencies(card, newCurrencies) } .flatMap { - walletAmountsRepository.updateAmountsForUserWallet( + val updatedBlockchains = updatedBlockchainNetworks + .map { it.blockchain } + val updatedWalletStores = walletStoresRepository.get(userWallet.walletId) + .firstOrNull() + ?.filter { it.blockchain in updatedBlockchains } + ?: return@flatMap CompletionResult.Success(Unit) + + walletAmountsRepository.updateAmountsForWalletStores( + walletStores = updatedWalletStores, userWallet = userWallet, fiatCurrency = appCurrencyProvider(), ) diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt index 0af5b8f85e..a054ed0ff2 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt @@ -29,6 +29,12 @@ interface WalletAmountsRepository { fiatCurrency: FiatCurrency, ): CompletionResult + suspend fun updateAmountsForWalletStores( + walletStores: List, + userWallet: UserWallet, + fiatCurrency: FiatCurrency, + ): CompletionResult + /** * Fetch wallet amounts and fiat rates then update [com.tangem.tap.domain.walletStores.storage.WalletStoresStorage] * and [com.tangem.tap.domain.walletStores.storage.WalletManagerStorage] with new data diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt index 5a59ae8c30..9496866746 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt @@ -42,7 +42,8 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.flow.first +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.withContext import timber.log.Timber import java.math.BigDecimal @@ -62,7 +63,7 @@ internal class DefaultWalletAmountsRepository( else withContext(Dispatchers.Default) { awaitAll( async { fetchAmountsForUserWallets(userWallets) }, - async { fetchFiatRates(userWallets, fiatCurrency) }, + async { fetchFiatRates(userWallets, walletStores = null, fiatCurrency) }, ) .fold() } @@ -75,46 +76,40 @@ internal class DefaultWalletAmountsRepository( return updateAmountsForUserWallets(listOf(userWallet), fiatCurrency) } + override suspend fun updateAmountsForWalletStores( + walletStores: List, + userWallet: UserWallet, + fiatCurrency: FiatCurrency, + ): CompletionResult { + return if (walletStores.isEmpty()) CompletionResult.Success(Unit) + else withContext(Dispatchers.Default) { + val userWalletId = userWallet.walletId + val scanResponse = userWallet.scanResponse + + awaitAll( + async { fetchAmountForWalletStores(userWalletId, scanResponse, walletStores) }, + async { fetchFiatRates(listOf(userWallet), walletStores, fiatCurrency) }, + ) + .fold() + } + } + override suspend fun updateAmountsForWalletStore( walletStore: WalletStoreModel, userWallet: UserWallet, fiatCurrency: FiatCurrency, - ): CompletionResult = withContext(Dispatchers.Default) { - val walletId = userWallet.walletId - val scanResponse = userWallet.scanResponse - - awaitAll( - async { - // TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository] - val walletManager = walletStore.walletManager - fetchAmountsForWalletStore(walletId, scanResponse, walletStore, walletManager) - }, - async { fetchFiatRates(listOf(userWallet), fiatCurrency) }, - ) - .fold() - } - - private suspend fun fetchAmountsForUserWallets( - userWallets: List, - ): CompletionResult = withContext(Dispatchers.Default) { - userWallets.map { async { fetchAmountsForUserWallet(it) } } - .awaitAll() - .fold() + ): CompletionResult { + return updateAmountsForWalletStores(listOf(walletStore), userWallet, fiatCurrency) } private suspend fun fetchFiatRates( userWallets: List, + walletStores: List?, fiatCurrency: FiatCurrency, ): CompletionResult { - val walletsIds = userWallets.map { it.walletId } - val walletStores = walletsIds - .flatMap { - walletStoresStorage.getAll() - .first() - .getOrElse(it) { emptyList() } - } + val walletStoresInternal = walletStores ?: getWalletStores(userWallets) - val currencies = walletStores + val currencies = walletStoresInternal .asSequence() .flatMap { it.walletsData } .map { it.currency } @@ -124,7 +119,7 @@ internal class DefaultWalletAmountsRepository( return withContext(dispatchers.io) { runCatching { tangemTechApi.getRates(fiatCurrency.code.lowercase(), coinsIds.joinToString(",")) } .onSuccess { - updateWalletStoresWithFiatRates(walletStores = walletStores, fiatRates = it.rates) + updateWalletStoresWithFiatRates(walletStores = walletStoresInternal, fiatRates = it.rates) return@withContext CompletionResult.Success(Unit) } .onFailure { @@ -137,7 +132,6 @@ internal class DefaultWalletAmountsRepository( error, """ Unable to fetch fiat rates - |- User wallets ids: $walletsIds |- Coins ids: $coinsIds """.trimIndent(), ) @@ -169,20 +163,34 @@ internal class DefaultWalletAmountsRepository( .fold() } + private suspend fun fetchAmountsForUserWallets( + userWallets: List, + ): CompletionResult = withContext(Dispatchers.Default) { + userWallets.map { async { fetchAmountsForUserWallet(it) } } + .awaitAll() + .fold() + } + private suspend fun fetchAmountsForUserWallet( userWallet: UserWallet, ): CompletionResult = withContext(Dispatchers.Default) { - val walletId = userWallet.walletId + val userWalletId = userWallet.walletId val scanResponse = userWallet.scanResponse - val walletStores = walletStoresStorage.getAll() - .first() - .getOrElse(walletId) { emptyList() } + val walletStores = getWalletStores(listOf(userWallet)) + fetchAmountForWalletStores(userWalletId, scanResponse, walletStores) + } + + private suspend fun fetchAmountForWalletStores( + userWalletId: UserWalletId, + scanResponse: ScanResponse, + walletStores: List, + ): CompletionResult = coroutineScope { walletStores.map { walletStore -> async { // TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository] val walletManager = walletStore.walletManager - fetchAmountsForWalletStore(walletId, scanResponse, walletStore, walletManager) + fetchAmountsForWalletStore(userWalletId, scanResponse, walletStore, walletManager) } } .awaitAll() @@ -190,7 +198,7 @@ internal class DefaultWalletAmountsRepository( } private suspend fun fetchAmountsForWalletStore( - walletId: UserWalletId, + userWalletId: UserWalletId, scanResponse: ScanResponse, walletStore: WalletStoreModel, walletManager: WalletManager?, @@ -200,11 +208,15 @@ internal class DefaultWalletAmountsRepository( } return when { - hasMissedDerivations -> updateWalletStoreWithMissedDerivation(walletStore) - walletManager == null -> updateWalletStoreWithUnreachable(walletStore) + hasMissedDerivations -> { + updateWalletStoreWithMissedDerivation(walletStore) + } + walletManager == null -> { + updateWalletStoreWithUnreachable(walletStore) + } else -> { withInternetConnection { walletManager.update() } - .map { updateWalletManagerWithAmounts(walletId, walletManager) } + .map { updateWalletManagerWithAmounts(userWalletId, walletManager) } .flatMap { updateWalletStoreWithAmounts( walletStore = walletStore, @@ -262,35 +274,6 @@ internal class DefaultWalletAmountsRepository( return CompletionResult.Success(Unit) } - private suspend inline fun withInternetConnection(crossinline block: suspend () -> Unit): CompletionResult { - return if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { - val error = WalletStoresError.NoInternetConnection - Timber.e(error) - CompletionResult.Failure(error) - } else withContext(Dispatchers.IO) { - catching { block() } - } - } - - private suspend fun updateWalletManagerWithAmounts( - walletId: UserWalletId, - walletManager: WalletManager, - ) = withContext(Dispatchers.Default) { - walletManagersStorage.update { prevManagers -> - val newManagersForUserWallet = prevManagers[walletId].orEmpty() - .toMutableList() - .apply { - replaceByOrAdd(walletManager) { - it.wallet.blockchain == it.wallet.blockchain - } - } - - prevManagers.apply { - set(walletId, newManagersForUserWallet) - } - } - } - private suspend fun updateWalletStoreWithError( walletStore: WalletStoreModel, wallet: Wallet, @@ -439,4 +422,44 @@ internal class DefaultWalletAmountsRepository( } } } + + private suspend inline fun withInternetConnection(crossinline block: suspend () -> Unit): CompletionResult { + return if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { + val error = WalletStoresError.NoInternetConnection + Timber.e(error) + CompletionResult.Failure(error) + } else withContext(Dispatchers.IO) { + catching { block() } + } + } + + private suspend fun updateWalletManagerWithAmounts( + userWalletId: UserWalletId, + walletManager: WalletManager, + ) = withContext(Dispatchers.Default) { + walletManagersStorage.update { prevManagers -> + val newManagersForUserWallet = prevManagers[userWalletId].orEmpty() + .toMutableList() + .apply { + replaceByOrAdd(walletManager) { + it.wallet.blockchain == it.wallet.blockchain + } + } + + prevManagers.apply { + set(userWalletId, newManagersForUserWallet) + } + } + } + + private suspend fun getWalletStores(userWallets: List): List { + return userWallets + .map { it.walletId } + .flatMap { userWalletId -> + walletStoresStorage.getAll() + .firstOrNull() + ?.get(userWalletId) + .orEmpty() + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt index 445ff655f2..2aa7ffcf2d 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt @@ -62,9 +62,11 @@ internal fun WalletStoreModel.updateWithSelf( ): WalletStoreModel { val oldStore = this return oldStore.copy( - walletManager = newWalletStore.walletManager, - walletRent = newWalletStore.walletRent, + derivationPath = newWalletStore.derivationPath, walletsData = oldStore.walletsData.updateWithSelf(newWalletStore.walletsData), + walletRent = newWalletStore.walletRent, + blockchainNetwork = newWalletStore.blockchainNetwork, + walletManager = newWalletStore.walletManager, ) } 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 9f587757a7..49e4285064 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 @@ -12,6 +12,7 @@ import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Token.ButtonRemoveToken import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchErrorNotification +import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.global.GlobalState @@ -182,7 +183,7 @@ class MultiWalletMiddleware { private fun scanAndUpdateCard( selectedUserWallet: UserWallet, state: WalletState?, - ) = scope.launch { + ) = scope.launch(Dispatchers.Default) { Analytics.send(MainScreen.CardWasScanned()) ScanCardProcessor.scan( cardId = selectedUserWallet.cardId, @@ -197,6 +198,7 @@ class MultiWalletMiddleware { }, ) .doOnSuccess { updatedUserWallet -> + store.dispatchOnMain(WalletAction.MultiWallet.AddMissingDerivations(emptyList())) store.state.globalState.tapWalletManager.loadData(updatedUserWallet, refresh = true) } } From 961b4b103578c0597c3122f8f75ffefe9e898b1b Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 30 Dec 2022 14:33:08 +0400 Subject: [PATCH 055/145] Updated on 2026-08-14 --- .../com/tangem/tap/features/tokens/redux/TokensMiddleware.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 10a7d4e03a..af6ab1607f 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 @@ -149,7 +149,7 @@ class TokensMiddleware { && blockchainsToAdd.isEmpty() && blockchainsToRemove.isEmpty() ) { store.dispatchDebugErrorNotification("Nothing to save") - store.dispatch(NavigationAction.PopBackTo()) + store.dispatchOnMain(NavigationAction.PopBackTo()) return@launch } From 0fc6a9818629abc129be0d17ca3cfe8896d834c5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 13 Dec 2022 12:28:17 +0300 Subject: [PATCH 056/145] Updated on 2026-08-14 --- .../src/main/res/values-de/strings_final.xml | 233 ------------------ .../src/main/res/values-fr/strings_final.xml | 233 ------------------ .../src/main/res/values-it/strings_final.xml | 233 ------------------ core/res/src/main/res/values-ru/strings.xml | 2 +- .../src/main/res/values-ru/strings_final.xml | 233 ------------------ .../res/src/main/res/values/strings_final.xml | 233 ------------------ 6 files changed, 1 insertion(+), 1166 deletions(-) delete mode 100644 core/res/src/main/res/values-de/strings_final.xml delete mode 100644 core/res/src/main/res/values-fr/strings_final.xml delete mode 100644 core/res/src/main/res/values-it/strings_final.xml delete mode 100644 core/res/src/main/res/values-ru/strings_final.xml delete mode 100644 core/res/src/main/res/values/strings_final.xml diff --git a/core/res/src/main/res/values-de/strings_final.xml b/core/res/src/main/res/values-de/strings_final.xml deleted file mode 100644 index 046904ddaf..0000000000 --- a/core/res/src/main/res/values-de/strings_final.xml +++ /dev/null @@ -1,233 +0,0 @@ - - - Tangem Wallet - 3 cards - 2 cards - Shipping - Free - I have a promo code… - Total - Other payment methods - Buy now - Meet\nTangem - Buy - Store - Send - Pay - Exchange - Lend - Borrow - Revolutionary Hardware Wallet - Store your crypto assets secure while keeping private keys contained in your card - Ultra Secure Backup - Up to - 3 physical cards - to one wallet - Thousands of Currencies - A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card - DeFi Compatible - Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services - The Wallet for Everyone - Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. - Order - Search tokens - You are currently running in Demo mode. All funds are not real. - This feature is disabled in Demo mode - Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. - Available networks - %s network not found. Please, add it first and try again. - Attention - Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. - Tokens in Solana network are not supported by this card due to firmware limitation. - Contract address copied! - Contract address - Required field - Please select the network - Decimal number must be a valid integer, no higher than %d - Contract address is invalid - Derivation path is invalid - Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. - This token/network has already been added to your list - Decimals - Network - Not selected - E.g. USD Coin - Name - E.g. USDC - Token symbol - BIP44 coin type - Default - The server is not available, please try again later - Total balance - The amount does not include some of your funds - Tokens - Manage tokens - No rate - Network is unreachable - Hide token - Hide %s - Hide - You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. - Unable to hide %s - The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. - Your wallet has not been backed up - To protect your assets, we advise you to carry out this procedure - Erneut versuchen - Chat - %s network - I understand - Yes - No - Russian bank cards are not accepted at the moment - Do you have a bank card of another country or a UnionPay card? - This network is not supported. Please select another network. - Card Settings - Security Mode - Change access code - Access code will be changed on this card only - Continue - Tangem Bot - Get your card ready! - Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. - Scan Card - App Settings - Keep the wallet in the app - Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. - Save Access Code - Biometric authentication will be requested instead of the access code for interactions with your card. - Removing the saved card deletes all the saved wallets and their access codes. - This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. - WalletConnect - Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - I understand that after performing this action, I will no longer have access to the current wallet - Reset the card - Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - Select network - Scan your card - To access all the networks you need to scan the card - Connection with this Dapp cannot be established due to its technical implementation. - Welcome back! - Use %s or scan a card to access the app - Log in with %s - Karte scannen - Save your Wallet - Would you like to use %s? - Would you like to use biometrics? - Access the app - Log into the app and check your balance without scanning the card - Access code - %s will be requested instead of the access code for interactions with your wallet - Biometrics will be requested instead of the access code for interactions with your wallet - Note that making a transaction with your funds will still require your card - Allow to use %s - Allow to use biometrics - New feature - My Wallets - Multi-currency - Single-currency - Add new wallet - Rename Wallet - Wallet name - Unlock all with %s - Invalid Tag. It won\'t be added to the transaction. - Invalid Memo. It won\'t be added to the transaction. - Tag - Memo - Attention - Tap the card with the visa logo - No funds for activation - Please contact support - Four identical digits isn\'t safe - Such a PIN can be brute-forced easily - Pin code - Connect - KYC - Verify your identity - Set PIN code - Register - Verify via Utorg - Refresh - Connect your card - Verify your identity - KYC is in progress - Connect your card to the decentralized payment system - To start using your card you have to pass the KYC process - Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. - PIN Code - Set PIN code for your SaltPay card - Chat with support - Please hold the card until the operation complete - To start the backup process you have to add Tangem card as your backup - No backup card - Backup card ready - Finalize the backup process by creating an access code - Prepare the SaltPay card - Tap the SaltPay card - Tap the Tangem card - Support - Claim %s - To get started, simply claim wxDAI to your wallet - Claim - Congratulations! Your first payment crypto card has been activated - Claiming - It will take a few seconds - Something went wrong - Please check you email for further instructions - Do you want to exit the activation process? - In this case, you will need to start from the beginning. - You have used a card from another wallet. Tap the card associated with this wallet - Referral program - Refer your friends to Tangem - You - Will get - for each wallet bought by your friend on your %s network address%s - Your friend - Will get a - %s discount - when buying a card on tangem.com - Your friends bought - Your personal code - Participate - terms and conditions - By tapping this button you accept - You\'ve accepted - of the referral program - Internal error: wallet manager not found - Bilanz: %s - Share - Copy - Erfolg - Give Permission - To continue you need to allow 1inch smart contracts to use your %s - Amount %s - Your Wallet - Spender - Approve - Swap of %s to - Swap - Insufficient funds - Give Permission - Permit and Swap - Failed to load the information about the referral program. Please try again later. - Failed to load the information about the referral program. Reason: %s. Please try again later. - Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. - Personal code copied! - Buy Tangem Wallet with discount!\n%s - %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. - Ungültige Adresse - This wallet has already been saved, you can add another one - Entfernen - Privacy policy - Enable biometric authorization - It looks like you have biometric authentication disabled, it is necessary to save wallets - Enable - Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. - %d selected - biometric authentication - biometrics - Enable biometric authentication - Go to settings to enable biometric authentication in the Tangem App - Scan the card - diff --git a/core/res/src/main/res/values-fr/strings_final.xml b/core/res/src/main/res/values-fr/strings_final.xml deleted file mode 100644 index 4b3d386d9b..0000000000 --- a/core/res/src/main/res/values-fr/strings_final.xml +++ /dev/null @@ -1,233 +0,0 @@ - - - Tangem Wallet - 3 cards - 2 cards - Shipping - Free - I have a promo code… - Total - Other payment methods - Buy now - Meet\nTangem - Buy - Store - Send - Pay - Exchange - Lend - Borrow - Revolutionary Hardware Wallet - Store your crypto assets secure while keeping private keys contained in your card - Ultra Secure Backup - Up to - 3 physical cards - to one wallet - Thousands of Currencies - A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card - DeFi Compatible - Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services - The Wallet for Everyone - Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. - Order - Search tokens - You are currently running in Demo mode. All funds are not real. - This feature is disabled in Demo mode - Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. - Available networks - %s network not found. Please, add it first and try again. - Attention - Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. - Tokens in Solana network are not supported by this card due to firmware limitation. - Contract address copied! - Contract address - Required field - Please select the network - Decimal number must be a valid integer, no higher than %d - Contract address is invalid - Derivation path is invalid - Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. - This token/network has already been added to your list - Decimals - Network - Not selected - E.g. USD Coin - Name - E.g. USDC - Token symbol - BIP44 coin type - Default - The server is not available, please try again later - Total balance - The amount does not include some of your funds - Tokens - Manage tokens - No rate - Network is unreachable - Hide token - Hide %s - Hide - You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. - Unable to hide %s - The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. - Your wallet has not been backed up - To protect your assets, we advise you to carry out this procedure - Réessayer - Chat - %s network - I understand - Yes - No - Russian bank cards are not accepted at the moment - Do you have a bank card of another country or a UnionPay card? - This network is not supported. Please select another network. - Card Settings - Security Mode - Change access code - Access code will be changed on this card only - Continue - Tangem Bot - Get your card ready! - Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. - Scan Card - App Settings - Keep the wallet in the app - Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. - Save Access Code - Biometric authentication will be requested instead of the access code for interactions with your card. - Removing the saved card deletes all the saved wallets and their access codes. - This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. - WalletConnect - Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - I understand that after performing this action, I will no longer have access to the current wallet - Reset the card - Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - Select network - Scan your card - To access all the networks you need to scan the card - Connection with this Dapp cannot be established due to its technical implementation. - Welcome back! - Use %s or scan a card to access the app - Log in with %s - Scannez la carte - Save your Wallet - Would you like to use %s? - Would you like to use biometrics? - Access the app - Log into the app and check your balance without scanning the card - Access code - %s will be requested instead of the access code for interactions with your wallet - Biometrics will be requested instead of the access code for interactions with your wallet - Note that making a transaction with your funds will still require your card - Allow to use %s - Allow to use biometrics - New feature - My Wallets - Multi-currency - Single-currency - Add new wallet - Rename Wallet - Wallet name - Unlock all with %s - Invalid Tag. It won\'t be added to the transaction. - Invalid Memo. It won\'t be added to the transaction. - Tag - Memo - Attention - Tap the card with the visa logo - No funds for activation - Please contact support - Four identical digits isn\'t safe - Such a PIN can be brute-forced easily - Pin code - Connect - KYC - Verify your identity - Set PIN code - Register - Verify via Utorg - Refresh - Connect your card - Verify your identity - KYC is in progress - Connect your card to the decentralized payment system - To start using your card you have to pass the KYC process - Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. - PIN Code - Set PIN code for your SaltPay card - Chat with support - Please hold the card until the operation complete - To start the backup process you have to add Tangem card as your backup - No backup card - Backup card ready - Finalize the backup process by creating an access code - Prepare the SaltPay card - Tap the SaltPay card - Tap the Tangem card - Support - Claim %s - To get started, simply claim wxDAI to your wallet - Claim - Congratulations! Your first payment crypto card has been activated - Claiming - It will take a few seconds - Something went wrong - Please check you email for further instructions - Do you want to exit the activation process? - In this case, you will need to start from the beginning. - You have used a card from another wallet. Tap the card associated with this wallet - Referral program - Refer your friends to Tangem - You - Will get - for each wallet bought by your friend on your %s network address%s - Your friend - Will get a - %s discount - when buying a card on tangem.com - Your friends bought - Your personal code - Participate - terms and conditions - By tapping this button you accept - You\'ve accepted - of the referral program - Internal error: wallet manager not found - Solde : %s - Share - Copy - Avec succès - Give Permission - To continue you need to allow 1inch smart contracts to use your %s - Amount %s - Your Wallet - Spender - Approve - Swap of %s to - Swap - Insufficient funds - Give Permission - Permit and Swap - Failed to load the information about the referral program. Please try again later. - Failed to load the information about the referral program. Reason: %s. Please try again later. - Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. - Personal code copied! - Buy Tangem Wallet with discount!\n%s - %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. - Adresse incorrecte - This wallet has already been saved, you can add another one - Supprimer - Privacy policy - Enable biometric authorization - It looks like you have biometric authentication disabled, it is necessary to save wallets - Enable - Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. - %d selected - biometric authentication - biometrics - Enable biometric authentication - Go to settings to enable biometric authentication in the Tangem App - Scan the card - diff --git a/core/res/src/main/res/values-it/strings_final.xml b/core/res/src/main/res/values-it/strings_final.xml deleted file mode 100644 index 53bd5f260c..0000000000 --- a/core/res/src/main/res/values-it/strings_final.xml +++ /dev/null @@ -1,233 +0,0 @@ - - - Tangem Wallet - 3 cards - 2 cards - Shipping - Free - I have a promo code… - Total - Other payment methods - Buy now - Meet\nTangem - Buy - Store - Send - Pay - Exchange - Lend - Borrow - Revolutionary Hardware Wallet - Store your crypto assets secure while keeping private keys contained in your card - Ultra Secure Backup - Up to - 3 physical cards - to one wallet - Thousands of Currencies - A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card - DeFi Compatible - Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services - The Wallet for Everyone - Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. - Order - Search tokens - You are currently running in Demo mode. All funds are not real. - This feature is disabled in Demo mode - Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. - Available networks - %s network not found. Please, add it first and try again. - Attention - Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. - Tokens in Solana network are not supported by this card due to firmware limitation. - Contract address copied! - Contract address - Required field - Please select the network - Decimal number must be a valid integer, no higher than %d - Contract address is invalid - Derivation path is invalid - Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. - This token/network has already been added to your list - Decimals - Network - Not selected - E.g. USD Coin - Name - E.g. USDC - Token symbol - BIP44 coin type - Default - The server is not available, please try again later - Total balance - The amount does not include some of your funds - Tokens - Manage tokens - No rate - Network is unreachable - Hide token - Hide %s - Hide - You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. - Unable to hide %s - The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. - Your wallet has not been backed up - To protect your assets, we advise you to carry out this procedure - Riprova - Chat - %s network - I understand - Yes - No - Russian bank cards are not accepted at the moment - Do you have a bank card of another country or a UnionPay card? - This network is not supported. Please select another network. - Card Settings - Security Mode - Change access code - Access code will be changed on this card only - Continue - Tangem Bot - Get your card ready! - Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. - Scan Card - App Settings - Keep the wallet in the app - Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. - Save Access Code - Biometric authentication will be requested instead of the access code for interactions with your card. - Removing the saved card deletes all the saved wallets and their access codes. - This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. - WalletConnect - Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - I understand that after performing this action, I will no longer have access to the current wallet - Reset the card - Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - Select network - Scan your card - To access all the networks you need to scan the card - Connection with this Dapp cannot be established due to its technical implementation. - Welcome back! - Use %s or scan a card to access the app - Log in with %s - Scansiona carta - Save your Wallet - Would you like to use %s? - Would you like to use biometrics? - Access the app - Log into the app and check your balance without scanning the card - Access code - %s will be requested instead of the access code for interactions with your wallet - Biometrics will be requested instead of the access code for interactions with your wallet - Note that making a transaction with your funds will still require your card - Allow to use %s - Allow to use biometrics - New feature - My Wallets - Multi-currency - Single-currency - Add new wallet - Rename Wallet - Wallet name - Unlock all with %s - Invalid Tag. It won\'t be added to the transaction. - Invalid Memo. It won\'t be added to the transaction. - Tag - Memo - Attention - Tap the card with the visa logo - No funds for activation - Please contact support - Four identical digits isn\'t safe - Such a PIN can be brute-forced easily - Pin code - Connect - KYC - Verify your identity - Set PIN code - Register - Verify via Utorg - Refresh - Connect your card - Verify your identity - KYC is in progress - Connect your card to the decentralized payment system - To start using your card you have to pass the KYC process - Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. - PIN Code - Set PIN code for your SaltPay card - Chat with support - Please hold the card until the operation complete - To start the backup process you have to add Tangem card as your backup - No backup card - Backup card ready - Finalize the backup process by creating an access code - Prepare the SaltPay card - Tap the SaltPay card - Tap the Tangem card - Support - Claim %s - To get started, simply claim wxDAI to your wallet - Claim - Congratulations! Your first payment crypto card has been activated - Claiming - It will take a few seconds - Something went wrong - Please check you email for further instructions - Do you want to exit the activation process? - In this case, you will need to start from the beginning. - You have used a card from another wallet. Tap the card associated with this wallet - Referral program - Refer your friends to Tangem - You - Will get - for each wallet bought by your friend on your %s network address%s - Your friend - Will get a - %s discount - when buying a card on tangem.com - Your friends bought - Your personal code - Participate - terms and conditions - By tapping this button you accept - You\'ve accepted - of the referral program - Internal error: wallet manager not found - Saldo: %s - Share - Copy - Con successo - Give Permission - To continue you need to allow 1inch smart contracts to use your %s - Amount %s - Your Wallet - Spender - Approve - Swap of %s to - Swap - Insufficient funds - Give Permission - Permit and Swap - Failed to load the information about the referral program. Please try again later. - Failed to load the information about the referral program. Reason: %s. Please try again later. - Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. - Personal code copied! - Buy Tangem Wallet with discount!\n%s - %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. - Indirizzo non valido - This wallet has already been saved, you can add another one - Rimuovere - Privacy policy - Enable biometric authorization - It looks like you have biometric authentication disabled, it is necessary to save wallets - Enable - Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. - %d selected - biometric authentication - biometrics - Enable biometric authentication - Go to settings to enable biometric authentication in the Tangem App - Scan the card - diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index d015da467f..ec5857499d 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -56,7 +56,7 @@ Например, USDC Символ токена Деривация по BIP44 - По умолчанию + По-умолчанию Сервер недоступен, повторите попытку позднее Баланс В сумме учтены не все монеты diff --git a/core/res/src/main/res/values-ru/strings_final.xml b/core/res/src/main/res/values-ru/strings_final.xml deleted file mode 100644 index c56fea1dcc..0000000000 --- a/core/res/src/main/res/values-ru/strings_final.xml +++ /dev/null @@ -1,233 +0,0 @@ - - - Tangem Wallet - 3 карты - 2 карты - Доставка - Бесплатно - У меня есть промо-код… - Итого - Другие способы оплаты - Купить сейчас - Встречайте\nTangem - Покупайте - Храните - Отправляйте - Расплачивайтесь - Обменивайте - Вкладывайте - Занимайте - Революционный аппаратный кошелек - Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. - Все ключи в безопасности - До - трех карт - с одним кошельком - Тысячи криптовалют - Аппаратный кошелек для ваших биткоинов, эфира и многих других валют одновременно — все в одной карте - Поддержка DeFi - Обменивайте, покупайте NFT, получайте займы и делайте вклады в более чем 100 различных децентрализованных сервисах - Кошелек для каждого - Используйте его на ходу, в любом месте, в любое время. Без проводов и батареек. Как только понадобится крипта, просто приложите карту к телефону. - Купить - Поиск токенов - Приложение работает в демонстрационном режиме. Средства на всех счетах ненастоящие. - Эта функция недоступна в демонстрационном режиме - Недостаточно средств для комиссии на вашем %s кошельке для отправки транзакции. Сначала пополните свой %s кошелек. - Доступные сети - Сеть %s не найдена. Пожалуйста, добавьте её и попробуйте заново. - Внимание - Внимание! Валюты на разных сетях имеют разные адреса. Убедитесь, что адрес соответствует сети, в которой вы отправляете средства. - Токены в сети Solana не поддерживаются этой картой из-за ограничений прошивки. - Адрес контракта скопирован! - Адрес контракта - Обязательное поле - Пожалуйста, выберите сеть - Количество знаков после запятой должно быть корректным числом не больше %d - Адрес контракта некорректен - Путь деривации некорректен - Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить - Этот токен/сеть уже находится в вашем списке - Знаков после запятой - Сеть - Не выбрано - Например, USD Coin - Название токена - Например, USDC - Символ токена - Деривация по BIP44 - По-умолчанию - Сервер недоступен, повторите попытку позднее - Баланс - В сумме учтены не все монеты - Токены - Управление токенами - Нет цены - Сеть недоступна - Скрыть токен - Скрыть %s - Скрыть - Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. - Невозможно скрыть %s - Токен %s является основной валютой в сети %s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети. - Бэкап кошелька не был произведен - Чтобы защитить свои активы, мы советуем вам выполнить эту процедуру - Повторить - Чат - Сеть %s - Я понял - Да - Нет - Карты банков РФ в данный момент не принимаются - У вас есть карта банка другой страны или платежной системы UnionPay? - Сеть не поддерживается. Пожалуйста, выберите другую сеть. - Настройки карты - Тип безопасности - Смена кода доступа - Код доступа будет изменен только на данной карте - Продолжить - Tangem Bot - Приготовьте свою карту - Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку. - Сканировать - Настройки приложения - Cохранение кошелька - Подключите функцию привязки карты в приложении, а также возможность биометрической аутентификации. Подпись транзакции все так же потребует карту. - Сохранение кода доступа - Подключите функцию хранения кодов доступа от карт на телефоне в зашифрованном виде, и при работе с картой вместо кода доступа будет запрашиваться биометрическая аутентификация. - При отключении функции сохранения кошелька все ранее сохраненные кошельки будут удалены. - Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком. - WalletConnect - Подключение к Dapps - Это действие приведет к полному удалению кошелька на этой карте. Кошелек невозможно будет восстановить или использовать данную карту для восстановления кода доступа - Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку - Сбросить карту - Сброс к заводским настройкам - Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать эту карту для восстановления кода доступа. - Выберите сеть - Отсканируйте карту - Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту - Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации. - C возвращением! - Используйте %s или код доступа для входа в приложение - Войти с %s - Сканировать карту - Сохраните ваш кошелек - Вы хотите использовать %s? - Вы хотите использовать биометрию? - Доступ в приложение - Войдите в приложение и следите за своим балансом без сканирования карты - Код доступа - Для операций с вашим кошельком будет запрашиваться %s вместо кода доступа карты - Для операций с вашим кошельком будет запрашиваться биометрия вместо кода доступа карты - Обратите внимание, что для совершения транзакции с вашими средствами по-прежнему потребуется ваша карта - Использовать %s - Использовать биометрию - Новый функционал - Мои кошельки - Мультивалютные - Одновалютные - Добавить новый кошелек - Переименование кошелька - Имя кошелька - Разблокировать все с %s - Недопустимый Tag. Он не будет добавлен в транзакцию. - Недопустимый Memo. Он не будет добавлен в транзакцию. - Tag - Memo - Внимание - Приложите карту с логотипом Visa - Недостаточно средств для активации - Пожалуйста обратитесь в службу поддержки - Ввод одинаковых цифр является не безопасным - Данный Код доступа может быть легко взломан - Код доступа - Подключиться - Верификация клиента - Подтвердите свою личность - Установить Код доступа - Зарегистрироваться - Верифицировать (Utorg) - Обновить - Подключите свою карту - Подтвердите свою личность - Подтверждение личности в процессе - Подключите вашу карту к децентрализованной платежной системе - Для начала работы с картой вам необходимо завершить процесс подтверждения личности - Пожалуйста дождитесь завершения процесса подтверждения личности. Вы будете уведомлены через e-mail. Обычно это занимает не более часа. Вы можете закрыть приложение и вернуться позже. - Код доступа - Установите Код доступа для вашей SaltPay карты - Чат поддержки - Пожалуйста, удерживайте карту до завершения операции - Для начала процесса бэкапа вам необходимо добавить Tangem карту - Бэкап карта не добавлена - Бэкап карта создана - Завершите процесс бэкапа создав код доступа - Приготовьте SaltPay карту - Приложите SaltPay карту - Приложите Tangem карту - Чат - Запросить %s - Для начала работы просто запросите начисление wxDai на свой кошелек - Запросить - Поздравляем! Ваша платежная крипто карта теперь активирована! - Запрашивается - Это займет несколько секунд - Что-то пошло не так - Более подробная информация отправлена на ваш адрес электронной почты. - Вы хотите выйти из процесса активации? - В этом случае вам будет необходимо начать процесс заново. - Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком. - Реферальная программа - Приведи друга в Tangem - Вы - Получите - на ваш адрес в сети %s%s за каждый кошелек, который купит ваш друг - Ваш друг - Получит - %s скидку - при покупке карточки на сайте tangem.com - Ваши друзья купили - Ваш персональный код - Участвовать - условия участия - Нажимая на эту кнопку вы принимаете - Вы приняли - в реферальной программе - Внутренняя ошибка: не удается найти менеджер кошельков - Баланс: %s - Поделиться - Копировать - Успешно - Дать разрешение - Чтобы продолжить, вам нужно разрешить смарт-контракту 1inch использовать ваш %s - Количество %s - Ваш кошелек - Отправитель - Подтвердить - Обмен %s на - Обмен - Недостаточно средств - Дать разрешение - Разрешить и обменять - Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. - Не удалось загрузить информацию по реферальной программе. Причина: %s. Пожалуйста, попробуйте позже. - Не удалось обработать вашу заявку на участие. Причина: %s. Пожалуйста, попробуйте позже. Если проблема сохранится, вы можете обратиться в техподдержку. - Персональный код скопирован! - Купи Tangem Wallet со скидкой!\n%s - Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s, он будет деактивирован, а все оставшиеся средства будут уничтожены. - Неверный адрес - Этот кошелек уже был сохранен, вы можете добавить другой - Удалить - Privacy policy - Включите биометрическую аутентификацию - Похоже, что у вас отключена биометрическая аутентификация, она необходима для сохранения кошельков - Включить - Функция сохранения кошелька позволяет вам использовать свой кошелек с биометрической аутентификацией, не прикладывая карту к телефону для получения доступа. - %d выбрано - биометрическую аутентификацию - биометрией - Включите биометрическую аутентификацию - Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem App - Отсканируйте карту - diff --git a/core/res/src/main/res/values/strings_final.xml b/core/res/src/main/res/values/strings_final.xml deleted file mode 100644 index 673ced5d90..0000000000 --- a/core/res/src/main/res/values/strings_final.xml +++ /dev/null @@ -1,233 +0,0 @@ - - - Tangem Wallet - 3 cards - 2 cards - Shipping - Free - I have a promo code… - Total - Other payment methods - Buy now - Meet\nTangem - Buy - Store - Send - Pay - Exchange - Lend - Borrow - Revolutionary Hardware Wallet - Store your crypto assets secure while keeping private keys contained in your card - Ultra Secure Backup - Up to - 3 physical cards - to one wallet - Thousands of Currencies - A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card - DeFi Compatible - Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services - The Wallet for Everyone - Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. - Order - Search tokens - You are currently running in Demo mode. All funds are not real. - This feature is disabled in Demo mode - Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. - Available networks - %s network not found. Please, add it first and try again. - Attention - Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. - Tokens in Solana network are not supported by this card due to firmware limitation. - Contract address copied! - Contract address - Required field - Please select the network - Decimal number must be a valid integer, no higher than %d - Contract address is invalid - Derivation path is invalid - Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. - This token/network has already been added to your list - Decimals - Network - Not selected - E.g. USD Coin - Name - E.g. USDC - Token symbol - BIP44 coin type - Default - The server is not available, please try again later - Total balance - The amount does not include some of your funds - Tokens - Manage tokens - No rate - Network is unreachable - Hide token - Hide %s - Hide - You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. - Unable to hide %s - The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. - Your wallet has not been backed up - To protect your assets, we advise you to carry out this procedure - Retry - Chat - %s network - I understand - Yes - No - Russian bank cards are not accepted at the moment - Do you have a bank card of another country or a UnionPay card? - This network is not supported. Please select another network. - Card Settings - Security Mode - Change Access Code - Access code will be changed on this card only - Continue - Tangem Bot - Get your card ready! - Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. - Scan Card - App Settings - Keep the wallet in the app - Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. - Save Access Code - Biometric authentication will be requested instead of the access code for interactions with your card. - Removing the saved card deletes all the saved wallets and their access codes. - This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. - WalletConnect - Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - I understand that after performing this action, I will no longer have access to the current wallet - Reset the Card - Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - Select network - Scan your card - To access all the networks you need to scan the card - Connection with this Dapp cannot be established due to its technical implementation. - Welcome back! - Use %s or scan a card to access the app - Log in with %s - Scan card - Save your wallet - Would you like to use %s? - Would you like to use biometrics? - Access the app - Log into the app and check your balance without scanning the card - Access code - %s will be requested instead of the access code for interactions with your wallet - Biometrics will be requested instead of the access code for interactions with your wallet - Note that making a transaction with your funds will still require your card - Allow to use %s - Allow to use biometrics - New feature - My Wallets - Multi-currency - Single-currency - Add new wallet - Rename Wallet - Wallet name - Unlock all with %s - Invalid Tag. It won\'t be added to the transaction. - Invalid Memo. It won\'t be added to the transaction. - Tag - Memo - Attention - Tap the card with the visa logo - No funds for activation - Please contact support - Four identical digits isn\'t safe - Such a PIN can be brute-forced easily - Pin code - Connect - KYC - Verify your identity - Set PIN code - Register - Verify via Utorg - Refresh - Connect your card - Verify your identity - KYC is in progress - Connect your card to the decentralized payment system - To start using your card you have to pass the KYC process - Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. - PIN Code - Set PIN code for your SaltPay card - Chat with support - Please hold the card until the operation complete - To start the backup process you have to add Tangem card as your backup - No backup card - Backup card ready - Finalize the backup process by creating an access code - Prepare the SaltPay card - Tap the SaltPay card - Tap the Tangem card - Support - Claim %s - To get started, simply claim wxDAI to your wallet - Claim - Congratulations! Your first payment crypto card has been activated - Claiming - It will take a few seconds - Something went wrong - Please check your email for further instructions - Do you want to exit the activation process? - In this case, you will need to start from the beginning. - You have used a card from another wallet. Tap the card associated with this wallet - Referral program - Refer your friends to Tangem - You - Will get - for each wallet bought by your friend on your %s network address%s - Your friend - Will get a - %s discount - when buying a card on tangem.com - Your friends bought - Your personal code - Participate - terms and conditions - By tapping this button you accept - You\'ve accepted - of the referral program - Internal error: wallet manager not found - Balance: %s - Share - Copy - Success - Give Permission - To continue you need to allow 1inch smart contracts to use your %s - Amount %s - Your Wallet - Spender - Approve - Swap of %s to - Swap - Insufficient funds - Give Permission - Permit and Swap - Failed to load the information about the referral program. Please try again later. - Failed to load the information about the referral program. Reason: %s. Please try again later. - Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. - Personal code copied! - Buy Tangem Wallet with discount!\n%s - %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. - Invalid address - This wallet has already been saved, you can add another one - Delete - Privacy policy - Enable biometric authorization - It looks like you have biometric authentication disabled, it is necessary to save wallets - Enable - Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. - %d selected - biometric authentication - biometrics - Enable biometric authentication - Go to settings to enable biometric authentication in the Tangem App - Scan the card - From f292e469bbf92ea6cff246299cdd34dec0d2551a Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 20 Dec 2022 11:37:56 +0300 Subject: [PATCH 057/145] Updated on 2026-08-14 --- .../details/ui/cardsettings/CardSettingsViewModel.kt | 1 + .../tap/features/details/ui/resetcard/ResetCardScreen.kt | 1 + .../features/details/ui/resetcard/ResetCardScreenState.kt | 1 + .../tap/features/details/ui/resetcard/ResetCardViewModel.kt | 5 +++++ 4 files changed, 8 insertions(+) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt index fd0ad8a3db..367ea44ac5 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt @@ -8,6 +8,7 @@ import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.details.redux.CardSettingsState import com.tangem.tap.features.details.redux.DetailsAction +import com.tangem.wallet.R import org.rekotlin.Store class CardSettingsViewModel(private val store: Store) { diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt index 05a69049a9..ca136e131a 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt @@ -37,6 +37,7 @@ fun ResetCardScreen(state: ResetCardScreenState, onBackPressed: () -> Unit) { SettingsScreensScaffold( content = { ResetCardView(state = state) }, onBackClick = onBackPressed, + backgroundColor = Color.Transparent, ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt index fee3e41a5b..1021252b07 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt @@ -3,6 +3,7 @@ package com.tangem.tap.features.details.ui.resetcard import com.tangem.tap.features.details.ui.cardsettings.TextReference data class ResetCardScreenState( + @StringRes val descriptionResId: Int, val accepted: Boolean = false, val descriptionText: TextReference, val onAcceptWarningToggleClick: (Boolean) -> Unit, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt index 9ba899bc7d..5dbcba166a 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt @@ -15,6 +15,11 @@ class ResetCardViewModel(private val store: Store) { ?: TextReference.Str(value = "") return ResetCardScreenState( + descriptionResId = if (state?.card?.backupStatus?.isActive == true) { + R.string.reset_card_with_backup_to_factory_message + } else { + R.string.reset_card_without_backup_to_factory_message + }, accepted = state?.resetConfirmed ?: false, descriptionText = descriptionText, onAcceptWarningToggleClick = { store.dispatch(DetailsAction.ResetToFactory.Confirm(it)) }, From 67cfbb98c7179123902c1a51940f85e0fdaf37fd Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 30 Dec 2022 15:28:55 +0300 Subject: [PATCH 058/145] Updated on 2026-08-14 --- .../tap/common/redux/navigation/NavigationMiddleware.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt index 871ef1ea5c..28357c1efd 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt @@ -57,11 +57,8 @@ val navigationMiddleware: Middleware = { _, state -> Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> { Settings.ACTION_BIOMETRIC_ENROLL } - Build.VERSION.SDK_INT >= Build.VERSION_CODES.P -> { - Settings.ACTION_FINGERPRINT_ENROLL - } else -> { - Settings.ACTION_SETTINGS + Settings.ACTION_SECURITY_SETTINGS } } val intent = Intent(settingsAction).apply { From 367d92f87ba67a9e0532bd08ff3b245432442e18 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 30 Dec 2022 16:29:09 +0400 Subject: [PATCH 059/145] Updated on 2026-08-14 --- .../tangem/tap/features/wallet/ui/WalletFragment.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 8a67196de1..f01805dd39 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 @@ -1,5 +1,6 @@ package com.tangem.tap.features.wallet.ui +import android.content.Context import android.os.Bundle import android.view.Menu import android.view.MenuInflater @@ -9,6 +10,7 @@ import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.transition.TransitionInflater @@ -60,6 +62,13 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber() + override fun onAttach(context: Context) { + super.onAttach(context) + activity?.lifecycleScope?.launchWhenCreated { + viewModel.launch() + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) @@ -82,7 +91,6 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber Date: Mon, 9 Jan 2023 18:08:31 +0300 Subject: [PATCH 060/145] Updated on 2026-08-14 --- .../tap/domain/tokens/UserTokensRepository.kt | 13 +++--------- .../DefaultWalletAmountsRepository.kt | 20 ------------------- .../details/ui/resetcard/ResetCardScreen.kt | 1 + .../ui/resetcard/ResetCardScreenState.kt | 1 - .../ui/resetcard/ResetCardViewModel.kt | 5 ----- 5 files changed, 4 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index 88b71c052e..2fe9306d6f 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -29,7 +29,7 @@ class UserTokensRepository( // TODO("After adding DI") replace with CoroutineDispatcherProvider suspend fun getUserTokens(card: CardDTO): List = withContext(dispatchers.io) { - val userId = getUserWalletId(card) ?: return emptyList() + val userId = getUserWalletId(card) ?: return@withContext emptyList() if (DemoHelper.isDemoCardId(card.cardId)) { return@withContext loadTokensOffline(card, userId).ifEmpty(::loadDemoCurrencies) } @@ -55,21 +55,14 @@ class UserTokensRepository( // TODO("After adding DI") replace with CoroutineDispatcherProvider suspend fun saveUserTokens(card: CardDTO, tokens: List) = withContext(dispatchers.io) { - val userId = getUserWalletId(card) ?: return + val userId = getUserWalletId(card) ?: return@withContext val userTokens = tokens.toUserTokensResponse() tangemTechApi.saveUserTokens(userId, userTokens) storageService.saveUserTokens(userId, userTokens) } - suspend fun removeUserTokens(card: CardDTO) { - val userId = getUserWalletId(card) ?: return - val userTokens = emptyList().toUserTokensResponse() - networkService.saveUserTokens(userId, userTokens) - storageService.saveUserTokens(userId, userTokens) - } - suspend fun loadBlockchainsToDerive(card: CardDTO): List = withContext(dispatchers.io) { - val userId = getUserWalletId(card) ?: return emptyList() + val userId = getUserWalletId(card) ?: return@withContext emptyList() val blockchainNetworks = loadTokensOffline(card = card, userId = userId).toBlockchainNetworks() if (DemoHelper.isDemoCardId(card.cardId)) { diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt index 9496866746..a9cb80f66e 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt @@ -143,26 +143,6 @@ internal class DefaultWalletAmountsRepository( } } - private suspend fun fetchAmountsForUserWallet( - userWallet: UserWallet, - ): CompletionResult = withContext(Dispatchers.Default) { - val walletId = userWallet.walletId - val scanResponse = userWallet.scanResponse - val walletStores = walletStoresStorage.getAll() - .first() - .getOrElse(walletId) { emptyList() } - - walletStores.map { walletStore -> - async { - // TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository] - val walletManager = walletStore.walletManager - fetchAmountsForWalletStore(walletId, scanResponse, walletStore, walletManager) - } - } - .awaitAll() - .fold() - } - private suspend fun fetchAmountsForUserWallets( userWallets: List, ): CompletionResult = withContext(Dispatchers.Default) { diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt index ca136e131a..02095d290a 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt @@ -20,6 +20,7 @@ import androidx.compose.material.IconToggleButton import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt index 1021252b07..fee3e41a5b 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt @@ -3,7 +3,6 @@ package com.tangem.tap.features.details.ui.resetcard import com.tangem.tap.features.details.ui.cardsettings.TextReference data class ResetCardScreenState( - @StringRes val descriptionResId: Int, val accepted: Boolean = false, val descriptionText: TextReference, val onAcceptWarningToggleClick: (Boolean) -> Unit, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt index 5dbcba166a..9ba899bc7d 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt @@ -15,11 +15,6 @@ class ResetCardViewModel(private val store: Store) { ?: TextReference.Str(value = "") return ResetCardScreenState( - descriptionResId = if (state?.card?.backupStatus?.isActive == true) { - R.string.reset_card_with_backup_to_factory_message - } else { - R.string.reset_card_without_backup_to_factory_message - }, accepted = state?.resetConfirmed ?: false, descriptionText = descriptionText, onAcceptWarningToggleClick = { store.dispatch(DetailsAction.ResetToFactory.Confirm(it)) }, From 6faf946814b56c255859e9299d59f44d49f6a829 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 10 Jan 2023 11:40:48 +0400 Subject: [PATCH 061/145] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 2 -- core/res/src/main/res/values-fr/strings.xml | 2 -- core/res/src/main/res/values-it/strings.xml | 2 -- core/res/src/main/res/values-ru/strings.xml | 8 +++----- core/res/src/main/res/values/strings.xml | 8 +++----- 5 files changed, 6 insertions(+), 16 deletions(-) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 80ac6ebe7b..0736fbbc65 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -1,8 +1,6 @@ Tangem Wallet - 3 cards - 2 cards Shipping Free I have a promo code… diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index e657f386a8..54f1eaf20d 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -1,8 +1,6 @@ Tangem Wallet - 3 cards - 2 cards Shipping Free I have a promo code… diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml index 698174469b..185618e41e 100644 --- a/core/res/src/main/res/values-it/strings.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -1,8 +1,6 @@ Tangem Wallet - 3 cards - 2 cards Shipping Free I have a promo code… diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index ec5857499d..4e96b15a4c 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -1,8 +1,6 @@ Tangem Wallet - 3 карты - 2 карты Доставка Бесплатно У меня есть промо-код… @@ -56,7 +54,7 @@ Например, USDC Символ токена Деривация по BIP44 - По-умолчанию + По умолчанию Сервер недоступен, повторите попытку позднее Баланс В сумме учтены не все монеты @@ -208,8 +206,8 @@ Ваши токены Другие токены Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. - Не удалось загрузить информацию по реферальной программе. Причина: %s. Пожалуйста, попробуйте позже. - Не удалось обработать вашу заявку на участие. Причина: %s. Пожалуйста, попробуйте позже. Если проблема сохранится, вы можете обратиться в техподдержку. + Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже. + Не удалось обработать вашу заявку на участие. Код ошибки: %s. Пожалуйста, попробуйте позже. Если проблема сохранится, вы можете обратиться в техподдержку. Персональный код скопирован! Купи Tangem Wallet со скидкой!\n%s Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s, он будет деактивирован, а все оставшиеся средства будут уничтожены. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 1c84b9004d..42bbac57a2 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1,8 +1,6 @@ Tangem Wallet - 3 cards - 2 cards Shipping Free I have a promo code… @@ -33,7 +31,7 @@ Search tokens You are currently running in Demo mode. All funds are not real. This feature is disabled in Demo mode - Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. + Not enough funds for fee in your %s wallet to send a transaction. Top up your %s wallet first. Available networks %s network not found. Please, add it first and try again. Attention @@ -208,8 +206,8 @@ Your tokens Other tokens Failed to load the information about the referral program. Please try again later. - Failed to load the information about the referral program. Reason: %s. Please try again later. - Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Failed to load the information about the referral program. Error code: %s. Please try again later. + Your participation request could not be processed. Error code: %s. Please try again later. If the problem persists — feel free to contact our support. Personal code copied! Buy Tangem Wallet with discount!\n%s %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. From 1675b44cd33a3887f3fee29cb5a47711e240da25 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 10 Jan 2023 12:29:20 +0400 Subject: [PATCH 062/145] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 2 ++ core/res/src/main/res/values-fr/strings.xml | 2 ++ core/res/src/main/res/values-it/strings.xml | 2 ++ core/res/src/main/res/values-ru/strings.xml | 2 ++ core/res/src/main/res/values/strings.xml | 2 ++ 5 files changed, 10 insertions(+) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 0736fbbc65..80ac6ebe7b 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -1,6 +1,8 @@ Tangem Wallet + 3 cards + 2 cards Shipping Free I have a promo code… diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 54f1eaf20d..e657f386a8 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -1,6 +1,8 @@ Tangem Wallet + 3 cards + 2 cards Shipping Free I have a promo code… diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml index 185618e41e..698174469b 100644 --- a/core/res/src/main/res/values-it/strings.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -1,6 +1,8 @@ Tangem Wallet + 3 cards + 2 cards Shipping Free I have a promo code… diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 4e96b15a4c..f3c13f8297 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -1,6 +1,8 @@ Tangem Wallet + 3 карты + 2 карты Доставка Бесплатно У меня есть промо-код… diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 42bbac57a2..86c0dff9b4 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1,6 +1,8 @@ Tangem Wallet + 3 cards + 2 cards Shipping Free I have a promo code… From 4cf372510109374c0365fdc937eb78dc2384ceea Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 10 Jan 2023 11:48:50 +0400 Subject: [PATCH 063/145] Updated on 2026-08-14 --- .../walletconnect/WalletConnectManager.kt | 90 +++++++++++-------- .../WalletConnectNetworkUtils.kt | 3 + 2 files changed, 54 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt index 37e488ccf8..0903eb8078 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt @@ -28,6 +28,7 @@ import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder import com.trustwallet.walletconnect.models.binance.WCBinanceTxConfirmParam import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction +import com.trustwallet.walletconnect.models.session.WCAddNetwork import com.trustwallet.walletconnect.models.session.WCSession import com.trustwallet.walletconnect.models.session.WCSessionUpdate import kotlinx.coroutines.delay @@ -103,7 +104,7 @@ class WalletConnectManager { fun updateSession(session: WalletConnectSession) { val updatedSession = sessions[session.session]?.copy( - wallet = session.wallet + wallet = session.wallet, ) if (updatedSession != null) { sessions[session.session] = updatedSession @@ -114,12 +115,11 @@ class WalletConnectManager { sessions[session.session]?.client?.updateSession( accounts = listOfNotNull(session.getAddress()), chainId = session.wallet.blockchain?.getChainId(), - approved = true + approved = true, ) - val updatedSession = sessions[session.session]?.copy( - wallet = session.wallet + wallet = session.wallet, ) if (updatedSession != null) { sessions[session.session] = updatedSession @@ -172,7 +172,7 @@ class WalletConnectManager { val accounts = listOf(blockchain.makeAddresses(key).first().value) val approved = activeData.client.approveSession( accounts = accounts, - chainId = blockchain.getChainId() ?: Blockchain.Ethereum.getChainId()!! + chainId = blockchain.getChainId() ?: Blockchain.Ethereum.getChainId()!!, ) if (approved) { val walletConnectSession = WalletConnectSession( @@ -180,23 +180,22 @@ class WalletConnectManager { remotePeerId = activeData.remotePeerId, wallet = activeData.wallet, session = session, - peerMeta = activeData.peerMeta!! + peerMeta = activeData.peerMeta!!, ) walletConnectRepository.saveSession(walletConnectSession) store.dispatchOnMain( WalletConnectAction.ApproveSession.Success( - walletConnectSession - ) + walletConnectSession, + ), ) - } } fun removeSimilarSessions(activeData: WalletConnectActiveData) { val sessionsToRemove = sessions.filter { it.value.wallet.walletPublicKey?.equals(activeData.wallet.walletPublicKey) == true - && it.value.peerMeta?.url == activeData.peerMeta?.url - && it.value.session != activeData.session + && it.value.peerMeta?.url == activeData.peerMeta?.url + && it.value.session != activeData.session } Timber.d("RemoveSimilarSessions: ${sessionsToRemove.values.map { it.client.session }}") sessionsToRemove.forEach { disconnect(it.value.session) } @@ -244,14 +243,14 @@ class WalletConnectManager { transaction = transaction, session = session, id = id, - type = type + type = type, ).guard { sessions[session.session] = activeData.copy(transactionData = null) store.dispatchOnMain( WalletConnectAction.RejectRequest( session.session, - id - ) + id, + ), ) return@launch } @@ -260,9 +259,9 @@ class WalletConnectManager { store.dispatchOnMain( GlobalAction.ShowDialog( WalletConnectDialog.RequestTransaction( - data.dialogData - ) - ) + data.dialogData, + ), + ), ) } } @@ -312,15 +311,15 @@ class WalletConnectManager { val activeData = sessions[session.session] ?: return scope.launch { val data = WalletConnectSdkHelper().prepareDataForPersonalSign( - message = message, session = session, id = id + message = message, session = session, id = id, ) sessions[session.session] = activeData.copy(personalSignData = data) store.dispatchOnMain( GlobalAction.ShowDialog( WalletConnectDialog.PersonalSign( - data.dialogData - ) - ) + data.dialogData, + ), + ), ) } } @@ -346,7 +345,6 @@ class WalletConnectManager { } } - fun setListeners(client: WCClient) { client.onSessionRequest = { id: Long, peer: WCPeerMeta -> Timber.d("OnSessionRequest: $peer") @@ -393,8 +391,8 @@ class WalletConnectManager { transaction = transaction, session = sessionData, id = id, - type = WcTransactionType.EthSendTransaction - ) + type = WcTransactionType.EthSendTransaction, + ), ) } } @@ -411,8 +409,8 @@ class WalletConnectManager { transaction = transaction, session = sessionData, id = id, - type = WcTransactionType.EthSignTransaction - ) + type = WcTransactionType.EthSignTransaction, + ), ) } } @@ -428,8 +426,8 @@ class WalletConnectManager { WalletConnectAction.HandlePersonalSignRequest( message, sessionData, - id - ) + id, + ), ) } } @@ -442,8 +440,8 @@ class WalletConnectManager { sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> store.dispatchOnMain( WalletConnectAction.BinanceTransaction.Trade( - id = id, order = order, sessionData = sessionData - ) + id = id, order = order, sessionData = sessionData, + ), ) } } @@ -451,8 +449,8 @@ class WalletConnectManager { sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> store.dispatchOnMain( WalletConnectAction.BinanceTransaction.Transfer( - id = id, order = order, sessionData = sessionData - ) + id = id, order = order, sessionData = sessionData, + ), ) } } @@ -467,16 +465,30 @@ class WalletConnectManager { } } client.onWalletChangeNetwork = { id: Long, chainId: Int -> - Timber.d("On WC Switch Chain") - val blockchain = Blockchain.fromChainId(chainId) - Timber.d("WC switch chainID\nNew Blockchain: $blockchain") - val session = sessions[client.session]?.toWalletConnectSession() - if (session != null) { - store.dispatchOnMain(WalletConnectAction.SwitchBlockchain(blockchain, session)) + switchChain(chainId, client) + } + client.onWalletAddNetwork = { id: Long, network: WCAddNetwork -> + // TODO + // In fact this method is used to add a EVM network. It provides RPC url and chain ID. + // Here now it just tries to switch to a EVM network with a provided chain ID. + try { + val chainId = Integer.decode(network.chainIdHex) + switchChain(chainId, client) + } catch (exception: Exception) { + Timber.d("WC add network error: chain could not be parsed") } } } + private fun switchChain(chainId: Int, client: WCClient) { + val blockchain = Blockchain.fromChainId(chainId) + Timber.d("WC switch chainID\nNew Blockchain: $blockchain") + val session = sessions[client.session]?.toWalletConnectSession() + if (session != null) { + store.dispatchOnMain(WalletConnectAction.SwitchBlockchain(blockchain, session)) + } + } + companion object { private val tangemPeerMeta = WCPeerMeta(name = "Tangem Wallet", url = "https://tangem.com") @@ -504,7 +516,7 @@ data class WalletConnectActiveData( remotePeerId = remotePeerId, wallet = wallet, session = session, - peerMeta = peerMeta + peerMeta = peerMeta, ) } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt index 6b2a457e43..d186cad24f 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt @@ -15,6 +15,9 @@ class WalletConnectNetworkUtils { peer.url.contains("pancakeswap.finance") -> { Blockchain.BSC } + peer.url.contains("optimism") -> { + Blockchain.Optimism + } chainId != null -> { Blockchain.fromChainId(chainId) } From da0318964e47b173381e3cf5c92b6e85e139cc6b Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 10 Jan 2023 14:16:28 +0400 Subject: [PATCH 064/145] Updated on 2026-08-14 --- .../com/tangem/tap/domain/tokens/Currency.kt | 3 +- .../tokens/LoadAvailableCoinsService.kt | 38 +++++++----- .../domain/tokens/OldUserTokensRepository.kt | 31 ++++++---- .../tap/domain/tokens/UserTokensRepository.kt | 8 ++- .../tokens/converters/CurrencyConverter.kt | 8 +-- .../features/tokens/redux/TokensMiddleware.kt | 6 +- .../tap/features/wallet/models/Currency.kt | 4 +- .../middlewares/AppCurrencyMiddleware.kt | 8 +-- .../persistence/FiatCurrenciesPrefStorage.kt | 8 +-- .../datasource/api/tangemTech/Responses.kt | 35 ----------- .../api/tangemTech/TangemTechApi.kt | 6 +- .../api/tangemTech/TangemTechService.kt | 24 ------- .../api/tangemTech/models/CoinsResponse.kt | 27 ++++++++ .../tangemTech/models/CurrenciesResponse.kt | 21 ++++--- .../api/tangemTech/models/RateResponse.kt | 8 +++ .../tangemTech/models/UserTokensResponse.kt | 23 +++---- domain/build.gradle.kts | 1 + .../java/com/tangem/domain/DomainDialog.kt | 4 +- .../common/extensions/TangemTechService.kt | 29 --------- .../addCustomToken/AddCustomTokenService.kt | 36 ++++++----- .../redux/AddCustomTokenAction.kt | 2 +- .../addCustomToken/redux/AddCustomTokenHub.kt | 62 +++++++++---------- .../redux/AddCustomTokenState.kt | 2 +- .../tangem/feature/swap/SwapRepositoryImpl.kt | 2 +- .../swap/converters/TokensConverter.kt | 2 +- 25 files changed, 186 insertions(+), 212 deletions(-) delete mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CoinsResponse.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/RateResponse.kt delete mode 100644 domain/src/main/java/com/tangem/domain/common/extensions/TangemTechService.kt 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 0e18c4018d..c70127f7e2 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 @@ -2,8 +2,8 @@ package com.tangem.tap.domain.tokens import com.squareup.moshi.JsonClass import com.tangem.blockchain.common.Blockchain +import com.tangem.datasource.api.tangemTech.models.CoinsResponse import com.tangem.domain.common.extensions.fromNetworkId -import com.tangem.datasource.api.tangemTech.CoinsResponse @JsonClass(generateAdapter = true) data class CurrencyFromJson( @@ -26,7 +26,6 @@ data class CurrenciesFromJson( val coins: List ) - fun List.toContracts(): List { return mapNotNull { Contract.fromJsonObject(it) } } diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt index fbbaad9022..072992eeb8 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt @@ -4,15 +4,17 @@ import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi import com.tangem.blockchain.common.Blockchain import com.tangem.common.services.Result -import com.tangem.domain.common.extensions.getListOfCoins -import com.tangem.domain.common.extensions.toNetworkId -import com.tangem.datasource.api.tangemTech.CoinsResponse -import com.tangem.datasource.api.tangemTech.TangemTechService import com.tangem.datasource.api.common.MoshiConverter +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.api.tangemTech.models.CoinsResponse +import com.tangem.domain.common.extensions.toNetworkId import com.tangem.tap.common.AssetReader +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext class LoadAvailableCoinsService( - private val networkService: TangemTechService, + private val tangemTechApi: TangemTechApi, + private val dispatchers: CoroutineDispatcherProvider, private val assetReader: AssetReader, ) { private val moshi: Moshi by lazy { MoshiConverter.defaultMoshi() } @@ -55,16 +57,23 @@ class LoadAvailableCoinsService( private suspend fun loadCoins( supportedBlockchains: List, offset: Int, - searchInput: String? = null + searchInput: String? = null, ): Result { - val networkIds = supportedBlockchains.toSet().map { it.toNetworkId() } - return networkService.getListOfCoins( - networkIds = networkIds, - active = true, - offset = offset, - limit = LOAD_PER_PAGE, - searchText = searchInput, - ) + return withContext(dispatchers.io) { + runCatching { + tangemTechApi.getCoins( + networkIds = supportedBlockchains.toSet().map(Blockchain::toNetworkId).joinToString(","), + active = true, + searchText = searchInput, + offset = offset, + limit = LOAD_PER_PAGE, + ) + } + .onSuccess { return@withContext Result.Success(it) } + .onFailure { return@withContext Result.Failure(it) } + + throw IllegalStateException("Unreachable code because runCatching must return result") + } } fun getTestnetCoins(): List { @@ -88,7 +97,6 @@ class LoadAvailableCoinsService( } } - data class LoadedCoins( val currencies: List, val moreAvailable: Boolean, diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt index 2aa1dce1a7..4db0f6fb33 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt @@ -4,21 +4,21 @@ import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Types import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationStyle -import com.tangem.common.services.Result -import com.tangem.domain.common.extensions.getTokens -import com.tangem.domain.common.extensions.toNetworkId -import com.tangem.datasource.api.tangemTech.TangemTechService import com.tangem.datasource.api.common.MoshiConverter +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.domain.common.extensions.toNetworkId import com.tangem.tap.common.FileReader import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.domain.tokens.models.TokenDao +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.async -import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.withContext @Deprecated("Use this only for migration") class OldUserTokensRepository( private val fileReader: FileReader, - private val tangemNetworkService: TangemTechService, + private val tangemTechApi: TangemTechApi, + private val dispatchers: CoroutineDispatcherProvider, ) { private val moshi = MoshiConverter.defaultMoshi() private val blockchainsAdapter: JsonAdapter> = moshi.adapter( @@ -104,20 +104,27 @@ class OldUserTokensRepository( return blockchainNetworks } - private suspend fun getTokensIds(tokens: List): Map = coroutineScope { + private suspend fun getTokensIds(tokens: List): Map = withContext(dispatchers.io) { tokens.map { async { - tangemNetworkService.getTokens( + tangemTechApi.getCoins( contractAddress = it.contractAddress, - networkId = it.blockchainDao.toBlockchain().toNetworkId(), + networkIds = it.blockchainDao.toBlockchain().toNetworkId(), active = true, ) } - }.map { it.await() } - .map { (it as? Result.Success)?.data?.coins?.firstOrNull()?.id } + } + .map { + runCatching { it.await() } + .onSuccess { return@map it.coins.firstOrNull()?.id } + .onFailure { return@map null } + + throw IllegalStateException("Unreachable code because runCatching must return result") + } .mapIndexedNotNull { index, id -> if (id == null) null else tokens[index].contractAddress to id - }.toMap() + } + .toMap() } companion object { diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index 2fe9306d6f..b8b585e1ee 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -16,7 +16,6 @@ import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.toBlockchainNetworks import com.tangem.tap.features.wallet.models.toCurrencies import com.tangem.tap.network.NetworkConnectivity -import com.tangem.tap.store import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext @@ -120,9 +119,12 @@ class UserTokensRepository( // TODO("After adding DI") get dependencies by DI fun init(context: Context, tangemTechService: TangemTechService): UserTokensRepository { val fileReader = AndroidFileReader(context) + val dispatchers = AppCoroutineDispatcherProvider() + val oldUserTokensRepository = OldUserTokensRepository( fileReader = fileReader, - tangemNetworkService = store.state.domainNetworks.tangemTechService, + tangemTechApi = tangemTechService.api, + dispatchers = dispatchers, ) val storageService = UserTokensStorageService( oldUserTokensRepository = oldUserTokensRepository, @@ -132,7 +134,7 @@ class UserTokensRepository( return UserTokensRepository( storageService = storageService, tangemTechApi = tangemTechService.api, - dispatchers = AppCoroutineDispatcherProvider(), + dispatchers = dispatchers, ) } } diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/converters/CurrencyConverter.kt b/app/src/main/java/com/tangem/tap/domain/tokens/converters/CurrencyConverter.kt index 48f55ed575..3ac6babc72 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/converters/CurrencyConverter.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/converters/CurrencyConverter.kt @@ -1,14 +1,14 @@ package com.tangem.tap.domain.tokens.converters -import com.tangem.datasource.api.tangemTech.models.TokenBody +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.domain.common.extensions.toNetworkId import com.tangem.tap.features.wallet.models.Currency import com.tangem.utils.converter.Converter -/** Converter from domain model [Currency] to data model [TokenBody] */ -object CurrencyConverter : Converter { +/** Converter from domain model [Currency] to data model [UserTokensResponse.Token] */ +object CurrencyConverter : Converter { - override fun convert(value: Currency) = TokenBody( + override fun convert(value: Currency) = UserTokensResponse.Token( id = value.coinId, networkId = value.blockchain.toNetworkId(), derivationPath = value.derivationPath, 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 af6ab1607f..0ac39553eb 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 @@ -40,6 +40,7 @@ import com.tangem.tap.store import com.tangem.tap.tangemSdkManager import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletCurrenciesManager +import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Middleware @@ -83,8 +84,9 @@ class TokensMiddleware { .filter { !it.isTestnet() } val loadCoinsService = LoadAvailableCoinsService( - store.state.domainNetworks.tangemTechService, - assetReader, + tangemTechApi = store.state.domainNetworks.tangemTechService.api, + dispatchers = AppCoroutineDispatcherProvider(), + assetReader = assetReader, ) scope.launch { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt index 00ea34457c..c67c02d950 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt @@ -1,7 +1,7 @@ package com.tangem.tap.features.wallet.models import com.tangem.blockchain.common.DerivationStyle -import com.tangem.datasource.api.tangemTech.models.TokenBody +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.toCoinId import com.tangem.domain.features.addCustomToken.CustomCurrency @@ -98,7 +98,7 @@ sealed interface Currency { ) } - fun fromTokenResponse(tokenBody: TokenBody): Currency? { + fun fromTokenResponse(tokenBody: UserTokensResponse.Token): Currency? { val blockchain = com.tangem.blockchain.common.Blockchain.fromNetworkId(tokenBody.networkId) ?: return null return when { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt index 3b6375373a..45f88a8666 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt @@ -1,6 +1,6 @@ package com.tangem.tap.features.wallet.redux.middlewares -import com.tangem.datasource.api.tangemTech.models.Currency +import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.MainScreen @@ -38,7 +38,7 @@ class AppCurrencyMiddleware( store.dispatchDialogShow( WalletDialog.CurrencySelectionDialog( currenciesList = storedFiatCurrencies.mapToUiModel(), - currentAppCurrency = appCurrencyProvider.invoke() + currentAppCurrency = appCurrencyProvider.invoke(), ) ) } @@ -52,7 +52,7 @@ class AppCurrencyMiddleware( store.dispatchDialogShow( WalletDialog.CurrencySelectionDialog( currenciesList = currenciesList.mapToUiModel(), - currentAppCurrency = appCurrencyProvider.invoke() + currentAppCurrency = appCurrencyProvider.invoke(), ) ) } @@ -77,7 +77,7 @@ class AppCurrencyMiddleware( } } - private fun List.mapToUiModel(): List { + private fun List.mapToUiModel(): List { return this.map { FiatCurrency( code = it.code, diff --git a/app/src/main/java/com/tangem/tap/persistence/FiatCurrenciesPrefStorage.kt b/app/src/main/java/com/tangem/tap/persistence/FiatCurrenciesPrefStorage.kt index 1a65320a5e..a52f38f854 100644 --- a/app/src/main/java/com/tangem/tap/persistence/FiatCurrenciesPrefStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/FiatCurrenciesPrefStorage.kt @@ -3,7 +3,7 @@ package com.tangem.tap.persistence import android.content.SharedPreferences import androidx.core.content.edit import com.tangem.common.json.MoshiJsonConverter -import com.tangem.datasource.api.tangemTech.models.Currency +import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse import com.tangem.tap.common.entities.FiatCurrency /** @@ -32,14 +32,14 @@ class FiatCurrenciesPrefStorage( preferences.edit { putString(APP_CURRENCY_KEY, json) } } - fun save(currencies: List) { + fun save(currencies: List) { val json: String = converter.toJson(currencies) return preferences.edit().putString(FIAT_CURRENCIES_KEY, json).apply() } - fun restore(): List { + fun restore(): List { val json = preferences.getString(FIAT_CURRENCIES_KEY, "") - val type = converter.typedList(Currency::class.java) + val type = converter.typedList(CurrenciesResponse.Currency::class.java) if (json.isNullOrBlank()) return emptyList() return converter.fromJson(json, type) ?: emptyList() diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt deleted file mode 100644 index 64e625c180..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.tangem.datasource.api.tangemTech - -import java.math.BigDecimal - -/** -[REDACTED_AUTHOR] - */ -interface HttpResponse -sealed interface TangemTechResponse : HttpResponse - -data class CoinsResponse( - val imageHost: String?, - val coins: List, - val total: Int, -) : TangemTechResponse { - - data class Coin( - val id: String, - val name: String, - val symbol: String, - val active: Boolean, - val networks: List = listOf(), - ) : TangemTechResponse { - - data class Network( - val networkId: String, - val contractAddress: String? = null, - val decimalCount: BigDecimal? = null, - val exchangeable: Boolean? = false, - ) : TangemTechResponse - } -} - -//rates.keys = networkId's -data class RatesResponse(val rates: Map) : TangemTechResponse \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index e42f069cac..df8c21d2ea 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -1,7 +1,9 @@ package com.tangem.datasource.api.tangemTech +import com.tangem.datasource.api.tangemTech.models.CoinsResponse import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse import com.tangem.datasource.api.tangemTech.models.GeoResponse +import com.tangem.datasource.api.tangemTech.models.RatesResponse import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import retrofit2.http.Body import retrofit2.http.GET @@ -10,12 +12,14 @@ import retrofit2.http.Path import retrofit2.http.Query /** + * Interface of Tangem Tech API + * [REDACTED_AUTHOR] */ interface TangemTechApi { @GET("coins") - suspend fun coins( + suspend fun getCoins( @Query("contractAddress") contractAddress: String? = null, @Query("exchangeable") exchangeable: Boolean? = null, @Query("networkIds") networkIds: String? = null, diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt index f1d66ce518..e55103871c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt @@ -1,12 +1,8 @@ package com.tangem.datasource.api.tangemTech -import com.tangem.common.services.Result -import com.tangem.common.services.performRequest import com.tangem.datasource.api.common.AddHeaderInterceptor import com.tangem.datasource.api.common.CacheControlHttpInterceptor import com.tangem.datasource.api.common.createRetrofitInstance -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext /** [REDACTED_AUTHOR] @@ -21,26 +17,6 @@ class TangemTechService( var api: TangemTechApi = createApi() - suspend fun coins( - contractAddress: String? = null, - networkIds: String? = null, - active: Boolean? = null, - searchText: String? = null, - offset: Int? = null, - limit: Int? = null, - ): Result = withContext(Dispatchers.IO) { - performRequest { - api.coins( - contractAddress = contractAddress, - networkIds = networkIds, - active = active, - searchText = searchText, - offset = offset, - limit = limit, - ) - } - } - fun addHeaderInterceptors(interceptors: List) { headerInterceptors.removeAll(interceptors) headerInterceptors.addAll(interceptors) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CoinsResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CoinsResponse.kt new file mode 100644 index 0000000000..0a19554bf0 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CoinsResponse.kt @@ -0,0 +1,27 @@ +package com.tangem.datasource.api.tangemTech.models + +import com.squareup.moshi.Json +import java.math.BigDecimal + +data class CoinsResponse( + @Json(name = "imageHost") val imageHost: String?, + @Json(name = "coins") val coins: List, + @Json(name = "total") val total: Int, +) { + + data class Coin( + @Json(name = "id") val id: String, + @Json(name = "name") val name: String, + @Json(name = "symbol") val symbol: String, + @Json(name = "active") val active: Boolean, + @Json(name = "networks") val networks: List = listOf(), + ) { + + data class Network( + @Json(name = "networkId") val networkId: String, + @Json(name = "contractAddress") val contractAddress: String? = null, + @Json(name = "decimalCount") val decimalCount: BigDecimal? = null, + @Json(name = "exchangeable") val exchangeable: Boolean? = false, + ) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CurrenciesResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CurrenciesResponse.kt index 608b133f1a..64b69d3240 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CurrenciesResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CurrenciesResponse.kt @@ -3,14 +3,15 @@ package com.tangem.datasource.api.tangemTech.models import com.squareup.moshi.Json data class CurrenciesResponse( - @Json(name = "currencies") val currencies: List -) + @Json(name = "currencies") val currencies: List, +) { -data class Currency( - @Json(name = "id") val id: String, - @Json(name = "code") val code: String, // this is an uppercase id - @Json(name = "name") val name: String, - @Json(name = "rateBTC") val rateBTC: String, - @Json(name = "unit") val unit: String, // $, €, ₽ - @Json(name = "type") val type: String -) \ No newline at end of file + data class Currency( + @Json(name = "id") val id: String, + @Json(name = "code") val code: String, // this is an uppercase id + @Json(name = "name") val name: String, + @Json(name = "rateBTC") val rateBTC: String, + @Json(name = "unit") val unit: String, // $, €, ₽ + @Json(name = "type") val type: String, + ) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/RateResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/RateResponse.kt new file mode 100644 index 0000000000..7355984410 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/RateResponse.kt @@ -0,0 +1,8 @@ +package com.tangem.datasource.api.tangemTech.models + +import com.squareup.moshi.Json + +//rates.keys = networkId's +data class RatesResponse( + @Json(name = "rates") val rates: Map, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensResponse.kt index 22c9d2fd4e..180deb01a0 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensResponse.kt @@ -6,15 +6,16 @@ data class UserTokensResponse( @Json(name = "version") val version: Int = 0, @Json(name = "group") val group: String? = null, @Json(name = "sort") val sort: String? = null, - @Json(name = "tokens") val tokens: List = emptyList(), -) + @Json(name = "tokens") val tokens: List = emptyList(), +) { -data class TokenBody( - @Json(name = "id") val id: String? = null, - @Json(name = "networkId") val networkId: String, - @Json(name = "derivationPath") val derivationPath: String? = null, - @Json(name = "name") val name: String, - @Json(name = "symbol") val symbol: String, - @Json(name = "decimals") val decimals: Int, - @Json(name = "contractAddress") val contractAddress: String?, -) \ No newline at end of file + data class Token( + @Json(name = "id") val id: String? = null, + @Json(name = "networkId") val networkId: String, + @Json(name = "derivationPath") val derivationPath: String? = null, + @Json(name = "name") val name: String, + @Json(name = "symbol") val symbol: String, + @Json(name = "decimals") val decimals: Int, + @Json(name = "contractAddress") val contractAddress: String?, + ) +} \ No newline at end of file diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index e365d69c25..93519446be 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -72,6 +72,7 @@ android { dependencies { implementation(project(":core:datasource")) + implementation(project(":core:utils")) implementation(project(":common")) /** Tangem libraries */ diff --git a/domain/src/main/java/com/tangem/domain/DomainDialog.kt b/domain/src/main/java/com/tangem/domain/DomainDialog.kt index 22e0ae8e28..c1f72a19de 100644 --- a/domain/src/main/java/com/tangem/domain/DomainDialog.kt +++ b/domain/src/main/java/com/tangem/domain/DomainDialog.kt @@ -1,7 +1,7 @@ package com.tangem.domain import com.tangem.common.extensions.VoidCallback -import com.tangem.datasource.api.tangemTech.CoinsResponse +import com.tangem.datasource.api.tangemTech.models.CoinsResponse /** [REDACTED_AUTHOR] @@ -14,6 +14,6 @@ sealed interface DomainDialog { val items: List, val networkIdConverter: (String) -> String, val onSelect: (CoinsResponse.Coin.Network) -> Unit, - val onClose: VoidCallback = {} + val onClose: VoidCallback = {}, ) : DomainDialog } \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/extensions/TangemTechService.kt b/domain/src/main/java/com/tangem/domain/common/extensions/TangemTechService.kt deleted file mode 100644 index 925eaa5f82..0000000000 --- a/domain/src/main/java/com/tangem/domain/common/extensions/TangemTechService.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.tangem.domain.common.extensions - -import com.tangem.common.services.Result -import com.tangem.datasource.api.tangemTech.CoinsResponse -import com.tangem.datasource.api.tangemTech.TangemTechService - -suspend fun TangemTechService.getTokens( - contractAddress: String, - networkId: String? = null, - active: Boolean? = null, -): Result = coins( - contractAddress = contractAddress, - networkIds = networkId, - active = active - ) - -suspend fun TangemTechService.getListOfCoins( - networkIds: List, - active: Boolean? = null, - searchText: String? = null, - offset: Int? = null, - limit: Int? = null -): Result = coins( - networkIds = networkIds.joinToString(","), - active = active, - searchText = searchText, - offset = offset, - limit = limit - ) \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt index 98a63b6565..8015e7fc84 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt @@ -1,15 +1,16 @@ package com.tangem.domain.features.addCustomToken -import com.tangem.common.services.Result -import com.tangem.domain.common.extensions.getTokens -import com.tangem.datasource.api.tangemTech.CoinsResponse -import com.tangem.datasource.api.tangemTech.TangemTechService +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.api.tangemTech.models.CoinsResponse +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext /** [REDACTED_AUTHOR] */ class AddCustomTokenService( - private val tangemTechService: TangemTechService, + private val tangemTechApi: TangemTechApi, + private val dispatchers: CoroutineDispatcherProvider, private val supportedTokenNetworkIds: List, ) { @@ -17,13 +18,17 @@ class AddCustomTokenService( contractAddress: String, networkId: String? = null, active: Boolean? = null, - ): Result> { - val networksIds = selectNetworksForSearch(networkId) - val result = tangemTechService.getTokens(contractAddress, networksIds, active) - return when (result) { - is Result.Success -> { + ): List = withContext(dispatchers.io) { + runCatching { + tangemTechApi.getCoins( + contractAddress = contractAddress, + networkIds = selectNetworksForSearch(networkId), + active = active, + ) + } + .onSuccess { response -> var coinsList = mutableListOf() - result.data.coins.forEach { coin -> + response.coins.forEach { coin -> val networksWithTheSameAddress = coin.networks .filter { it.contractAddress != null || it.decimalCount != null } .filter { it.contractAddress?.equals(contractAddress, ignoreCase = true) == true } @@ -37,10 +42,13 @@ class AddCustomTokenService( // https://tangem.slack.com/archives/GMXC6PP71/p1649672562078679 coinsList = mutableListOf(coinsList[0]) } - Result.Success(coinsList) + return@withContext coinsList } - is Result.Failure -> result - } + .onFailure { + return@withContext emptyList() + } + + throw IllegalStateException("Unreachable code because runCatching must return result") } private fun selectNetworksForSearch(networkId: String?): String { 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 3a5946f963..cc41cdb3d4 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,13 +2,13 @@ package com.tangem.domain.features.addCustomToken.redux import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationStyle +import com.tangem.datasource.api.tangemTech.models.CoinsResponse 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.CustomCurrency import com.tangem.domain.features.addCustomToken.CustomTokenFieldId -import com.tangem.datasource.api.tangemTech.CoinsResponse import org.rekotlin.Action /** 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 3a48aa34a7..b0ffab36b3 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 @@ -3,7 +3,7 @@ package com.tangem.domain.features.addCustomToken.redux import android.webkit.ValueCallback import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.guard -import com.tangem.common.services.Result +import com.tangem.datasource.api.tangemTech.models.CoinsResponse import com.tangem.domain.AddCustomTokenError import com.tangem.domain.AddCustomTokenError.Warning.PotentialScamToken import com.tangem.domain.AddCustomTokenError.Warning.TokenAlreadyAdded @@ -56,7 +56,7 @@ import com.tangem.domain.redux.domainStore import com.tangem.domain.redux.extensions.dispatchOnMain import com.tangem.domain.redux.global.DomainGlobalAction import com.tangem.domain.redux.global.DomainGlobalState -import com.tangem.datasource.api.tangemTech.CoinsResponse +import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -232,11 +232,13 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT } if (state.screenState.derivationPath.isEnabled != derivationIsSupportedByNetwork) { - val action = Screen.UpdateTokenFields(listOf( - DerivationPath to state.screenState.derivationPath.copy( - isEnabled = derivationIsSupportedByNetwork, + val action = Screen.UpdateTokenFields( + listOf( + DerivationPath to state.screenState.derivationPath.copy( + isEnabled = derivationIsSupportedByNetwork, + ), ), - )) + ) dispatchOnMain(action) } } @@ -297,10 +299,12 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT } } - dispatchOnMain(Warning.Replace( - remove = warningsRemove, - add = warningsAdd, - )) + dispatchOnMain( + Warning.Replace( + remove = warningsRemove, + add = warningsAdd, + ), + ) } private suspend fun updateAddButton() { @@ -340,9 +344,7 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT } } - private suspend fun requestInfoAboutToken( - contractAddress: String, - ): List { + private suspend fun requestInfoAboutToken(contractAddress: String): List { val tangemTechServiceManager = requireNotNull(hubState.tangemTechServiceManager) dispatchOnMain(Screen.UpdateTokenFields(listOf(ContractAddress to ViewStates.TokenField(isLoading = true)))) @@ -355,15 +357,8 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT // got the result faster than 500ms and the delay would only be the difference between them. delay(500) - val foundTokensResult = tangemTechServiceManager.findToken(contractAddress, selectedNetworkId) - val result = when (foundTokensResult) { - is Result.Success -> foundTokensResult.data - is Result.Failure -> { -// val warning = Warning.Network.CheckAddressRequestError -// dispatchOnMain(Warning.Add(setOf(warning))) - emptyList() - } - } + val result = tangemTechServiceManager.findToken(contractAddress, selectedNetworkId) + dispatchOnMain(Screen.UpdateTokenFields(listOf(ContractAddress to ViewStates.TokenField(isLoading = false)))) return result } @@ -431,10 +426,7 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT derivationStyle = hubState.cardDerivationStyle ) - private suspend fun fillTokenFields( - token: CoinsResponse.Coin, - coinNetwork: CoinsResponse.Coin.Network, - ) { + private suspend fun fillTokenFields(token: CoinsResponse.Coin, coinNetwork: CoinsResponse.Coin.Network) { val blockchain = Blockchain.fromNetworkId(coinNetwork.networkId) ?: Blockchain.Unknown Network.setFieldValue(Field.Data(blockchain, false)) Name.setFieldValue(Field.Data(token.name, false)) @@ -460,11 +452,13 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT private suspend fun enableDisableTokenDetailFields(isEnabled: Boolean = true) { val state = hubState - val action = Screen.UpdateTokenFields(listOf( - Name to state.screenState.name.copy(isEnabled = isEnabled), - Symbol to state.screenState.symbol.copy(isEnabled = isEnabled), - Decimals to state.screenState.decimals.copy(isEnabled = isEnabled), - )) + val action = Screen.UpdateTokenFields( + listOf( + Name to state.screenState.name.copy(isEnabled = isEnabled), + Symbol to state.screenState.symbol.copy(isEnabled = isEnabled), + Decimals to state.screenState.decimals.copy(isEnabled = isEnabled), + ), + ) dispatchOnMain(action) } @@ -597,8 +591,9 @@ private class AddCustomTokenReducer( .filter { it.canHandleTokens() } .map { it.toNetworkId() } val tangemTechServiceManager = AddCustomTokenService( - tangemTechService = globalState.networkServices.tangemTechService, - supportedTokenNetworkIds = supportedTokenNetworkIds + tangemTechApi = globalState.networkServices.tangemTechService.api, + dispatchers = AppCoroutineDispatcherProvider(), + supportedTokenNetworkIds = supportedTokenNetworkIds, ) val form = Form(AddCustomTokenState.createFormFields(card, CustomTokenType.Blockchain)) state.copy( @@ -745,5 +740,4 @@ private class AddCustomTokenReducer( private fun updateFormState(state: AddCustomTokenState): AddCustomTokenState { return state.copy(form = Form(state.form.fieldList)) } - } \ No newline at end of file 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 978232de57..ad0be88966 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 @@ -3,6 +3,7 @@ package com.tangem.domain.features.addCustomToken.redux import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationStyle import com.tangem.common.json.MoshiJsonConverter +import com.tangem.datasource.api.tangemTech.models.CoinsResponse import com.tangem.domain.AddCustomTokenError import com.tangem.domain.DomainWrapped import com.tangem.domain.common.CardDTO @@ -37,7 +38,6 @@ import com.tangem.domain.features.addCustomToken.TokenDerivationPathField import com.tangem.domain.features.addCustomToken.TokenField import com.tangem.domain.redux.DomainState import com.tangem.domain.redux.state.StringActionStateConverter -import com.tangem.datasource.api.tangemTech.CoinsResponse import org.rekotlin.Action import org.rekotlin.StateType diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt index 088a47045f..36e89a8e56 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt @@ -33,7 +33,7 @@ internal class SwapRepositoryImpl @Inject constructor( override suspend fun getExchangeableTokens(networkId: String): List { return withContext(coroutineDispatcher.io) { - tokensConverter.convertList(tangemTechApi.coins(exchangeable = true, networkIds = networkId).coins) + tokensConverter.convertList(tangemTechApi.getCoins(exchangeable = true, networkIds = networkId).coins) } } diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/TokensConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/TokensConverter.kt index 11596db8bc..c78b8b3638 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/TokensConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/TokensConverter.kt @@ -1,6 +1,6 @@ package com.tangem.feature.swap.converters -import com.tangem.datasource.api.tangemTech.CoinsResponse +import com.tangem.datasource.api.tangemTech.models.CoinsResponse import com.tangem.feature.swap.domain.models.data.Currency import com.tangem.utils.converter.Converter import javax.inject.Inject From c1e167fe71dc36a5e714f271c895783ea277e810 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 10 Jan 2023 17:32:11 +0400 Subject: [PATCH 065/145] Updated on 2026-08-14 --- .../datasource/api/common/Interceptors.kt | 30 ---------- .../api/tangemTech/TangemTechService.kt | 55 ++++++++++--------- .../com/tangem/datasource/di/NetworkModule.kt | 10 ++-- domain/build.gradle.kts | 1 + .../com/tangem/domain/common/LogConfig.kt | 1 - .../domain/redux/global/DomainGlobalHub.kt | 25 ++++----- .../domain/redux/global/DomainGlobalState.kt | 10 ++-- 7 files changed, 51 insertions(+), 81 deletions(-) delete mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/common/Interceptors.kt diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/Interceptors.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/Interceptors.kt deleted file mode 100644 index 17b348b924..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/Interceptors.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.tangem.datasource.api.common - -import okhttp3.Interceptor -import okhttp3.Response - -/** -[REDACTED_AUTHOR] - */ -open class AddHeaderInterceptor( - private val headers: Map, -) : Interceptor { - - override fun intercept(chain: Interceptor.Chain): Response { - val request = chain.request().newBuilder().apply { - headers.forEach { - addHeader(it.key, it.value) - } - }.build() - - return chain.proceed(request) - } -} - -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 diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt index e55103871c..c38d6f6f22 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt @@ -1,39 +1,42 @@ package com.tangem.datasource.api.tangemTech -import com.tangem.datasource.api.common.AddHeaderInterceptor -import com.tangem.datasource.api.common.CacheControlHttpInterceptor -import com.tangem.datasource.api.common.createRetrofitInstance +import com.tangem.datasource.api.common.MoshiConverter +import com.tangem.datasource.utils.RequestHeader +import com.tangem.datasource.utils.RequestHeader.AuthenticationHeader +import com.tangem.datasource.utils.RequestHeader.CacheControlHeader +import com.tangem.datasource.utils.addHeaders +import com.tangem.datasource.utils.allowLogging +import okhttp3.OkHttpClient +import retrofit2.Retrofit /** [REDACTED_AUTHOR] */ -class TangemTechService( - private val logIsEnabled: Boolean = false, -) { - - private val headerInterceptors = mutableListOf( - CacheControlHttpInterceptor(cacheMaxAge), - ) +//TODO("Remove after removing Redux") +@Deprecated("Use TangemTechApi") +object TangemTechService { var api: TangemTechApi = createApi() + private set - fun addHeaderInterceptors(interceptors: List) { - headerInterceptors.removeAll(interceptors) - headerInterceptors.addAll(interceptors) - api = createApi() + private const val TANGEM_TECH_BASE_URL = "https://api.tangem-tech.com/v1/" + + fun addAuthenticationHeader(header: AuthenticationHeader) { + api = createApi(header) } - private fun createApi(): TangemTechApi { - val retrofit = createRetrofitInstance( - baseUrl = baseUrl, - interceptors = headerInterceptors.toList(), - logEnabled = logIsEnabled, - ) - return retrofit.create(TangemTechApi::class.java) - } - - companion object { - const val baseUrl = "https://api.tangem-tech.com/v1/" - const val cacheMaxAge = 600 + private fun createApi(header: RequestHeader? = null): TangemTechApi { + val headers = mutableListOf(CacheControlHeader).apply { header?.let(::add) } + return Retrofit.Builder() + .addConverterFactory(MoshiConverter.createFactory()) + .baseUrl(TANGEM_TECH_BASE_URL) + .client( + OkHttpClient.Builder() + .addHeaders(*headers.toTypedArray()) + .allowLogging() + .build(), + ) + .build() + .create(TangemTechApi::class.java) } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index fc33504a48..84de623ee4 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -37,15 +37,15 @@ class NetworkModule { fun provideTangemTechApi(authProvider: AuthProvider, moshi: Moshi): TangemTechApi { return Retrofit.Builder() .addConverterFactory(MoshiConverterFactory.create(moshi)) - .baseUrl(DEV_TANGEM_TECH_BASE_URL) + .baseUrl(PROD_TANGEM_TECH_BASE_URL) .client( OkHttpClient.Builder() .addHeaders( CacheControlHeader, - AuthenticationHeader(authProvider) + AuthenticationHeader(authProvider), ) .allowLogging() - .build() + .build(), ) .build() .create(TangemTechApi::class.java) @@ -58,12 +58,12 @@ class NetworkModule { .addConverterFactory( MoshiConverterFactory.create(moshi), ) - .baseUrl(DEV_TANGEM_TECH_BASE_URL) + .baseUrl(PROD_TANGEM_TECH_BASE_URL) .client( OkHttpClient.Builder() .addHeaders(AuthenticationHeader(authProvider)) .allowLogging() - .build() + .build(), ) .build() .create(ReferralApi::class.java) diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index 93519446be..33ffebc8ed 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -74,6 +74,7 @@ dependencies { implementation(project(":core:datasource")) implementation(project(":core:utils")) implementation(project(":common")) + implementation(project(":libs:auth")) /** Tangem libraries */ implementation(Tangem.blockchain) { diff --git a/domain/src/main/java/com/tangem/domain/common/LogConfig.kt b/domain/src/main/java/com/tangem/domain/common/LogConfig.kt index c4f96f4b55..ebe3cef306 100644 --- a/domain/src/main/java/com/tangem/domain/common/LogConfig.kt +++ b/domain/src/main/java/com/tangem/domain/common/LogConfig.kt @@ -13,7 +13,6 @@ object LogConfig { object NetworkLogConfig { val mercuryoService: Boolean = false val moonPayService: Boolean = false - val tangemTechService: Boolean = BuildConfig.DEBUG val paymentologyApiService: Boolean = BuildConfig.DEBUG val blockchainSdkNetwork: Boolean = BuildConfig.DEBUG } 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 38c30c5177..2e255c502b 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 @@ -2,10 +2,11 @@ package com.tangem.domain.redux.global import android.webkit.ValueCallback import com.tangem.common.extensions.toHexString +import com.tangem.datasource.utils.RequestHeader import com.tangem.domain.redux.BaseStoreHub import com.tangem.domain.redux.DomainState import com.tangem.domain.redux.ReStoreReducer -import com.tangem.datasource.api.common.CardPublicKeyHttpInterceptor +import com.tangem.lib.auth.AuthProvider import org.rekotlin.Action /** @@ -22,17 +23,8 @@ internal class DomainGlobalHub : BaseStoreHub("DomainGlobalHu return storeState.copy(globalState = newHubState) } - override suspend fun handleAction( - action: Action, - storeState: DomainState, - cancel: ValueCallback - ) { + override suspend fun handleAction(action: Action, storeState: DomainState, cancel: ValueCallback) { if (action !is DomainGlobalAction) return - - val state = storeState.globalState - - // when (action) { - // } } override fun getReducer(): ReStoreReducer = DomainGlobalReducer() @@ -43,9 +35,14 @@ private class DomainGlobalReducer : ReStoreReducer { override fun reduceAction(action: Action, state: DomainGlobalState): DomainGlobalState { return when (action) { is DomainGlobalAction.SaveScanNoteResponse -> { - val cardPublicKeyHex = action.scanResponse.card.cardPublicKey.toHexString() - state.networkServices.tangemTechService.addHeaderInterceptors( - listOf(CardPublicKeyHttpInterceptor(cardPublicKeyHex)) + val card = action.scanResponse.card + state.networkServices.tangemTechService.addAuthenticationHeader( + RequestHeader.AuthenticationHeader( + object : AuthProvider { + override fun getCardPublicKey(): String = card.cardPublicKey.toHexString() + override fun getCardId(): String = card.cardId + }, + ), ) state.copy(scanResponse = action.scanResponse) } 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 a013b2128f..8a3bd3f4ed 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 @@ -1,10 +1,10 @@ package com.tangem.domain.redux.global +import com.tangem.datasource.api.paymentology.PaymentologyApiService +import com.tangem.datasource.api.tangemTech.TangemTechService import com.tangem.domain.DomainDialog import com.tangem.domain.common.LogConfig import com.tangem.domain.common.ScanResponse -import com.tangem.datasource.api.paymentology.PaymentologyApiService -import com.tangem.datasource.api.tangemTech.TangemTechService /** [REDACTED_AUTHOR] @@ -19,8 +19,8 @@ data class DomainGlobalState( ) data class NetworkServices( - val tangemTechService: TangemTechService = TangemTechService( - LogConfig.network.tangemTechService), + val tangemTechService: TangemTechService = TangemTechService, val paymentologyService: PaymentologyApiService = PaymentologyApiService( - LogConfig.network.paymentologyApiService), + LogConfig.network.paymentologyApiService, + ), ) From 93c85e4ce9ffc0dca61a73a0d4ad5ce6a6c35b7b Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 15 Nov 2022 16:45:21 +0500 Subject: [PATCH 066/145] Updated on 2026-08-14 --- .../com/tangem/tap/common/redux/global/GlobalAction.kt | 2 +- .../products/wallet/redux/OnboardingWalletMiddleware.kt | 2 +- .../products/wallet/redux/OnboardingWalletReducer.kt | 2 +- .../onboarding/products/wallet/ui/BackupAnimator.kt | 2 ++ .../products/wallet/ui/OnboardingWalletFragment.kt | 7 ++++--- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index 1f51374cae..1417d013d1 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -47,7 +47,7 @@ sealed class GlobalAction : Action { * For SaltPay cards unfinished backup resumed after scanning the card on HomeScreen through Onboarding.Start. * See more Onboarding.Start, CheckForUnfinishedBackup */ - object StartForUnfinishedBackup : Onboarding() + data class StartForUnfinishedBackup(val addedBackupCardsCount: Int) : Onboarding() object Stop : Onboarding() } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 980c0ef64b..537b815ec3 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -347,7 +347,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) } } is BackupAction.ResumeFoundUnfinishedBackup -> { - store.dispatch(GlobalAction.Onboarding.StartForUnfinishedBackup) + store.dispatch(GlobalAction.Onboarding.StartForUnfinishedBackup(backupService.addedBackupCardsCount)) store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) } is BackupAction.DismissBackup -> { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt index 8cca79e9dd..43b61349ab 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt @@ -52,7 +52,7 @@ private class ReducerForGlobalAction { is GlobalAction.Onboarding.StartForUnfinishedBackup -> { OnboardingWalletState( backupState = state.backupState.copy( - maxBackupCards = 2, + maxBackupCards = action.addedBackupCardsCount, canSkipBackup = false, ), isSaltPay = false, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt index 66f90f159c..6453fdca92 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt @@ -134,6 +134,7 @@ class WalletBackupAnimator( 1 -> { if (firstBackupCardAnimated) return firstBackupCardAnimated = true + FIRST_BACKUP.alpha(1f) cardsWidget.leapfrogWidget.leap { SECOND_BACKUP.alpha(0.4f) @@ -143,6 +144,7 @@ class WalletBackupAnimator( 2 -> { if (secondBackupCardAnimated) return secondBackupCardAnimated = true + SECOND_BACKUP.alpha(1f) cardsWidget.leapfrogWidget.leap { ORIGIN.alpha(0.4f) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt index 2ca75121a9..f91b444e9c 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt @@ -90,7 +90,7 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle override fun loadToolbarMenu(): MenuProvider? = OnboardingMenuProvider() - private fun reInitCardsWidget(backupCardsCounts: Int) = with(binding) { + private fun reInitCardsWidgetIfNeeded(backupCardsCounts: Int) = with(binding) { val viewBackupCount = flCardsContainer.childCount - 1 if (viewBackupCount <= 0) return@with if (viewBackupCount == backupCardsCounts) return@with @@ -137,7 +137,7 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle override fun newState(state: OnboardingWalletState) { if (activity == null || view == null) return - if (state.isSaltPay) reInitCardsWidget(1) + if (state.isSaltPay) reInitCardsWidgetIfNeeded(1) animator.updateBackupState(state.backupState) requireActivity().invalidateOptionsMenu() @@ -322,7 +322,7 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle private fun showWritePrimaryCard(state: BackupState, isSaltPay: Boolean) = with(binding) { accessCodeDialog?.dismiss() - reInitCardsWidget(state.backupCardsNumber) + reInitCardsWidgetIfNeeded(state.backupCardsNumber) prepareViewForFinalizeStep() val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4)) @@ -358,6 +358,7 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle private fun showWriteBackupCard(state: BackupState, isSaltPay: Boolean) = with(binding) { prepareViewForFinalizeStep() + reInitCardsWidgetIfNeeded(state.backupCardsNumber) val cardNumber = (state.backupStep as? BackupStep.WriteBackupCard)?.cardNumber ?: 1 if (isSaltPay) { tvHeader.text = getString(R.string.onboarding_saltpay_title_backup_card) From 035a08433928ccec5d7928854821d3a176262ade Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 19 Nov 2022 16:01:49 +0500 Subject: [PATCH 067/145] Updated on 2026-08-14 --- .../tangem/tap/common/extensions/Context.kt | 20 +++++++++++++++++++ .../wallet/redux/OnboardingWalletAction.kt | 5 +++-- .../redux/OnboardingWalletMiddleware.kt | 11 +++++----- .../wallet/redux/OnboardingWalletReducer.kt | 2 +- .../wallet/redux/OnboardingWalletState.kt | 3 ++- .../saltPay/ui/OnboardingSaltPayView.kt | 4 +++- .../wallet/ui/OnboardingWalletFragment.kt | 18 +++++++++++------ 7 files changed, 47 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Context.kt b/app/src/main/java/com/tangem/tap/common/extensions/Context.kt index cba31fe77c..1a6ff8b260 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Context.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Context.kt @@ -1,7 +1,11 @@ package com.tangem.tap.common.extensions +import android.content.ContentResolver import android.content.Context import android.content.pm.PackageManager +import android.content.res.Resources +import android.net.Uri +import androidx.annotation.AnyRes import androidx.core.content.ContextCompat fun Context.readFile(fileName: String): String = @@ -19,4 +23,20 @@ fun Context.readAssetAsString(fileName: String): String { fun Context.isPermissionGranted(permission: String): Boolean { return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED +} + +/** + * Get uri to any resource type via given Resource Instance + * @param resId - resource id + * @throws Resources.NotFoundException if the given ID does not exist. + * @return - Uri to resource by the given ID + */ +@Throws(Resources.NotFoundException::class) +fun Context.resourceUri(@AnyRes resId: Int): Uri { + return Uri.parse( + ContentResolver.SCHEME_ANDROID_RESOURCE + + "://" + resources.getResourcePackageName(resId) + + '/' + resources.getResourceTypeName(resId) + + '/' + resources.getResourceEntryName(resId), + ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt index f3ff070f97..eabcd70384 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.onboarding.products.wallet.redux import android.graphics.Bitmap +import android.net.Uri import org.rekotlin.Action sealed class OnboardingWalletAction : Action { @@ -13,8 +14,8 @@ sealed class OnboardingWalletAction : Action { object ResumeBackup : OnboardingWalletAction() - object LoadArtwork : OnboardingWalletAction() - class SetArtworkUrl(val artworkUrl: String?) : OnboardingWalletAction() + data class LoadArtwork(val cardArtworkUriForUnfinishedBackup: Uri? = null) : OnboardingWalletAction() + class SetArtworkUrl(val artworkUri: Uri?) : OnboardingWalletAction() object OnBackPressed : OnboardingWalletAction() } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 537b815ec3..d1e4c0f576 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.onboarding.products.wallet.redux +import android.net.Uri import com.tangem.blockchain.common.Blockchain import com.tangem.common.CompletionResult import com.tangem.common.extensions.ifNotNull @@ -100,11 +101,11 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: } is OnboardingWalletAction.LoadArtwork -> { scope.launch { - val artwork = onboardingManager?.loadArtworkUrl() - val cardArtwork = if (artwork == Artwork.DEFAULT_IMG_URL) { - null - } else { - artwork + val cardArtwork = when (onboardingManager) { + null -> action.cardArtworkUriForUnfinishedBackup + else -> onboardingManager.loadArtworkUrl() + .takeIf { it != Artwork.DEFAULT_IMG_URL } + ?.let { Uri.parse(it) } } store.dispatchOnMain(OnboardingWalletAction.SetArtworkUrl(cardArtwork)) } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt index 43b61349ab..2e50716048 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt @@ -29,7 +29,7 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingWallet ) is OnboardingWalletAction.ResumeBackup -> state.copy(step = OnboardingWalletStep.Backup) is OnboardingWalletAction.SetArtworkUrl -> { - state.copy(cardArtworkUrl = action.artworkUrl) + state.copy(cardArtworkUri = action.artworkUri) } is OnboardingWalletAction.Done -> state.copy(step = OnboardingWalletStep.Done) else -> state diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt index aa04272085..23da378819 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.onboarding.products.wallet.redux import android.graphics.Bitmap +import android.net.Uri import com.tangem.common.CardFilter import com.tangem.common.extensions.VoidCallback import com.tangem.domain.common.SaltPayWorkaround @@ -17,7 +18,7 @@ data class OnboardingWalletState( val backupState: BackupState = BackupState(), val onboardingSaltPayState: OnboardingSaltPayState? = null, val isSaltPay: Boolean = false, - val cardArtworkUrl: String? = null, + val cardArtworkUri: Uri? = null, val showConfetti: Boolean = false, ) : StateType { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt index 429a84cbab..c1df20ee2c 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay.ui +import android.net.Uri import android.view.View import android.view.ViewGroup import android.view.animation.OvershootInterpolator @@ -97,7 +98,8 @@ internal class OnboardingSaltPayView( crossfade(enable = true) } } else { - walletFragment.loadImageIntoImageView(state.onboardingSaltPayState.saltPayCardArtworkUrl, imvFrontCard) + val uri = Uri.parse(state.onboardingSaltPayState.saltPayCardArtworkUrl) + walletFragment.loadImageIntoImageView(uri, imvFrontCard) } imvFirstBackupCard.load(R.drawable.card_placeholder_wallet) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt index f91b444e9c..f6dd395cf4 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.onboarding.products.wallet.ui +import android.net.Uri import android.os.Bundle import android.util.TypedValue import android.view.View @@ -23,6 +24,7 @@ import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.inflate +import com.tangem.tap.common.extensions.resourceUri import com.tangem.tap.common.extensions.show import com.tangem.tap.features.BaseFragment import com.tangem.tap.features.FragmentOnBackPressedHandler @@ -85,7 +87,11 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle store.dispatch(OnboardingWalletAction.Init) store.dispatch(OnboardingSaltPayAction.Init) - store.dispatch(OnboardingWalletAction.LoadArtwork) + store.dispatch( + OnboardingWalletAction.LoadArtwork( + cardArtworkUriForUnfinishedBackup = requireContext().resourceUri(R.drawable.card_placeholder_wallet), + ), + ) } override fun loadToolbarMenu(): MenuProvider? = OnboardingMenuProvider() @@ -148,15 +154,15 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle if (state.isSaltPay) { onboardingSaltPayView.newState(state) } else { - loadImageIntoImageView(state.cardArtworkUrl, binding.imvFrontCard) - loadImageIntoImageView(state.cardArtworkUrl, binding.imvFirstBackupCard) - loadImageIntoImageView(state.cardArtworkUrl, binding.imvSecondBackupCard) + loadImageIntoImageView(state.cardArtworkUri, binding.imvFrontCard) + loadImageIntoImageView(state.cardArtworkUri, binding.imvFirstBackupCard) + loadImageIntoImageView(state.cardArtworkUri, binding.imvSecondBackupCard) handleOnboardingStep(state) } } - internal fun loadImageIntoImageView(url: String?, view: ImageView) { - view.load(url) { + internal fun loadImageIntoImageView(uri: Uri?, view: ImageView) { + view.load(uri) { placeholder(R.drawable.card_placeholder_black) error(R.drawable.card_placeholder_black) fallback(R.drawable.card_placeholder_black) From 9c2ed501565785d8834c73f288423e9232b9b26b Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 10 Jan 2023 18:09:17 +0400 Subject: [PATCH 068/145] Updated on 2026-08-14 --- .../datasource/api/referral/ReferralApi.kt | 22 --------- .../api/referral/models/ReferralResponse.kt | 48 ------------------ .../api/tangemTech/TangemTechApi.kt | 11 +++++ .../api/tangemTech/models/ReferralResponse.kt | 49 +++++++++++++++++++ .../models/StartReferralBody.kt | 2 +- .../com/tangem/datasource/di/NetworkModule.kt | 19 ------- .../referral/converters/ReferralConverter.kt | 7 ++- .../referral/data/ReferralRepositoryImpl.kt | 6 +-- .../referral/di/ReferralRepositoryModule.kt | 8 +-- 9 files changed, 72 insertions(+), 100 deletions(-) delete mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt delete mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/ReferralResponse.kt rename core/datasource/src/main/java/com/tangem/datasource/api/{referral => tangemTech}/models/StartReferralBody.kt (85%) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt deleted file mode 100644 index c3b7ff2200..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.tangem.datasource.api.referral - -import com.tangem.datasource.api.referral.models.ReferralResponse -import com.tangem.datasource.api.referral.models.StartReferralBody -import retrofit2.http.Body -import retrofit2.http.GET -import retrofit2.http.POST -import retrofit2.http.Path - -/** - * Api for referral feature - */ -interface ReferralApi { - - /** Returns referral status by [walletId] */ - @GET("referral/{walletId}") - suspend fun getReferralStatus(@Path("walletId") walletId: String): ReferralResponse - - /** Make user referral, requires [StartReferralBody] */ - @POST("referral") - suspend fun startReferral(@Body startReferralBody: StartReferralBody): ReferralResponse -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt deleted file mode 100644 index 3066ab45a7..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.tangem.datasource.api.referral.models - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass -import org.joda.time.DateTime - -/** - * Main response class for referral API - * contains all necessary info about users program status - */ -data class ReferralResponse( - @Json(name = "conditions") val conditions: Conditions, - @Json(name = "referral") val referral: Referral?, -) - -data class Conditions( - @Json(name = "award") val award: Int, - @Json(name = "discount") val discount: Discount, - @Json(name = "awards") val awards: List, - @Json(name = "tosLink") val tosLink: String, -) - -data class Discount( - @Json(name = "amount") val amount: Int, - @Json(name = "type") val discountType: String, -) - -data class Award( - @Json(name = "amount") val amount: Int, - @Json(name = "token") val token: Token, -) - -data class Token( - @Json(name = "id") val id: String, - @Json(name = "name") val name: String, - @Json(name = "symbol") val symbol: String, - @Json(name = "networkId") val networkId: String, - @Json(name = "contractAddress") val contractAddress: String?, - @Json(name = "decimalCount") val decimalCount: Int?, -) - -data class Referral( - @Json(name = "shareLink") val shareLink: String, - @Json(name = "address") val address: String, - @Json(name = "promoCode") val promocode: String, - @Json(name = "walletsPurchased") val walletsPurchased: Int, - @Json(name = "termsAcceptedAt") val termsAcceptedAt: String?, -) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index df8c21d2ea..711963d504 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -4,9 +4,12 @@ import com.tangem.datasource.api.tangemTech.models.CoinsResponse import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse import com.tangem.datasource.api.tangemTech.models.GeoResponse import com.tangem.datasource.api.tangemTech.models.RatesResponse +import com.tangem.datasource.api.tangemTech.models.ReferralResponse +import com.tangem.datasource.api.tangemTech.models.StartReferralBody import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import retrofit2.http.Body import retrofit2.http.GET +import retrofit2.http.POST import retrofit2.http.PUT import retrofit2.http.Path import retrofit2.http.Query @@ -46,4 +49,12 @@ interface TangemTechApi { @PUT("user-tokens/{user-id}") suspend fun saveUserTokens(@Path(value = "user-id") userId: String, @Body userTokens: UserTokensResponse) + + /** Returns referral status by [walletId] */ + @GET("referral/{walletId}") + suspend fun getReferralStatus(@Path("walletId") walletId: String): ReferralResponse + + /** Make user referral, requires [StartReferralBody] */ + @POST("referral") + suspend fun startReferral(@Body startReferralBody: StartReferralBody): ReferralResponse } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/ReferralResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/ReferralResponse.kt new file mode 100644 index 0000000000..82a76a3ed4 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/ReferralResponse.kt @@ -0,0 +1,49 @@ +package com.tangem.datasource.api.tangemTech.models + +import com.squareup.moshi.Json + +/** + * Main response class for referral API + * contains all necessary info about users program status + */ +data class ReferralResponse( + @Json(name = "conditions") val conditions: Conditions, + @Json(name = "referral") val referral: Referral?, +) { + + data class Conditions( + @Json(name = "award") val award: Int, + @Json(name = "discount") val discount: Discount, + @Json(name = "awards") val awards: List, + @Json(name = "tosLink") val tosLink: String, + ) { + + data class Discount( + @Json(name = "amount") val amount: Int, + @Json(name = "type") val discountType: String, + ) + + data class Award( + @Json(name = "amount") val amount: Int, + @Json(name = "token") val token: Token, + ) { + + data class Token( + @Json(name = "id") val id: String, + @Json(name = "name") val name: String, + @Json(name = "symbol") val symbol: String, + @Json(name = "networkId") val networkId: String, + @Json(name = "contractAddress") val contractAddress: String?, + @Json(name = "decimalCount") val decimalCount: Int?, + ) + } + } + + data class Referral( + @Json(name = "shareLink") val shareLink: String, + @Json(name = "address") val address: String, + @Json(name = "promoCode") val promocode: String, + @Json(name = "walletsPurchased") val walletsPurchased: Int, + @Json(name = "termsAcceptedAt") val termsAcceptedAt: String?, + ) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/StartReferralBody.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/StartReferralBody.kt similarity index 85% rename from core/datasource/src/main/java/com/tangem/datasource/api/referral/models/StartReferralBody.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/StartReferralBody.kt index 98a0a8ab90..8f2c933f19 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/StartReferralBody.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/StartReferralBody.kt @@ -1,4 +1,4 @@ -package com.tangem.datasource.api.referral.models +package com.tangem.datasource.api.tangemTech.models import com.squareup.moshi.Json diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index 84de623ee4..42434bf7d4 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -3,7 +3,6 @@ package com.tangem.datasource.di import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import com.tangem.datasource.api.common.BigDecimalAdapter -import com.tangem.datasource.api.referral.ReferralApi import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.utils.RequestHeader.AuthenticationHeader import com.tangem.datasource.utils.RequestHeader.CacheControlHeader @@ -51,24 +50,6 @@ class NetworkModule { .create(TangemTechApi::class.java) } - @Provides - @Singleton - fun provideReferralApi(authProvider: AuthProvider, moshi: Moshi): ReferralApi { - return Retrofit.Builder() - .addConverterFactory( - MoshiConverterFactory.create(moshi), - ) - .baseUrl(PROD_TANGEM_TECH_BASE_URL) - .client( - OkHttpClient.Builder() - .addHeaders(AuthenticationHeader(authProvider)) - .allowLogging() - .build(), - ) - .build() - .create(ReferralApi::class.java) - } - private companion object { const val PROD_TANGEM_TECH_BASE_URL = "https://api.tangem-tech.com/v1/" const val DEV_TANGEM_TECH_BASE_URL = "https://devapi.tangem-tech.com/v1/" diff --git a/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt b/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt index 7e468c0500..71f237eaac 100644 --- a/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt +++ b/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt @@ -1,7 +1,6 @@ package com.tangem.feature.referral.converters -import com.tangem.datasource.api.referral.models.ReferralResponse -import com.tangem.datasource.api.referral.models.Token +import com.tangem.datasource.api.tangemTech.models.ReferralResponse import com.tangem.feature.referral.domain.models.DiscountType import com.tangem.feature.referral.domain.models.ReferralData import com.tangem.feature.referral.domain.models.ReferralInfo @@ -46,9 +45,9 @@ class ReferralConverter @Inject constructor() : Converter { +private class TokenConverter : Converter { - override fun convert(value: Token): TokenData { + override fun convert(value: ReferralResponse.Conditions.Award.Token): TokenData { return TokenData( id = value.id, name = value.name, diff --git a/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt b/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt index bf2387f070..371254979a 100644 --- a/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt +++ b/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt @@ -1,7 +1,7 @@ package com.tangem.feature.referral.data -import com.tangem.datasource.api.referral.ReferralApi -import com.tangem.datasource.api.referral.models.StartReferralBody +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.api.tangemTech.models.StartReferralBody import com.tangem.feature.referral.converters.ReferralConverter import com.tangem.feature.referral.domain.ReferralRepository import com.tangem.feature.referral.domain.models.ReferralData @@ -10,7 +10,7 @@ import kotlinx.coroutines.withContext import javax.inject.Inject internal class ReferralRepositoryImpl @Inject constructor( - private val referralApi: ReferralApi, + private val referralApi: TangemTechApi, private val referralConverter: ReferralConverter, private val coroutineDispatcher: CoroutineDispatcherProvider, ) : ReferralRepository { diff --git a/features/referral/data/src/main/java/com/tangem/feature/referral/di/ReferralRepositoryModule.kt b/features/referral/data/src/main/java/com/tangem/feature/referral/di/ReferralRepositoryModule.kt index 7a88c423cc..1f723fe16f 100644 --- a/features/referral/data/src/main/java/com/tangem/feature/referral/di/ReferralRepositoryModule.kt +++ b/features/referral/data/src/main/java/com/tangem/feature/referral/di/ReferralRepositoryModule.kt @@ -1,6 +1,6 @@ package com.tangem.feature.referral.di -import com.tangem.datasource.api.referral.ReferralApi +import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.feature.referral.converters.ReferralConverter import com.tangem.feature.referral.data.ReferralRepositoryImpl import com.tangem.feature.referral.domain.ReferralRepository @@ -18,12 +18,14 @@ class ReferralRepositoryModule { @Provides @Singleton fun provideReferralRepository( - referralApi: ReferralApi, + tangemTechApi: TangemTechApi, referralConverter: ReferralConverter, coroutineDispatcherProvider: CoroutineDispatcherProvider, ): ReferralRepository { return ReferralRepositoryImpl( - referralApi, referralConverter, coroutineDispatcherProvider, + referralApi = tangemTechApi, + referralConverter = referralConverter, + coroutineDispatcher = coroutineDispatcherProvider, ) } } \ No newline at end of file From e8d727212b8a7240ccf352fee31186fb74805a00 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 10 Jan 2023 20:44:39 +0300 Subject: [PATCH 069/145] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../disclaimer/ui/DisclaimerFragment.kt | 5 +- .../middlewares/AppCurrencyMiddleware.kt | 88 ------------------- .../com/tangem/tap/proxy/AppStateHolder.kt | 32 ------- core/res/src/main/res/values/strings.xml | 13 ++- .../components/appbar/AppBarWithBackButton.kt | 77 ---------------- .../feature/referral/ui/ReferralScreen.kt | 1 - 7 files changed, 11 insertions(+), 207 deletions(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 8453faa078..897f0f7c40 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 8453faa078220801858166081947972639f166db +Subproject commit 897f0f7c402fd53c5ee1fa4ab601ecd4e43ff389 diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt index b48cfd0e82..506b8beae9 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt @@ -38,6 +38,7 @@ class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubs enterTransition = inflater.inflateTransition(android.R.transition.fade) exitTransition = inflater.inflateTransition(android.R.transition.fade) } + else -> {} } } @@ -121,8 +122,4 @@ class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubs override fun handleOnBackPressed() { store.dispatch(DisclaimerAction.OnBackPressed) } - - override fun handleOnBackPressed() { - store.dispatch(DisclaimerAction.OnBackPressed) - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt index e15571e1ce..a2b7aa110e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt @@ -19,94 +19,6 @@ import com.tangem.tap.store import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.launch -class AppCurrencyMiddleware( - private val walletRepository: WalletRepository, - private val tapWalletManager: TapWalletManager, - private val fiatCurrenciesPrefStorage: FiatCurrenciesPrefStorage, - private val appCurrencyProvider: () -> FiatCurrency, -) { - fun handle(action: WalletAction.AppCurrencyAction) { - when (action) { - is WalletAction.AppCurrencyAction.ChooseAppCurrency -> showSelector() - is WalletAction.AppCurrencyAction.SelectAppCurrency -> selectCurrency(action) - } - } - - private fun showSelector() { - val storedFiatCurrencies = fiatCurrenciesPrefStorage.restore() - if (storedFiatCurrencies.isNotEmpty()) { - store.dispatchDialogShow( - WalletDialog.CurrencySelectionDialog( - currenciesList = storedFiatCurrencies.mapToUiModel(), - currentAppCurrency = appCurrencyProvider.invoke(), - ) - ) - } - - scope.launch { - runCatching { walletRepository.getCurrencyList() } - .onSuccess { - val currenciesList = it.currencies - if (currenciesList.isNotEmpty() && !currenciesList.toSet().equals(storedFiatCurrencies.toSet())) { - fiatCurrenciesPrefStorage.save(currenciesList) - store.dispatchDialogShow( - WalletDialog.CurrencySelectionDialog( - currenciesList = currenciesList.mapToUiModel(), - currentAppCurrency = appCurrencyProvider.invoke(), - ) - ) - } - } - } - } - - private fun selectCurrency(action: WalletAction.AppCurrencyAction.SelectAppCurrency) { - Analytics.send(MainScreen.MainCurrencyChanged(AnalyticsParam.CurrencyType.FiatCurrency(action.fiatCurrency))) - fiatCurrenciesPrefStorage.saveAppCurrency(action.fiatCurrency) - store.dispatch(GlobalAction.ChangeAppCurrency(action.fiatCurrency)) - store.dispatch(DetailsAction.ChangeAppCurrency(action.fiatCurrency)) - store.dispatch(WalletSelectorAction.ChangeAppCurrency(action.fiatCurrency)) - val selectedUserWallet = userWalletsListManager.selectedUserWalletSync - if (selectedUserWallet != null) { - scope.launch { - tapWalletManager.loadData(selectedUserWallet, refresh = true) - } - } else { - tapWalletManager.rates.clear() - store.dispatch(WalletAction.LoadFiatRate()) - } - } - - private fun List.mapToUiModel(): List { - return this.map { - FiatCurrency( - code = it.code, - name = it.name, - symbol = it.unit - ) - } - } -}package com.tangem.tap.features.wallet.redux.middlewares - -import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse -import com.tangem.core.analytics.Analytics -import com.tangem.tap.common.analytics.events.AnalyticsParam -import com.tangem.tap.common.analytics.events.MainScreen -import com.tangem.tap.common.entities.FiatCurrency -import com.tangem.tap.common.extensions.dispatchDialogShow -import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.domain.TapWalletManager -import com.tangem.tap.features.details.redux.DetailsAction -import com.tangem.tap.features.wallet.domain.WalletRepository -import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.features.wallet.redux.models.WalletDialog -import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction -import com.tangem.tap.persistence.FiatCurrenciesPrefStorage -import com.tangem.tap.scope -import com.tangem.tap.store -import com.tangem.tap.userWalletsListManager -import kotlinx.coroutines.launch - class AppCurrencyMiddleware( private val walletRepository: WalletRepository, private val tapWalletManager: TapWalletManager, diff --git a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt index 1d4763e548..110652b772 100644 --- a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt @@ -12,38 +12,6 @@ import com.tangem.tap.features.wallet.redux.WalletState import org.rekotlin.Store import javax.inject.Inject -/** - * Holds objects from old modules, that missing in DI graph - * Object sets manually to use in new modules and [AppStateHolder] proxies its to DI - */ -class AppStateHolder @Inject constructor(){ - - var scanResponse: ScanResponse? = null - var walletState: WalletState? = null - var userTokensRepository: UserTokensRepository? = null - var mainStore: Store? = null - var tangemSdkManager: TangemSdkManager? = null - var tangemSdk: TangemSdk? = null - var walletStoresManager: WalletStoresManager? = null - var userWalletsListManager: UserWalletsListManager? = null - - fun getActualCard(): CardDTO? { - return scanResponse?.card - } -}package com.tangem.tap.proxy - -import com.tangem.TangemSdk -import com.tangem.domain.common.CardDTO -import com.tangem.domain.common.ScanResponse -import com.tangem.tap.common.redux.AppState -import com.tangem.tap.domain.TangemSdkManager -import com.tangem.tap.domain.tokens.UserTokensRepository -import com.tangem.tap.domain.userWalletList.UserWalletsListManager -import com.tangem.tap.domain.walletStores.WalletStoresManager -import com.tangem.tap.features.wallet.redux.WalletState -import org.rekotlin.Store -import javax.inject.Inject - /** * Holds objects from old modules, that missing in DI graph * Object sets manually to use in new modules and [AppStateHolder] proxies its to DI diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index afed5beb51..db71c3e8d1 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -33,7 +33,7 @@ Search tokens You are currently running in Demo mode. All funds are not real. This feature is disabled in Demo mode - Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. + Not enough funds for fee in your %s wallet to send a transaction. Top up your %s wallet first. Available networks %s network not found. Please, add it first and try again. Attention @@ -100,7 +100,7 @@ WalletConnect Connect to Dapps Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. - Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet or use the card to recover the access code. + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. I understand that after performing this action, I will no longer have access to the current wallet Reset the Card Reset to Factory Settings @@ -191,6 +191,8 @@ Share Copy Success + You Send + You Receive Give Permission To continue you need to allow 1inch smart contracts to use your %s Amount %s @@ -202,9 +204,12 @@ Insufficient funds Give Permission Permit and Swap + Choose token + Your tokens + Other tokens Failed to load the information about the referral program. Please try again later. - Failed to load the information about the referral program. Reason: %s. Please try again later. - Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Failed to load the information about the referral program. Error code: %s. Please try again later. + Your participation request could not be processed. Error code: %s. Please try again later. If the problem persists — feel free to contact our support. Personal code copied! Buy Tangem Wallet with discount!\n%s %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt index f60eea64cd..384bdd6e14 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt @@ -69,83 +69,6 @@ fun PreviewAppBarWithBackButtonInLightTheme() { } } -@Preview(widthDp = 360, heightDp = 56, showBackground = true) -@Composable -fun PreviewAppBarWithBackButtonInDarkTheme() { - TangemTheme(isDark = true) { - AppBarWithBackButton(text = "Title", onBackClick = {}) - } -}package com.tangem.core.ui.components.appbar - -import androidx.annotation.DrawableRes -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material.Icon -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.dimensionResource -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview -import com.tangem.core.ui.R -import com.tangem.core.ui.res.TangemTheme - -/** - * App bar with back button and optional title - * - * @param text optional title - * @param onBackClick the lambda to be invoked when this icon is pressed - * - * @see Figma component - */ -@Composable -fun AppBarWithBackButton( - text: String? = null, - @DrawableRes iconRes: Int? = null, - onBackClick: () -> Unit, -) { - Row( - modifier = Modifier - .background(color = TangemTheme.colors.background.secondary) - .fillMaxWidth() - .padding(all = dimensionResource(R.dimen.spacing16)), - horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.spacing16)), - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - painter = painterResource(iconRes ?: R.drawable.ic_back_24), - contentDescription = null, - modifier = Modifier - .size(size = dimensionResource(R.dimen.size24)) - .clickable { onBackClick() }, - tint = TangemTheme.colors.icon.primary1, - ) - if (!text.isNullOrBlank()) { - Text( - text = text, - color = TangemTheme.colors.text.primary1, - maxLines = 1, - style = TangemTheme.typography.subtitle1, - ) - } - } -} - -@Preview(widthDp = 360, heightDp = 56, showBackground = true) -@Composable -fun PreviewAppBarWithBackButtonInLightTheme() { - TangemTheme(isDark = false) { - AppBarWithBackButton(text = "Title", onBackClick = {}) - } -} - @Preview(widthDp = 360, heightDp = 56, showBackground = true) @Composable fun PreviewAppBarWithBackButtonInDarkTheme() { diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt index ba3eedc499..9a16507f78 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt @@ -134,7 +134,6 @@ private fun ReferralContent(stateHolder: ReferralStateHolder, onAgreementClicked AppBarWithBackButton( text = stringResource(R.string.details_referral_title), onBackClick = stateHolder.headerState.onBackClicked, - modifier = Modifier.background(color = TangemTheme.colors.background.secondary), ) } item { Header() } From dda12556d8e82d4d1cb130c1d8c5cb406c2f1d60 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 10 Jan 2023 18:09:17 +0400 Subject: [PATCH 070/145] Updated on 2026-08-14 --- .../datasource/api/referral/ReferralApi.kt | 22 --------- .../api/referral/models/ReferralResponse.kt | 46 ----------------- .../api/tangemTech/TangemTechApi.kt | 11 +++++ .../api/tangemTech/models/ReferralResponse.kt | 49 +++++++++++++++++++ .../models/StartReferralBody.kt | 2 +- .../com/tangem/datasource/di/NetworkModule.kt | 19 ------- .../referral/converters/ReferralConverter.kt | 7 ++- .../referral/data/ReferralRepositoryImpl.kt | 6 +-- .../referral/di/ReferralRepositoryModule.kt | 8 +-- 9 files changed, 72 insertions(+), 98 deletions(-) delete mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt delete mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/ReferralResponse.kt rename core/datasource/src/main/java/com/tangem/datasource/api/{referral => tangemTech}/models/StartReferralBody.kt (85%) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt deleted file mode 100644 index c3b7ff2200..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.tangem.datasource.api.referral - -import com.tangem.datasource.api.referral.models.ReferralResponse -import com.tangem.datasource.api.referral.models.StartReferralBody -import retrofit2.http.Body -import retrofit2.http.GET -import retrofit2.http.POST -import retrofit2.http.Path - -/** - * Api for referral feature - */ -interface ReferralApi { - - /** Returns referral status by [walletId] */ - @GET("referral/{walletId}") - suspend fun getReferralStatus(@Path("walletId") walletId: String): ReferralResponse - - /** Make user referral, requires [StartReferralBody] */ - @POST("referral") - suspend fun startReferral(@Body startReferralBody: StartReferralBody): ReferralResponse -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt deleted file mode 100644 index f58c6570f6..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.tangem.datasource.api.referral.models - -import com.squareup.moshi.Json - -/** - * Main response class for referral API - * contains all necessary info about users program status - */ -data class ReferralResponse( - @Json(name = "conditions") val conditions: Conditions, - @Json(name = "referral") val referral: Referral?, -) - -data class Conditions( - @Json(name = "award") val award: Int, - @Json(name = "discount") val discount: Discount, - @Json(name = "awards") val awards: List, - @Json(name = "tosLink") val tosLink: String, -) - -data class Discount( - @Json(name = "amount") val amount: Int, - @Json(name = "type") val discountType: String, -) - -data class Award( - @Json(name = "amount") val amount: Int, - @Json(name = "token") val token: Token, -) - -data class Token( - @Json(name = "id") val id: String, - @Json(name = "name") val name: String, - @Json(name = "symbol") val symbol: String, - @Json(name = "networkId") val networkId: String, - @Json(name = "contractAddress") val contractAddress: String?, - @Json(name = "decimalCount") val decimalCount: Int?, -) - -data class Referral( - @Json(name = "shareLink") val shareLink: String, - @Json(name = "address") val address: String, - @Json(name = "promoCode") val promocode: String, - @Json(name = "walletsPurchased") val walletsPurchased: Int, - @Json(name = "termsAcceptedAt") val termsAcceptedAt: String?, -) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index df8c21d2ea..711963d504 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -4,9 +4,12 @@ import com.tangem.datasource.api.tangemTech.models.CoinsResponse import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse import com.tangem.datasource.api.tangemTech.models.GeoResponse import com.tangem.datasource.api.tangemTech.models.RatesResponse +import com.tangem.datasource.api.tangemTech.models.ReferralResponse +import com.tangem.datasource.api.tangemTech.models.StartReferralBody import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import retrofit2.http.Body import retrofit2.http.GET +import retrofit2.http.POST import retrofit2.http.PUT import retrofit2.http.Path import retrofit2.http.Query @@ -46,4 +49,12 @@ interface TangemTechApi { @PUT("user-tokens/{user-id}") suspend fun saveUserTokens(@Path(value = "user-id") userId: String, @Body userTokens: UserTokensResponse) + + /** Returns referral status by [walletId] */ + @GET("referral/{walletId}") + suspend fun getReferralStatus(@Path("walletId") walletId: String): ReferralResponse + + /** Make user referral, requires [StartReferralBody] */ + @POST("referral") + suspend fun startReferral(@Body startReferralBody: StartReferralBody): ReferralResponse } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/ReferralResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/ReferralResponse.kt new file mode 100644 index 0000000000..82a76a3ed4 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/ReferralResponse.kt @@ -0,0 +1,49 @@ +package com.tangem.datasource.api.tangemTech.models + +import com.squareup.moshi.Json + +/** + * Main response class for referral API + * contains all necessary info about users program status + */ +data class ReferralResponse( + @Json(name = "conditions") val conditions: Conditions, + @Json(name = "referral") val referral: Referral?, +) { + + data class Conditions( + @Json(name = "award") val award: Int, + @Json(name = "discount") val discount: Discount, + @Json(name = "awards") val awards: List, + @Json(name = "tosLink") val tosLink: String, + ) { + + data class Discount( + @Json(name = "amount") val amount: Int, + @Json(name = "type") val discountType: String, + ) + + data class Award( + @Json(name = "amount") val amount: Int, + @Json(name = "token") val token: Token, + ) { + + data class Token( + @Json(name = "id") val id: String, + @Json(name = "name") val name: String, + @Json(name = "symbol") val symbol: String, + @Json(name = "networkId") val networkId: String, + @Json(name = "contractAddress") val contractAddress: String?, + @Json(name = "decimalCount") val decimalCount: Int?, + ) + } + } + + data class Referral( + @Json(name = "shareLink") val shareLink: String, + @Json(name = "address") val address: String, + @Json(name = "promoCode") val promocode: String, + @Json(name = "walletsPurchased") val walletsPurchased: Int, + @Json(name = "termsAcceptedAt") val termsAcceptedAt: String?, + ) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/StartReferralBody.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/StartReferralBody.kt similarity index 85% rename from core/datasource/src/main/java/com/tangem/datasource/api/referral/models/StartReferralBody.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/StartReferralBody.kt index 98a0a8ab90..8f2c933f19 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/StartReferralBody.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/StartReferralBody.kt @@ -1,4 +1,4 @@ -package com.tangem.datasource.api.referral.models +package com.tangem.datasource.api.tangemTech.models import com.squareup.moshi.Json diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index 84de623ee4..42434bf7d4 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -3,7 +3,6 @@ package com.tangem.datasource.di import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import com.tangem.datasource.api.common.BigDecimalAdapter -import com.tangem.datasource.api.referral.ReferralApi import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.utils.RequestHeader.AuthenticationHeader import com.tangem.datasource.utils.RequestHeader.CacheControlHeader @@ -51,24 +50,6 @@ class NetworkModule { .create(TangemTechApi::class.java) } - @Provides - @Singleton - fun provideReferralApi(authProvider: AuthProvider, moshi: Moshi): ReferralApi { - return Retrofit.Builder() - .addConverterFactory( - MoshiConverterFactory.create(moshi), - ) - .baseUrl(PROD_TANGEM_TECH_BASE_URL) - .client( - OkHttpClient.Builder() - .addHeaders(AuthenticationHeader(authProvider)) - .allowLogging() - .build(), - ) - .build() - .create(ReferralApi::class.java) - } - private companion object { const val PROD_TANGEM_TECH_BASE_URL = "https://api.tangem-tech.com/v1/" const val DEV_TANGEM_TECH_BASE_URL = "https://devapi.tangem-tech.com/v1/" diff --git a/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt b/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt index 00925bfd13..f6a4242365 100644 --- a/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt +++ b/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt @@ -1,7 +1,6 @@ package com.tangem.feature.referral.converters -import com.tangem.datasource.api.referral.models.ReferralResponse -import com.tangem.datasource.api.referral.models.Token +import com.tangem.datasource.api.tangemTech.models.ReferralResponse import com.tangem.feature.referral.domain.models.DiscountType import com.tangem.feature.referral.domain.models.ReferralData import com.tangem.feature.referral.domain.models.ReferralInfo @@ -46,9 +45,9 @@ class ReferralConverter @Inject constructor() : Converter { +private class TokenConverter : Converter { - override fun convert(value: Token): TokenData { + override fun convert(value: ReferralResponse.Conditions.Award.Token): TokenData { return TokenData( id = value.id, name = value.name, diff --git a/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt b/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt index bf2387f070..371254979a 100644 --- a/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt +++ b/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt @@ -1,7 +1,7 @@ package com.tangem.feature.referral.data -import com.tangem.datasource.api.referral.ReferralApi -import com.tangem.datasource.api.referral.models.StartReferralBody +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.api.tangemTech.models.StartReferralBody import com.tangem.feature.referral.converters.ReferralConverter import com.tangem.feature.referral.domain.ReferralRepository import com.tangem.feature.referral.domain.models.ReferralData @@ -10,7 +10,7 @@ import kotlinx.coroutines.withContext import javax.inject.Inject internal class ReferralRepositoryImpl @Inject constructor( - private val referralApi: ReferralApi, + private val referralApi: TangemTechApi, private val referralConverter: ReferralConverter, private val coroutineDispatcher: CoroutineDispatcherProvider, ) : ReferralRepository { diff --git a/features/referral/data/src/main/java/com/tangem/feature/referral/di/ReferralRepositoryModule.kt b/features/referral/data/src/main/java/com/tangem/feature/referral/di/ReferralRepositoryModule.kt index 7a88c423cc..1f723fe16f 100644 --- a/features/referral/data/src/main/java/com/tangem/feature/referral/di/ReferralRepositoryModule.kt +++ b/features/referral/data/src/main/java/com/tangem/feature/referral/di/ReferralRepositoryModule.kt @@ -1,6 +1,6 @@ package com.tangem.feature.referral.di -import com.tangem.datasource.api.referral.ReferralApi +import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.feature.referral.converters.ReferralConverter import com.tangem.feature.referral.data.ReferralRepositoryImpl import com.tangem.feature.referral.domain.ReferralRepository @@ -18,12 +18,14 @@ class ReferralRepositoryModule { @Provides @Singleton fun provideReferralRepository( - referralApi: ReferralApi, + tangemTechApi: TangemTechApi, referralConverter: ReferralConverter, coroutineDispatcherProvider: CoroutineDispatcherProvider, ): ReferralRepository { return ReferralRepositoryImpl( - referralApi, referralConverter, coroutineDispatcherProvider, + referralApi = tangemTechApi, + referralConverter = referralConverter, + coroutineDispatcher = coroutineDispatcherProvider, ) } } \ No newline at end of file From f72b524195237f6b56cbae53920be52d19f25f0b Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 11 Jan 2023 14:57:29 +0300 Subject: [PATCH 071/145] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../tangem/tap/domain/configurable/config/ConfigManager.kt | 4 ++-- .../tangem/tap/domain/configurable/config/JsonModels.kt | 7 ++++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 897f0f7c40..93150ef69d 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 897f0f7c402fd53c5ee1fa4ab601ecd4e43ff389 +Subproject commit 93150ef69daf244a92266d7cdfbc9294b745b4e2 diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt index 13563d3c1b..989d16359e 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt @@ -103,7 +103,7 @@ class ConfigManager { tronGridApiKey = values.tronGridApiKey, saltPayAuthToken = values.saltPay.credentials.token, ), - appsFlyerDevKey = values.appsFlyerDevKey, + appsFlyerDevKey = values.appsFlyer.appsFlyerDevKey, amplitudeApiKey = values.amplitudeApiKey, shopify = values.shopifyShop, zendesk = values.zendesk, @@ -132,7 +132,7 @@ class ConfigManager { infuraProjectId = values.infuraProjectId, saltPayAuthToken = values.saltPay.credentials.token, ), - appsFlyerDevKey = values.appsFlyerDevKey, + appsFlyerDevKey = values.appsFlyer.appsFlyerDevKey, amplitudeApiKey = values.amplitudeApiKey, shopify = values.shopifyShop, zendesk = values.zendesk, diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt index 1966ac9303..77a1f0d6cc 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt @@ -29,7 +29,7 @@ class ConfigValueModel( val bscQuiknodeApiKey: String, val blockcypherTokens: Set?, val infuraProjectId: String?, - val appsFlyerDevKey: String, + val appsFlyer: AppsFlyer, val shopifyShop: ShopifyShop?, val zendesk: ZendeskConfig?, val saltPay: SaltPayConfig, @@ -37,6 +37,11 @@ class ConfigValueModel( val amplitudeApiKey: String, ) +data class AppsFlyer( + val appsFlyerDevKey: String, + val appsFlyerAppID: String, +) + class ConfigModel( val features: FeatureModel?, val configValues: ConfigValueModel?, From c6dac76deba6eaa1a8bcce6e06ac52c34d1b0a60 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 12 Jan 2023 17:02:16 +0400 Subject: [PATCH 072/145] Updated on 2026-08-14 --- .../saltPay/SaltPayActivationManager.kt | 56 ++++++++--- .../redux/OnboardingSaltPayMiddleware.kt | 34 +------ .../api/paymentology/PaymentologyApi.kt | 31 +++--- .../paymentology/PaymentologyApiService.kt | 71 ++++---------- .../datasource/api/paymentology/Requests.kt | 91 ----------------- .../datasource/api/paymentology/Responses.kt | 97 ------------------- .../request/CheckRegistrationRequests.kt | 15 +++ .../models/request/RegisterKYCRequest.kt | 33 +++++++ .../models/request/RegisterWalletRequest.kt | 44 +++++++++ .../models/response/AttestationResponse.kt | 33 +++++++ .../paymentology/models/response/KYCStatus.kt | 10 ++ .../models/response/RegisterWalletResponse.kt | 9 ++ .../models/response/RegistrationResponse.kt | 25 +++++ .../models/response/ResponseError.kt | 16 +++ .../com/tangem/datasource/di/NetworkModule.kt | 2 + .../domain/redux/global/DomainGlobalState.kt | 5 +- 16 files changed, 269 insertions(+), 303 deletions(-) delete mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/paymentology/Requests.kt delete mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/paymentology/Responses.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/request/CheckRegistrationRequests.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/request/RegisterKYCRequest.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/request/RegisterWalletRequest.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/response/AttestationResponse.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/response/KYCStatus.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/response/RegisterWalletResponse.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/response/RegistrationResponse.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/response/ResponseError.kt diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt index 187425c2af..9085764bf1 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt @@ -9,20 +9,25 @@ import com.tangem.blockchain.common.TransactionSigner import com.tangem.blockchain.extensions.successOr import com.tangem.common.extensions.guard import com.tangem.common.extensions.isZero +import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result +import com.tangem.common.services.performRequest +import com.tangem.datasource.api.paymentology.PaymentologyApiService +import com.tangem.datasource.api.paymentology.models.request.CheckRegistrationRequests +import com.tangem.datasource.api.paymentology.models.request.RegisterKYCRequest +import com.tangem.datasource.api.paymentology.models.request.RegisterWalletRequest +import com.tangem.datasource.api.paymentology.models.response.AttestationResponse +import com.tangem.datasource.api.paymentology.models.response.RegisterWalletResponse +import com.tangem.datasource.api.paymentology.models.response.RegistrationResponse +import com.tangem.datasource.api.paymentology.models.response.tryExtractError import com.tangem.domain.common.extensions.successOr import com.tangem.domain.common.util.UserWalletId -import com.tangem.datasource.api.paymentology.AttestationResponse -import com.tangem.datasource.api.paymentology.PaymentologyApiService -import com.tangem.datasource.api.paymentology.RegisterKYCRequest -import com.tangem.datasource.api.paymentology.RegisterWalletRequest -import com.tangem.datasource.api.paymentology.RegisterWalletResponse -import com.tangem.datasource.api.paymentology.RegistrationResponse -import com.tangem.datasource.api.paymentology.tryExtractError import com.tangem.operations.attestation.AttestWalletKeyResponse import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.domain.getFirstToken import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import java.math.BigDecimal /** @@ -55,14 +60,30 @@ class SaltPayActivationManager( } suspend fun registerKYC(): Result { - return when (val result = paymentologyService.registerKYC(makeRegisterKYCRequest())) { + val result = withContext(Dispatchers.IO) { + performRequest { paymentologyService.api.registerKYC(makeRegisterKYCRequest()) } + } + return when (result) { is Result.Success -> Result.Success(Unit) is Result.Failure -> result } } suspend fun checkRegistration(): Result { - val response: RegistrationResponse = paymentologyService.checkRegistration(cardId, cardPublicKey) + val response: RegistrationResponse = withContext(Dispatchers.IO) { + performRequest { + paymentologyService.api.checkRegistration( + request = CheckRegistrationRequests( + requests = listOf( + CheckRegistrationRequests.Item( + cardId = cardId, + publicKey = cardPublicKey.toHexString(), + ), + ), + ), + ) + } + } .successOr { return it } .tryExtractError() .successOr { return it } @@ -80,7 +101,16 @@ class SaltPayActivationManager( } suspend fun requestAttestationChallenge(): Result { - return paymentologyService.requestAttestationChallenge(cardId, cardPublicKey) + return withContext(Dispatchers.IO) { + performRequest { + paymentologyService.api.requestAttestationChallenge( + request = CheckRegistrationRequests.Item( + cardId = cardId, + publicKey = cardPublicKey.toHexString(), + ), + ) + } + } .successOr { return it } .tryExtractError() } @@ -105,7 +135,9 @@ class SaltPayActivationManager( cardSignature = attestResponse.cardSignature ?: byteArrayOf(), pin = pinCode, ) - return paymentologyService.registerWallet(request) + return withContext(Dispatchers.IO) { + performRequest { paymentologyService.api.registerWallet(request) } + } .successOr { return it } .tryExtractError() } @@ -163,7 +195,7 @@ class SaltPayActivationManager( companion object { fun stub(): SaltPayActivationManager = SaltPayActivationManager( kycProvider = SaltPayConfig.stub().kycProvider, - paymentologyService = PaymentologyApiService.stub(), + paymentologyService = PaymentologyApiService, gnosisRegistrator = GnosisRegistrator.stub(), cardId = "", cardPublicKey = byteArrayOf(), diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt index ce3390eba1..81d7ad4138 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt @@ -6,11 +6,11 @@ import com.tangem.blockchain.extensions.successOr import com.tangem.common.Filter import com.tangem.common.extensions.guard import com.tangem.common.services.Result +import com.tangem.core.analytics.Analytics +import com.tangem.datasource.api.paymentology.models.response.KYCStatus +import com.tangem.datasource.api.paymentology.models.response.RegistrationResponse import com.tangem.domain.common.extensions.successOr import com.tangem.domain.common.extensions.withMainContext -import com.tangem.datasource.api.paymentology.KYCStatus -import com.tangem.datasource.api.paymentology.RegistrationResponse -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchOnMain @@ -412,34 +412,6 @@ private suspend fun checkGasIfNeeded( return result } -@Throws(SaltPayActivationError::class) -fun RegistrationResponse.Item.toSaltPayStep(currentStep: SaltPayActivationStep): SaltPayActivationStep { - return when { - passed != true -> throw SaltPayActivationError.CardNotPassed(this.error) - disabledByAdmin == true -> throw SaltPayActivationError.CardDisabled(this.error) - - // go to claim screen - active == true -> SaltPayActivationStep.Claim - - // pinSet is false, go toPin screen - pinSet == false -> SaltPayActivationStep.NeedPin - - kycStatus != null -> { - when (kycStatus) { - KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayActivationStep.KycIntro - KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting - KYCStatus.CORRECTION_REQUESTED, KYCStatus.REJECTED -> SaltPayActivationStep.KycReject - KYCStatus.APPROVED -> SaltPayActivationStep.Claim - null -> throw UnsupportedOperationException() - } - } - // kycDate is set, go to kyc waiting screen - kycDate != null -> SaltPayActivationStep.KycWaiting - - else -> SaltPayActivationStep.KycIntro - } -} - @Throws(SaltPayActivationError::class) private fun assertPinValid(pin: String, pinLength: Int) { val array = pin.toCharArray() diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/PaymentologyApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/PaymentologyApi.kt index de7c678d3b..d3d092493f 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/PaymentologyApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/PaymentologyApi.kt @@ -1,39 +1,38 @@ package com.tangem.datasource.api.paymentology +import com.tangem.datasource.api.paymentology.models.request.CheckRegistrationRequests +import com.tangem.datasource.api.paymentology.models.request.RegisterKYCRequest +import com.tangem.datasource.api.paymentology.models.request.RegisterWalletRequest +import com.tangem.datasource.api.paymentology.models.response.AttestationResponse +import com.tangem.datasource.api.paymentology.models.response.RegisterWalletResponse +import com.tangem.datasource.api.paymentology.models.response.RegistrationResponse import retrofit2.http.Body import retrofit2.http.Headers import retrofit2.http.POST /** + * Interface of Paymentology Api. + * + * IMPORTANT: Cannot replace [Headers] annotations with OkHttpClient.addHeader(), because OkHttp adds encoding to + * content type value. But Paymentology API expects a value without the charset. + * [REDACTED_AUTHOR] */ interface PaymentologyApi { @Headers("Content-Type: application/json") @POST("card/verify") - suspend fun checkRegistration( - @Body request: CheckRegistrationRequests, - ): RegistrationResponse + suspend fun checkRegistration(@Body request: CheckRegistrationRequests): RegistrationResponse @Headers("Content-Type: application/json") @POST("card/get_challenge") - suspend fun requestAttestationChallenge( - @Body request: CheckRegistrationRequests.Item, - ): AttestationResponse + suspend fun requestAttestationChallenge(@Body request: CheckRegistrationRequests.Item): AttestationResponse @Headers("Content-Type: application/json") @POST("card/set_pin") - suspend fun registerWallet( - @Body request: RegisterWalletRequest, - ): RegisterWalletResponse + suspend fun registerWallet(@Body request: RegisterWalletRequest): RegisterWalletResponse @Headers("Content-Type: application/json") @POST("card/kyc") - suspend fun registerKYC( - @Body request: RegisterKYCRequest, - ): RegisterWalletResponse - - companion object { - val baseUrl: String = "https://paymentologygate.oa.r.appspot.com/" - } + suspend fun registerKYC(@Body request: RegisterKYCRequest): RegisterWalletResponse } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/PaymentologyApiService.kt b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/PaymentologyApiService.kt index f46a17b36d..f5dad3a728 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/PaymentologyApiService.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/PaymentologyApiService.kt @@ -1,63 +1,30 @@ package com.tangem.datasource.api.paymentology -import com.tangem.common.extensions.toHexString -import com.tangem.common.services.Result -import com.tangem.common.services.performRequest import com.tangem.datasource.api.common.MoshiConverter -import com.tangem.datasource.api.common.createRetrofitInstance -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext +import com.tangem.datasource.utils.allowLogging +import okhttp3.OkHttpClient +import retrofit2.Retrofit /** [REDACTED_AUTHOR] */ -class PaymentologyApiService( - private val logEnabled: Boolean, -) { - private val api = createRetrofitInstance( - baseUrl = PaymentologyApi.baseUrl, - converterFactory = MoshiConverter.createFactory(MoshiConverter.sdkMoshi()), - logEnabled = logEnabled, - ).create(PaymentologyApi::class.java) +//TODO("Remove after removing Redux") +@Deprecated("Use PaymentologyApi") +object PaymentologyApiService { + val api = createApi() - suspend fun checkRegistration( - cardId: String, - publicKey: ByteArray, - ): Result = withContext(Dispatchers.IO) { - val requestItem = CheckRegistrationRequests.Item(cardId, publicKey.toHexString()) - val request = CheckRegistrationRequests(listOf(requestItem)) - performRequest { - api.checkRegistration(request) - } - } + private const val PAYMENTOLOGY_BASE_URL: String = "https://paymentologygate.oa.r.appspot.com/" - suspend fun requestAttestationChallenge( - cardId: String, - publicKey: ByteArray, - ): Result = withContext(Dispatchers.IO) { - val requestItem = CheckRegistrationRequests.Item(cardId, publicKey.toHexString()) - performRequest { - api.requestAttestationChallenge(requestItem) - } - } - - suspend fun registerWallet( - request: RegisterWalletRequest, - ): Result = withContext(Dispatchers.IO) { - performRequest { - api.registerWallet(request) - } - } - - suspend fun registerKYC( - request: RegisterKYCRequest, - ): Result = withContext(Dispatchers.IO) { - performRequest { - api.registerKYC(request) - } - } - - companion object { - fun stub(): PaymentologyApiService = PaymentologyApiService(false) + private fun createApi(): PaymentologyApi { + return Retrofit.Builder() + .addConverterFactory(MoshiConverter.createFactory(MoshiConverter.sdkMoshi())) + .baseUrl(PAYMENTOLOGY_BASE_URL) + .client( + OkHttpClient.Builder() + .allowLogging() + .build(), + ) + .build() + .create(PaymentologyApi::class.java) } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/Requests.kt b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/Requests.kt deleted file mode 100644 index cb0206934e..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/Requests.kt +++ /dev/null @@ -1,91 +0,0 @@ -package com.tangem.datasource.api.paymentology - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass -import com.tangem.common.extensions.calculateHashCode - -/** -[REDACTED_AUTHOR] - */ -data class CheckRegistrationRequests( - val requests: List, -) { - @JsonClass(generateAdapter = true) - data class Item( - @Json(name = "CID") - val cardId: String = "", - val publicKey: String = "", - ) -} - -data class RegisterWalletRequest( - @Json(name = "CID") - val cardId: String, - val publicKey: ByteArray, - val walletPublicKey: ByteArray, - val walletSalt: ByteArray, - val walletSignature: ByteArray, - val cardSalt: ByteArray, - val cardSignature: ByteArray, - @Json(name = "PIN") - val pin: String, -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as RegisterWalletRequest - - if (cardId != other.cardId) return false - if (!publicKey.contentEquals(other.publicKey)) return false - if (!walletPublicKey.contentEquals(other.walletPublicKey)) return false - if (!walletSalt.contentEquals(other.walletSalt)) return false - if (!walletSignature.contentEquals(other.walletSignature)) return false - if (!cardSalt.contentEquals(other.cardSalt)) return false - if (!cardSignature.contentEquals(other.cardSignature)) return false - if (pin != other.pin) return false - - return true - } - - override fun hashCode(): Int = calculateHashCode( - cardId.hashCode(), - publicKey.contentHashCode(), - walletPublicKey.contentHashCode(), - walletSalt.contentHashCode(), - walletSignature.contentHashCode(), - cardSalt.contentHashCode(), - cardSignature.contentHashCode(), - pin.hashCode(), - ) -} - -data class RegisterKYCRequest( - @Json(name = "CID") - val cardId: String, - val publicKey: ByteArray, - val kycProvider: String, - val kycRefId: String, -) { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as RegisterKYCRequest - - if (cardId != other.cardId) return false - if (!publicKey.contentEquals(other.publicKey)) return false - if (kycProvider != other.kycProvider) return false - if (kycRefId != other.kycRefId) return false - - return true - } - - override fun hashCode(): Int = calculateHashCode( - cardId.hashCode(), - publicKey.contentHashCode(), - kycProvider.hashCode(), - kycRefId.hashCode(), - ) -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/Responses.kt b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/Responses.kt deleted file mode 100644 index d62630b73e..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/Responses.kt +++ /dev/null @@ -1,97 +0,0 @@ -package com.tangem.datasource.api.paymentology - -import com.squareup.moshi.Json -import com.tangem.common.extensions.calculateHashCode -import com.tangem.common.services.Result - -/** -[REDACTED_AUTHOR] - */ -interface ResponseError { - val success: Boolean - val error: String? - val errorCode: Int? - - fun makeErrorMessage(): String { - return error ?: "unknown error" - } -} - -inline fun ResponseError.tryExtractError(): Result = when (success) { - true -> Result.Success(this as T) - else -> Result.Failure(Throwable(makeErrorMessage())) -} - -data class RegistrationResponse( - val results: List = listOf(), - override val success: Boolean, - override val error: String?, - override val errorCode: Int?, -) : ResponseError { - - data class Item( - @Json(name = "CID") - val cardId: String, - val passed: Boolean?, - val active: Boolean?, - @Json(name = "pin_set") - val pinSet: Boolean?, - @Json(name = "blockchain_init") - val blockchainInit: Boolean?, - @Json(name = "kyc_passed") - val kycPassed: Boolean?, - @Json(name = "kyc_provider") - val kycProvider: String?, - @Json(name = "kyc_date") - val kycDate: String?, - @Json(name = "kyc_status") - val kycStatus: KYCStatus?, - @Json(name = "disabled_by_admin") - val disabledByAdmin: Boolean?, - val error: String?, - ) -} - -enum class KYCStatus { - NOT_STARTED, - STARTED, - WAITING_FOR_APPROVAL, - CORRECTION_REQUESTED, - REJECTED, - APPROVED, -} - -data class AttestationResponse( - val challenge: ByteArray?, - override val success: Boolean, - override val error: String?, - override val errorCode: Int?, -) : ResponseError { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as AttestationResponse - - if (!challenge.contentEquals(other.challenge)) return false - if (success != other.success) return false - if (error != other.error) return false - if (errorCode != other.errorCode) return false - - return true - } - - override fun hashCode(): Int = calculateHashCode( - challenge.contentHashCode(), - success.hashCode(), - error.hashCode(), - errorCode.hashCode(), - ) -} - -data class RegisterWalletResponse( - override val success: Boolean, - override val error: String?, - override val errorCode: Int?, -) : ResponseError \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/request/CheckRegistrationRequests.kt b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/request/CheckRegistrationRequests.kt new file mode 100644 index 0000000000..e9c4152928 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/request/CheckRegistrationRequests.kt @@ -0,0 +1,15 @@ +package com.tangem.datasource.api.paymentology.models.request + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +data class CheckRegistrationRequests( + @Json(name = "requests") val requests: List, +) { + + @JsonClass(generateAdapter = true) + data class Item( + @Json(name = "CID") val cardId: String = "", + @Json(name = "publicKey") val publicKey: String = "", + ) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/request/RegisterKYCRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/request/RegisterKYCRequest.kt new file mode 100644 index 0000000000..d22c3e74d3 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/request/RegisterKYCRequest.kt @@ -0,0 +1,33 @@ +package com.tangem.datasource.api.paymentology.models.request + +import com.squareup.moshi.Json +import com.tangem.common.extensions.calculateHashCode + +data class RegisterKYCRequest( + @Json(name = "CID") val cardId: String, + @Json(name = "publicKey") val publicKey: ByteArray, + @Json(name = "kycProvider") val kycProvider: String, + @Json(name = "kycRefId") val kycRefId: String, +) { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as RegisterKYCRequest + + if (cardId != other.cardId) return false + if (!publicKey.contentEquals(other.publicKey)) return false + if (kycProvider != other.kycProvider) return false + if (kycRefId != other.kycRefId) return false + + return true + } + + override fun hashCode(): Int = calculateHashCode( + cardId.hashCode(), + publicKey.contentHashCode(), + kycProvider.hashCode(), + kycRefId.hashCode(), + ) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/request/RegisterWalletRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/request/RegisterWalletRequest.kt new file mode 100644 index 0000000000..ad784d9c04 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/request/RegisterWalletRequest.kt @@ -0,0 +1,44 @@ +package com.tangem.datasource.api.paymentology.models.request + +import com.squareup.moshi.Json +import com.tangem.common.extensions.calculateHashCode + +data class RegisterWalletRequest( + @Json(name = "CID") val cardId: String, + @Json(name = "publicKey") val publicKey: ByteArray, + @Json(name = "walletPublicKey") val walletPublicKey: ByteArray, + @Json(name = "walletSalt") val walletSalt: ByteArray, + @Json(name = "walletSignature") val walletSignature: ByteArray, + @Json(name = "cardSalt") val cardSalt: ByteArray, + @Json(name = "cardSignature") val cardSignature: ByteArray, + @Json(name = "PIN") val pin: String, +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as RegisterWalletRequest + + if (cardId != other.cardId) return false + if (!publicKey.contentEquals(other.publicKey)) return false + if (!walletPublicKey.contentEquals(other.walletPublicKey)) return false + if (!walletSalt.contentEquals(other.walletSalt)) return false + if (!walletSignature.contentEquals(other.walletSignature)) return false + if (!cardSalt.contentEquals(other.cardSalt)) return false + if (!cardSignature.contentEquals(other.cardSignature)) return false + if (pin != other.pin) return false + + return true + } + + override fun hashCode(): Int = calculateHashCode( + cardId.hashCode(), + publicKey.contentHashCode(), + walletPublicKey.contentHashCode(), + walletSalt.contentHashCode(), + walletSignature.contentHashCode(), + cardSalt.contentHashCode(), + cardSignature.contentHashCode(), + pin.hashCode(), + ) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/response/AttestationResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/response/AttestationResponse.kt new file mode 100644 index 0000000000..170e1caf92 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/response/AttestationResponse.kt @@ -0,0 +1,33 @@ +package com.tangem.datasource.api.paymentology.models.response + +import com.squareup.moshi.Json +import com.tangem.common.extensions.calculateHashCode + +data class AttestationResponse( + @Json(name = "challenge") val challenge: ByteArray?, + @Json(name = "success") override val success: Boolean, + @Json(name = "error") override val error: String?, + @Json(name = "errorCode") override val errorCode: Int?, +) : ResponseError { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as AttestationResponse + + if (!challenge.contentEquals(other.challenge)) return false + if (success != other.success) return false + if (error != other.error) return false + if (errorCode != other.errorCode) return false + + return true + } + + override fun hashCode(): Int = calculateHashCode( + challenge.contentHashCode(), + success.hashCode(), + error.hashCode(), + errorCode.hashCode(), + ) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/response/KYCStatus.kt b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/response/KYCStatus.kt new file mode 100644 index 0000000000..4ce64674d0 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/response/KYCStatus.kt @@ -0,0 +1,10 @@ +package com.tangem.datasource.api.paymentology.models.response + +enum class KYCStatus { + NOT_STARTED, + STARTED, + WAITING_FOR_APPROVAL, + CORRECTION_REQUESTED, + REJECTED, + APPROVED, +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/response/RegisterWalletResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/response/RegisterWalletResponse.kt new file mode 100644 index 0000000000..d2a1edac8d --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/response/RegisterWalletResponse.kt @@ -0,0 +1,9 @@ +package com.tangem.datasource.api.paymentology.models.response + +import com.squareup.moshi.Json + +data class RegisterWalletResponse( + @Json(name = "success") override val success: Boolean, + @Json(name = "error") override val error: String?, + @Json(name = "errorCode") override val errorCode: Int?, +) : ResponseError \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/response/RegistrationResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/response/RegistrationResponse.kt new file mode 100644 index 0000000000..83c0a03822 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/response/RegistrationResponse.kt @@ -0,0 +1,25 @@ +package com.tangem.datasource.api.paymentology.models.response + +import com.squareup.moshi.Json + +data class RegistrationResponse( + @Json(name = "results") val results: List = listOf(), + @Json(name = "success") override val success: Boolean, + @Json(name = "error") override val error: String?, + @Json(name = "errorCode") override val errorCode: Int?, +) : ResponseError { + + data class Item( + @Json(name = "CID") val cardId: String, + @Json(name = "passed") val passed: Boolean?, + @Json(name = "active") val active: Boolean?, + @Json(name = "pin_set") val pinSet: Boolean?, + @Json(name = "blockchain_init") val blockchainInit: Boolean?, + @Json(name = "kyc_passed") val kycPassed: Boolean?, + @Json(name = "kyc_provider") val kycProvider: String?, + @Json(name = "kyc_date") val kycDate: String?, + @Json(name = "kyc_status") val kycStatus: KYCStatus?, + @Json(name = "disabled_by_admin") val disabledByAdmin: Boolean?, + @Json(name = "error") val error: String?, + ) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/response/ResponseError.kt b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/response/ResponseError.kt new file mode 100644 index 0000000000..3bead696c9 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/models/response/ResponseError.kt @@ -0,0 +1,16 @@ +package com.tangem.datasource.api.paymentology.models.response + +import com.tangem.common.services.Result + +interface ResponseError { + val success: Boolean + val error: String? + val errorCode: Int? + + fun makeErrorMessage(): String = error ?: "unknown error" +} + +inline fun ResponseError.tryExtractError(): Result = when (success) { + true -> Result.Success(this as T) + else -> Result.Failure(Throwable(makeErrorMessage())) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index 42434bf7d4..f339b5636b 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -53,5 +53,7 @@ class NetworkModule { private companion object { const val PROD_TANGEM_TECH_BASE_URL = "https://api.tangem-tech.com/v1/" const val DEV_TANGEM_TECH_BASE_URL = "https://devapi.tangem-tech.com/v1/" + + private const val PAYMENTOLOGY_BASE_URL: String = "https://paymentologygate.oa.r.appspot.com/" } } \ No newline at end of file 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 8a3bd3f4ed..c71c7da659 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 @@ -3,7 +3,6 @@ package com.tangem.domain.redux.global import com.tangem.datasource.api.paymentology.PaymentologyApiService import com.tangem.datasource.api.tangemTech.TangemTechService import com.tangem.domain.DomainDialog -import com.tangem.domain.common.LogConfig import com.tangem.domain.common.ScanResponse /** @@ -20,7 +19,5 @@ data class DomainGlobalState( data class NetworkServices( val tangemTechService: TangemTechService = TangemTechService, - val paymentologyService: PaymentologyApiService = PaymentologyApiService( - LogConfig.network.paymentologyApiService, - ), + val paymentologyService: PaymentologyApiService = PaymentologyApiService, ) From fa2d958ffffac639007c672b953776c5e323da3c Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 12 Jan 2023 16:54:51 +0300 Subject: [PATCH 073/145] Updated on 2026-08-14 --- .../tap/common/redux/global/GlobalReducer.kt | 1 + .../middlewares/TradeCryptoMiddleware.kt | 2 +- .../com/tangem/tap/proxy/AppStateHolder.kt | 4 +- .../tangem/tap/proxy/UserWalletManagerImpl.kt | 32 +++++++++++ .../java/com/tangem/utils/FormatExtensions.kt | 48 ++++++++++++++++ .../tangem/feature/swap/SwapRepositoryImpl.kt | 18 ++++-- .../swap/converters/ApproveConverter.kt | 2 +- .../swap/converters/QuotesConverter.kt | 4 +- .../feature/swap/converters/SwapConverter.kt | 4 +- .../swap/converters/TokensConverter.kt | 2 +- .../feature/swap/domain/SwapInteractor.kt | 8 +-- .../feature/swap/domain/SwapInteractorImpl.kt | 57 +++++++++++++++---- .../feature/swap/domain/SwapRepository.kt | 14 +++-- .../swap/domain/cache/SwapDataCache.kt | 8 +-- .../swap/domain/cache/SwapDataCacheImpl.kt | 10 ++-- .../converters/CryptoCurrencyConverter.kt | 2 +- .../domain/models/{data => }/ApproveModel.kt | 2 +- .../swap/domain/models/{data => }/Currency.kt | 2 +- .../domain/models/{data => }/DataError.kt | 2 +- .../feature/swap/domain/models/QuoteModel.kt | 20 +++++++ .../domain/models/{data => }/SwapAmount.kt | 2 +- .../domain/models/{data => }/SwapDataModel.kt | 2 +- .../domain/models/{data => }/SwapState.kt | 8 ++- .../models/{data => }/TransactionModel.kt | 2 +- .../models/{ => cache}/SwapDataHolder.kt | 9 ++- .../{ => data}/AggregatedSwapDataModel.kt | 4 +- .../tangem/feature/swap/ui/StateBuilder.kt | 21 ++++--- .../feature/swap/viewmodels/SwapViewModel.kt | 10 ++-- .../tangem/lib/crypto/UserWalletManager.kt | 16 ++++++ .../lib/crypto/models/ProxyFiatCurrency.kt | 7 +++ 30 files changed, 248 insertions(+), 75 deletions(-) rename features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/{data => }/ApproveModel.kt (88%) rename features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/{data => }/Currency.kt (94%) rename features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/{data => }/DataError.kt (91%) create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/QuoteModel.kt rename features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/{data => }/SwapAmount.kt (95%) rename features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/{data => }/SwapDataModel.kt (89%) rename features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/{data => }/SwapState.kt (66%) rename features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/{data => }/TransactionModel.kt (93%) rename features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/{ => cache}/SwapDataHolder.kt (54%) rename features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/{ => data}/AggregatedSwapDataModel.kt (75%) create mode 100644 libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyFiatCurrency.kt 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 543e734f8f..4dc8390a9c 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 @@ -39,6 +39,7 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde globalState.copy(scanResponse = action.scanResponse) } is GlobalAction.ChangeAppCurrency -> { + appStateHolder.appFiatCurrency = action.appCurrency globalState.copy(appCurrency = action.appCurrency) } is GlobalAction.RestoreAppCurrency.Success -> { 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 4a3dabc865..17be87d7b4 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 @@ -28,7 +28,7 @@ import com.tangem.tap.store import kotlinx.coroutines.launch import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json -import com.tangem.feature.swap.domain.models.data.Currency as SwapCurrency +import com.tangem.feature.swap.domain.models.Currency as SwapCurrency class TradeCryptoMiddleware { fun handle(state: () -> AppState?, action: WalletAction.TradeCryptoAction) { diff --git a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt index 110652b772..ed21a083df 100644 --- a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt @@ -3,6 +3,7 @@ package com.tangem.tap.proxy import com.tangem.TangemSdk import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse +import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.tokens.UserTokensRepository @@ -16,7 +17,7 @@ import javax.inject.Inject * Holds objects from old modules, that missing in DI graph * Object sets manually to use in new modules and [AppStateHolder] proxies its to DI */ -class AppStateHolder @Inject constructor(){ +class AppStateHolder @Inject constructor() { var scanResponse: ScanResponse? = null var walletState: WalletState? = null @@ -26,6 +27,7 @@ class AppStateHolder @Inject constructor(){ var tangemSdk: TangemSdk? = null var walletStoresManager: WalletStoresManager? = null var userWalletsListManager: UserWalletsListManager? = null + var appFiatCurrency: FiatCurrency = FiatCurrency.Default fun getActualCard(): CardDTO? { return scanResponse?.card diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt index 926eeb68b5..9ba2438963 100644 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -3,6 +3,7 @@ package com.tangem.tap.proxy import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationParams import com.tangem.blockchain.common.DerivationStyle +import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.derivationStyle @@ -12,11 +13,14 @@ import com.tangem.lib.crypto.UserWalletManager import com.tangem.lib.crypto.models.Currency import com.tangem.lib.crypto.models.Currency.NativeToken import com.tangem.lib.crypto.models.Currency.NonNativeToken +import com.tangem.lib.crypto.models.ProxyAmount +import com.tangem.lib.crypto.models.ProxyFiatCurrency import com.tangem.tap.domain.extensions.makeWalletManagerForApp import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.redux.WalletAction import org.rekotlin.Action +import java.math.BigDecimal class UserWalletManagerImpl( private val appStateHolder: AppStateHolder, @@ -105,6 +109,28 @@ class UserWalletManagerImpl( } } + override fun getCurrentWalletTokensBalance(networkId: String): Map { + val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + val walletManager = getActualWalletManager(blockchain) + return walletManager.wallet.amounts.map { entry -> + val amount = entry.value + amount.currencySymbol to ProxyAmount( + amount.currencySymbol, + amount.value ?: BigDecimal.ZERO, + amount.decimals, + ) + }.toMap() + } + + override fun getUserAppCurrency(): ProxyFiatCurrency { + val appCurrency = appStateHolder.appFiatCurrency + return ProxyFiatCurrency( + code = appCurrency.code, + name = appCurrency.name, + symbol = appCurrency.symbol, + ) + } + private fun addNativeTokenToWalletAction(token: NativeToken, card: CardDTO): Action { val blockchain = Blockchain.fromNetworkId(token.networkId) val scanResponse = appStateHolder.scanResponse @@ -154,4 +180,10 @@ class UserWalletManagerImpl( private fun createDerivationParams(derivationStyle: DerivationStyle?): DerivationParams? { return derivationStyle?.let { DerivationParams.Default(derivationStyle) } //todo clarify if its need to add Custom } + + private fun getActualWalletManager(blockchain: Blockchain): WalletManager { + val card = requireNotNull(appStateHolder.getActualCard()) { "card not found" } + val blockchainNetwork = BlockchainNetwork(blockchain, card) + return requireNotNull(appStateHolder.walletState?.getWalletManager(blockchainNetwork)) { "no wallet manager found" } + } } \ No newline at end of file diff --git a/core/utils/src/main/java/com/tangem/utils/FormatExtensions.kt b/core/utils/src/main/java/com/tangem/utils/FormatExtensions.kt index 218d035d61..46b5621758 100644 --- a/core/utils/src/main/java/com/tangem/utils/FormatExtensions.kt +++ b/core/utils/src/main/java/com/tangem/utils/FormatExtensions.kt @@ -6,6 +6,7 @@ import java.text.DecimalFormat import java.text.DecimalFormatSymbols import java.util.* +//todo determine where to place this extensions fun BigDecimal.toFormattedString( decimals: Int, roundingMode: RoundingMode = RoundingMode.DOWN, locale: Locale = Locale.US, ): String { @@ -18,4 +19,51 @@ fun BigDecimal.toFormattedString( this.roundingMode = roundingMode } return df.format(this) +} + +fun BigDecimal.toFiatString( + rateValue: BigDecimal, + fiatCurrencyName: String, + formatWithSpaces: Boolean = false +): String { + val fiatValue = rateValue.multiply(this) + return fiatValue.toFormattedFiatValue(fiatCurrencyName, formatWithSpaces) +} + +fun BigDecimal.toFormattedFiatValue( + fiatCurrencyName: String, + formatWithSpaces: Boolean = false +): String { + val fiatValue = this.setScale(2, RoundingMode.HALF_UP) + .let { if (formatWithSpaces) it.formatWithSpaces() else it } + return " ${fiatValue}  $fiatCurrencyName" +} + +fun BigDecimal.formatWithSpaces(): String { + val str = this.toString() + var integerStr = str.substringBefore('.') + val reminderStr = str.substringAfter('.') + val packets = arrayListOf() + + var index: Int = integerStr.length + while (0 < index) { + if (index <= 3) { + packets.add(integerStr) + break + } + index -= 3 + packets.add(integerStr.substring(startIndex = index)) + integerStr = integerStr.substring(startIndex = 0, endIndex = index) + } + + return buildString { + packets.reversed().forEachIndexed { index, packet -> + append(packet) + if (index != packets.lastIndex) append(' ') + } + if (reminderStr.isNotBlank()) { + append('.') + append(reminderStr) + } + } } \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt index 36e89a8e56..10cdf4a902 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt @@ -10,12 +10,12 @@ import com.tangem.feature.swap.converters.QuotesConverter import com.tangem.feature.swap.converters.SwapConverter import com.tangem.feature.swap.converters.TokensConverter import com.tangem.feature.swap.domain.SwapRepository -import com.tangem.feature.swap.domain.models.AggregatedSwapDataModel -import com.tangem.feature.swap.domain.models.data.ApproveModel -import com.tangem.feature.swap.domain.models.data.Currency -import com.tangem.feature.swap.domain.models.data.SwapDataModel -import com.tangem.feature.swap.domain.models.data.SwapState.QuoteModel -import com.tangem.feature.swap.domain.models.data.mapErrors +import com.tangem.feature.swap.domain.models.ApproveModel +import com.tangem.feature.swap.domain.models.Currency +import com.tangem.feature.swap.domain.models.QuoteModel +import com.tangem.feature.swap.domain.models.SwapDataModel +import com.tangem.feature.swap.domain.models.data.AggregatedSwapDataModel +import com.tangem.feature.swap.domain.models.mapErrors import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext import javax.inject.Inject @@ -31,6 +31,12 @@ internal class SwapRepositoryImpl @Inject constructor( private val coroutineDispatcher: CoroutineDispatcherProvider, ) : SwapRepository { + override suspend fun getRates(currencyId: String, tokenIds: List): Map { + return withContext(coroutineDispatcher.io) { + tangemTechApi.getRates(currencyId.lowercase(), tokenIds.joinToString(",")).rates + } + } + override suspend fun getExchangeableTokens(networkId: String): List { return withContext(coroutineDispatcher.io) { tokensConverter.convertList(tangemTechApi.getCoins(exchangeable = true, networkIds = networkId).coins) diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ApproveConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ApproveConverter.kt index c5d5d0e600..4d3574869d 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ApproveConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ApproveConverter.kt @@ -1,7 +1,7 @@ package com.tangem.feature.swap.converters import com.tangem.datasource.api.oneinch.models.ApproveCalldataResponse -import com.tangem.feature.swap.domain.models.data.ApproveModel +import com.tangem.feature.swap.domain.models.ApproveModel import com.tangem.utils.converter.Converter import javax.inject.Inject diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt index 4cb95d4c03..57f0569e9c 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt @@ -1,8 +1,8 @@ package com.tangem.feature.swap.converters import com.tangem.datasource.api.oneinch.models.QuoteResponse -import com.tangem.feature.swap.domain.models.data.SwapState.QuoteModel -import com.tangem.feature.swap.domain.models.data.createFromAmountWithOffset +import com.tangem.feature.swap.domain.models.QuoteModel +import com.tangem.feature.swap.domain.models.createFromAmountWithOffset import com.tangem.utils.converter.Converter import javax.inject.Inject diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt index 87dec263da..ebe2d92a13 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt @@ -2,8 +2,8 @@ package com.tangem.feature.swap.converters import com.tangem.datasource.api.oneinch.models.SwapResponse import com.tangem.datasource.api.oneinch.models.TransactionDto -import com.tangem.feature.swap.domain.models.data.SwapDataModel -import com.tangem.feature.swap.domain.models.data.TransactionModel +import com.tangem.feature.swap.domain.models.SwapDataModel +import com.tangem.feature.swap.domain.models.TransactionModel import com.tangem.utils.converter.Converter import javax.inject.Inject diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/TokensConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/TokensConverter.kt index c78b8b3638..8330c0c5f8 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/TokensConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/TokensConverter.kt @@ -1,7 +1,7 @@ package com.tangem.feature.swap.converters import com.tangem.datasource.api.tangemTech.models.CoinsResponse -import com.tangem.feature.swap.domain.models.data.Currency +import com.tangem.feature.swap.domain.models.Currency import com.tangem.utils.converter.Converter import javax.inject.Inject diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt index 2741292876..421b21d439 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt @@ -1,15 +1,13 @@ package com.tangem.feature.swap.domain -import com.tangem.feature.swap.domain.models.data.Currency -import com.tangem.feature.swap.domain.models.data.SwapAmount -import com.tangem.feature.swap.domain.models.data.SwapState +import com.tangem.feature.swap.domain.models.Currency +import com.tangem.feature.swap.domain.models.SwapAmount +import com.tangem.feature.swap.domain.models.SwapState interface SwapInteractor { suspend fun getTokensToSwap(networkId: String): List - suspend fun getTokenBalance(tokenId: String): String - @Throws(IllegalStateException::class) suspend fun givePermissionToSwap(tokenToApprove: Currency) diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 9ffbf013a1..482f6c34a5 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -2,14 +2,17 @@ package com.tangem.feature.swap.domain import com.tangem.feature.swap.domain.cache.SwapDataCache import com.tangem.feature.swap.domain.converters.CryptoCurrencyConverter -import com.tangem.feature.swap.domain.models.data.Currency -import com.tangem.feature.swap.domain.models.data.DataError -import com.tangem.feature.swap.domain.models.data.SwapAmount -import com.tangem.feature.swap.domain.models.data.SwapState -import com.tangem.feature.swap.domain.models.data.TransactionModel -import com.tangem.feature.swap.domain.models.data.toStringWithRightOffset +import com.tangem.feature.swap.domain.models.Currency +import com.tangem.feature.swap.domain.models.DataError +import com.tangem.feature.swap.domain.models.QuoteModel +import com.tangem.feature.swap.domain.models.SwapAmount +import com.tangem.feature.swap.domain.models.SwapState +import com.tangem.feature.swap.domain.models.TransactionModel +import com.tangem.feature.swap.domain.models.toStringWithRightOffset import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.UserWalletManager +import com.tangem.utils.toFiatString +import com.tangem.utils.toFormattedString import java.math.BigDecimal import javax.inject.Inject @@ -32,10 +35,6 @@ internal class SwapInteractorImpl @Inject constructor( } } - override suspend fun getTokenBalance(tokenId: String): String { - TODO("Not yet implemented") - } - override suspend fun givePermissionToSwap(tokenToApprove: Currency) { cache.getNetworkId()?.let { networkId -> if (tokenToApprove is Currency.NonNativeToken) { @@ -85,7 +84,7 @@ internal class SwapInteractorImpl @Inject constructor( fromCurrency = fromToken, toCurrency = toToken, ) - return quoteDataModel + return updateBalances(networkId, fromToken, toToken, quoteDataModel) } else { return SwapState.SwapError(quotes.error) } @@ -129,6 +128,41 @@ internal class SwapInteractorImpl @Inject constructor( } } + private suspend fun updateBalances( + networkId: String, + fromToken: Currency, + toToken: Currency, + quoteModel: QuoteModel, + ): SwapState.QuotesLoadedState { + val appCurrency = userWalletManager.getUserAppCurrency() + val rates = repository.getRates(appCurrency.code, listOf(fromToken.id, toToken.id)) + val tokensBalance = userWalletManager.getCurrentWalletTokensBalance(networkId) + val fromTokenBalance = tokensBalance[fromToken.symbol]?.let { + it.value.toFormattedString(it.decimals) + } + val toTokenBalance = tokensBalance[toToken.symbol]?.let { + it.value.toFormattedString(it.decimals) + } + return SwapState.QuotesLoadedState( + fromTokenAmount = quoteModel.fromTokenAmount, + toTokenAmount = quoteModel.toTokenAmount, + fromTokenAddress = quoteModel.fromTokenAddress, + toTokenAddress = quoteModel.toTokenAddress, + estimatedGas = quoteModel.estimatedGas, + isAllowedToSpend = quoteModel.isAllowedToSpend, + fromTokenWalletBalance = fromTokenBalance ?: ZERO_BALANCE, + fromTokenFiatBalance = quoteModel.fromTokenAmount.value.toFiatString( + rates[fromToken.id]?.toBigDecimal() ?: BigDecimal.ZERO, + appCurrency.symbol, + ), + toTokenWalletBalance = toTokenBalance ?: ZERO_BALANCE, + toTokenFiatBalance = quoteModel.fromTokenAmount.value.toFiatString( + rates[fromToken.id]?.toBigDecimal() ?: BigDecimal.ZERO, + appCurrency.symbol, + ), + ) + } + private fun getWalletAddress(networkId: String): String { return userWalletManager.getWalletAddress(networkId) } @@ -150,5 +184,6 @@ internal class SwapInteractorImpl @Inject constructor( companion object { private const val DEFAULT_SLIPPAGE = 2 + private const val ZERO_BALANCE = "0" } } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt index 55a973b7e9..7082d123c4 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt @@ -1,13 +1,15 @@ package com.tangem.feature.swap.domain -import com.tangem.feature.swap.domain.models.AggregatedSwapDataModel -import com.tangem.feature.swap.domain.models.data.ApproveModel -import com.tangem.feature.swap.domain.models.data.Currency -import com.tangem.feature.swap.domain.models.data.SwapDataModel -import com.tangem.feature.swap.domain.models.data.SwapState +import com.tangem.feature.swap.domain.models.ApproveModel +import com.tangem.feature.swap.domain.models.Currency +import com.tangem.feature.swap.domain.models.QuoteModel +import com.tangem.feature.swap.domain.models.SwapDataModel +import com.tangem.feature.swap.domain.models.data.AggregatedSwapDataModel interface SwapRepository { + suspend fun getRates(currencyId: String, tokenIds: List): Map + suspend fun getExchangeableTokens(networkId: String): List suspend fun findBestQuote( @@ -15,7 +17,7 @@ interface SwapRepository { fromTokenAddress: String, toTokenAddress: String, amount: String, - ): AggregatedSwapDataModel + ): AggregatedSwapDataModel /** * Returns address of 1inch router that must be trusted diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt index 4318e5dbfc..6acefb6794 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt @@ -1,9 +1,9 @@ package com.tangem.feature.swap.domain.cache -import com.tangem.feature.swap.domain.models.ExchangeCurrencies -import com.tangem.feature.swap.domain.models.data.Currency -import com.tangem.feature.swap.domain.models.data.SwapAmount -import com.tangem.feature.swap.domain.models.data.SwapState.QuoteModel +import com.tangem.feature.swap.domain.models.Currency +import com.tangem.feature.swap.domain.models.QuoteModel +import com.tangem.feature.swap.domain.models.SwapAmount +import com.tangem.feature.swap.domain.models.cache.ExchangeCurrencies interface SwapDataCache { diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt index 2ee0425726..0431b67ff0 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt @@ -1,10 +1,10 @@ package com.tangem.feature.swap.domain.cache -import com.tangem.feature.swap.domain.models.ExchangeCurrencies -import com.tangem.feature.swap.domain.models.SwapDataHolder -import com.tangem.feature.swap.domain.models.data.Currency -import com.tangem.feature.swap.domain.models.data.SwapAmount -import com.tangem.feature.swap.domain.models.data.SwapState.QuoteModel +import com.tangem.feature.swap.domain.models.Currency +import com.tangem.feature.swap.domain.models.QuoteModel +import com.tangem.feature.swap.domain.models.SwapAmount +import com.tangem.feature.swap.domain.models.cache.ExchangeCurrencies +import com.tangem.feature.swap.domain.models.cache.SwapDataHolder class SwapDataCacheImpl : SwapDataCache { diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/converters/CryptoCurrencyConverter.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/converters/CryptoCurrencyConverter.kt index 161f9661a2..68c3895f79 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/converters/CryptoCurrencyConverter.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/converters/CryptoCurrencyConverter.kt @@ -1,6 +1,6 @@ package com.tangem.feature.swap.domain.converters -import com.tangem.feature.swap.domain.models.data.Currency +import com.tangem.feature.swap.domain.models.Currency import com.tangem.utils.converter.Converter class CryptoCurrencyConverter : Converter { diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/ApproveModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ApproveModel.kt similarity index 88% rename from features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/ApproveModel.kt rename to features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ApproveModel.kt index a553a8d4bf..2fce5634f9 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/ApproveModel.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ApproveModel.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.swap.domain.models.data +package com.tangem.feature.swap.domain.models /** * Approve model diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/Currency.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/Currency.kt similarity index 94% rename from features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/Currency.kt rename to features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/Currency.kt index 947c62e7bc..bcd8017e56 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/Currency.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/Currency.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.swap.domain.models.data +package com.tangem.feature.swap.domain.models import kotlinx.serialization.Serializable diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/DataError.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/DataError.kt similarity index 91% rename from features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/DataError.kt rename to features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/DataError.kt index 60e80e094e..def35afd55 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/DataError.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/DataError.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.swap.domain.models.data +package com.tangem.feature.swap.domain.models enum class DataError { NO_ERROR, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/QuoteModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/QuoteModel.kt new file mode 100644 index 0000000000..1d4aa9afdb --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/QuoteModel.kt @@ -0,0 +1,20 @@ +package com.tangem.feature.swap.domain.models + +/** + * Quote model holds data about current amounts of exchange and fees + * + * @property fromTokenAmount amount of token you want to exchange + * @property toTokenAmount amount of token you want to receive + * @property fromTokenAddress address token you want to exchange + * @property toTokenAddress address token you want to receive + * @property estimatedGas fee + * @property isAllowedToSpend does spend allowed + */ +data class QuoteModel( + val fromTokenAmount: SwapAmount, + val toTokenAmount: SwapAmount, + val fromTokenAddress: String, + val toTokenAddress: String, + val estimatedGas: Int, + val isAllowedToSpend: Boolean = false, +) \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapAmount.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapAmount.kt similarity index 95% rename from features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapAmount.kt rename to features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapAmount.kt index 9b5738306a..342ce251e7 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapAmount.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapAmount.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.swap.domain.models.data +package com.tangem.feature.swap.domain.models import com.tangem.utils.toFormattedString import java.math.BigDecimal diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapDataModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataModel.kt similarity index 89% rename from features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapDataModel.kt rename to features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataModel.kt index d7b70ca338..f18018c63e 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapDataModel.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataModel.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.swap.domain.models.data +package com.tangem.feature.swap.domain.models /** * Swap transaction model diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapState.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapState.kt similarity index 66% rename from features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapState.kt rename to features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapState.kt index 6b00f55db1..82bf5808f5 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/SwapState.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapState.kt @@ -1,12 +1,16 @@ -package com.tangem.feature.swap.domain.models.data +package com.tangem.feature.swap.domain.models sealed interface SwapState { - data class QuoteModel( + data class QuotesLoadedState( val fromTokenAmount: SwapAmount, val toTokenAmount: SwapAmount, val fromTokenAddress: String, val toTokenAddress: String, + val fromTokenWalletBalance: String, + val fromTokenFiatBalance: String, + val toTokenWalletBalance: String, + val toTokenFiatBalance: String, val estimatedGas: Int, val isAllowedToSpend: Boolean = false, ) : SwapState diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/TransactionModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/TransactionModel.kt similarity index 93% rename from features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/TransactionModel.kt rename to features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/TransactionModel.kt index 978c7e5ec7..eea39441ca 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/TransactionModel.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/TransactionModel.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.swap.domain.models.data +package com.tangem.feature.swap.domain.models /** * Transaction model diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataHolder.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/cache/SwapDataHolder.kt similarity index 54% rename from features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataHolder.kt rename to features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/cache/SwapDataHolder.kt index e974b51f15..41e159253f 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataHolder.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/cache/SwapDataHolder.kt @@ -1,9 +1,8 @@ -package com.tangem.feature.swap.domain.models +package com.tangem.feature.swap.domain.models.cache -import com.tangem.feature.swap.domain.models.data.Currency -import com.tangem.feature.swap.domain.models.data.SwapAmount -import com.tangem.feature.swap.domain.models.data.SwapState.QuoteModel -import java.math.BigDecimal +import com.tangem.feature.swap.domain.models.Currency +import com.tangem.feature.swap.domain.models.QuoteModel +import com.tangem.feature.swap.domain.models.SwapAmount data class SwapDataHolder( val quoteModel: QuoteModel? = null, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/AggregatedSwapDataModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/AggregatedSwapDataModel.kt similarity index 75% rename from features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/AggregatedSwapDataModel.kt rename to features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/AggregatedSwapDataModel.kt index 2554398d3c..2a7eefc5b3 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/AggregatedSwapDataModel.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/AggregatedSwapDataModel.kt @@ -1,6 +1,6 @@ -package com.tangem.feature.swap.domain.models +package com.tangem.feature.swap.domain.models.data -import com.tangem.feature.swap.domain.models.data.DataError +import com.tangem.feature.swap.domain.models.DataError /** * Model that aggregate data model from repository return with error [DataError] if it exists diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 65a6894659..067ecab8bf 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -1,8 +1,8 @@ package com.tangem.feature.swap.ui -import com.tangem.feature.swap.domain.models.data.Currency -import com.tangem.feature.swap.domain.models.data.SwapState -import com.tangem.feature.swap.domain.models.data.formatToUIRepresentation +import com.tangem.feature.swap.domain.models.Currency +import com.tangem.feature.swap.domain.models.SwapState +import com.tangem.feature.swap.domain.models.formatToUIRepresentation import com.tangem.feature.swap.models.FeeState import com.tangem.feature.swap.models.SwapButton import com.tangem.feature.swap.models.SwapCardData @@ -50,7 +50,7 @@ class StateBuilder { ): SwapStateHolder { return uiStateHolder.copy( sendCardData = SwapCardData( - type = (uiStateHolder.sendCardData.type as TransactionCardType.SendCard), + type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard), amount = uiStateHolder.sendCardData.amount, amountEquivalent = uiStateHolder.sendCardData.amountEquivalent, tokenIconUrl = fromToken.logoUrl, @@ -72,16 +72,19 @@ class StateBuilder { fun createQuotesLoadedState( uiStateHolder: SwapStateHolder, - quoteModel: SwapState.QuoteModel, + quoteModel: SwapState.QuotesLoadedState, fromToken: Currency, onSwapClick: () -> Unit, ): SwapStateHolder { return uiStateHolder.copy( sendCardData = SwapCardData( - type = (uiStateHolder.sendCardData.type as TransactionCardType.SendCard) - .copy(permissionIsGiven = quoteModel.isAllowedToSpend), + type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard) + .copy( + permissionIsGiven = quoteModel.isAllowedToSpend, + balance = quoteModel.fromTokenWalletBalance, + ), amount = quoteModel.fromTokenAmount.formatToUIRepresentation(), - amountEquivalent = uiStateHolder.sendCardData.amountEquivalent, + amountEquivalent = quoteModel.fromTokenFiatBalance, tokenIconUrl = uiStateHolder.sendCardData.tokenIconUrl, tokenCurrency = uiStateHolder.sendCardData.tokenCurrency, canSelectAnotherToken = uiStateHolder.sendCardData.canSelectAnotherToken, @@ -89,7 +92,7 @@ class StateBuilder { receiveCardData = SwapCardData( type = TransactionCardType.ReceiveCard(), amount = quoteModel.toTokenAmount.formatToUIRepresentation(), - amountEquivalent = uiStateHolder.receiveCardData.amountEquivalent, + amountEquivalent = quoteModel.toTokenFiatBalance, tokenIconUrl = uiStateHolder.receiveCardData.tokenIconUrl, tokenCurrency = uiStateHolder.receiveCardData.tokenCurrency, canSelectAnotherToken = uiStateHolder.receiveCardData.canSelectAnotherToken, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index 0609a4e8cf..dfcf661be7 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -8,9 +8,9 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.tangem.feature.swap.domain.SwapInteractor -import com.tangem.feature.swap.domain.models.data.Currency -import com.tangem.feature.swap.domain.models.data.SwapState -import com.tangem.feature.swap.domain.models.data.createFromAmountWithoutOffset +import com.tangem.feature.swap.domain.models.Currency +import com.tangem.feature.swap.domain.models.SwapState +import com.tangem.feature.swap.domain.models.createFromAmountWithoutOffset import com.tangem.feature.swap.models.SwapStateHolder import com.tangem.feature.swap.presentation.SwapFragment import com.tangem.feature.swap.router.SwapRouter @@ -97,7 +97,7 @@ internal class SwapViewModel @Inject constructor( } .onSuccess { swapState -> when (swapState) { - is SwapState.QuoteModel -> { + is SwapState.QuotesLoadedState -> { uiState = stateBuilder.createQuotesLoadedState(uiState, swapState, fromToken) { onSwapClick(toToken, swapState) } @@ -113,7 +113,7 @@ internal class SwapViewModel @Inject constructor( } } - private fun onSwapClick(toToken: Currency, quoteModel: SwapState.QuoteModel) { + private fun onSwapClick(toToken: Currency, quoteModel: SwapState.QuotesLoadedState) { viewModelScope.launch(dispatchers.main) { runCatching { if (!quoteModel.isAllowedToSpend) { diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt index ef60119263..b29cfc2535 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt @@ -1,6 +1,8 @@ package com.tangem.lib.crypto import com.tangem.lib.crypto.models.Currency +import com.tangem.lib.crypto.models.ProxyAmount +import com.tangem.lib.crypto.models.ProxyFiatCurrency /** * Provider for user tokens data @@ -37,4 +39,18 @@ interface UserWalletManager { * @param networkId for currency */ fun getWalletAddress(networkId: String): String + + /** + * Return balances from wallet found by networkId + * + * @param networkId + * @return map of + */ + @Throws(IllegalStateException::class) + fun getCurrentWalletTokensBalance(networkId: String): Map + + /** + * Returns selected app currency + */ + fun getUserAppCurrency(): ProxyFiatCurrency } \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyFiatCurrency.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyFiatCurrency.kt new file mode 100644 index 0000000000..5a46bdef84 --- /dev/null +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyFiatCurrency.kt @@ -0,0 +1,7 @@ +package com.tangem.lib.crypto.models + +data class ProxyFiatCurrency( + val code: String, + val name: String, + val symbol: String, +) \ No newline at end of file From 584d3b593587a6dea89a0e21d18d16662121db93 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 13 Jan 2023 13:58:40 +0400 Subject: [PATCH 074/145] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 2 ++ core/res/src/main/res/values-fr/strings.xml | 2 ++ core/res/src/main/res/values-it/strings.xml | 2 ++ core/res/src/main/res/values-ru/strings.xml | 2 ++ core/res/src/main/res/values/strings.xml | 2 ++ .../com/tangem/core/ui/res/TangemDimens.kt | 1 + .../swap/models/SwapPermissionStateHolder.kt | 25 ++++++++++++------- .../feature/swap/models/SwapStateHolder.kt | 2 +- .../ui/SwapPermissionBottomSheetContent.kt | 6 ++--- .../com/tangem/feature/swap/ui/SwapScreen.kt | 5 ++-- .../feature/swap/ui/SwapScreenContent.kt | 19 ++++++++++++++ 11 files changed, 53 insertions(+), 15 deletions(-) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index c45019e608..e81c23629f 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -207,6 +207,8 @@ Choose token Your tokens Other tokens + Waiting + Transaction in progress… Failed to load the information about the referral program. Please try again later. Failed to load the information about the referral program. Reason: %s. Please try again later. Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 141884f103..7fa8a61c7d 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -207,6 +207,8 @@ Choose token Your tokens Other tokens + Waiting + Transaction in progress… Failed to load the information about the referral program. Please try again later. Failed to load the information about the referral program. Reason: %s. Please try again later. Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml index 191394da45..d183fa7130 100644 --- a/core/res/src/main/res/values-it/strings.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -207,6 +207,8 @@ Choose token Your tokens Other tokens + Waiting + Transaction in progress… Failed to load the information about the referral program. Please try again later. Failed to load the information about the referral program. Reason: %s. Please try again later. Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index ab80bdbeee..1301436efc 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -207,6 +207,8 @@ Выберите токен Ваши токены Другие токены + Подождите + Транзакция в процессе… Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже. Не удалось обработать вашу заявку на участие. Код ошибки: %s. Пожалуйста, попробуйте позже. Если проблема сохранится, вы можете обратиться в техподдержку. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index db71c3e8d1..0824f29b0f 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -207,6 +207,8 @@ Choose token Your tokens Other tokens + Waiting + Transaction in progress… Failed to load the information about the referral program. Please try again later. Failed to load the information about the referral program. Error code: %s. Please try again later. Your participation request could not be processed. Error code: %s. Please try again later. If the problem persists — feel free to contact our support. diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt index 3fdc09bdb6..c97f04e9f0 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt @@ -31,6 +31,7 @@ data class TangemDimens internal constructor( // region Size val size0: Dp = 0.dp, val size0_5: Dp = 0.5.dp, + val size2: Dp = 2.dp, val size4: Dp = 4.dp, val size8: Dp = 8.dp, val size11: Dp = 11.dp, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapPermissionStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapPermissionStateHolder.kt index 76d4ee8587..0fd2f4fef7 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapPermissionStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapPermissionStateHolder.kt @@ -1,14 +1,21 @@ package com.tangem.feature.swap.models -data class SwapPermissionStateHolder( - val currency: String, - val amount: String, - val walletAddress: String, - val spenderAddress: String, - val fee: String, - val approveButton: ApprovePermissionButton, - val cancelButton: CancelPermissionButton, -) +sealed class SwapPermissionState { + + object InProgress : SwapPermissionState() + + object Empty : SwapPermissionState() + + data class ReadyForRequest( + val currency: String, + val amount: String, + val walletAddress: String, + val spenderAddress: String, + val fee: String, + val approveButton: ApprovePermissionButton, + val cancelButton: CancelPermissionButton, + ) : SwapPermissionState() +} data class ApprovePermissionButton( val enabled: Boolean, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt index f8e9dada94..3f78f0a311 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt @@ -9,7 +9,7 @@ data class SwapStateHolder( val fee: FeeState = FeeState.Loaded(), val warnings: List = emptyList(), - val permissionState: SwapPermissionStateHolder? = null, + val permissionState: SwapPermissionState = SwapPermissionState.Empty, val successState: SwapSuccessStateHolder? = null, val selectTokenState: SwapSelectTokenStateHolder? = null, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheetContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheetContent.kt index 0d8f96198f..e81b1da59c 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheetContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheetContent.kt @@ -26,12 +26,12 @@ import com.tangem.core.ui.components.atoms.Hand import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.swap.models.ApprovePermissionButton import com.tangem.feature.swap.models.CancelPermissionButton -import com.tangem.feature.swap.models.SwapPermissionStateHolder +import com.tangem.feature.swap.models.SwapPermissionState import com.tangem.feature.swap.presentation.R @Composable fun SwapPermissionBottomSheetContent( - data: SwapPermissionStateHolder?, + data: SwapPermissionState.ReadyForRequest?, onCancel: () -> Unit, ) { Column( @@ -209,7 +209,7 @@ fun Preview_AgreementBottomSheet_InDarkTheme() { } } -private val previewData = SwapPermissionStateHolder( +private val previewData = SwapPermissionState.ReadyForRequest( currency = "DAI", amount = "∞", walletAddress = "", diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt index e49409bd4e..d93ee56774 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.res.stringResource import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.swap.models.SwapPermissionState import com.tangem.feature.swap.models.SwapStateHolder import com.tangem.feature.swap.presentation.R import kotlinx.coroutines.launch @@ -34,7 +35,7 @@ internal fun SwapScreen(stateHolder: SwapStateHolder) { }, sheetContent = { SwapPermissionBottomSheetContent( - data = stateHolder.permissionState, + data = stateHolder.permissionState as? SwapPermissionState.ReadyForRequest, onCancel = { coroutineScope.launch { bottomSheetScaffoldState.bottomSheetState.collapse() } }, ) }, @@ -51,7 +52,7 @@ internal fun SwapScreen(stateHolder: SwapStateHolder) { onPermissionWarningClick = { coroutineScope.launch { if (bottomSheetScaffoldState.bottomSheetState.isCollapsed - && stateHolder.permissionState != null + && stateHolder.permissionState is SwapPermissionState.ReadyForRequest ) { bottomSheetScaffoldState.bottomSheetState.expand() } else { diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index 41c04fcfbd..a0475d02a6 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -14,6 +14,7 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.Card +import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme @@ -27,6 +28,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.CardWithIcon import com.tangem.core.ui.components.ClickableWarningCard import com.tangem.core.ui.components.Keyboard import com.tangem.core.ui.components.PrimaryButtonIconRight @@ -39,6 +41,7 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.swap.models.FeeState import com.tangem.feature.swap.models.SwapButton import com.tangem.feature.swap.models.SwapCardData +import com.tangem.feature.swap.models.SwapPermissionState import com.tangem.feature.swap.models.SwapStateHolder import com.tangem.feature.swap.models.SwapWarning import com.tangem.feature.swap.models.TransactionCardType @@ -84,6 +87,21 @@ internal fun SwapScreenContent( ) } + if (state.permissionState is SwapPermissionState.InProgress) { + CardWithIcon( + title = stringResource(id = R.string.swapping_pending_transaction_title), + description = stringResource(id = R.string.swapping_pending_transaction_subtitle), + icon = { + CircularProgressIndicator( + modifier = Modifier + .size(TangemTheme.dimens.size16), + color = TangemTheme.colors.icon.primary1, + strokeWidth = TangemTheme.dimens.size2, + ) + }, + ) + } + PrimaryButtonIconRight( modifier = Modifier.fillMaxWidth(), text = stringResource(id = R.string.swapping_swap), @@ -261,6 +279,7 @@ private val state = SwapStateHolder( networkCurrency = "MATIC", swapButton = SwapButton(enabled = true, loading = false, onClick = {}), onRefresh = {}, onBackClicked = {}, onChangeCardsClicked = {}, + permissionState = SwapPermissionState.InProgress, ) @Preview From 83571f3bd571f2662171e402c77e4c634fa0edb7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 13 Jan 2023 17:01:04 +0300 Subject: [PATCH 075/145] Updated on 2026-08-14 --- .../tap/proxy/TransactionManagerImpl.kt | 6 + .../tangem/tap/proxy/UserWalletManagerImpl.kt | 5 + .../tangem/datasource/di/OneInchApisModule.kt | 3 + .../feature/swap/converters/SwapConverter.kt | 5 +- .../swap/domain/AllowPermissionsHandler.kt | 8 + .../domain/AllowPermissionsHandlerImpl.kt | 19 ++ .../feature/swap/domain/SwapInteractor.kt | 5 + .../feature/swap/domain/SwapInteractorImpl.kt | 232 ++++++++++++++---- .../swap/domain/cache/SwapDataCache.kt | 6 + .../swap/domain/cache/SwapDataCacheImpl.kt | 18 ++ .../swap/domain/di/SwapDomainModule.kt | 2 + .../feature/swap/domain/models/QuoteModel.kt | 2 - .../swap/domain/models/SwapDataModel.kt | 6 +- .../feature/swap/domain/models/SwapState.kt | 22 +- .../domain/models/cache/SwapDataHolder.kt | 4 + .../tangem/feature/swap/ui/StateBuilder.kt | 34 ++- .../feature/swap/viewmodels/SwapViewModel.kt | 32 ++- .../tangem/lib/crypto/TransactionManager.kt | 2 + .../tangem/lib/crypto/UserWalletManager.kt | 6 + 19 files changed, 360 insertions(+), 57 deletions(-) create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/AllowPermissionsHandler.kt create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/AllowPermissionsHandlerImpl.kt diff --git a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt index bec8c6eebe..fc7d49d169 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -66,6 +66,12 @@ class TransactionManagerImpl( return Blockchain.fromNetworkId(networkId)?.decimals() ?: error("blockchain not found") } + override fun calculateFee(networkId: String, gasPrice: String, estimatedGas: Int): BigDecimal { + val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + val gasPriceValue = requireNotNull(gasPrice.toLongOrNull()) { "gasprice should be Long" } + return (gasPriceValue * estimatedGas).toBigDecimal().movePointLeft(blockchain.decimals()) + } + @Throws(IllegalStateException::class) override suspend fun getFee( networkId: String, diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt index 9ba2438963..991c499fb4 100644 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -131,6 +131,11 @@ class UserWalletManagerImpl( ) } + override fun getCurrencyByNetworkId(networkId: String): String { + val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + return blockchain.currency + } + private fun addNativeTokenToWalletAction(token: NativeToken, card: CardDTO): Action { val blockchain = Blockchain.fromNetworkId(token.networkId) val scanResponse = appStateHolder.scanResponse diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/OneInchApisModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/OneInchApisModule.kt index 55ee683715..7befbaaaf6 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/OneInchApisModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/OneInchApisModule.kt @@ -23,6 +23,7 @@ class OneInchApisModule { val apiFactory = OneInchApiFactory() apiFactory.putApi(ETH_NETWORK, createOneInchApiWithUrl(ONE_INCH_BASE_URL + ONE_INCH_ETH_PATH, moshi)) apiFactory.putApi(BSC_NETWORK, createOneInchApiWithUrl(ONE_INCH_BASE_URL + ONE_INCH_BSC_PATH, moshi)) + apiFactory.putApi(POLYGON_NETWORK, createOneInchApiWithUrl(ONE_INCH_BASE_URL + ONE_INCH_POLYGON_PATH, moshi)) return apiFactory } @@ -45,8 +46,10 @@ class OneInchApisModule { private const val ONE_INCH_BASE_URL = "https://api.1inch.io/v5.0/" private const val ONE_INCH_ETH_PATH = "1/" private const val ONE_INCH_BSC_PATH = "56/" + private const val ONE_INCH_POLYGON_PATH = "137/" private const val ETH_NETWORK = "ethereum" private const val BSC_NETWORK = "binance-smart-chain" + private const val POLYGON_NETWORK = "polygon-pos" } } \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt index ebe2d92a13..31df2484b4 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt @@ -4,6 +4,7 @@ import com.tangem.datasource.api.oneinch.models.SwapResponse import com.tangem.datasource.api.oneinch.models.TransactionDto import com.tangem.feature.swap.domain.models.SwapDataModel import com.tangem.feature.swap.domain.models.TransactionModel +import com.tangem.feature.swap.domain.models.createFromAmountWithOffset import com.tangem.utils.converter.Converter import javax.inject.Inject @@ -13,8 +14,8 @@ class SwapConverter @Inject constructor() : Converter() + + override fun addAddressToInProgress(tokenAddress: String) { + allowPermissionsInProgress.add(tokenAddress) + } + + override fun removeAddressFromProgress(tokenAddress: String) { + allowPermissionsInProgress.remove(tokenAddress) + } + + override fun isAddressAllowanceInProgress(tokenAddress: String): Boolean { + return allowPermissionsInProgress.contains(tokenAddress) + } +} \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt index 421b21d439..dbbbbe6fdb 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt @@ -8,6 +8,11 @@ interface SwapInteractor { suspend fun getTokensToSwap(networkId: String): List + /** + * Give permission to swap + * + * @param tokenToApprove use token which you want to swap + */ @Throws(IllegalStateException::class) suspend fun givePermissionToSwap(tokenToApprove: Currency) diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 482f6c34a5..919c71d9ab 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -2,15 +2,17 @@ package com.tangem.feature.swap.domain import com.tangem.feature.swap.domain.cache.SwapDataCache import com.tangem.feature.swap.domain.converters.CryptoCurrencyConverter +import com.tangem.feature.swap.domain.models.ApproveModel import com.tangem.feature.swap.domain.models.Currency import com.tangem.feature.swap.domain.models.DataError -import com.tangem.feature.swap.domain.models.QuoteModel +import com.tangem.feature.swap.domain.models.PermissionDataState import com.tangem.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.domain.models.SwapState import com.tangem.feature.swap.domain.models.TransactionModel import com.tangem.feature.swap.domain.models.toStringWithRightOffset import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.UserWalletManager +import com.tangem.lib.crypto.models.transactions.SendTxResult import com.tangem.utils.toFiatString import com.tangem.utils.toFormattedString import java.math.BigDecimal @@ -21,6 +23,7 @@ internal class SwapInteractorImpl @Inject constructor( private val userWalletManager: UserWalletManager, private val repository: SwapRepository, private val cache: SwapDataCache, + private val allowPermissionsHandler: AllowPermissionsHandler, ) : SwapInteractor { private val cryptoCurrencyConverter = CryptoCurrencyConverter() @@ -40,9 +43,10 @@ internal class SwapInteractorImpl @Inject constructor( if (tokenToApprove is Currency.NonNativeToken) { val estimatedGas = requireNotNull(cache.getLastQuote()?.estimatedGas) { "estimatedGas not found call findBestQuote" } - val transactionData = repository.dataToApprove(networkId, tokenToApprove.contractAddress) + val transactionData = + requireNotNull(cache.getApproveTransactionData()) { "getApproveTransactionData not found, call findQuotes" } val gasPrice = transactionData.gasPrice.toBigDecimalOrNull() ?: error("cannot parse gasPrice") - val fee = gasPrice.multiply(estimatedGas.toBigDecimal()).movePointLeft(tokenToApprove.decimalCount) + val fee = transactionManager.calculateFee(networkId, gasPrice.toPlainString(), estimatedGas) val result = transactionManager.sendTransaction( networkId = networkId, amountToSend = BigDecimal.ZERO, @@ -51,43 +55,46 @@ internal class SwapInteractorImpl @Inject constructor( destinationAddress = transactionData.toAddress, dataToSign = transactionData.data, ) + when (result) { + SendTxResult.Success -> { + allowPermissionsHandler.addAddressToInProgress(tokenToApprove.contractAddress) + } + SendTxResult.UserCancelledError -> TODO() + is SendTxResult.BlockchainSdkError -> TODO() + is SendTxResult.TangemSdkError -> TODO() + is SendTxResult.UnknownError -> TODO() + } } } } override suspend fun findBestQuote(fromToken: Currency, toToken: Currency, amount: SwapAmount): SwapState { - val networkId = cache.getNetworkId() + val networkId = requireNotNull(cache.getNetworkId()) { "no networkId found, please call getTokensToSwap first" } val fromTokenAddress = getTokenAddress(fromToken) val toTokenAddress = getTokenAddress(toToken) - val isAllowedToSpend = if (networkId.isNullOrEmpty()) { - error("no networkId found, please call getTokensToSwap first") - } else { - val allowance = - repository.checkTokensSpendAllowance( - networkId = networkId, - tokenAddress = fromTokenAddress, - walletAddress = userWalletManager.getWalletAddress(networkId), - ) - allowance.error == DataError.NO_ERROR && allowance.dataModel != "0" - } - repository.findBestQuote( - networkId = networkId, - fromTokenAddress = fromTokenAddress, - toTokenAddress = toTokenAddress, - amount = amount.toStringWithRightOffset(), - ).let { quotes -> - val quoteDataModel = quotes.dataModel - if (quoteDataModel != null) { - cache.cacheSwapParams( - quoteModel = quoteDataModel.copy(isAllowedToSpend = isAllowedToSpend), - amount = amount, - fromCurrency = fromToken, - toCurrency = toToken, - ) - return updateBalances(networkId, fromToken, toToken, quoteDataModel) - } else { - return SwapState.SwapError(quotes.error) + val isAllowedToSpend = checkAllowance(networkId, fromTokenAddress) + val isBalanceZero = isNotZeroBalance(fromToken, networkId) + return if (isAllowedToSpend && isBalanceZero) { + if (allowPermissionsHandler.isAddressAllowanceInProgress(fromTokenAddress)) { + allowPermissionsHandler.removeAddressFromProgress(toTokenAddress) } + loadSwapData( + networkId = networkId, + fromTokenAddress = fromTokenAddress, + toTokenAddress = toTokenAddress, + fromToken = fromToken, + toToken = toToken, + amount = amount, + ) + } else { + loadQuoteData( + networkId = networkId, + fromTokenAddress = fromTokenAddress, + toTokenAddress = toTokenAddress, + amount = amount, + fromToken = fromToken, + toToken = toToken, + ) } } @@ -128,11 +135,120 @@ internal class SwapInteractorImpl @Inject constructor( } } + private suspend fun checkAllowance(networkId: String, fromTokenAddress: String): Boolean { + val allowance = repository.checkTokensSpendAllowance( + networkId = networkId, + tokenAddress = fromTokenAddress, + walletAddress = userWalletManager.getWalletAddress(networkId), + ) + return allowance.error == DataError.NO_ERROR && allowance.dataModel != ZERO_BALANCE + } + + /** + * Load quote data calls only if spend is not allowed for token contract address + */ + private suspend fun loadQuoteData( + networkId: String, + fromTokenAddress: String, + toTokenAddress: String, + amount: SwapAmount, + fromToken: Currency, + toToken: Currency, + ): SwapState { + repository.findBestQuote( + networkId = networkId, + fromTokenAddress = fromTokenAddress, + toTokenAddress = toTokenAddress, + amount = amount.toStringWithRightOffset(), + ).let { quotes -> + val quoteDataModel = quotes.dataModel + if (quoteDataModel != null) { + cache.cacheSwapParams( + quoteModel = quoteDataModel, + amount = amount, + fromCurrency = fromToken, + toCurrency = toToken, + ) + val transactionData = repository.dataToApprove(networkId, getTokenAddress(fromToken)) + cache.cacheApproveTransactionData(transactionData) + val swapState = updateBalances( + networkId = networkId, + fromToken = fromToken, + toToken = toToken, + fromTokenAmount = quoteDataModel.fromTokenAmount, + toTokenAmount = quoteDataModel.toTokenAmount, + fee = transactionManager.calculateFee( + networkId = networkId, + estimatedGas = quoteDataModel.estimatedGas, + gasPrice = transactionData.gasPrice, + ), + isAllowedToSpend = false, + ) + return updatePermissionState( + networkId = networkId, + fromToken = fromToken, + quotesLoadedState = swapState, + estimatedGas = quoteDataModel.estimatedGas, + transactionData = transactionData, + ) + } else { + return SwapState.SwapError(quotes.error) + } + } + } + + /** + * Load swap data calls only if spend is allowed for token contract address + */ + private suspend fun loadSwapData( + networkId: String, + fromTokenAddress: String, + toTokenAddress: String, + fromToken: Currency, + toToken: Currency, + amount: SwapAmount, + ): SwapState { + repository.prepareSwapTransaction( + networkId = networkId, + fromTokenAddress = fromTokenAddress, + toTokenAddress = toTokenAddress, + amount = amount.toStringWithRightOffset(), + slippage = DEFAULT_SLIPPAGE, + fromWalletAddress = getWalletAddress(networkId), + ).let { + val swapData = it.dataModel + if (swapData != null) { + cache.cacheSwapData(swapData) + val swapState = updateBalances( + networkId = networkId, + fromToken = fromToken, + toToken = toToken, + fromTokenAmount = swapData.fromTokenAmount, + toTokenAmount = swapData.toTokenAmount, + fee = transactionManager.calculateFee( + networkId = networkId, + estimatedGas = swapData.transaction.gas.toIntOrNull() ?: DEFAULT_GAS, + gasPrice = swapData.transaction.gasPrice, + ), + isAllowedToSpend = false, + ) + return swapState.copy( + permissionState = PermissionDataState.Empty, + ) + } else { + return SwapState.SwapError(it.error) + } + } + } + private suspend fun updateBalances( networkId: String, fromToken: Currency, toToken: Currency, - quoteModel: QuoteModel, + fromTokenAmount: SwapAmount, + toTokenAmount: SwapAmount, + fee: BigDecimal, + isAllowedToSpend: Boolean, ): SwapState.QuotesLoadedState { val appCurrency = userWalletManager.getUserAppCurrency() val rates = repository.getRates(appCurrency.code, listOf(fromToken.id, toToken.id)) @@ -144,25 +260,58 @@ internal class SwapInteractorImpl @Inject constructor( it.value.toFormattedString(it.decimals) } return SwapState.QuotesLoadedState( - fromTokenAmount = quoteModel.fromTokenAmount, - toTokenAmount = quoteModel.toTokenAmount, - fromTokenAddress = quoteModel.fromTokenAddress, - toTokenAddress = quoteModel.toTokenAddress, - estimatedGas = quoteModel.estimatedGas, - isAllowedToSpend = quoteModel.isAllowedToSpend, + fromTokenAmount = fromTokenAmount, + toTokenAmount = toTokenAmount, + fromTokenAddress = getTokenAddress(fromToken), + toTokenAddress = getTokenAddress(toToken), + fee = fee.toPlainString(), + isAllowedToSpend = isAllowedToSpend, fromTokenWalletBalance = fromTokenBalance ?: ZERO_BALANCE, - fromTokenFiatBalance = quoteModel.fromTokenAmount.value.toFiatString( + fromTokenFiatBalance = fromTokenAmount.value.toFiatString( rates[fromToken.id]?.toBigDecimal() ?: BigDecimal.ZERO, appCurrency.symbol, ), toTokenWalletBalance = toTokenBalance ?: ZERO_BALANCE, - toTokenFiatBalance = quoteModel.fromTokenAmount.value.toFiatString( + toTokenFiatBalance = fromTokenAmount.value.toFiatString( rates[fromToken.id]?.toBigDecimal() ?: BigDecimal.ZERO, appCurrency.symbol, ), ) } + private fun updatePermissionState( + networkId: String, + fromToken: Currency, + quotesLoadedState: SwapState.QuotesLoadedState, + estimatedGas: Int, + transactionData: ApproveModel, + ): SwapState.QuotesLoadedState { + if (allowPermissionsHandler.isAddressAllowanceInProgress(fromToken.networkId)) { + return quotesLoadedState.copy( + permissionState = PermissionDataState.PermissionLoading, + ) + } + return quotesLoadedState.copy( + permissionState = PermissionDataState.PermissionReadyForRequest( + currency = userWalletManager.getCurrencyByNetworkId(networkId), + amount = "infinite", //FIXME + walletAddress = getWalletAddress(networkId), + spenderAddress = transactionData.toAddress, + fee = transactionManager.calculateFee( + networkId = networkId, + gasPrice = transactionData.gasPrice, + estimatedGas = estimatedGas, + ).toPlainString(), + ), + ) + } + + private fun isNotZeroBalance(fromToken: Currency, networkId: String): Boolean { + /** to compare [BigDecimal] use only comparator */ + return (userWalletManager.getCurrentWalletTokensBalance(networkId)[fromToken.symbol]?.value + ?: BigDecimal.ZERO).compareTo(BigDecimal.ZERO) != 0 + } + private fun getWalletAddress(networkId: String): String { return userWalletManager.getWalletAddress(networkId) } @@ -185,5 +334,6 @@ internal class SwapInteractorImpl @Inject constructor( companion object { private const val DEFAULT_SLIPPAGE = 2 private const val ZERO_BALANCE = "0" + private const val DEFAULT_GAS = 300000 } } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt index 6acefb6794..073920c985 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt @@ -1,17 +1,23 @@ package com.tangem.feature.swap.domain.cache +import com.tangem.feature.swap.domain.models.ApproveModel import com.tangem.feature.swap.domain.models.Currency import com.tangem.feature.swap.domain.models.QuoteModel import com.tangem.feature.swap.domain.models.SwapAmount +import com.tangem.feature.swap.domain.models.SwapDataModel import com.tangem.feature.swap.domain.models.cache.ExchangeCurrencies interface SwapDataCache { fun cacheNetworkId(networkId: String) fun cacheSwapParams(quoteModel: QuoteModel, amount: SwapAmount, fromCurrency: Currency, toCurrency: Currency) + fun cacheApproveTransactionData(approve: ApproveModel) + fun cacheSwapData(swapDataModel: SwapDataModel) fun cacheAvailableToSwapTokens(networkId: String, tokens: List) fun getAvailableTokens(networkId: String): List + fun getApproveTransactionData(): ApproveModel? fun getLastQuote(): QuoteModel? + fun getLastSwapData(): SwapDataModel? fun getAmountToSwap(): SwapAmount? fun getNetworkId(): String? fun getExchangeCurrencies(): ExchangeCurrencies? diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt index 0431b67ff0..b5ca9e38a1 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt @@ -1,8 +1,10 @@ package com.tangem.feature.swap.domain.cache +import com.tangem.feature.swap.domain.models.ApproveModel import com.tangem.feature.swap.domain.models.Currency import com.tangem.feature.swap.domain.models.QuoteModel import com.tangem.feature.swap.domain.models.SwapAmount +import com.tangem.feature.swap.domain.models.SwapDataModel import com.tangem.feature.swap.domain.models.cache.ExchangeCurrencies import com.tangem.feature.swap.domain.models.cache.SwapDataHolder @@ -28,6 +30,18 @@ class SwapDataCacheImpl : SwapDataCache { ) } + override fun cacheSwapData(swapDataModel: SwapDataModel) { + lastDataForSwap = lastDataForSwap.copy(swapModel = swapDataModel) + } + + override fun getLastSwapData(): SwapDataModel? { + return lastDataForSwap.swapModel + } + + override fun cacheApproveTransactionData(approve: ApproveModel) { + lastDataForSwap = lastDataForSwap.copy(approveTxModel = approve) + } + override fun getExchangeCurrencies(): ExchangeCurrencies? { return lastDataForSwap.exchangeCurrencies } @@ -48,6 +62,10 @@ class SwapDataCacheImpl : SwapDataCache { return availableTokensForNetwork.getOrElse(networkId) { emptyList() } } + override fun getApproveTransactionData(): ApproveModel? { + return lastDataForSwap.approveTxModel + } + override fun getLastQuote(): QuoteModel? { return lastDataForSwap.quoteModel } diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt index eb5d59171e..c5e2782f7e 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -1,5 +1,6 @@ package com.tangem.feature.swap.domain.di +import com.tangem.feature.swap.domain.AllowPermissionsHandlerImpl import com.tangem.feature.swap.domain.SwapInteractor import com.tangem.feature.swap.domain.SwapInteractorImpl import com.tangem.feature.swap.domain.SwapRepository @@ -28,6 +29,7 @@ class SwapDomainModule { userWalletManager = userWalletManager, repository = swapRepository, cache = SwapDataCacheImpl(), + allowPermissionsHandler = AllowPermissionsHandlerImpl(), ) } } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/QuoteModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/QuoteModel.kt index 1d4aa9afdb..14b36eed8b 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/QuoteModel.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/QuoteModel.kt @@ -8,7 +8,6 @@ package com.tangem.feature.swap.domain.models * @property fromTokenAddress address token you want to exchange * @property toTokenAddress address token you want to receive * @property estimatedGas fee - * @property isAllowedToSpend does spend allowed */ data class QuoteModel( val fromTokenAmount: SwapAmount, @@ -16,5 +15,4 @@ data class QuoteModel( val fromTokenAddress: String, val toTokenAddress: String, val estimatedGas: Int, - val isAllowedToSpend: Boolean = false, ) \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataModel.kt index f18018c63e..8af39f1219 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataModel.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataModel.kt @@ -11,7 +11,7 @@ package com.tangem.feature.swap.domain.models data class SwapDataModel( val fromTokenAddress: String, val toTokenAddress: String, - val toTokenAmount: String, - val fromTokenAmount: String, - val transaction: TransactionModel + val toTokenAmount: SwapAmount, + val fromTokenAmount: SwapAmount, + val transaction: TransactionModel, ) \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapState.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapState.kt index 82bf5808f5..60ec7a9471 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapState.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapState.kt @@ -1,5 +1,7 @@ package com.tangem.feature.swap.domain.models +import java.math.BigDecimal + sealed interface SwapState { data class QuotesLoadedState( @@ -11,8 +13,9 @@ sealed interface SwapState { val fromTokenFiatBalance: String, val toTokenWalletBalance: String, val toTokenFiatBalance: String, - val estimatedGas: Int, + val fee: String, val isAllowedToSpend: Boolean = false, + val permissionState: PermissionDataState = PermissionDataState.Empty, ) : SwapState data class SwapSuccess( @@ -23,4 +26,21 @@ sealed interface SwapState { data class SwapError( val errorType: DataError, ) : SwapState +} + +sealed class PermissionDataState { + + data class PermissionReadyForRequest( + val currency: String, + val amount: String, + val walletAddress: String, + val spenderAddress: String, + val fee: String, + ) : PermissionDataState() + + object PermissionFailed : PermissionDataState() + + object PermissionLoading : PermissionDataState() + + object Empty : PermissionDataState() } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/cache/SwapDataHolder.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/cache/SwapDataHolder.kt index 41e159253f..764ec2f37e 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/cache/SwapDataHolder.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/cache/SwapDataHolder.kt @@ -1,11 +1,15 @@ package com.tangem.feature.swap.domain.models.cache +import com.tangem.feature.swap.domain.models.ApproveModel import com.tangem.feature.swap.domain.models.Currency import com.tangem.feature.swap.domain.models.QuoteModel import com.tangem.feature.swap.domain.models.SwapAmount +import com.tangem.feature.swap.domain.models.SwapDataModel data class SwapDataHolder( val quoteModel: QuoteModel? = null, + val swapModel: SwapDataModel? = null, + val approveTxModel: ApproveModel? = null, val amountToSwap: SwapAmount? = null, val networkId: String? = null, val exchangeCurrencies: ExchangeCurrencies? = null, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 067ecab8bf..5f7817f92c 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -1,11 +1,15 @@ package com.tangem.feature.swap.ui import com.tangem.feature.swap.domain.models.Currency +import com.tangem.feature.swap.domain.models.PermissionDataState import com.tangem.feature.swap.domain.models.SwapState import com.tangem.feature.swap.domain.models.formatToUIRepresentation +import com.tangem.feature.swap.models.ApprovePermissionButton +import com.tangem.feature.swap.models.CancelPermissionButton import com.tangem.feature.swap.models.FeeState import com.tangem.feature.swap.models.SwapButton import com.tangem.feature.swap.models.SwapCardData +import com.tangem.feature.swap.models.SwapPermissionStateHolder import com.tangem.feature.swap.models.SwapStateHolder import com.tangem.feature.swap.models.SwapWarning import com.tangem.feature.swap.models.TransactionCardType @@ -75,6 +79,7 @@ class StateBuilder { quoteModel: SwapState.QuotesLoadedState, fromToken: Currency, onSwapClick: () -> Unit, + onGivePermissionClick: () -> Unit, ): SwapStateHolder { return uiStateHolder.copy( sendCardData = SwapCardData( @@ -102,7 +107,8 @@ class StateBuilder { } else { emptyList() }, - fee = FeeState.Loaded(quoteModel.estimatedGas.toString()), + permissionState = convertPermissionState(quoteModel.permissionState, onGivePermissionClick), + fee = FeeState.Loaded(quoteModel.fee), swapButton = SwapButton( enabled = quoteModel.isAllowedToSpend, loading = false, @@ -110,4 +116,30 @@ class StateBuilder { ), ) } + + private fun convertPermissionState( + permissionDataState: PermissionDataState, + onGivePermissionClick: () -> Unit, + ): SwapPermissionStateHolder? { + return when (permissionDataState) { + PermissionDataState.Empty -> null + PermissionDataState.PermissionFailed -> null + PermissionDataState.PermissionLoading -> null + is PermissionDataState.PermissionReadyForRequest -> SwapPermissionStateHolder( + currency = permissionDataState.currency, + amount = permissionDataState.amount, + walletAddress = permissionDataState.walletAddress, + spenderAddress = permissionDataState.spenderAddress, + fee = permissionDataState.fee, + approveButton = ApprovePermissionButton( + enabled = true, + onClick = onGivePermissionClick, + ), + cancelButton = CancelPermissionButton( + enabled = true, + onClick = {}, + ), + ) + } + } } \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index dfcf661be7..1ba7b06511 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -98,9 +98,13 @@ internal class SwapViewModel @Inject constructor( .onSuccess { swapState -> when (swapState) { is SwapState.QuotesLoadedState -> { - uiState = stateBuilder.createQuotesLoadedState(uiState, swapState, fromToken) { - onSwapClick(toToken, swapState) - } + uiState = stateBuilder.createQuotesLoadedState( + uiStateHolder = uiState, + quoteModel = swapState, + fromToken = fromToken, + onSwapClick = { onSwapClick(toToken, swapState) }, + onGivePermissionClick = { givePermissionsToSwap(fromToken) }, + ) } is SwapState.SwapSuccess -> { //todo implement @@ -116,10 +120,12 @@ internal class SwapViewModel @Inject constructor( private fun onSwapClick(toToken: Currency, quoteModel: SwapState.QuotesLoadedState) { viewModelScope.launch(dispatchers.main) { runCatching { - if (!quoteModel.isAllowedToSpend) { - swapInteractor.givePermissionToSwap(toToken) - } else { - swapInteractor.onSwap() + withContext(dispatchers.io) { + if (!quoteModel.isAllowedToSpend) { + swapInteractor.givePermissionToSwap(toToken) + } else { + swapInteractor.onSwap() + } } } .onSuccess { @@ -137,6 +143,18 @@ internal class SwapViewModel @Inject constructor( } } + private fun givePermissionsToSwap(tokenToApprove: Currency) { + viewModelScope.launch(dispatchers.main) { + runCatching { + withContext(dispatchers.io) { + swapInteractor.givePermissionToSwap(tokenToApprove) + } + } + .onSuccess { } + .onFailure { } + } + } + companion object { private const val MIN_TOKENS_IN_LIST = 2 private const val INITIAL_AMOUNT = "1" diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt index 6da184fb5a..34f2f3d06b 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt @@ -29,4 +29,6 @@ interface TransactionManager { @Throws(IllegalStateException::class) fun getNativeTokenDecimals(networkId: String): Int + + fun calculateFee(networkId: String, gasPrice: String, estimatedGas: Int): BigDecimal } \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt index b29cfc2535..3faa50aa4c 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt @@ -53,4 +53,10 @@ interface UserWalletManager { * Returns selected app currency */ fun getUserAppCurrency(): ProxyFiatCurrency + + /** + * @param networkId + * @return currency name + */ + fun getCurrencyByNetworkId(networkId: String): String } \ No newline at end of file From 4102fb014390a22fc542a56e5574291cfd22007e Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 13 Jan 2023 17:34:24 +0300 Subject: [PATCH 076/145] Updated on 2026-08-14 --- .../java/com/tangem/feature/swap/ui/StateBuilder.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 5f7817f92c..33bd25c82d 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -9,7 +9,7 @@ import com.tangem.feature.swap.models.CancelPermissionButton import com.tangem.feature.swap.models.FeeState import com.tangem.feature.swap.models.SwapButton import com.tangem.feature.swap.models.SwapCardData -import com.tangem.feature.swap.models.SwapPermissionStateHolder +import com.tangem.feature.swap.models.SwapPermissionState import com.tangem.feature.swap.models.SwapStateHolder import com.tangem.feature.swap.models.SwapWarning import com.tangem.feature.swap.models.TransactionCardType @@ -120,12 +120,12 @@ class StateBuilder { private fun convertPermissionState( permissionDataState: PermissionDataState, onGivePermissionClick: () -> Unit, - ): SwapPermissionStateHolder? { + ): SwapPermissionState { return when (permissionDataState) { - PermissionDataState.Empty -> null - PermissionDataState.PermissionFailed -> null - PermissionDataState.PermissionLoading -> null - is PermissionDataState.PermissionReadyForRequest -> SwapPermissionStateHolder( + PermissionDataState.Empty -> SwapPermissionState.Empty + PermissionDataState.PermissionFailed -> SwapPermissionState.Empty + PermissionDataState.PermissionLoading -> SwapPermissionState.InProgress + is PermissionDataState.PermissionReadyForRequest -> SwapPermissionState.ReadyForRequest( currency = permissionDataState.currency, amount = permissionDataState.amount, walletAddress = permissionDataState.walletAddress, From 76f955568eb0b57ade659be019995a4fe489ebdd Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 13 Jan 2023 20:21:38 +0400 Subject: [PATCH 077/145] Updated on 2026-08-14 --- .../java/com/tangem/tap/TapApplication.kt | 22 ++-------- .../tokens/LoadAvailableCoinsService.kt | 4 +- .../domain/tokens/OldUserTokensRepository.kt | 2 +- .../domain/tokens/UserTokensStorageService.kt | 3 +- .../tap/domain/twins/TwinCardsManager.kt | 2 +- .../walletconnect/WalletConnectRepository.kt | 3 +- .../persistence/CardBalanceStateAdapter.kt | 20 +++++++++ .../tap/persistence/PreferencesStorage.kt | 20 +++++---- .../tap/persistence/ToppedUpWalletStorage.kt | 17 -------- .../datasource/api/common/MoshiConverter.kt | 31 ++++++-------- .../tangem/datasource/api/common/Retrofit.kt | 6 +-- .../api/oneinch/OneInchErrorsHandler.kt | 3 +- .../paymentology/PaymentologyApiService.kt | 2 +- .../api/tangemTech/TangemTechService.kt | 2 +- .../tangem/datasource/di/MoshiDaggerKey.kt | 13 ++++++ .../com/tangem/datasource/di/MoshiModule.kt | 42 +++++++++++++++++++ .../com/tangem/datasource/di/NetworkModule.kt | 32 +++++++------- .../tangem/datasource/di/OneInchApisModule.kt | 2 +- 18 files changed, 134 insertions(+), 92 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/persistence/CardBalanceStateAdapter.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/di/MoshiDaggerKey.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/di/MoshiModule.kt diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 6b082cfdda..608ca53cbe 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -10,15 +10,13 @@ import com.tangem.LogFormat import com.tangem.blockchain.common.BlockchainSdkConfig import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder -import com.tangem.common.json.MoshiJsonConverter -import com.tangem.datasource.api.common.BigDecimalAdapter +import com.tangem.core.analytics.Analytics import com.tangem.datasource.api.common.MoshiConverter import com.tangem.domain.DomainLayer import com.tangem.domain.common.LogConfig import com.tangem.tap.common.AndroidAssetReader import com.tangem.tap.common.AssetReader import com.tangem.tap.common.IntentHandler -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsFactory import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder import com.tangem.tap.common.analytics.filters.BasicSignInFilter @@ -52,7 +50,6 @@ import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository import com.tangem.tap.domain.walletStores.repository.di.provideDefaultImplementation import com.tangem.tap.domain.walletconnect.WalletConnectRepository import com.tangem.tap.network.NetworkConnectivity -import com.tangem.tap.persistence.CardBalanceStateAdapter import com.tangem.tap.persistence.PreferencesStorage import com.tangem.tap.proxy.AppStateHolder import com.tangem.wallet.BuildConfig @@ -137,14 +134,13 @@ class TapApplication : Application(), ImageLoaderFactory { activityResultCaller = foregroundActivityObserver registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks) - initMoshiConverter() DomainLayer.init() NetworkConnectivity.createInstance(store, this) preferencesStorage = PreferencesStorage(this) walletConnectRepository = WalletConnectRepository(this) assetReader = AndroidAssetReader(this) - val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.INSTANCE.moshi) + val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.sdkMoshi) initConfigManager(configLoader, ::initWithConfigDependency) initWarningMessagesManager() @@ -159,18 +155,6 @@ class TapApplication : Application(), ImageLoaderFactory { appStateHolder.walletStoresManager = walletStoresManager } - //todo refactor: move to datasource and provide via DI - private fun initMoshiConverter() { - fun appAdapters(): List = listOf( - BigDecimalAdapter(), - CardBalanceStateAdapter(), - ) - MoshiConverter.reInitInstance( - adapters = appAdapters() + MoshiJsonConverter.getTangemSdkAdapters(), - typedAdapters = MoshiJsonConverter.getTangemSdkTypedAdapters(), - ) - } - override fun newImageLoader(): ImageLoader { return createCoilImageLoader( context = this, @@ -207,7 +191,7 @@ class TapApplication : Application(), ImageLoaderFactory { config = config, isDebug = BuildConfig.DEBUG, logConfig = LogConfig.analyticsHandlers, - jsonConverter = MoshiConverter.INSTANCE, + jsonConverter = MoshiConverter.sdkMoshiConverter, ) factory.build(Analytics, buildData) } diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt index 072992eeb8..179d3a22ea 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt @@ -1,7 +1,6 @@ package com.tangem.tap.domain.tokens import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.Moshi import com.tangem.blockchain.common.Blockchain import com.tangem.common.services.Result import com.tangem.datasource.api.common.MoshiConverter @@ -17,9 +16,8 @@ class LoadAvailableCoinsService( private val dispatchers: CoroutineDispatcherProvider, private val assetReader: AssetReader, ) { - private val moshi: Moshi by lazy { MoshiConverter.defaultMoshi() } private val currenciesAdapter: JsonAdapter = - moshi.adapter(CurrenciesFromJson::class.java) + MoshiConverter.networkMoshi.adapter(CurrenciesFromJson::class.java) suspend fun getSupportedTokens( isTestNet: Boolean = false, diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt index 4db0f6fb33..96d98f91a5 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt @@ -20,7 +20,7 @@ class OldUserTokensRepository( private val tangemTechApi: TangemTechApi, private val dispatchers: CoroutineDispatcherProvider, ) { - private val moshi = MoshiConverter.defaultMoshi() + private val moshi = MoshiConverter.networkMoshi private val blockchainsAdapter: JsonAdapter> = moshi.adapter( Types.newParameterizedType(List::class.java, Blockchain::class.java), ) diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt index 93d4d909ce..9df51fb6f4 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt @@ -13,9 +13,8 @@ class UserTokensStorageService( private val oldUserTokensRepository: OldUserTokensRepository, private val fileReader: FileReader, ) { - private val moshi = MoshiConverter.defaultMoshi() private val userTokensAdapter: JsonAdapter = - moshi.adapter(UserTokensResponse::class.java) + MoshiConverter.networkMoshi.adapter(UserTokensResponse::class.java) fun getUserTokens(userId: String): List? { return try { diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index b910fcb589..6b250dcef9 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -83,7 +83,7 @@ class TwinCardsManager( } private fun getAdapter(): JsonAdapter> { - return MoshiConverter.defaultMoshi().adapter( + return MoshiConverter.sdkMoshi.adapter( Types.newParameterizedType(List::class.java, Issuer::class.java), ) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt index c796b81261..12df7aeb8e 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt @@ -16,8 +16,7 @@ import timber.log.Timber import java.nio.charset.Charset class WalletConnectRepository(val context: Application) { - private val moshi = MoshiConverter.defaultMoshi() - private val walletConnectAdapter: JsonAdapter> = moshi.adapter( + private val walletConnectAdapter: JsonAdapter> = MoshiConverter.sdkMoshi.adapter( Types.newParameterizedType(List::class.java, SessionDao::class.java), ) diff --git a/app/src/main/java/com/tangem/tap/persistence/CardBalanceStateAdapter.kt b/app/src/main/java/com/tangem/tap/persistence/CardBalanceStateAdapter.kt new file mode 100644 index 0000000000..fe16a0c017 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/persistence/CardBalanceStateAdapter.kt @@ -0,0 +1,20 @@ +package com.tangem.tap.persistence + +import com.squareup.moshi.FromJson +import com.squareup.moshi.ToJson +import com.tangem.tap.common.analytics.events.AnalyticsParam + +class CardBalanceStateAdapter { + + @ToJson + fun toJson(src: AnalyticsParam.CardBalanceState): String = src.value + + @FromJson + fun fromJson(json: String): AnalyticsParam.CardBalanceState { + return when (json) { + AnalyticsParam.CardBalanceState.Empty.value -> AnalyticsParam.CardBalanceState.Empty + AnalyticsParam.CardBalanceState.Full.value -> AnalyticsParam.CardBalanceState.Full + else -> throw IllegalArgumentException() + } + } +} \ 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 d419aafffc..d662cdb29d 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -4,29 +4,35 @@ import android.app.Application import android.content.Context import android.content.SharedPreferences import androidx.core.content.edit -import com.tangem.datasource.api.common.MoshiConverter +import com.tangem.common.json.MoshiJsonConverter +import com.tangem.datasource.api.common.BigDecimalAdapter import java.util.* class PreferencesStorage(applicationContext: Application) { - private val preferences: SharedPreferences = - applicationContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) - val appRatingLaunchObserver: AppRatingLaunchObserver val usedCardsPrefStorage: UsedCardsPrefStorage val fiatCurrenciesPrefStorage: FiatCurrenciesPrefStorage val disclaimerPrefStorage: DisclaimerPrefStorage val toppedUpWalletStorage: ToppedUpWalletStorage + private val preferences: SharedPreferences = + applicationContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) + + private val moshiConverter = MoshiJsonConverter( + adapters = listOf(BigDecimalAdapter(), CardBalanceStateAdapter()) + MoshiJsonConverter.getTangemSdkAdapters(), + typedAdapters = MoshiJsonConverter.getTangemSdkTypedAdapters(), + ) + init { incrementLaunchCounter() appRatingLaunchObserver = AppRatingLaunchObserver(preferences, getCountOfLaunches()) - usedCardsPrefStorage = UsedCardsPrefStorage(preferences, MoshiConverter.INSTANCE) + usedCardsPrefStorage = UsedCardsPrefStorage(preferences, moshiConverter) usedCardsPrefStorage.migrate() - fiatCurrenciesPrefStorage = FiatCurrenciesPrefStorage(preferences, MoshiConverter.INSTANCE) + fiatCurrenciesPrefStorage = FiatCurrenciesPrefStorage(preferences, moshiConverter) fiatCurrenciesPrefStorage.migrate() disclaimerPrefStorage = DisclaimerPrefStorage(preferences) - toppedUpWalletStorage = ToppedUpWalletStorage(preferences, MoshiConverter.INSTANCE) + toppedUpWalletStorage = ToppedUpWalletStorage(preferences, moshiConverter) } var chatFirstLaunchTime: Long? diff --git a/app/src/main/java/com/tangem/tap/persistence/ToppedUpWalletStorage.kt b/app/src/main/java/com/tangem/tap/persistence/ToppedUpWalletStorage.kt index e270b2b820..75759b8d75 100644 --- a/app/src/main/java/com/tangem/tap/persistence/ToppedUpWalletStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/ToppedUpWalletStorage.kt @@ -2,10 +2,7 @@ package com.tangem.tap.persistence import android.content.SharedPreferences import androidx.core.content.edit -import com.squareup.moshi.FromJson -import com.squareup.moshi.ToJson import com.tangem.common.json.MoshiJsonConverter -import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.filters.BasicTopUpFilter import timber.log.Timber @@ -59,18 +56,4 @@ class ToppedUpWalletStorage( companion object { private const val KEY = "userWalletsInfo" } -} - -class CardBalanceStateAdapter { - @ToJson - fun toJson(src: AnalyticsParam.CardBalanceState): String = src.value - - @FromJson - fun fromJson(json: String): AnalyticsParam.CardBalanceState { - return when (json) { - AnalyticsParam.CardBalanceState.Empty.value -> AnalyticsParam.CardBalanceState.Empty - AnalyticsParam.CardBalanceState.Full.value -> AnalyticsParam.CardBalanceState.Full - else -> throw IllegalArgumentException() - } - } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/MoshiConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/MoshiConverter.kt index 054206db9d..6bd52cfecb 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/MoshiConverter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/MoshiConverter.kt @@ -1,33 +1,28 @@ package com.tangem.datasource.api.common -import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import com.tangem.common.json.MoshiJsonConverter -import retrofit2.Converter import retrofit2.converter.moshi.MoshiConverterFactory /** [REDACTED_AUTHOR] */ -//todo needs to be refactored +//TODO("Remove after removing Redux") +@Deprecated("Provide by DI") object MoshiConverter { - //todo refactor: provide via DI - var INSTANCE = MoshiJsonConverter() - private set + val networkMoshi: Moshi = Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .add(BigDecimalAdapter()) + .build() - fun reInitInstance( - adapters: List = listOf(), - typedAdapters: Map, JsonAdapter<*>> = mapOf(), - ) { - INSTANCE = MoshiJsonConverter(adapters, typedAdapters) - } + val networkMoshiConverter: MoshiConverterFactory = MoshiConverterFactory.create(networkMoshi) - fun createFactory(moshi: Moshi = INSTANCE.moshi): Converter.Factory = MoshiConverterFactory.create(moshi) + val sdkMoshiConverter: MoshiJsonConverter = MoshiJsonConverter( + adapters = listOf(BigDecimalAdapter()) + MoshiJsonConverter.getTangemSdkAdapters(), + typedAdapters = MoshiJsonConverter.getTangemSdkTypedAdapters(), + ) - //todo provide via DI using quealifiers - fun defaultMoshi(): Moshi = INSTANCE.moshi - - //todo provide via DI using quealifiers - fun sdkMoshi(): Moshi = MoshiJsonConverter.INSTANCE.moshi + val sdkMoshi: Moshi = sdkMoshiConverter.moshi } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/Retrofit.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/Retrofit.kt index 01e19dfada..86b4ec51f4 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/Retrofit.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/Retrofit.kt @@ -3,16 +3,14 @@ package com.tangem.datasource.api.common import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor -import retrofit2.Converter import retrofit2.Retrofit import java.util.concurrent.TimeUnit -// TODO: refactoring: make it better through factory +@Deprecated("Create and provide by DI") fun createRetrofitInstance( baseUrl: String, okHttpBuilder: OkHttpClient.Builder = OkHttpClient.Builder(), interceptors: List = emptyList(), - converterFactory: Converter.Factory = MoshiConverter.createFactory(), logEnabled: Boolean, ): Retrofit { okHttpBuilder.apply { @@ -27,7 +25,7 @@ fun createRetrofitInstance( return Retrofit.Builder() .baseUrl(baseUrl) - .addConverterFactory(converterFactory) + .addConverterFactory(MoshiConverter.networkMoshiConverter) .client(okHttpBuilder.build()) .build() } diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchErrorsHandler.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchErrorsHandler.kt index 792c9866d7..4fdc784ecb 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchErrorsHandler.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchErrorsHandler.kt @@ -3,11 +3,12 @@ package com.tangem.datasource.api.oneinch import com.squareup.moshi.Moshi import com.tangem.datasource.api.oneinch.errors.OneIncResponseException import com.tangem.datasource.api.oneinch.models.SwapErrorDto +import com.tangem.datasource.di.NetworkMoshi import retrofit2.HttpException import retrofit2.Response import javax.inject.Inject -class OneInchErrorsHandler @Inject constructor(moshi: Moshi) { +class OneInchErrorsHandler @Inject constructor(@NetworkMoshi moshi: Moshi) { private val errorMoshiAdapter = moshi.adapter(SwapErrorDto::class.java) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/PaymentologyApiService.kt b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/PaymentologyApiService.kt index f5dad3a728..ddf7762dca 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/PaymentologyApiService.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/PaymentologyApiService.kt @@ -17,7 +17,7 @@ object PaymentologyApiService { private fun createApi(): PaymentologyApi { return Retrofit.Builder() - .addConverterFactory(MoshiConverter.createFactory(MoshiConverter.sdkMoshi())) + .addConverterFactory(MoshiConverter.networkMoshiConverter) .baseUrl(PAYMENTOLOGY_BASE_URL) .client( OkHttpClient.Builder() diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt index c38d6f6f22..e9ee67a9e7 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt @@ -28,7 +28,7 @@ object TangemTechService { private fun createApi(header: RequestHeader? = null): TangemTechApi { val headers = mutableListOf(CacheControlHeader).apply { header?.let(::add) } return Retrofit.Builder() - .addConverterFactory(MoshiConverter.createFactory()) + .addConverterFactory(MoshiConverter.networkMoshiConverter) .baseUrl(TANGEM_TECH_BASE_URL) .client( OkHttpClient.Builder() diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/MoshiDaggerKey.kt b/core/datasource/src/main/java/com/tangem/datasource/di/MoshiDaggerKey.kt new file mode 100644 index 0000000000..0bae74e68b --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/di/MoshiDaggerKey.kt @@ -0,0 +1,13 @@ +package com.tangem.datasource.di + +import javax.inject.Qualifier + +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class SdkMoshi + +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class NetworkMoshi \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/MoshiModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/MoshiModule.kt new file mode 100644 index 0000000000..0ab6faea17 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/di/MoshiModule.kt @@ -0,0 +1,42 @@ +package com.tangem.datasource.di + +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import com.tangem.common.json.MoshiJsonConverter +import com.tangem.datasource.api.common.BigDecimalAdapter +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class MoshiModule { + + @Provides + @Singleton + @NetworkMoshi + fun provideNetworkMoshi(): Moshi { + return Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .add(BigDecimalAdapter()) + .build() + } + + @Provides + @Singleton + @SdkMoshi + fun provideSdkMoshi(sdkMoshiJsonConverter: MoshiJsonConverter): Moshi { + return sdkMoshiJsonConverter.moshi + } + + @Provides + @Singleton + fun provideSdkMoshiConverter(): MoshiJsonConverter { + return MoshiJsonConverter( + adapters = listOf(BigDecimalAdapter()) + MoshiJsonConverter.getTangemSdkAdapters(), + typedAdapters = MoshiJsonConverter.getTangemSdkTypedAdapters(), + ) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index f339b5636b..d0a72c025c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -1,11 +1,9 @@ package com.tangem.datasource.di import com.squareup.moshi.Moshi -import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory -import com.tangem.datasource.api.common.BigDecimalAdapter +import com.tangem.datasource.api.paymentology.PaymentologyApi import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.datasource.utils.RequestHeader.AuthenticationHeader -import com.tangem.datasource.utils.RequestHeader.CacheControlHeader +import com.tangem.datasource.utils.RequestHeader.* import com.tangem.datasource.utils.addHeaders import com.tangem.datasource.utils.allowLogging import com.tangem.lib.auth.AuthProvider @@ -24,16 +22,7 @@ class NetworkModule { @Provides @Singleton - fun provideMoshi(): Moshi { - return Moshi.Builder() - .add(KotlinJsonAdapterFactory()) - .add(BigDecimalAdapter()) - .build() - } - - @Provides - @Singleton - fun provideTangemTechApi(authProvider: AuthProvider, moshi: Moshi): TangemTechApi { + fun provideTangemTechApi(authProvider: AuthProvider, @NetworkMoshi moshi: Moshi): TangemTechApi { return Retrofit.Builder() .addConverterFactory(MoshiConverterFactory.create(moshi)) .baseUrl(PROD_TANGEM_TECH_BASE_URL) @@ -50,6 +39,21 @@ class NetworkModule { .create(TangemTechApi::class.java) } + @Provides + @Singleton + fun providePaymentologyApi(@NetworkMoshi moshi: Moshi): PaymentologyApi { + return Retrofit.Builder() + .addConverterFactory(MoshiConverterFactory.create(moshi)) + .baseUrl(PAYMENTOLOGY_BASE_URL) + .client( + OkHttpClient.Builder() + .allowLogging() + .build(), + ) + .build() + .create(PaymentologyApi::class.java) + } + private companion object { const val PROD_TANGEM_TECH_BASE_URL = "https://api.tangem-tech.com/v1/" const val DEV_TANGEM_TECH_BASE_URL = "https://devapi.tangem-tech.com/v1/" diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/OneInchApisModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/OneInchApisModule.kt index 7befbaaaf6..fa6cf8a14d 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/OneInchApisModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/OneInchApisModule.kt @@ -19,7 +19,7 @@ class OneInchApisModule { @Provides @Singleton - fun provideOneInchApiFactory(moshi: Moshi): OneInchApiFactory { + fun provideOneInchApiFactory(@NetworkMoshi moshi: Moshi): OneInchApiFactory { val apiFactory = OneInchApiFactory() apiFactory.putApi(ETH_NETWORK, createOneInchApiWithUrl(ONE_INCH_BASE_URL + ONE_INCH_ETH_PATH, moshi)) apiFactory.putApi(BSC_NETWORK, createOneInchApiWithUrl(ONE_INCH_BASE_URL + ONE_INCH_BSC_PATH, moshi)) From 7256473282c9e2958004dd3814a0fa9252e726c0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 16 Jan 2023 19:28:11 +0800 Subject: [PATCH 078/145] Updated on 2026-08-14 --- build.gradle.kts | 4 + buildSrc/build.gradle.kts | 9 + .../main/java/detekt-convention.gradle.kts | 24 + config/detekt/detekt.yml | 655 ++++++++++++++++++ 4 files changed, 692 insertions(+) create mode 100644 buildSrc/src/main/java/detekt-convention.gradle.kts create mode 100644 config/detekt/detekt.yml diff --git a/build.gradle.kts b/build.gradle.kts index f772f94772..f71b201932 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,6 +30,10 @@ allprojects { } } +subprojects { + apply(plugin = "detekt-convention") +} + tasks.register("clean", Delete::class) { delete(rootProject.buildDir) } \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index b22ed732fd..af3fd561b4 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -3,5 +3,14 @@ plugins { } repositories { + google() mavenCentral() +} + +dependencies { + implementation("com.android.tools.build:gradle:7.1.3") + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10") + //TODO("Upgrade version to 1.22.0 after upgrading kotlin version to 1.7.21") + implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.21.0") + implementation("com.squareup:javapoet:1.13.0") } \ No newline at end of file diff --git a/buildSrc/src/main/java/detekt-convention.gradle.kts b/buildSrc/src/main/java/detekt-convention.gradle.kts new file mode 100644 index 0000000000..01220d13e0 --- /dev/null +++ b/buildSrc/src/main/java/detekt-convention.gradle.kts @@ -0,0 +1,24 @@ +import io.gitlab.arturbosch.detekt.Detekt + +plugins { + id("io.gitlab.arturbosch.detekt") +} + +tasks.withType { + parallel = true + ignoreFailures = false + autoCorrect = true + buildUponDefaultConfig = true + + config.setFrom(rootProject.files("config/detekt/detekt.yml")) + include("**/*.kt") + exclude("**/resources/**", "**/build/**") + reports { + txt { + required.set(true) + outputLocation.set(rootProject.file("build/reports/detekt.txt")) + } + } + + jvmTarget = "1.8" +} \ No newline at end of file diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml new file mode 100644 index 0000000000..306dcd59df --- /dev/null +++ b/config/detekt/detekt.yml @@ -0,0 +1,655 @@ +build: + maxIssues: 0 + +config: + validation: true + warningsAsErrors: false + +processors: + active: true + +console-reports: + active: true + +output-reports: + active: true + exclude: + # - 'TxtOutputReport' + - 'XmlOutputReport' + - 'HtmlOutputReport' + - 'MdOutputReport' + +comments: + active: false + AbsentOrWrongFileLicense: + active: false + licenseTemplateFile: 'license.template' + licenseTemplateIsRegex: false + CommentOverPrivateFunction: + active: false + CommentOverPrivateProperty: + active: false + DeprecatedBlockTag: + active: false + EndOfSentenceFormat: + active: false + endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' + KDocReferencesNonPublicProperty: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**'] + OutdatedDocumentation: + active: false + matchTypeParameters: true + matchDeclarationsOrder: true + allowParamOnConstructorProperties: false + UndocumentedPublicClass: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**'] + searchInNestedClass: true + searchInInnerClass: true + searchInInnerObject: true + searchInInnerInterface: true + UndocumentedPublicFunction: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**'] + UndocumentedPublicProperty: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**'] + +complexity: + active: true + ComplexMethod: + active: true + ComplexCondition: + active: true + threshold: 4 + ComplexInterface: + active: false + threshold: 10 + includeStaticDeclarations: false + includePrivateDeclarations: false + LabeledExpression: + active: false + ignoredLabels: [] + LargeClass: + active: true + threshold: 300 + LongMethod: + active: true + threshold: 60 + LongParameterList: + active: true + functionThreshold: 6 + constructorThreshold: 7 + ignoreDefaultParameters: true + ignoreDataClasses: true + ignoreAnnotated: ['Provides'] + MethodOverloading: + active: true + threshold: 6 + NamedArguments: + active: true + threshold: 3 + ignoreArgumentsMatchingNames: false + NestedBlockDepth: + active: true + threshold: 5 + NestedScopeFunctions: + active: false + threshold: 1 + functions: + - 'kotlin.apply' + - 'kotlin.run' + - 'kotlin.with' + - 'kotlin.let' + - 'kotlin.also' + ReplaceSafeCallChainWithRun: + active: true + StringLiteralDuplication: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**'] + threshold: 3 + ignoreAnnotation: true + excludeStringsWithLessThan5Characters: true + ignoreStringsRegex: '$^' + TooManyFunctions: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**'] + thresholdInFiles: 20 + thresholdInClasses: 20 + thresholdInInterfaces: 20 + thresholdInObjects: 20 + thresholdInEnums: 20 + ignoreDeprecated: false + ignorePrivate: true + ignoreOverridden: false + +coroutines: + active: true + GlobalCoroutineUsage: + active: true + InjectDispatcher: + active: false #TODO + dispatcherNames: + - 'IO' + - 'Default' + - 'Unconfined' + RedundantSuspendModifier: + active: true + SleepInsteadOfDelay: + active: true + SuspendFunWithCoroutineScopeReceiver: + active: false + SuspendFunWithFlowReturnType: + active: true + +empty-blocks: + active: true + EmptyCatchBlock: + active: true + allowedExceptionNameRegex: '_|(ignore|expected).*' + EmptyClassBlock: + active: true + EmptyDefaultConstructor: + active: true + EmptyDoWhileBlock: + active: true + EmptyElseBlock: + active: true + EmptyFinallyBlock: + active: true + EmptyForBlock: + active: true + EmptyFunctionBlock: + active: true + ignoreOverridden: true + EmptyIfBlock: + active: true + EmptyInitBlock: + active: true + EmptyKtFile: + active: true + EmptySecondaryConstructor: + active: true + EmptyTryBlock: + active: true + EmptyWhenBlock: + active: true + EmptyWhileBlock: + active: true + +exceptions: + active: true + ExceptionRaisedInUnexpectedLocation: + active: true + methodNames: + - 'equals' + - 'finalize' + - 'hashCode' + - 'toString' + InstanceOfCheckForException: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**'] + NotImplementedDeclaration: + active: false + ObjectExtendsThrowable: + active: true + PrintStackTrace: + active: true + RethrowCaughtException: + active: true + ReturnFromFinally: + active: true + ignoreLabeled: false + SwallowedException: + active: false + ignoredExceptionTypes: + - 'InterruptedException' + - 'MalformedURLException' + - 'NumberFormatException' + - 'ParseException' + allowedExceptionNameRegex: '_|(ignore|expected).*' + ThrowingExceptionFromFinally: + active: true + ThrowingExceptionInMain: + active: false + ThrowingExceptionsWithoutMessageOrCause: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**'] + exceptions: + - 'ArrayIndexOutOfBoundsException' + - 'Exception' + - 'IllegalArgumentException' + - 'IllegalMonitorStateException' + - 'IllegalStateException' + - 'IndexOutOfBoundsException' + - 'NullPointerException' + - 'RuntimeException' + - 'Throwable' + ThrowingNewInstanceOfSameException: + active: true + TooGenericExceptionCaught: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**'] + exceptionNames: + - 'ArrayIndexOutOfBoundsException' + - 'Error' + - 'Exception' + - 'IllegalMonitorStateException' + - 'IndexOutOfBoundsException' + - 'NullPointerException' + - 'RuntimeException' + - 'Throwable' + allowedExceptionNameRegex: '_|(ignore|expected).*' + TooGenericExceptionThrown: + active: true + exceptionNames: + - 'Error' + - 'Exception' + - 'RuntimeException' + - 'Throwable' + +naming: + active: true + BooleanPropertyNaming: + active: true + allowedPattern: '^(is|has|are)' + ignoreOverridden: true + ClassNaming: + active: true + classPattern: '[A-Z][a-zA-Z0-9]*' + ConstructorParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*' + privateParameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + ignoreOverridden: true + EnumNaming: + active: true + enumEntryPattern: '[A-Z][_a-zA-Z0-9]*' + ForbiddenClassName: + active: false + forbiddenName: [] + FunctionMaxLength: + active: false + maximumFunctionNameLength: 30 + FunctionMinLength: + active: false + minimumFunctionNameLength: 3 + FunctionNaming: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**'] + functionPattern: '[a-z][a-zA-Z0-9]*' + excludeClassPattern: '$^' + ignoreOverridden: true + ignoreAnnotated: ['Composable'] + FunctionParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + ignoreOverridden: true + InvalidPackageDeclaration: + active: true + rootPackage: '' + requireRootInDeclaration: false + LambdaParameterNaming: + active: false + parameterPattern: '[a-z][A-Za-z0-9]*|_' + MatchingDeclarationName: + active: true + mustBeFirst: true + MemberNameEqualsClassName: + active: false + ignoreOverridden: true + NoNameShadowing: + active: true + NonBooleanPropertyPrefixedWithIs: + active: true + ObjectPropertyNaming: + active: true + constantPattern: '[A-Za-z][_A-Za-z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' + PackageNaming: + active: true + packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*' + TopLevelPropertyNaming: + active: true + constantPattern: '[A-Z][_A-Z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' + VariableMaxLength: + active: false + maximumVariableNameLength: 50 + VariableMinLength: + active: false + minimumVariableNameLength: 3 + VariableNaming: + active: true + variablePattern: '[a-z][A-Za-z0-9]*' + privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + ignoreOverridden: true + +performance: + active: true + ArrayPrimitive: + active: true + CouldBeSequence: + active: false + threshold: 3 + ForEachOnRange: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**'] + SpreadOperator: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**'] + UnnecessaryTemporaryInstantiation: + active: true + +potential-bugs: + active: true + AvoidReferentialEquality: + active: true + forbiddenTypePatterns: + - 'kotlin.String' + CastToNullableType: + active: true + Deprecation: + active: false + DontDowncastCollectionTypes: + active: true + DoubleMutabilityForCollection: + active: true + mutableTypes: + - 'kotlin.collections.MutableList' + - 'kotlin.collections.MutableMap' + - 'kotlin.collections.MutableSet' + - 'java.util.ArrayList' + - 'java.util.LinkedHashSet' + - 'java.util.HashSet' + - 'java.util.LinkedHashMap' + - 'java.util.HashMap' + ElseCaseInsteadOfExhaustiveWhen: + active: false + EqualsAlwaysReturnsTrueOrFalse: + active: true + EqualsWithHashCodeExist: + active: true + ExitOutsideMain: + active: false + ExplicitGarbageCollectionCall: + active: true + HasPlatformType: + active: true + IgnoredReturnValue: + active: true + returnValueAnnotations: + - '*.CheckResult' + - '*.CheckReturnValue' + ignoreReturnValueAnnotations: + - '*.CanIgnoreReturnValue' + ignoreFunctionCall: [] + ImplicitDefaultLocale: + active: true + ImplicitUnitReturnType: + active: false + allowExplicitReturnType: true + InvalidRange: + active: true + IteratorHasNextCallsNextMethod: + active: true + IteratorNotThrowingNoSuchElementException: + active: true + LateinitUsage: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**'] + ignoreAnnotated: ['Inject'] + ignoreOnClassesPattern: '' + MapGetWithNotNullAssertionOperator: + active: true + MissingPackageDeclaration: + active: true + excludes: ['**/*.kts'] + NullCheckOnMutableProperty: + active: true + NullableToStringCall: + active: true + UnconditionalJumpStatementInLoop: + active: true + UnnecessaryNotNullOperator: + active: true + UnnecessarySafeCall: + active: true + UnreachableCatchBlock: + active: true + UnreachableCode: + active: true + UnsafeCallOnNullableType: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**'] + UnsafeCast: + active: true + UnusedUnaryOperator: + active: true + UselessPostfixExpression: + active: true + WrongEqualsTypeParameter: + active: true + +style: + active: true + CanBeNonNullable: + active: false + CascadingCallWrapping: + active: false + includeElvis: true + ClassOrdering: + active: true + CollapsibleIfStatements: + active: true + DataClassContainsFunctions: + active: false + conversionFunctionPrefix: + - 'to' + DataClassShouldBeImmutable: + active: false + DestructuringDeclarationWithTooManyEntries: + active: true + maxDestructuringEntries: 3 + EqualsNullCall: + active: false + EqualsOnSignatureLine: + active: true + ExplicitCollectionElementAccessMethod: + active: true + ExplicitItLambdaParameter: + active: true + ExpressionBodySyntax: + active: false + includeLineWrapping: true + ForbiddenComment: + active: false + values: + - 'FIXME:' + - 'STOPSHIP:' + - 'TODO:' + allowedPatterns: '' + customMessage: '' + ForbiddenImport: + active: false + imports: [] + forbiddenPatterns: '' + ForbiddenMethodCall: + active: false + methods: + - reason: 'print does not allow you to configure the output stream. Use a logger instead.' + value: 'kotlin.io.print' + - reason: 'println does not allow you to configure the output stream. Use a logger instead.' + value: 'kotlin.io.println' + ForbiddenSuppress: + active: false + rules: [] + ForbiddenVoid: + active: false + ignoreOverridden: false + ignoreUsageInGenerics: false + FunctionOnlyReturningConstant: + active: true + ignoreOverridableFunction: true + ignoreActualFunction: true + LoopWithTooManyJumpStatements: + active: true + maxJumpCount: 2 + MagicNumber: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/*.kts'] + ignoreNumbers: + - '-1' + - '0' + - '1' + - '2' + ignoreHashCodeFunction: true + ignorePropertyDeclaration: true + ignoreLocalVariableDeclaration: false + ignoreConstantDeclaration: true + ignoreCompanionObjectPropertyDeclaration: true + ignoreAnnotation: false + ignoreNamedArgument: true + ignoreEnums: false + ignoreRanges: false + ignoreExtensionFunctions: true + ignoreAnnotated: ['Preview'] + MandatoryBracesIfStatements: + active: true + MandatoryBracesLoops: + active: true + MaxChainedCallsOnSameLine: + active: true + maxChainedCalls: 5 + MaxLineLength: + active: true + maxLineLength: 120 + excludePackageStatements: true + excludeImportStatements: true + excludeCommentStatements: false + MayBeConst: + active: true + ModifierOrder: + active: true + MultilineLambdaItParameter: + active: true + NestedClassesVisibility: + active: true + NewLineAtEndOfFile: + active: false + NoTabs: + active: true + NullableBooleanCheck: + active: true + ObjectLiteralToLambda: + active: true + OptionalAbstractKeyword: + active: true + OptionalUnit: + active: true + OptionalWhenBraces: + active: false + PreferToOverPairSyntax: + active: false + ProtectedMemberInFinalClass: + active: true + RedundantExplicitType: + active: false + RedundantHigherOrderMapUsage: + active: true + RedundantVisibilityModifierRule: + active: true + ReturnCount: + active: false + max: 2 + excludedFunctions: + - 'equals' + excludeLabeled: false + excludeReturnFromLambda: true + excludeGuardClauses: false + SafeCast: + active: true + SerialVersionUIDInSerializableClass: + active: true + SpacingBetweenPackageAndImports: + active: true + ThrowsCount: + active: false + max: 2 + excludeGuardClauses: false + TrailingWhitespace: + active: false + UnderscoresInNumericLiterals: + active: false + acceptableLength: 4 + allowNonStandardGrouping: false + UnnecessaryAbstractClass: + active: true + UnnecessaryAnnotationUseSiteTarget: + active: false + UnnecessaryApply: + active: true + UnnecessaryBackticks: + active: true + UnnecessaryFilter: + active: true + UnnecessaryInheritance: + active: true + UnnecessaryInnerClass: + active: false + UnnecessaryLet: + active: true + UnnecessaryParentheses: + active: true + UntilInsteadOfRangeTo: + active: true + UnusedImports: + active: true + UnusedPrivateClass: + active: true + UnusedPrivateMember: + active: true + allowedNames: '(_|ignored|expected|serialVersionUID)' + ignoreAnnotated: ['Preview'] + UseAnyOrNoneInsteadOfFind: + active: true + UseArrayLiteralsInAnnotations: + active: true + UseCheckNotNull: + active: true + UseCheckOrError: + active: true + UseDataClass: + active: false + allowVars: false + UseEmptyCounterpart: + active: true + UseIfEmptyOrIfBlank: + active: true + UseIfInsteadOfWhen: + active: false + UseIsNullOrEmpty: + active: true + UseOrEmpty: + active: true + UseRequire: + active: true + UseRequireNotNull: + active: true + UselessCallOnNotNull: + active: true + UtilityClassWithPublicConstructor: + active: true + VarCouldBeVal: + active: true + ignoreLateinitVar: false + WildcardImport: + active: false + excludeImports: + - 'java.util.*' From fc8fcfce192a96ca50aa53054284b821650c6bb3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 13 Jan 2023 22:35:37 +0400 Subject: [PATCH 079/145] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 4 + core/res/src/main/res/values-fr/strings.xml | 4 + core/res/src/main/res/values-it/strings.xml | 4 + core/res/src/main/res/values-ru/strings.xml | 4 + core/res/src/main/res/values/strings.xml | 4 + .../com/tangem/core/ui/components/Dialogs.kt | 143 ++++++++++++++++++ .../core/ui/components/SuccessScreen.kt | 84 ++++++++-- .../com/tangem/core/ui/components/Warnings.kt | 11 +- .../res/drawable/ic_arrow_top_right_24.xml | 10 ++ .../swap/models/SwapSelectTokenStateHolder.kt | 4 +- .../feature/swap/models/SwapStateHolder.kt | 9 +- .../swap/models/SwapSuccessStateHolder.kt | 1 + .../tangem/feature/swap/ui/StateBuilder.kt | 14 +- .../feature/swap/ui/SwapScreenContent.kt | 23 ++- .../feature/swap/ui/SwapSelectTokenScreen.kt | 120 +++++++-------- .../feature/swap/ui/SwapSuccessScreen.kt | 44 +++++- .../tangem/feature/swap/ui/TransactionCard.kt | 45 ++---- 17 files changed, 395 insertions(+), 133 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt create mode 100644 core/ui/src/main/res/drawable/ic_arrow_top_right_24.xml diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index e81c23629f..e62a78ccb6 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -191,6 +191,7 @@ Share Copy Erfolg + Close You Send You Receive Give Permission @@ -209,6 +210,9 @@ Other tokens Waiting Transaction in progress… + In progress + View in Explorer + not available Failed to load the information about the referral program. Please try again later. Failed to load the information about the referral program. Reason: %s. Please try again later. Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 7fa8a61c7d..ada856b314 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -191,6 +191,7 @@ Share Copy Avec succès + Close You Send You Receive Give Permission @@ -209,6 +210,9 @@ Other tokens Waiting Transaction in progress… + In progress + View in Explorer + not available Failed to load the information about the referral program. Please try again later. Failed to load the information about the referral program. Reason: %s. Please try again later. Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml index d183fa7130..d1e4f054a7 100644 --- a/core/res/src/main/res/values-it/strings.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -191,6 +191,7 @@ Share Copy Con successo + Close You Send You Receive Give Permission @@ -209,6 +210,9 @@ Other tokens Waiting Transaction in progress… + In progress + View in Explorer + not available Failed to load the information about the referral program. Please try again later. Failed to load the information about the referral program. Reason: %s. Please try again later. Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 1301436efc..9fa9a20bee 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -191,6 +191,7 @@ Поделиться Копировать Успешно + Закрыть Вы отправляете Вы получаете Дать разрешение @@ -209,6 +210,9 @@ Другие токены Подождите Транзакция в процессе… + В процессе + Открыть в обозревателе + не доступен Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже. Не удалось обработать вашу заявку на участие. Код ошибки: %s. Пожалуйста, попробуйте позже. Если проблема сохранится, вы можете обратиться в техподдержку. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 0824f29b0f..8a2711bb79 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -191,6 +191,7 @@ Share Copy Success + Close You Send You Receive Give Permission @@ -209,6 +210,9 @@ Other tokens Waiting Transaction in progress… + In progress + View in Explorer + not available Failed to load the information about the referral program. Please try again later. Failed to load the information about the referral program. Error code: %s. Please try again later. Your participation request could not be processed. Error code: %s. Please try again later. If the problem persists — feel free to contact our support. diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt new file mode 100644 index 0000000000..b9f323d670 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt @@ -0,0 +1,143 @@ +package com.tangem.core.ui.components + +import androidx.compose.foundation.layout.padding +import androidx.compose.material.AlertDialog +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.R +import com.tangem.core.ui.res.TangemTheme + +/** + * Simple alert dialog with a message and 'OK' button + * + * @param message message to show + * @param title title to show (no title if null) + * @param confirmButton title and action for confirm button + * @param dismissButton title and action for dismiss button (no dismiss button if null) + * @param onDismissDialog action to perform when dialog is closed + * + * @see Figma component + */ +@Composable +fun BasicDialog( + modifier: Modifier = Modifier, + message: String, + title: String? = null, + confirmButton: DialogButton, + dismissButton: DialogButton? = null, + onDismissDialog: () -> Unit, +) { + AlertDialog( + text = { + Text( + text = message, + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, + ) + }, + title = if (title != null) { + { + Text( + text = title, + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + ) + } + } else { + null + }, + onDismissRequest = onDismissDialog, + confirmButton = { + TextButton( + modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12), + text = confirmButton.title ?: stringResource(id = R.string.common_ok), + onClick = confirmButton.onClick, + ) + }, + dismissButton = { + if (dismissButton != null) { + TextButton( + modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12), + text = dismissButton.title ?: stringResource(id = R.string.common_cancel), + onClick = dismissButton.onClick, + ) + } + }, + shape = TangemTheme.shapes.roundedCornersLarge, + modifier = modifier + .padding(TangemTheme.dimens.spacing24), + ) +} + +data class DialogButton( + val title: String? = null, + val onClick: () -> Unit, +) + +@Composable +fun SimpleOkDialog( + modifier: Modifier = Modifier, + message: String, + onDismissDialog: () -> Unit, +) { + BasicDialog( + modifier = modifier, + message = message, + confirmButton = DialogButton(onClick = onDismissDialog), + onDismissDialog = onDismissDialog, + ) +} + +// region Preview + +@Composable +private fun SimpleOkDialogPreview() = SimpleOkDialog( + message = "All protected passwords will be deleted from the " + + "secure storage, you must enter the wallet password to work with the app.", +) {} + +@Composable +private fun BasicDialogPreview() = BasicDialog( + message = "All protected passwords will be deleted from the secure storage, you must enter the wallet password to work with the app", + title = "Attention", + confirmButton = DialogButton {}, + dismissButton = DialogButton {}, +) {} + +@Preview(showBackground = true) +@Composable +fun Preview_SimpleOkDialog_InLightTheme() { + TangemTheme(isDark = false) { + SimpleOkDialogPreview() + } +} + +@Preview(showBackground = true) +@Composable +fun Preview_BasicDialog_InLightTheme() { + TangemTheme(isDark = false) { + BasicDialogPreview() + } +} + +@Preview(showBackground = true) +@Composable +fun Preview_SimpleOkDialog_InDarkTheme() { + TangemTheme(isDark = true) { + SimpleOkDialogPreview() + } +} + +@Preview(showBackground = true) +@Composable +fun Preview_BasicDialog_InDarkTheme() { + TangemTheme(isDark = true) { + BasicDialogPreview() + } +} + +// endregion Preview \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/SuccessScreen.kt b/core/ui/src/main/java/com/tangem/core/ui/components/SuccessScreen.kt index 9464d4355c..c87490b133 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/SuccessScreen.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/SuccessScreen.kt @@ -1,5 +1,7 @@ package com.tangem.core.ui.components +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -18,6 +20,7 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -26,25 +29,37 @@ import com.tangem.core.ui.R import com.tangem.core.ui.res.TangemTheme /** - * Screen for showing success + * Screen for showing result * - * @param successMessage + * @param resultMessage message to show + * @param title title to show + * @param resultColor color which will tint the round icon of the result + * @param icon icon to show in the middle of the round icon + * @param secondaryButtonIcon icon to show in the secondary button + * @param secondaryButtonText label of the secondary button + * @param onSecondaryButtonClick action on clicking secondary button * @param onButtonClick action on clicking "Done" button * * @see Figma component */ @Composable -fun SuccessScreenContent( +fun ResultScreenContent( modifier: Modifier = Modifier, - successMessage: String, + resultMessage: String, + @StringRes title: Int = R.string.common_success, + resultColor: Color = TangemTheme.colors.icon.accent, + @DrawableRes icon: Int = R.drawable.ic_check_24, + @DrawableRes secondaryButtonIcon: Int? = null, + @StringRes secondaryButtonText: Int? = null, + onSecondaryButtonClick: (() -> Unit)? = null, onButtonClick: () -> Unit, ) { Column( modifier = modifier .fillMaxSize() .verticalScroll(rememberScrollState()) - .background(TangemTheme.colors.background.primary) + .background(TangemTheme.colors.background.secondary) .padding( horizontal = TangemTheme.dimens.spacing16, vertical = TangemTheme.dimens.spacing32, @@ -53,10 +68,10 @@ fun SuccessScreenContent( verticalArrangement = Arrangement.Center, ) { SpacerHHalf() - SuccessImage() + SuccessImage(resultColor = resultColor, icon = icon) SpacerH50() Text( - text = stringResource(id = R.string.common_success), + text = stringResource(id = title), style = TangemTheme.typography.h2, color = TangemTheme.colors.text.primary1, textAlign = TextAlign.Center, @@ -64,15 +79,25 @@ fun SuccessScreenContent( ) SpacerH12() Text( - text = successMessage, + text = resultMessage, style = TangemTheme.typography.subtitle1, color = TangemTheme.colors.text.secondary, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth(), ) SpacerHHalf() + if (onSecondaryButtonClick != null && secondaryButtonText != null) { + SecondaryButtonForResultScreen( + secondaryButtonText = secondaryButtonText, + secondaryButtonIcon = secondaryButtonIcon, + onSecondaryButtonClick = onSecondaryButtonClick, + modifier = Modifier + .fillMaxWidth(), + ) + SpacerH12() + } PrimaryButton( - text = stringResource(id = R.string.common_done), + text = stringResource(id = R.string.common_close), modifier = Modifier .fillMaxWidth(), onClick = { onButtonClick() }, @@ -81,11 +106,15 @@ fun SuccessScreenContent( } @Composable -fun SuccessImage() { +fun SuccessImage( + resultColor: Color, + @DrawableRes icon: Int, +) { Box( modifier = Modifier .background( - color = TangemTheme.colors.icon.accent.copy(alpha = 0.2f), + color = (resultColor) + .copy(alpha = 0.2f), shape = CircleShape, ), contentAlignment = Alignment.Center, @@ -94,7 +123,7 @@ fun SuccessImage() { modifier = Modifier .padding(TangemTheme.dimens.spacing24) .background( - color = TangemTheme.colors.icon.accent, + color = resultColor, shape = CircleShape, ) .height(TangemTheme.dimens.size93) @@ -102,7 +131,7 @@ fun SuccessImage() { contentAlignment = Alignment.Center, ) { Icon( - painter = painterResource(id = R.drawable.ic_check_24), + painter = painterResource(id = icon), contentDescription = null, tint = TangemTheme.colors.icon.primary2, modifier = Modifier.size(TangemTheme.dimens.size40), @@ -111,12 +140,37 @@ fun SuccessImage() { } } +@Composable +private fun SecondaryButtonForResultScreen( + modifier: Modifier = Modifier, + @StringRes secondaryButtonText: Int, + @DrawableRes secondaryButtonIcon: Int? = null, + onSecondaryButtonClick: (() -> Unit), +) { + if (secondaryButtonIcon != null) { + SecondaryButtonIconLeft( + text = stringResource(id = secondaryButtonText), + icon = painterResource(id = secondaryButtonIcon), + onClick = onSecondaryButtonClick, + modifier = modifier, + ) + } else { + SecondaryButton( + text = stringResource(id = secondaryButtonText), + onClick = onSecondaryButtonClick, + modifier = modifier, + ) + } +} + // region preview @Composable private fun SuccessScreenPreview() { - SuccessScreenContent( - successMessage = "Swap of 1 000 DAI to 1 131,46 MATIC", + ResultScreenContent( + resultMessage = "Swap of 1 000 DAI to 1 131,46 MATIC", + secondaryButtonText = R.string.swapping_success_view_explorer_button_title, + onSecondaryButtonClick = {}, onButtonClick = {}, ) } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt index 86a1085ebf..0b0968581c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt @@ -28,6 +28,7 @@ fun WarningCard( modifier: Modifier = Modifier, title: String, description: String, + icon: @Composable (() -> Unit)? = null, ) { WarningCardSurface( modifier = modifier, @@ -35,6 +36,7 @@ fun WarningCard( WarningBody( title = title, description = description, + icon = icon, ) }, ) @@ -55,12 +57,13 @@ fun ClickableWarningCard( modifier: Modifier = Modifier, title: String, description: String, + icon: @Composable (() -> Unit)? = null, onClick: () -> Unit, ) { WarningCardSurface( modifier = modifier, content = { - WarningBody(title = title, description = description) { + WarningBody(title = title, description = description, icon = icon) { SpacerW12() Icon( painter = painterResource(id = R.drawable.ic_chevron_right_24), @@ -88,12 +91,13 @@ fun RefreshableWaringCard( modifier: Modifier = Modifier, title: String, description: String, + icon: @Composable (() -> Unit)? = null, onClick: () -> Unit, ) { WarningCardSurface( modifier = modifier, content = { - WarningBody(title = title, description = description) { + WarningBody(title = title, description = description, icon = icon) { SpacerW12() Icon( painter = painterResource(id = R.drawable.ic_refresh_24), @@ -113,6 +117,7 @@ private fun WarningBody( modifier: Modifier = Modifier, title: String, description: String, + icon: @Composable (() -> Unit)? = null, additionalContent: @Composable () -> Unit = {}, ) { IconWithTitleAndDescription( @@ -120,7 +125,7 @@ private fun WarningBody( title = title, description = description, additionalContent = additionalContent, - icon = { + icon = icon ?: { Image( painter = painterResource(id = R.drawable.img_attention_20), contentDescription = null, diff --git a/core/ui/src/main/res/drawable/ic_arrow_top_right_24.xml b/core/ui/src/main/res/drawable/ic_arrow_top_right_24.xml new file mode 100644 index 0000000000..e23e12a295 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_arrow_top_right_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSelectTokenStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSelectTokenStateHolder.kt index 4510f9ece6..cf80940a59 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSelectTokenStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSelectTokenStateHolder.kt @@ -1,8 +1,7 @@ package com.tangem.feature.swap.models data class SwapSelectTokenStateHolder( - val addedTokens: List, - val otherTokens: List, + val tokens: List, val onSearchEntered: (String) -> Unit, val onTokenSelected: (String) -> Unit, ) @@ -12,6 +11,7 @@ data class TokenToSelect( val name: String, val symbol: String, val iconUrl: String, + val available: Boolean = true, val addedTokenBalanceData: TokenBalanceData? = null, ) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt index 3f78f0a311..391a4fdfc4 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt @@ -6,8 +6,9 @@ data class SwapStateHolder( val sendCardData: SwapCardData, val receiveCardData: SwapCardData, val networkCurrency: String, - val fee: FeeState = FeeState.Loaded(), + val fee: FeeState = FeeState.Empty, val warnings: List = emptyList(), + val alert: SwapWarning.GenericWarning? = null, val permissionState: SwapPermissionState = SwapPermissionState.Empty, val successState: SwapSuccessStateHolder? = null, @@ -29,6 +30,7 @@ data class SwapCardData( val amountEquivalent: String?, val tokenIconUrl: String, val tokenCurrency: String, + val balance: String, @DrawableRes val networkIconRes: Int? = null, val canSelectAnotherToken: Boolean = false, ) @@ -40,6 +42,9 @@ data class SwapButton( ) sealed interface FeeState { + + object Empty : FeeState + data class Loaded( val fee: String = "", ) : FeeState @@ -52,8 +57,6 @@ sealed interface FeeState { sealed interface TransactionCardType { data class SendCard( - val balance: String, - val permissionIsGiven: Boolean, val onAmountChanged: ((String) -> Unit), ) : TransactionCardType diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSuccessStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSuccessStateHolder.kt index 9fad250c3a..5b6a026b1f 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSuccessStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSuccessStateHolder.kt @@ -2,4 +2,5 @@ package com.tangem.feature.swap.models data class SwapSuccessStateHolder( val message: String, + val onSecondaryButtonClick: () -> Unit, ) \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 33bd25c82d..5e91941a56 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -22,12 +22,13 @@ class StateBuilder { fun createInitialLoadingState(networkCurrency: String, onAmountChanged: (String) -> Unit): SwapStateHolder { return SwapStateHolder( sendCardData = SwapCardData( - type = TransactionCardType.SendCard("", true, onAmountChanged), + type = TransactionCardType.SendCard(onAmountChanged), amount = null, amountEquivalent = null, tokenIconUrl = "", tokenCurrency = "", canSelectAnotherToken = false, + balance = "", ), receiveCardData = SwapCardData( type = TransactionCardType.ReceiveCard(), @@ -36,6 +37,7 @@ class StateBuilder { tokenIconUrl = "", tokenCurrency = "", canSelectAnotherToken = false, + balance = "", ), fee = FeeState.Loading, networkCurrency = networkCurrency, @@ -60,6 +62,7 @@ class StateBuilder { tokenIconUrl = fromToken.logoUrl, tokenCurrency = fromToken.symbol, canSelectAnotherToken = mainTokenId != fromToken.id, + balance = "", ), receiveCardData = SwapCardData( type = TransactionCardType.ReceiveCard(), @@ -68,6 +71,7 @@ class StateBuilder { tokenIconUrl = toToken.logoUrl, tokenCurrency = toToken.symbol, canSelectAnotherToken = mainTokenId != toToken.id, + balance = "", ), fee = FeeState.Loading, swapButton = SwapButton(enabled = false, loading = true, onClick = {}), @@ -83,16 +87,13 @@ class StateBuilder { ): SwapStateHolder { return uiStateHolder.copy( sendCardData = SwapCardData( - type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard) - .copy( - permissionIsGiven = quoteModel.isAllowedToSpend, - balance = quoteModel.fromTokenWalletBalance, - ), + type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard), amount = quoteModel.fromTokenAmount.formatToUIRepresentation(), amountEquivalent = quoteModel.fromTokenFiatBalance, tokenIconUrl = uiStateHolder.sendCardData.tokenIconUrl, tokenCurrency = uiStateHolder.sendCardData.tokenCurrency, canSelectAnotherToken = uiStateHolder.sendCardData.canSelectAnotherToken, + balance = quoteModel.fromTokenWalletBalance, ), receiveCardData = SwapCardData( type = TransactionCardType.ReceiveCard(), @@ -101,6 +102,7 @@ class StateBuilder { tokenIconUrl = uiStateHolder.receiveCardData.tokenIconUrl, tokenCurrency = uiStateHolder.receiveCardData.tokenCurrency, canSelectAnotherToken = uiStateHolder.receiveCardData.canSelectAnotherToken, + balance = quoteModel.toTokenWalletBalance, ), warnings = if (!quoteModel.isAllowedToSpend) { listOf(SwapWarning.PermissionNeeded(fromToken.symbol)) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index a0475d02a6..0d7242fde3 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -1,6 +1,7 @@ package com.tangem.feature.swap.ui import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -28,6 +29,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.BasicDialog import com.tangem.core.ui.components.CardWithIcon import com.tangem.core.ui.components.ClickableWarningCard import com.tangem.core.ui.components.Keyboard @@ -133,6 +135,10 @@ internal fun SwapScreenContent( textAlign = TextAlign.Start, ) } + + if (state.alert != null) { + BasicDialog(message = state.alert.message, onDismissDialog = state.alert.onClick) + } } } @@ -147,6 +153,7 @@ private fun MainInfo(state: SwapStateHolder) { Column { TransactionCard( type = state.sendCardData.type, + balance = state.sendCardData.balance, amount = state.sendCardData.amount, amountEquivalent = state.sendCardData.amountEquivalent, tokenIconUrl = state.sendCardData.tokenIconUrl, @@ -157,6 +164,7 @@ private fun MainInfo(state: SwapStateHolder) { SpacerH16() TransactionCard( type = state.receiveCardData.type, + balance = state.receiveCardData.balance, amount = state.receiveCardData.amount, amountEquivalent = state.receiveCardData.amountEquivalent, tokenIconUrl = state.receiveCardData.tokenIconUrl, @@ -203,6 +211,7 @@ private fun FeeItem(feeState: FeeState, currency: String) { warningText = stringResource(id = R.string.token_details_send_blocked_fee_format, currency), ) } + is FeeState.Empty -> {} } } @@ -227,6 +236,13 @@ private fun SwapWarnings( id = R.string.swapping_permission_subheader, warning.tokenCurrency, ), + icon = { + Image( + painter = painterResource(id = com.tangem.core.ui.R.drawable.ic_locked_24), + contentDescription = null, + modifier = Modifier.size(TangemTheme.dimens.size20), + ) + }, onClick = onApproveWarningClick, ) } @@ -252,13 +268,14 @@ private fun SwapWarnings( // region preview private val sendCard = SwapCardData( - type = TransactionCardType.SendCard("123", false, {}), + type = TransactionCardType.SendCard {}, amount = "1 000 000", amountEquivalent = "1 000 000", tokenIconUrl = "", tokenCurrency = "DAI", networkIconRes = R.drawable.img_polygon_22, canSelectAnotherToken = false, + balance = "123", ) private val receiveCard = SwapCardData( @@ -269,17 +286,19 @@ private val receiveCard = SwapCardData( tokenCurrency = "DAI", networkIconRes = R.drawable.img_polygon_22, canSelectAnotherToken = true, + balance = "33333", ) private val state = SwapStateHolder( sendCardData = sendCard, receiveCardData = receiveCard, fee = FeeState.Loaded(fee = "0.155 MATIC (0.14 $)"), - warnings = emptyList(), + warnings = listOf(SwapWarning.PermissionNeeded("DAI")), networkCurrency = "MATIC", swapButton = SwapButton(enabled = true, loading = false, onClick = {}), onRefresh = {}, onBackClicked = {}, onChangeCardsClicked = {}, permissionState = SwapPermissionState.InProgress, + // alert = SwapWarning.GenericWarning("There was an error. Please try again.") {}, ) @Preview diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt index f8be3fa3c2..2b2ad1c321 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt @@ -1,9 +1,5 @@ -@file:OptIn(ExperimentalFoundationApi::class) - package com.tangem.feature.swap.ui -import androidx.annotation.StringRes -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -23,9 +19,10 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ColorMatrix import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import coil.compose.SubcomposeAsyncImage import coil.request.ImageRequest @@ -44,8 +41,8 @@ fun SwapSelectTokenScreen(state: SwapSelectTokenStateHolder, onBack: () -> Unit) TangemTheme { Scaffold( - content = { - ListOfTokens(state = state) + content = { padding -> + ListOfTokens(state = state, Modifier.padding(padding)) }, topBar = { ExpandableSearchView( @@ -61,9 +58,9 @@ fun SwapSelectTokenScreen(state: SwapSelectTokenStateHolder, onBack: () -> Unit) } @Composable -private fun ListOfTokens(state: SwapSelectTokenStateHolder) { +private fun ListOfTokens(state: SwapSelectTokenStateHolder, modifier: Modifier = Modifier) { LazyColumn( - modifier = Modifier + modifier = modifier .fillMaxWidth() .background(color = TangemTheme.colors.background.primary) .padding( @@ -74,27 +71,10 @@ private fun ListOfTokens(state: SwapSelectTokenStateHolder) { ) { - stickyHeader { - Header(title = R.string.swapping_token_list_your_tokens) - } - - - itemsIndexed(items = state.addedTokens) { index, item -> + itemsIndexed(items = state.tokens) { index, item -> TokenItem(token = item, onTokenClick = { state.onTokenSelected(item.id) }) - if (index != state.addedTokens.lastIndex) Divider( - color = TangemTheme.colors.stroke.primary, - startIndent = TangemTheme.dimens.spacing54, - ) - } - - stickyHeader { - Header(title = R.string.swapping_token_list_other_tokens) - } - - itemsIndexed(items = state.otherTokens) { index, item -> - TokenItem(token = item, onTokenClick = { state.onTokenSelected(item.id) }) - if (index != state.otherTokens.lastIndex) Divider( + if (index != state.tokens.lastIndex) Divider( color = TangemTheme.colors.stroke.primary, startIndent = TangemTheme.dimens.spacing54, ) @@ -102,21 +82,6 @@ private fun ListOfTokens(state: SwapSelectTokenStateHolder) { } } -@Composable -private fun Header(@StringRes title: Int) { - Text( - text = stringResource(id = title).uppercase(), - style = TangemTheme.typography.overline, - color = TangemTheme.colors.text.tertiary, - modifier = Modifier - .fillMaxWidth() - .background(color = TangemTheme.colors.background.primary) - .padding(vertical = TangemTheme.dimens.spacing6), - textAlign = TextAlign.Start, - - ) -} - @Composable private fun TokenItem( token: TokenToSelect, @@ -133,25 +98,10 @@ private fun TokenItem( .clickable( onClick = { onTokenClick(token.id) }, ), + verticalAlignment = Alignment.CenterVertically, ) { - Box( - modifier = Modifier - .padding(end = TangemTheme.dimens.spacing12) - .align(Alignment.CenterVertically), - ) { - val iconModifier = Modifier - .size(TangemTheme.dimens.size40) - SubcomposeAsyncImage( - modifier = iconModifier, - model = ImageRequest.Builder(LocalContext.current) - .data(token.iconUrl) - .crossfade(true) - .build(), - contentDescription = token.id, - loading = { TokenImageShimmer(modifier = iconModifier) }, - error = { CurrencyPlaceholderIcon(modifier = iconModifier, id = token.id) }, - ) - } + + TokenIcon(token = token) Column( modifier = Modifier @@ -160,7 +110,11 @@ private fun TokenItem( Text( text = token.name, style = TangemTheme.typography.subtitle1, - color = TangemTheme.colors.text.primary1, + color = if (token.available) { + TangemTheme.colors.text.primary1 + } else { + TangemTheme.colors.text.tertiary + }, ) SpacerW2() Text( @@ -172,7 +126,14 @@ private fun TokenItem( Spacer(modifier = Modifier.weight(1f)) - if (token.addedTokenBalanceData != null) { + + if (!token.available) { + Text( + text = stringResource(id = R.string.swapping_token_not_available), + style = TangemTheme.typography.caption, + color = TangemTheme.colors.text.tertiary, + ) + } else if (token.addedTokenBalanceData != null) { Column( horizontalAlignment = Alignment.End, verticalArrangement = Arrangement.Center, @@ -196,6 +157,35 @@ private fun TokenItem( } } +@Composable +private fun TokenIcon(token: TokenToSelect) { + Box( + modifier = Modifier + .padding(end = TangemTheme.dimens.spacing12), + ) { + val iconModifier = Modifier + .size(TangemTheme.dimens.size40) + val colorFilter = if (!token.available) { + val matrix = ColorMatrix().apply { setToSaturation(0f) } + ColorFilter.colorMatrix(matrix) + } else { + null + } + SubcomposeAsyncImage( + modifier = iconModifier, + model = ImageRequest.Builder(LocalContext.current) + .data(token.iconUrl) + .crossfade(true) + .build(), + contentDescription = token.id, + loading = { TokenImageShimmer(modifier = iconModifier) }, + error = { CurrencyPlaceholderIcon(modifier = iconModifier, id = token.id) }, + alpha = if (!token.available) 0.7f else 1f, + colorFilter = colorFilter, + ) + } +} + @Composable private fun TokenImageShimmer( modifier: Modifier = Modifier, @@ -224,12 +214,14 @@ private val token = TokenToSelect( ), ) +private val tokenNotAvailable = token.copy(available = false) + @Preview @Composable fun TokenScreenPreview() { SwapSelectTokenScreen( state = SwapSelectTokenStateHolder( - listOf(token, token, token), listOf(token, token, token), {}, {}, + listOf(token, tokenNotAvailable, token), {}, {}, ), ) { diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt index 2311562db4..f2bc335b92 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt @@ -1,9 +1,12 @@ package com.tangem.feature.swap.ui +import androidx.compose.foundation.layout.padding import androidx.compose.material.Scaffold import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import com.tangem.core.ui.components.SuccessScreenContent +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.ResultScreenContent import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.swap.models.SwapSuccessStateHolder @@ -13,10 +16,17 @@ import com.tangem.feature.swap.presentation.R fun SwapSuccessScreen(state: SwapSuccessStateHolder, onBack: () -> Unit) { TangemTheme { Scaffold( - content = { - SuccessScreenContent( - successMessage = state.message, + content = { padding -> + ResultScreenContent( + resultMessage = state.message, + resultColor = TangemTheme.colors.icon.attention, onButtonClick = onBack, + icon = R.drawable.ic_clock_24, + secondaryButtonIcon = R.drawable.ic_arrow_top_right_24, + onSecondaryButtonClick = state.onSecondaryButtonClick, + secondaryButtonText = R.string.swapping_success_view_explorer_button_title, + title = R.string.swapping_success_view_title, + modifier = Modifier.padding(padding), ) }, topBar = { @@ -28,4 +38,28 @@ fun SwapSuccessScreen(state: SwapSuccessStateHolder, onBack: () -> Unit) { }, ) } -} \ No newline at end of file +} + +// region preview + +private val state = SwapSuccessStateHolder( + message = "Swap of 1 000 DAI to 1 131,46 MATIC", +) {} + +@Preview(showBackground = true) +@Composable +fun Preview_Success_InLightTheme() { + TangemTheme(isDark = false) { + SwapSuccessScreen(state) {} + } +} + +@Preview(showBackground = true) +@Composable +fun Preview_Success_InDarkTheme() { + TangemTheme(isDark = true) { + SwapSuccessScreen(state) {} + } +} + +// endregion preview \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt index f5448c3386..ff81cb50e9 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt @@ -56,7 +56,6 @@ import com.tangem.core.ui.components.CurrencyPlaceholderIcon import com.tangem.core.ui.components.FontSizeRange import com.tangem.core.ui.components.ResizableText import com.tangem.core.ui.components.SpacerH4 -import com.tangem.core.ui.components.SpacerW12 import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.swap.models.TransactionCardType import com.valentinilk.shimmer.shimmer @@ -65,6 +64,7 @@ import com.valentinilk.shimmer.shimmer fun TransactionCard( modifier: Modifier = Modifier, type: TransactionCardType, + balance: String, amount: String?, amountEquivalent: String?, tokenIconUrl: String, @@ -87,7 +87,7 @@ fun TransactionCard( horizontalAlignment = Alignment.Start, ) { - Header(type = type) + Header(balance = balance, type = type) Content( type = type, @@ -124,6 +124,7 @@ fun TransactionCard( @Composable private fun Header( type: TransactionCardType, + balance: String, modifier: Modifier = Modifier, ) { Row( @@ -148,15 +149,13 @@ private fun Header( style = MaterialTheme.typography.subtitle2, modifier = Modifier.padding(top = TangemTheme.dimens.spacing12), ) - if (type is TransactionCardType.SendCard) { - Text( - text = stringResource(R.string.common_balance, type.balance), - color = TangemTheme.colors.text.tertiary, - maxLines = 1, - style = MaterialTheme.typography.body2, - modifier = Modifier.padding(top = TangemTheme.dimens.spacing12), - ) - } + Text( + text = stringResource(R.string.common_balance, balance), + color = TangemTheme.colors.text.tertiary, + maxLines = 1, + style = MaterialTheme.typography.body2, + modifier = Modifier.padding(top = TangemTheme.dimens.spacing12), + ) } } @@ -176,27 +175,6 @@ private fun Content( horizontalArrangement = Arrangement.Start, verticalAlignment = Alignment.Bottom, ) { - - if (type is TransactionCardType.SendCard && !type.permissionIsGiven) { - Box( - modifier = Modifier - .size(TangemTheme.dimens.size56) - .background( - color = TangemTheme.colors.button.secondary, - shape = RoundedCornerShape(TangemTheme.dimens.size12), - ), - contentAlignment = Alignment.Center, - ) { - Icon( - painter = painterResource(id = R.drawable.ic_locked_24), - contentDescription = null, - tint = TangemTheme.colors.icon.primary1, - ) - } - - SpacerW12() - } - Column( modifier = Modifier .padding( @@ -453,13 +431,14 @@ fun Preview_SwapMainCard_InDarkTheme() { @Composable private fun TransactionCardPreview() { TransactionCard( - type = TransactionCardType.SendCard("123", false, {}), + type = TransactionCardType.SendCard {}, amount = "1 000 000", amountEquivalent = "1 000 000", tokenIconUrl = "", tokenCurrency = "DAI", networkIconRes = R.drawable.img_polygon_22, onChangeTokenClick = {}, + balance = "123", ) } From 3a9e6a7abd9ac72360d9bfebe476efdf7cf51724 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 17 Jan 2023 15:41:17 +0400 Subject: [PATCH 080/145] Updated on 2026-08-14 --- .../main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index 0d7242fde3..a75d723ff7 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -29,12 +29,12 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview -import com.tangem.core.ui.components.BasicDialog import com.tangem.core.ui.components.CardWithIcon import com.tangem.core.ui.components.ClickableWarningCard import com.tangem.core.ui.components.Keyboard import com.tangem.core.ui.components.PrimaryButtonIconRight import com.tangem.core.ui.components.RefreshableWaringCard +import com.tangem.core.ui.components.SimpleOkDialog import com.tangem.core.ui.components.SmallInfoCard import com.tangem.core.ui.components.SmallInfoCardWithWarning import com.tangem.core.ui.components.SpacerH16 @@ -137,7 +137,7 @@ internal fun SwapScreenContent( } if (state.alert != null) { - BasicDialog(message = state.alert.message, onDismissDialog = state.alert.onClick) + SimpleOkDialog(message = state.alert.message, onDismissDialog = state.alert.onClick) } } } From 570d631ad3bea7fc6520dd4af9e280befdb0125f Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 17 Jan 2023 14:18:16 +0300 Subject: [PATCH 081/145] Updated on 2026-08-14 --- .../redux/WalletSelectorAction.kt | 2 -- .../redux/WalletSelectorMiddleware.kt | 36 ++++++++++++++----- .../redux/WalletSelectorReducer.kt | 1 - 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt index 0985f4b68c..17824db23f 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt @@ -55,7 +55,5 @@ internal sealed interface WalletSelectorAction : Action { val fiatCurrency: FiatCurrency, ) : WalletSelectorAction - data class HandleError(val error: TangemError) : WalletSelectorAction - object CloseError : WalletSelectorAction } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index 38832e455a..5b15d07266 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -29,7 +29,6 @@ import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletStoresManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import org.rekotlin.Middleware @@ -82,7 +81,6 @@ internal class WalletSelectorMiddleware { is WalletSelectorAction.UnlockWithBiometry.Success, is WalletSelectorAction.BalanceLoaded, is WalletSelectorAction.IsLockedChanged, - is WalletSelectorAction.HandleError, is WalletSelectorAction.CloseError, -> Unit } @@ -93,7 +91,6 @@ internal class WalletSelectorMiddleware { walletStoresManager.fetch(userWallets) .doOnFailure { error -> Timber.e(error, "Unable to fetch wallet stores") - store.dispatchOnMain(WalletSelectorAction.HandleError(error)) } } } @@ -126,6 +123,7 @@ internal class WalletSelectorMiddleware { scope.launch { userWalletsListManager.unlockWithBiometry() .doOnFailure { error -> + Timber.e(error, "Unable to unlock all user wallets") store.dispatchOnMain(WalletSelectorAction.UnlockWithBiometry.Error(error)) } .doOnSuccess { @@ -158,12 +156,14 @@ internal class WalletSelectorMiddleware { .doOnFailure { error -> // Rollback policy if card saving was failed tangemSdkManager.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode) + Timber.e(error, "Unable to save user wallet") store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) } }, onFailure = { error -> // Rollback policy if card scanning was failed tangemSdkManager.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode) + Timber.e(error, "Unable to scan card") store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) }, ) @@ -194,7 +194,13 @@ internal class WalletSelectorMiddleware { } } .doOnFailure { error -> - store.dispatchOnMain(WalletSelectorAction.HandleError(error)) + Timber.e( + error, + """ + Unable to select user wallet + |- Wallet ID to select: $userWalletId + """.trimIndent(), + ) } .doOnSuccess { val selectedUserWallet = userWalletsListManager.selectedUserWalletSync @@ -238,7 +244,14 @@ internal class WalletSelectorMiddleware { ) } .doOnFailure { error -> - store.dispatchOnMain(WalletSelectorAction.HandleError(error)) + Timber.e( + error, + """ + Unable to delete user wallets + |- Wallets IDs to delete: $userWalletsIds + |- Current wallets IDs: ${state.wallets.map { it.id }} + """.trimIndent(), + ) } } } @@ -249,7 +262,14 @@ internal class WalletSelectorMiddleware { scope.launch { userWalletsListManager.update(userWalletId) { it.copy(name = newName) } .doOnFailure { error -> - store.dispatchOnMain(WalletSelectorAction.HandleError(error)) + Timber.e( + error, + """ + Unable to rename user wallet + |- Wallet ID: $userWalletId + |- New name: $newName + """.trimIndent(), + ) } } } @@ -257,10 +277,10 @@ internal class WalletSelectorMiddleware { private fun refreshUserWalletsAmounts() { scope.launch { walletStoresManager.updateAmounts( - userWallets = userWalletsListManager.userWallets.first(), + userWallets = userWalletsListManager.userWallets.firstOrNull().orEmpty(), ) .doOnFailure { error -> - store.dispatchOnMain(WalletSelectorAction.HandleError(error)) + Timber.e(error, "Unable to refresh user wallets amounts") } } } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt index b64fe0a3ce..9a8ba16eae 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt @@ -27,7 +27,6 @@ internal object WalletSelectorReducer { is WalletSelectorAction.BalanceLoaded -> state.copy( wallets = state.wallets.updateWithBalance(action.userWalletModel), ) - is WalletSelectorAction.HandleError -> state.copy(error = action.error) is WalletSelectorAction.CloseError -> state.copy(error = null) is WalletSelectorAction.UnlockWithBiometry -> state.copy( isUnlockInProgress = true, From bd3ee9e2371db864b09f984dc3e7c275cd8c44af Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 17 Jan 2023 16:23:33 +0300 Subject: [PATCH 082/145] Updated on 2026-08-14 --- .../middlewares/TradeCryptoMiddleware.kt | 2 +- .../tap/proxy/TransactionManagerImpl.kt | 12 +- .../tangem/tap/proxy/UserWalletManagerImpl.kt | 9 +- .../api/oneinch/models/SwapResponse.kt | 3 +- .../tangem/utils/converter/TwoWayConverter.kt | 8 + .../tangem/utils/coroutines/CoroutineExt.kt | 21 ++ .../tangem/feature/swap/SwapRepositoryImpl.kt | 9 +- .../swap/converters/ApproveConverter.kt | 2 +- .../swap/converters/QuotesConverter.kt | 2 +- .../feature/swap/converters/SwapConverter.kt | 4 +- .../swap/converters/TokensConverter.kt | 2 +- .../feature/swap/domain/SwapInteractor.kt | 19 +- .../feature/swap/domain/SwapInteractorImpl.kt | 229 ++++++++++++++---- .../feature/swap/domain/SwapRepository.kt | 8 +- .../swap/domain/cache/SwapDataCache.kt | 20 +- .../swap/domain/cache/SwapDataCacheImpl.kt | 47 +++- .../converters/CryptoCurrencyConverter.kt | 38 ++- .../domain/models/cache/SwapDataHolder.kt | 8 +- .../models/{ => domain}/ApproveModel.kt | 2 +- .../domain/models/{ => domain}/Currency.kt | 2 +- .../domain/models/{ => domain}/QuoteModel.kt | 4 +- .../models/{ => domain}/SwapDataModel.kt | 4 +- .../models/{ => domain}/TransactionModel.kt | 2 +- .../swap/domain/models/{ => ui}/SwapState.kt | 10 +- .../swap/domain/models/ui/TokensDataState.kt | 28 +++ .../swap/converters/TokensDataConverter.kt | 35 +++ .../tangem/feature/swap/models/UiActions.kt | 11 + .../tangem/feature/swap/ui/StateBuilder.kt | 56 +++-- .../tangem/feature/swap/ui/TransactionCard.kt | 2 +- .../feature/swap/viewmodels/PeriodicTask.kt | 43 ++++ .../feature/swap/viewmodels/SwapViewModel.kt | 203 +++++++++++----- .../tangem/lib/crypto/TransactionManager.kt | 3 +- .../tangem/lib/crypto/UserWalletManager.kt | 2 +- 33 files changed, 643 insertions(+), 207 deletions(-) create mode 100644 core/utils/src/main/java/com/tangem/utils/converter/TwoWayConverter.kt rename features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/{ => domain}/ApproveModel.kt (87%) rename features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/{ => domain}/Currency.kt (93%) rename features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/{ => domain}/QuoteModel.kt (83%) rename features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/{ => domain}/SwapDataModel.kt (80%) rename features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/{ => domain}/TransactionModel.kt (93%) rename features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/{ => ui}/SwapState.kt (83%) create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/TokensDataState.kt create mode 100644 features/swap/presentation/src/main/java/com/tangem/feature/swap/converters/TokensDataConverter.kt create mode 100644 features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt create mode 100644 features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/PeriodicTask.kt 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 17be87d7b4..042bf0f667 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 @@ -28,7 +28,7 @@ import com.tangem.tap.store import kotlinx.coroutines.launch import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json -import com.tangem.feature.swap.domain.models.Currency as SwapCurrency +import com.tangem.feature.swap.domain.models.domain.Currency as SwapCurrency class TradeCryptoMiddleware { fun handle(state: () -> AppState?, action: WalletAction.TradeCryptoAction) { diff --git a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt index fc7d49d169..ba55c7532b 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -27,7 +27,6 @@ import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.store import com.tangem.tap.tangemSdk import java.math.BigDecimal -import java.math.BigInteger class TransactionManagerImpl( private val appStateHolder: AppStateHolder, @@ -38,6 +37,7 @@ class TransactionManagerImpl( amountToSend: BigDecimal, currencyToSend: Currency, feeAmount: BigDecimal, + estimatedGas: Int, destinationAddress: String, dataToSign: String, ): SendTxResult { @@ -49,7 +49,7 @@ class TransactionManagerImpl( amount = amount, fee = Amount(value = feeAmount, blockchain = blockchain), destination = destinationAddress, - ).copy(hash = dataToSign, extras = createExtras(walletManager, feeAmount, dataToSign)) + ).copy(hash = dataToSign, extras = createExtras(walletManager, estimatedGas, dataToSign)) val signer = transactionSigner(walletManager) @@ -95,10 +95,6 @@ class TransactionManagerImpl( } } - override fun getNativeAddress(networkId: String): String { - return "" //todo implement - } - private fun handleSendResult(result: SimpleResult): SendTxResult { when (result) { is SimpleResult.Success -> { @@ -155,14 +151,14 @@ class TransactionManagerImpl( private fun createExtras( walletManager: WalletManager, - feeAmount: BigDecimal, + estimatedGas: Int, transactionHash: String, ): TransactionExtras? { return when (walletManager) { is EthereumWalletManager -> { return EthereumTransactionExtras( data = transactionHash.removePrefix(HEX_PREFIX).hexToBytes(), - gasLimit = BigInteger.valueOf(DEFAULT_GAS_LIMIT), + gasLimit = estimatedGas.toBigInteger(), ) } else -> { diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt index 991c499fb4..95818b9980 100644 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -27,13 +27,13 @@ class UserWalletManagerImpl( private val walletManagerFactory: WalletManagerFactory, ) : UserWalletManager { - override suspend fun getUserTokens(): List { + override suspend fun getUserTokens(networkId: String): List { val card = appStateHolder.getActualCard() val userTokensRepository = requireNotNull(appStateHolder.userTokensRepository) { "userTokensRepository is null" } - if (card != null) { + return if (card != null) { userTokensRepository.getUserTokens(card) - .filter { it.isToken() } + .filter { it.blockchain.toNetworkId() == networkId } .map { if (it is com.tangem.tap.features.wallet.models.Currency.Token) { NonNativeToken( @@ -53,8 +53,9 @@ class UserWalletManagerImpl( ) } } + } else { + emptyList() } - return emptyList() } override fun getWalletId(): String { diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/SwapResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/SwapResponse.kt index 1f25a391e0..4a72fcccc5 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/SwapResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/SwapResponse.kt @@ -7,6 +7,5 @@ data class SwapResponse( @Json(name = "toToken") val toToken: TokenOneInchDto, @Json(name = "toTokenAmount") val toTokenAmount: String, @Json(name = "fromTokenAmount") val fromTokenAmount: String, - @Json(name = "protocols") val protocols: List, - @Json(name = "transaction") val transaction: TransactionDto, + @Json(name = "tx") val transaction: TransactionDto, ) \ No newline at end of file diff --git a/core/utils/src/main/java/com/tangem/utils/converter/TwoWayConverter.kt b/core/utils/src/main/java/com/tangem/utils/converter/TwoWayConverter.kt new file mode 100644 index 0000000000..444e4048ab --- /dev/null +++ b/core/utils/src/main/java/com/tangem/utils/converter/TwoWayConverter.kt @@ -0,0 +1,8 @@ +package com.tangem.utils.converter + +interface TwoWayConverter : Converter { + fun convertBack(value: O): I + fun convertListBack(input: List): List { + return input.map { convertBack(it) } + } +} \ No newline at end of file diff --git a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt index 87097ce482..4fbd7b0ccb 100644 --- a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt +++ b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt @@ -1,10 +1,31 @@ package com.tangem.utils.coroutines import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext suspend fun runCatching(dispatcher: CoroutineDispatcher, block: suspend () -> R): Result { return runCatching { withContext(dispatcher) { block() } } +} + +class Debouncer { + + private var debounceJob: Job? = null + + fun debounce( + waitMs: Long = 300L, + coroutineScope: CoroutineScope, + destinationFunction: () -> Unit, + ) { + debounceJob?.cancel() + debounceJob = coroutineScope.launch { + delay(waitMs) + destinationFunction.invoke() + } + } } \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt index 10cdf4a902..cdcc527c0e 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt @@ -10,10 +10,10 @@ import com.tangem.feature.swap.converters.QuotesConverter import com.tangem.feature.swap.converters.SwapConverter import com.tangem.feature.swap.converters.TokensConverter import com.tangem.feature.swap.domain.SwapRepository -import com.tangem.feature.swap.domain.models.ApproveModel -import com.tangem.feature.swap.domain.models.Currency -import com.tangem.feature.swap.domain.models.QuoteModel -import com.tangem.feature.swap.domain.models.SwapDataModel +import com.tangem.feature.swap.domain.models.domain.ApproveModel +import com.tangem.feature.swap.domain.models.domain.Currency +import com.tangem.feature.swap.domain.models.domain.QuoteModel +import com.tangem.feature.swap.domain.models.domain.SwapDataModel import com.tangem.feature.swap.domain.models.data.AggregatedSwapDataModel import com.tangem.feature.swap.domain.models.mapErrors import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -109,6 +109,7 @@ internal class SwapRepositoryImpl @Inject constructor( slippage = slippage, ), ) + AggregatedSwapDataModel(swapConverter.convert(swapResponse)) } catch (ex: OneIncResponseException) { AggregatedSwapDataModel(null, mapErrors(ex.data.description)) diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ApproveConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ApproveConverter.kt index 4d3574869d..758763e942 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ApproveConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ApproveConverter.kt @@ -1,7 +1,7 @@ package com.tangem.feature.swap.converters import com.tangem.datasource.api.oneinch.models.ApproveCalldataResponse -import com.tangem.feature.swap.domain.models.ApproveModel +import com.tangem.feature.swap.domain.models.domain.ApproveModel import com.tangem.utils.converter.Converter import javax.inject.Inject diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt index 57f0569e9c..b867bb47cf 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt @@ -1,7 +1,7 @@ package com.tangem.feature.swap.converters import com.tangem.datasource.api.oneinch.models.QuoteResponse -import com.tangem.feature.swap.domain.models.QuoteModel +import com.tangem.feature.swap.domain.models.domain.QuoteModel import com.tangem.feature.swap.domain.models.createFromAmountWithOffset import com.tangem.utils.converter.Converter import javax.inject.Inject diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt index 31df2484b4..b580b090af 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt @@ -2,8 +2,8 @@ package com.tangem.feature.swap.converters import com.tangem.datasource.api.oneinch.models.SwapResponse import com.tangem.datasource.api.oneinch.models.TransactionDto -import com.tangem.feature.swap.domain.models.SwapDataModel -import com.tangem.feature.swap.domain.models.TransactionModel +import com.tangem.feature.swap.domain.models.domain.SwapDataModel +import com.tangem.feature.swap.domain.models.domain.TransactionModel import com.tangem.feature.swap.domain.models.createFromAmountWithOffset import com.tangem.utils.converter.Converter import javax.inject.Inject diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/TokensConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/TokensConverter.kt index 8330c0c5f8..b40a330485 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/TokensConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/TokensConverter.kt @@ -1,7 +1,7 @@ package com.tangem.feature.swap.converters import com.tangem.datasource.api.tangemTech.models.CoinsResponse -import com.tangem.feature.swap.domain.models.Currency +import com.tangem.feature.swap.domain.models.domain.Currency import com.tangem.utils.converter.Converter import javax.inject.Inject diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt index dbbbbe6fdb..edef9f475c 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt @@ -1,20 +1,27 @@ package com.tangem.feature.swap.domain -import com.tangem.feature.swap.domain.models.Currency import com.tangem.feature.swap.domain.models.SwapAmount -import com.tangem.feature.swap.domain.models.SwapState +import com.tangem.feature.swap.domain.models.cache.ExchangeCurrencies +import com.tangem.feature.swap.domain.models.domain.Currency +import com.tangem.feature.swap.domain.models.ui.FoundTokensState +import com.tangem.feature.swap.domain.models.ui.SwapState +import com.tangem.feature.swap.domain.models.ui.TokensDataState interface SwapInteractor { - suspend fun getTokensToSwap(networkId: String): List + suspend fun initTokensToSwap(initialCurrency: Currency): TokensDataState + + suspend fun onSearchToken(searchQuery: String): FoundTokensState + + fun getExchangeCurrencies(): ExchangeCurrencies? + + fun findTokenById(id: String): Currency? /** * Give permission to swap - * - * @param tokenToApprove use token which you want to swap */ @Throws(IllegalStateException::class) - suspend fun givePermissionToSwap(tokenToApprove: Currency) + suspend fun givePermissionToSwap() @Throws(IllegalStateException::class) suspend fun findBestQuote( diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 919c71d9ab..f49cafea71 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -2,16 +2,23 @@ package com.tangem.feature.swap.domain import com.tangem.feature.swap.domain.cache.SwapDataCache import com.tangem.feature.swap.domain.converters.CryptoCurrencyConverter -import com.tangem.feature.swap.domain.models.ApproveModel -import com.tangem.feature.swap.domain.models.Currency import com.tangem.feature.swap.domain.models.DataError -import com.tangem.feature.swap.domain.models.PermissionDataState import com.tangem.feature.swap.domain.models.SwapAmount -import com.tangem.feature.swap.domain.models.SwapState -import com.tangem.feature.swap.domain.models.TransactionModel +import com.tangem.feature.swap.domain.models.cache.ExchangeCurrencies +import com.tangem.feature.swap.domain.models.domain.ApproveModel +import com.tangem.feature.swap.domain.models.domain.Currency import com.tangem.feature.swap.domain.models.toStringWithRightOffset +import com.tangem.feature.swap.domain.models.ui.FoundTokensState +import com.tangem.feature.swap.domain.models.ui.PermissionDataState +import com.tangem.feature.swap.domain.models.ui.PreselectTokens +import com.tangem.feature.swap.domain.models.ui.SwapState +import com.tangem.feature.swap.domain.models.ui.TokenBalanceData +import com.tangem.feature.swap.domain.models.ui.TokenWithBalance +import com.tangem.feature.swap.domain.models.ui.TokensDataState import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.UserWalletManager +import com.tangem.lib.crypto.models.ProxyAmount +import com.tangem.lib.crypto.models.ProxyFiatCurrency import com.tangem.lib.crypto.models.transactions.SendTxResult import com.tangem.utils.toFiatString import com.tangem.utils.toFormattedString @@ -28,21 +35,91 @@ internal class SwapInteractorImpl @Inject constructor( private val cryptoCurrencyConverter = CryptoCurrencyConverter() - override suspend fun getTokensToSwap(networkId: String): List { + override suspend fun initTokensToSwap(initialCurrency: Currency): TokensDataState { + val networkId = initialCurrency.networkId + cache.cacheNetworkId(networkId) val availableTokens = cache.getAvailableTokens(networkId) - return availableTokens.ifEmpty { + val allLoadedTokens = availableTokens.ifEmpty { val tokens = repository.getExchangeableTokens(networkId) cache.cacheAvailableToSwapTokens(networkId, tokens) - cache.cacheNetworkId(networkId) tokens - } + }.filter { it.symbol != initialCurrency.symbol } + + //replace tokens in wallet tokens list with loaded same + val loadedOnWalletsMap = mutableSetOf() + val tokensInWallet = userWalletManager.getUserTokens(networkId) + .filter { it.symbol != initialCurrency.symbol } + .map { token -> + allLoadedTokens.firstOrNull { it.symbol == token.symbol }?.let { + loadedOnWalletsMap.add(it.symbol) + it + } ?: cryptoCurrencyConverter.convertBack(token) + } + val loadedTokens = allLoadedTokens + .filter { + !loadedOnWalletsMap.contains(it.symbol) + } + cache.cacheLoadedTokens(loadedTokens) + cache.cacheInWalletTokens(tokensInWallet) + val tokensBalance = userWalletManager.getCurrentWalletTokensBalance(networkId) + val appCurrency = userWalletManager.getUserAppCurrency() + val rates = repository.getRates(appCurrency.code, tokensInWallet.map { it.id }) + return TokensDataState( + preselectTokens = PreselectTokens( + fromToken = initialCurrency, + toToken = selectToToken(initialCurrency, tokensInWallet, loadedTokens), + ), + foundTokensState = FoundTokensState( + tokensInWallet = getTokensWithBalance(tokensInWallet, tokensBalance, rates, appCurrency), + loadedTokens = loadedTokens.map { TokenWithBalance(it) }, + ), + ) } - override suspend fun givePermissionToSwap(tokenToApprove: Currency) { + override suspend fun onSearchToken(searchQuery: String): FoundTokensState { + val networkId = requireNotNull(cache.getNetworkId()) { "networkId is null" } + val searchQueryLowerCase = searchQuery.lowercase() + val tokensInWallet = cache.getInWalletTokens() + .filter { + it.name.lowercase().contains(searchQueryLowerCase) + || it.symbol.lowercase().contains(searchQueryLowerCase) + } + val loadedTokens = cache.getLoadedTokens() + .filter { + it.name.lowercase().contains(searchQueryLowerCase) + || it.symbol.lowercase().contains(searchQueryLowerCase) + } + val tokensBalance = userWalletManager.getCurrentWalletTokensBalance(networkId) + val appCurrency = userWalletManager.getUserAppCurrency() + val rates = repository.getRates(appCurrency.code, tokensInWallet.map { it.id }) + return FoundTokensState( + tokensInWallet = getTokensWithBalance(tokensInWallet, tokensBalance, rates, appCurrency), + loadedTokens = loadedTokens.map { TokenWithBalance(it) }, + ) + } + + override fun getExchangeCurrencies(): ExchangeCurrencies? { + return cache.getExchangeCurrencies() + } + + override fun findTokenById(id: String): Currency? { + val tokensInWallet = cache.getInWalletTokens() + val loadedTokens = cache.getLoadedTokens() + return tokensInWallet.firstOrNull { it.id == id } ?: loadedTokens.firstOrNull { it.id == id } + } + + override suspend fun givePermissionToSwap() { cache.getNetworkId()?.let { networkId -> - if (tokenToApprove is Currency.NonNativeToken) { + val currencyToSend = + requireNotNull(cache.getExchangeCurrencies()?.fromCurrency) { "currency is not selected" } + if (currencyToSend is Currency.NonNativeToken) { val estimatedGas = - requireNotNull(cache.getLastQuote()?.estimatedGas) { "estimatedGas not found call findBestQuote" } + increaseByPercents( + TWENTY_FIVE_PERCENTS, + requireNotNull(cache.getLastQuote()?.estimatedGas) { + "estimatedGas not found call findBestQuote" + }, + ) val transactionData = requireNotNull(cache.getApproveTransactionData()) { "getApproveTransactionData not found, call findQuotes" } val gasPrice = transactionData.gasPrice.toBigDecimalOrNull() ?: error("cannot parse gasPrice") @@ -50,14 +127,15 @@ internal class SwapInteractorImpl @Inject constructor( val result = transactionManager.sendTransaction( networkId = networkId, amountToSend = BigDecimal.ZERO, - currencyToSend = cryptoCurrencyConverter.convert(tokenToApprove), + currencyToSend = cryptoCurrencyConverter.convert(currencyToSend), feeAmount = fee, + estimatedGas = estimatedGas, destinationAddress = transactionData.toAddress, dataToSign = transactionData.data, ) when (result) { SendTxResult.Success -> { - allowPermissionsHandler.addAddressToInProgress(tokenToApprove.contractAddress) + allowPermissionsHandler.addAddressToInProgress(currencyToSend.contractAddress) } SendTxResult.UserCancelledError -> TODO() is SendTxResult.BlockchainSdkError -> TODO() @@ -69,12 +147,14 @@ internal class SwapInteractorImpl @Inject constructor( } override suspend fun findBestQuote(fromToken: Currency, toToken: Currency, amount: SwapAmount): SwapState { - val networkId = requireNotNull(cache.getNetworkId()) { "no networkId found, please call getTokensToSwap first" } + val networkId = requireNotNull(cache.getNetworkId()) { "no networkId found, please call initTokens first" } val fromTokenAddress = getTokenAddress(fromToken) val toTokenAddress = getTokenAddress(toToken) val isAllowedToSpend = checkAllowance(networkId, fromTokenAddress) - val isBalanceZero = isNotZeroBalance(fromToken, networkId) - return if (isAllowedToSpend && isBalanceZero) { + val isNotZeroBalance = isNotZeroBalance(fromToken, networkId) + cache.cacheExchangeCurrencies(fromToken, toToken) + cache.cacheAmountToSwap(amount) + return if (isAllowedToSpend && isNotZeroBalance) { if (allowPermissionsHandler.isAddressAllowanceInProgress(fromTokenAddress)) { allowPermissionsHandler.removeAddressFromProgress(toTokenAddress) } @@ -94,37 +174,41 @@ internal class SwapInteractorImpl @Inject constructor( amount = amount, fromToken = fromToken, toToken = toToken, + isAllowedToSpend = isAllowedToSpend, ) } } override suspend fun onSwap(): SwapState { - val quoteModel = cache.getLastQuote() - val amountToSwap = cache.getAmountToSwap() - val networkId = cache.getNetworkId() - if (quoteModel != null && amountToSwap != null && networkId != null) { - repository.prepareSwapTransaction( - networkId = networkId, - fromTokenAddress = quoteModel.fromTokenAddress, - toTokenAddress = quoteModel.toTokenAddress, - amount = amountToSwap.toStringWithRightOffset(), - slippage = DEFAULT_SLIPPAGE, - fromWalletAddress = getWalletAddress(networkId), - ).let { - val swapData = it.dataModel - if (swapData != null) { - signTransactionData(swapData.transaction) //todo implement - return SwapState.SwapSuccess( - quoteModel.fromTokenAmount, - quoteModel.toTokenAmount, - ) - } else { - return SwapState.SwapError(it.error) - } + val swapData = requireNotNull(cache.getLastSwapData()) { "swap data is not ready" } + val networkId = requireNotNull(cache.getNetworkId()) { "no networkId found, please call getTokensToSwap first" } + val currencyToSend = requireNotNull(cache.getExchangeCurrencies()?.fromCurrency) { "currency is not selected" } + val amountToSwap = requireNotNull(cache.getAmountToSwap()) { "" } + val estimatedGas = + increaseByPercents(TWENTY_FIVE_PERCENTS, swapData.transaction.gas.toIntOrNull() ?: DEFAULT_GAS) + val fee = transactionManager.calculateFee( + networkId = networkId, + gasPrice = swapData.transaction.gasPrice, + estimatedGas = estimatedGas, + ) + val result = transactionManager.sendTransaction( + networkId = networkId, + amountToSend = amountToSwap.value, + currencyToSend = cryptoCurrencyConverter.convert(currencyToSend), + feeAmount = fee, + estimatedGas = estimatedGas, + destinationAddress = swapData.transaction.toWalletAddress, + dataToSign = swapData.transaction.data, + ) + when (result) { + SendTxResult.Success -> { } - } else { - throw IllegalStateException("cache is empty, call 'findBestQuote' first") + SendTxResult.UserCancelledError -> TODO() + is SendTxResult.BlockchainSdkError -> TODO() + is SendTxResult.TangemSdkError -> TODO() + is SendTxResult.UnknownError -> TODO() } + return SwapState.SwapError(DataError.UNKNOWN_ERROR) } override fun getTokenDecimals(token: Currency): Int { @@ -135,6 +219,47 @@ internal class SwapInteractorImpl @Inject constructor( } } + private fun selectToToken( + initialToken: Currency, + tokensInWallet: List, + loadedTokens: List, + ): Currency { + val toToken = if (tokensInWallet.isNotEmpty()) { + tokensInWallet.firstOrNull { it.symbol != initialToken.symbol } + ?: loadedTokens.first { it.symbol != initialToken.symbol } + } else { + val findUsdt = loadedTokens.firstOrNull { it.symbol == "USDT" && it.symbol != initialToken.symbol } + if (findUsdt == null) { + val findUsdc = loadedTokens.firstOrNull { it.symbol == "USDC" && it.symbol != initialToken.symbol } + findUsdc ?: loadedTokens.first { it.symbol != initialToken.symbol } + } else { + findUsdt + } + } + return toToken + } + + private fun getTokensWithBalance( + tokens: List, + balances: Map, + rates: Map, + appCurrency: ProxyFiatCurrency, + ): List { + return tokens.map { + val balance = balances[it.id] + TokenWithBalance( + token = it, + tokenBalanceData = TokenBalanceData( + amount = balance?.let { b -> b.value.toFormattedString(b.decimals) }, + amountEquivalent = balance?.value?.toFiatString( + rates[it.id]?.toBigDecimal() ?: BigDecimal.ZERO, + appCurrency.symbol, + ), + ), + ) + } + } + private suspend fun checkAllowance(networkId: String, fromTokenAddress: String): Boolean { val allowance = repository.checkTokensSpendAllowance( networkId = networkId, @@ -154,6 +279,7 @@ internal class SwapInteractorImpl @Inject constructor( amount: SwapAmount, fromToken: Currency, toToken: Currency, + isAllowedToSpend: Boolean, ): SwapState { repository.findBestQuote( networkId = networkId, @@ -163,12 +289,7 @@ internal class SwapInteractorImpl @Inject constructor( ).let { quotes -> val quoteDataModel = quotes.dataModel if (quoteDataModel != null) { - cache.cacheSwapParams( - quoteModel = quoteDataModel, - amount = amount, - fromCurrency = fromToken, - toCurrency = toToken, - ) + cache.cacheQuoteData(quoteModel = quoteDataModel) val transactionData = repository.dataToApprove(networkId, getTokenAddress(fromToken)) cache.cacheApproveTransactionData(transactionData) val swapState = updateBalances( @@ -182,7 +303,7 @@ internal class SwapInteractorImpl @Inject constructor( estimatedGas = quoteDataModel.estimatedGas, gasPrice = transactionData.gasPrice, ), - isAllowedToSpend = false, + isAllowedToSpend = isAllowedToSpend, ) return updatePermissionState( networkId = networkId, @@ -230,7 +351,7 @@ internal class SwapInteractorImpl @Inject constructor( estimatedGas = swapData.transaction.gas.toIntOrNull() ?: DEFAULT_GAS, gasPrice = swapData.transaction.gasPrice, ), - isAllowedToSpend = false, + isAllowedToSpend = true, ) return swapState.copy( permissionState = PermissionDataState.Empty, @@ -264,7 +385,7 @@ internal class SwapInteractorImpl @Inject constructor( toTokenAmount = toTokenAmount, fromTokenAddress = getTokenAddress(fromToken), toTokenAddress = getTokenAddress(toToken), - fee = fee.toPlainString(), + fee = "${fee.toPlainString()} ${userWalletManager.getCurrencyByNetworkId(networkId)}", isAllowedToSpend = isAllowedToSpend, fromTokenWalletBalance = fromTokenBalance ?: ZERO_BALANCE, fromTokenFiatBalance = fromTokenAmount.value.toFiatString( @@ -319,7 +440,7 @@ internal class SwapInteractorImpl @Inject constructor( private fun getTokenAddress(currency: Currency): String { return when (currency) { is Currency.NativeToken -> { - transactionManager.getNativeAddress(currency.networkId) + DEFAULT_BLOCKCHAIN_ADDRESS } is Currency.NonNativeToken -> { currency.contractAddress @@ -327,13 +448,15 @@ internal class SwapInteractorImpl @Inject constructor( } } - //todo implement after merge referral - private fun signTransactionData(transaction: TransactionModel) { + private fun increaseByPercents(percents: Int, value: Int): Int { + return value * (percents / 100 + 1) } companion object { private const val DEFAULT_SLIPPAGE = 2 private const val ZERO_BALANCE = "0" private const val DEFAULT_GAS = 300000 + private const val DEFAULT_BLOCKCHAIN_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" + private const val TWENTY_FIVE_PERCENTS = 25 } } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt index 7082d123c4..f7a2c78934 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt @@ -1,9 +1,9 @@ package com.tangem.feature.swap.domain -import com.tangem.feature.swap.domain.models.ApproveModel -import com.tangem.feature.swap.domain.models.Currency -import com.tangem.feature.swap.domain.models.QuoteModel -import com.tangem.feature.swap.domain.models.SwapDataModel +import com.tangem.feature.swap.domain.models.domain.ApproveModel +import com.tangem.feature.swap.domain.models.domain.Currency +import com.tangem.feature.swap.domain.models.domain.QuoteModel +import com.tangem.feature.swap.domain.models.domain.SwapDataModel import com.tangem.feature.swap.domain.models.data.AggregatedSwapDataModel interface SwapRepository { diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt index 073920c985..a415b03edc 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt @@ -1,24 +1,30 @@ package com.tangem.feature.swap.domain.cache -import com.tangem.feature.swap.domain.models.ApproveModel -import com.tangem.feature.swap.domain.models.Currency -import com.tangem.feature.swap.domain.models.QuoteModel import com.tangem.feature.swap.domain.models.SwapAmount -import com.tangem.feature.swap.domain.models.SwapDataModel import com.tangem.feature.swap.domain.models.cache.ExchangeCurrencies +import com.tangem.feature.swap.domain.models.domain.ApproveModel +import com.tangem.feature.swap.domain.models.domain.Currency +import com.tangem.feature.swap.domain.models.domain.QuoteModel +import com.tangem.feature.swap.domain.models.domain.SwapDataModel interface SwapDataCache { fun cacheNetworkId(networkId: String) - fun cacheSwapParams(quoteModel: QuoteModel, amount: SwapAmount, fromCurrency: Currency, toCurrency: Currency) - fun cacheApproveTransactionData(approve: ApproveModel) + fun cacheQuoteData(quoteModel: QuoteModel) fun cacheSwapData(swapDataModel: SwapDataModel) + fun cacheAmountToSwap(amount: SwapAmount) + fun cacheApproveTransactionData(approve: ApproveModel) fun cacheAvailableToSwapTokens(networkId: String, tokens: List) + fun cacheInWalletTokens(tokens: List) + fun cacheLoadedTokens(tokens: List) + fun cacheExchangeCurrencies(fromToken: Currency, toToken: Currency) fun getAvailableTokens(networkId: String): List fun getApproveTransactionData(): ApproveModel? fun getLastQuote(): QuoteModel? fun getLastSwapData(): SwapDataModel? - fun getAmountToSwap(): SwapAmount? fun getNetworkId(): String? + fun getAmountToSwap(): SwapAmount? fun getExchangeCurrencies(): ExchangeCurrencies? + fun getInWalletTokens(): List + fun getLoadedTokens(): List } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt index b5ca9e38a1..582eb1ff73 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt @@ -1,35 +1,54 @@ package com.tangem.feature.swap.domain.cache -import com.tangem.feature.swap.domain.models.ApproveModel -import com.tangem.feature.swap.domain.models.Currency -import com.tangem.feature.swap.domain.models.QuoteModel import com.tangem.feature.swap.domain.models.SwapAmount -import com.tangem.feature.swap.domain.models.SwapDataModel import com.tangem.feature.swap.domain.models.cache.ExchangeCurrencies import com.tangem.feature.swap.domain.models.cache.SwapDataHolder +import com.tangem.feature.swap.domain.models.domain.ApproveModel +import com.tangem.feature.swap.domain.models.domain.Currency +import com.tangem.feature.swap.domain.models.domain.QuoteModel +import com.tangem.feature.swap.domain.models.domain.SwapDataModel class SwapDataCacheImpl : SwapDataCache { private var lastDataForSwap: SwapDataHolder = SwapDataHolder() private val availableTokensForNetwork: MutableMap> = mutableMapOf() + private val lastInWalletTokens = mutableListOf() + private val lastLoadedTokens = mutableListOf() - override fun cacheSwapParams( + override fun cacheQuoteData( quoteModel: QuoteModel, - amount: SwapAmount, - fromCurrency: Currency, - toCurrency: Currency, ) { + lastDataForSwap = lastDataForSwap.copy(quoteModel = quoteModel) + } + + override fun cacheExchangeCurrencies(fromToken: Currency, toToken: Currency) { lastDataForSwap = lastDataForSwap.copy( - quoteModel = quoteModel, - amountToSwap = amount, exchangeCurrencies = ExchangeCurrencies( - fromCurrency = fromCurrency, - toCurrency = toCurrency, + fromCurrency = fromToken, + toCurrency = toToken, ), ) } + override fun cacheInWalletTokens(tokens: List) { + lastInWalletTokens.clear() + lastInWalletTokens.addAll(tokens) + } + + override fun cacheLoadedTokens(tokens: List) { + lastLoadedTokens.clear() + lastLoadedTokens.addAll(tokens) + } + + override fun getInWalletTokens(): List { + return lastInWalletTokens + } + + override fun getLoadedTokens(): List { + return lastLoadedTokens + } + override fun cacheSwapData(swapDataModel: SwapDataModel) { lastDataForSwap = lastDataForSwap.copy(swapModel = swapDataModel) } @@ -54,6 +73,10 @@ class SwapDataCacheImpl : SwapDataCache { lastDataForSwap = lastDataForSwap.copy(networkId = networkId) } + override fun cacheAmountToSwap(amount: SwapAmount) { + lastDataForSwap = lastDataForSwap.copy(amountToSwap = amount) + } + override fun getNetworkId(): String? { return lastDataForSwap.networkId } diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/converters/CryptoCurrencyConverter.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/converters/CryptoCurrencyConverter.kt index 68c3895f79..90b978100e 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/converters/CryptoCurrencyConverter.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/converters/CryptoCurrencyConverter.kt @@ -1,14 +1,15 @@ package com.tangem.feature.swap.domain.converters -import com.tangem.feature.swap.domain.models.Currency -import com.tangem.utils.converter.Converter +import com.tangem.feature.swap.domain.models.domain.Currency +import com.tangem.utils.converter.TwoWayConverter +import com.tangem.lib.crypto.models.Currency as CryptoCurrency -class CryptoCurrencyConverter : Converter { +class CryptoCurrencyConverter : TwoWayConverter { - override fun convert(value: Currency): com.tangem.lib.crypto.models.Currency { + override fun convert(value: Currency): CryptoCurrency { return when (value) { is Currency.NonNativeToken -> { - com.tangem.lib.crypto.models.Currency.NonNativeToken( + CryptoCurrency.NonNativeToken( id = value.id, name = value.name, symbol = value.symbol, @@ -18,7 +19,7 @@ class CryptoCurrencyConverter : Converter { - com.tangem.lib.crypto.models.Currency.NativeToken( + CryptoCurrency.NativeToken( id = value.id, name = value.name, symbol = value.symbol, @@ -27,4 +28,29 @@ class CryptoCurrencyConverter : Converter { + Currency.NonNativeToken( + id = value.id, + name = value.name, + symbol = value.symbol, + networkId = value.networkId, + contractAddress = value.contractAddress, + decimalCount = value.decimalCount, + logoUrl = "", + ) + } + is CryptoCurrency.NativeToken -> { + Currency.NativeToken( + id = value.id, + name = value.name, + symbol = value.symbol, + networkId = value.networkId, + logoUrl = "", + ) + } + } + } } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/cache/SwapDataHolder.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/cache/SwapDataHolder.kt index 764ec2f37e..1271078626 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/cache/SwapDataHolder.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/cache/SwapDataHolder.kt @@ -1,10 +1,10 @@ package com.tangem.feature.swap.domain.models.cache -import com.tangem.feature.swap.domain.models.ApproveModel -import com.tangem.feature.swap.domain.models.Currency -import com.tangem.feature.swap.domain.models.QuoteModel +import com.tangem.feature.swap.domain.models.domain.ApproveModel +import com.tangem.feature.swap.domain.models.domain.Currency +import com.tangem.feature.swap.domain.models.domain.QuoteModel import com.tangem.feature.swap.domain.models.SwapAmount -import com.tangem.feature.swap.domain.models.SwapDataModel +import com.tangem.feature.swap.domain.models.domain.SwapDataModel data class SwapDataHolder( val quoteModel: QuoteModel? = null, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ApproveModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/ApproveModel.kt similarity index 87% rename from features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ApproveModel.kt rename to features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/ApproveModel.kt index 2fce5634f9..c21b52c0fa 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ApproveModel.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/ApproveModel.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.swap.domain.models +package com.tangem.feature.swap.domain.models.domain /** * Approve model diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/Currency.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/Currency.kt similarity index 93% rename from features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/Currency.kt rename to features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/Currency.kt index bcd8017e56..fec99447b7 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/Currency.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/Currency.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.swap.domain.models +package com.tangem.feature.swap.domain.models.domain import kotlinx.serialization.Serializable diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/QuoteModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/QuoteModel.kt similarity index 83% rename from features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/QuoteModel.kt rename to features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/QuoteModel.kt index 14b36eed8b..871b9d77ea 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/QuoteModel.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/QuoteModel.kt @@ -1,4 +1,6 @@ -package com.tangem.feature.swap.domain.models +package com.tangem.feature.swap.domain.models.domain + +import com.tangem.feature.swap.domain.models.SwapAmount /** * Quote model holds data about current amounts of exchange and fees diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/SwapDataModel.kt similarity index 80% rename from features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataModel.kt rename to features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/SwapDataModel.kt index 8af39f1219..e2b5356100 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapDataModel.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/SwapDataModel.kt @@ -1,4 +1,6 @@ -package com.tangem.feature.swap.domain.models +package com.tangem.feature.swap.domain.models.domain + +import com.tangem.feature.swap.domain.models.SwapAmount /** * Swap transaction model diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/TransactionModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/TransactionModel.kt similarity index 93% rename from features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/TransactionModel.kt rename to features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/TransactionModel.kt index eea39441ca..ac5b7b2b05 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/TransactionModel.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/TransactionModel.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.swap.domain.models +package com.tangem.feature.swap.domain.models.domain /** * Transaction model diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapState.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt similarity index 83% rename from features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapState.kt rename to features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt index 60ec7a9471..77f86cbf47 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapState.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt @@ -1,6 +1,7 @@ -package com.tangem.feature.swap.domain.models +package com.tangem.feature.swap.domain.models.ui -import java.math.BigDecimal +import com.tangem.feature.swap.domain.models.DataError +import com.tangem.feature.swap.domain.models.SwapAmount sealed interface SwapState { @@ -18,11 +19,6 @@ sealed interface SwapState { val permissionState: PermissionDataState = PermissionDataState.Empty, ) : SwapState - data class SwapSuccess( - val fromTokenAmount: SwapAmount, - val toTokenAmount: SwapAmount, - ) : SwapState - data class SwapError( val errorType: DataError, ) : SwapState diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/TokensDataState.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/TokensDataState.kt new file mode 100644 index 0000000000..2fcb993b1d --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/TokensDataState.kt @@ -0,0 +1,28 @@ +package com.tangem.feature.swap.domain.models.ui + +import com.tangem.feature.swap.domain.models.domain.Currency + +data class TokensDataState( + val preselectTokens: PreselectTokens, + val foundTokensState: FoundTokensState, +) + +data class FoundTokensState( + val tokensInWallet: List, + val loadedTokens: List, +) + +data class PreselectTokens( + val fromToken: Currency, + val toToken: Currency, +) + +data class TokenWithBalance( + val token: Currency, + val tokenBalanceData: TokenBalanceData? = null, +) + +data class TokenBalanceData( + val amount: String?, + val amountEquivalent: String?, +) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/converters/TokensDataConverter.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/converters/TokensDataConverter.kt new file mode 100644 index 0000000000..4e5deed03d --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/converters/TokensDataConverter.kt @@ -0,0 +1,35 @@ +package com.tangem.feature.swap.converters + +import com.tangem.feature.swap.domain.models.ui.FoundTokensState +import com.tangem.feature.swap.domain.models.ui.TokenWithBalance +import com.tangem.feature.swap.models.SwapSelectTokenStateHolder +import com.tangem.feature.swap.models.TokenBalanceData +import com.tangem.feature.swap.models.TokenToSelect +import com.tangem.utils.converter.Converter + +class TokensDataConverter( + private val onSearchEntered: (String) -> Unit, + private val onTokenSelected: (String) -> Unit, +) : Converter { + + override fun convert(value: FoundTokensState): SwapSelectTokenStateHolder { + return SwapSelectTokenStateHolder( + tokens = (value.tokensInWallet + value.loadedTokens).map { tokenWithBalanceToTokenToSelect(it) }, + onSearchEntered = onSearchEntered, + onTokenSelected = onTokenSelected, + ) + } + + private fun tokenWithBalanceToTokenToSelect(tokenWithBalance: TokenWithBalance): TokenToSelect { + return TokenToSelect( + id = tokenWithBalance.token.id, + name = tokenWithBalance.token.name, + symbol = tokenWithBalance.token.symbol, + iconUrl = tokenWithBalance.token.logoUrl, + addedTokenBalanceData = TokenBalanceData( + amount = tokenWithBalance.tokenBalanceData?.amount, + amountEquivalent = tokenWithBalance.tokenBalanceData?.amountEquivalent, + ), + ) + } +} \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt new file mode 100644 index 0000000000..60f8a74218 --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt @@ -0,0 +1,11 @@ +package com.tangem.feature.swap.models + +data class UiActions( + val onSearchEntered: (String) -> Unit, + val onTokenSelected: (String) -> Unit, + val onAmountChanged: (String) -> Unit, + val onSwapClick: () -> Unit, + val onGivePermissionClick: () -> Unit, + val onChangeCardsClicked: () -> Unit, + val onBackClicked: () -> Unit, +) \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 5e91941a56..4d90a1c669 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -1,9 +1,11 @@ package com.tangem.feature.swap.ui -import com.tangem.feature.swap.domain.models.Currency -import com.tangem.feature.swap.domain.models.PermissionDataState -import com.tangem.feature.swap.domain.models.SwapState +import com.tangem.feature.swap.converters.TokensDataConverter +import com.tangem.feature.swap.domain.models.domain.Currency import com.tangem.feature.swap.domain.models.formatToUIRepresentation +import com.tangem.feature.swap.domain.models.ui.FoundTokensState +import com.tangem.feature.swap.domain.models.ui.PermissionDataState +import com.tangem.feature.swap.domain.models.ui.SwapState import com.tangem.feature.swap.models.ApprovePermissionButton import com.tangem.feature.swap.models.CancelPermissionButton import com.tangem.feature.swap.models.FeeState @@ -13,16 +15,19 @@ import com.tangem.feature.swap.models.SwapPermissionState import com.tangem.feature.swap.models.SwapStateHolder import com.tangem.feature.swap.models.SwapWarning import com.tangem.feature.swap.models.TransactionCardType +import com.tangem.feature.swap.models.UiActions /** * State builder creates a specific states for SwapScreen */ -class StateBuilder { +class StateBuilder(val actions: UiActions) { - fun createInitialLoadingState(networkCurrency: String, onAmountChanged: (String) -> Unit): SwapStateHolder { + private val tokensDataConverter = TokensDataConverter(actions.onSearchEntered, actions.onTokenSelected) + + fun createInitialLoadingState(networkCurrency: String): SwapStateHolder { return SwapStateHolder( sendCardData = SwapCardData( - type = TransactionCardType.SendCard(onAmountChanged), + type = TransactionCardType.SendCard(actions.onAmountChanged), amount = null, amountEquivalent = null, tokenIconUrl = "", @@ -43,8 +48,8 @@ class StateBuilder { networkCurrency = networkCurrency, swapButton = SwapButton(enabled = false, loading = true, onClick = {}), onRefresh = {}, - onBackClicked = {}, - onChangeCardsClicked = {}, + onBackClicked = actions.onBackClicked, + onChangeCardsClicked = actions.onChangeCardsClicked, ) } @@ -66,8 +71,8 @@ class StateBuilder { ), receiveCardData = SwapCardData( type = TransactionCardType.ReceiveCard(), - amount = uiStateHolder.receiveCardData.amount, - amountEquivalent = uiStateHolder.receiveCardData.amountEquivalent, + amount = null, + amountEquivalent = null, tokenIconUrl = toToken.logoUrl, tokenCurrency = toToken.symbol, canSelectAnotherToken = mainTokenId != toToken.id, @@ -75,6 +80,7 @@ class StateBuilder { ), fee = FeeState.Loading, swapButton = SwapButton(enabled = false, loading = true, onClick = {}), + permissionState = uiStateHolder.permissionState, ) } @@ -82,8 +88,6 @@ class StateBuilder { uiStateHolder: SwapStateHolder, quoteModel: SwapState.QuotesLoadedState, fromToken: Currency, - onSwapClick: () -> Unit, - onGivePermissionClick: () -> Unit, ): SwapStateHolder { return uiStateHolder.copy( sendCardData = SwapCardData( @@ -109,16 +113,31 @@ class StateBuilder { } else { emptyList() }, - permissionState = convertPermissionState(quoteModel.permissionState, onGivePermissionClick), + permissionState = convertPermissionState(quoteModel.permissionState, actions.onGivePermissionClick), fee = FeeState.Loaded(quoteModel.fee), swapButton = SwapButton( enabled = quoteModel.isAllowedToSpend, loading = false, - onClick = onSwapClick, + onClick = actions.onSwapClick, ), ) } + fun createSwapInProgressState(uiState: SwapStateHolder): SwapStateHolder { + return uiState.copy( + swapButton = uiState.swapButton.copy( + loading = true, + enabled = false, + ), + ) + } + + fun addTokensToState(uiState: SwapStateHolder, dataState: FoundTokensState): SwapStateHolder { + return uiState.copy( + selectTokenState = tokensDataConverter.convert(dataState), + ) + } + private fun convertPermissionState( permissionDataState: PermissionDataState, onGivePermissionClick: () -> Unit, @@ -144,4 +163,13 @@ class StateBuilder { ) } } + + fun updateSwapAmount(uiState: SwapStateHolder, amount: String, amountEquivalent: String): SwapStateHolder { + return uiState.copy( + sendCardData = uiState.sendCardData.copy( + amount = amount, + amountEquivalent = amountEquivalent, + ), + ) + } } \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt index ff81cb50e9..c5a0edeca4 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt @@ -208,7 +208,7 @@ private fun Content( } is TransactionCardType.SendCard -> { AutoSizeTextField( - amount = amount ?: "0", + amount = amount ?: "1", onAmoutChanged = { type.onAmountChanged(it) }, ) } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/PeriodicTask.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/PeriodicTask.kt new file mode 100644 index 0000000000..138ce3c20c --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/PeriodicTask.kt @@ -0,0 +1,43 @@ +package com.tangem.feature.swap.viewmodels + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import java.util.concurrent.atomic.AtomicBoolean + +class PeriodicTask( + private val delay: Long, + private val task: suspend () -> Unit, +) { + + private var isActive: AtomicBoolean = AtomicBoolean(false) + + suspend fun runTaskWithDelay() { + isActive.set(true) + while (isActive.get()) { + task.invoke() + delay(delay) + } + } + + fun cancel() { + isActive.set(false) + } +} + +class SingleTaskScheduler { + + private var lastTask: PeriodicTask? = null + + fun scheduleTask(scope: CoroutineScope, task: PeriodicTask) { + lastTask?.cancel() + lastTask = task + scope.launch { + task.runTaskWithDelay() + } + } + + fun cancelTask() { + lastTask?.cancel() + } +} \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index 1ba7b06511..f99d5d9ac5 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -8,18 +8,21 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.tangem.feature.swap.domain.SwapInteractor -import com.tangem.feature.swap.domain.models.Currency -import com.tangem.feature.swap.domain.models.SwapState import com.tangem.feature.swap.domain.models.createFromAmountWithoutOffset +import com.tangem.feature.swap.domain.models.domain.Currency +import com.tangem.feature.swap.domain.models.ui.FoundTokensState +import com.tangem.feature.swap.domain.models.ui.SwapState import com.tangem.feature.swap.models.SwapStateHolder +import com.tangem.feature.swap.models.UiActions import com.tangem.feature.swap.presentation.SwapFragment import com.tangem.feature.swap.router.SwapRouter import com.tangem.feature.swap.router.SwapScreen import com.tangem.feature.swap.ui.StateBuilder import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.Debouncer +import com.tangem.utils.coroutines.runCatching import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import javax.inject.Inject @@ -32,23 +35,31 @@ internal class SwapViewModel @Inject constructor( savedStateHandle: SavedStateHandle, ) : ViewModel() { - private val stateBuilder = StateBuilder() + private val stateBuilder = StateBuilder( + actions = createUiActions(), + ) + + private val amountDebouncer = Debouncer() + private val singleTaskScheduler = SingleTaskScheduler() private val currency = Json.decodeFromString( savedStateHandle[SwapFragment.CURRENCY_BUNDLE_KEY] ?: error("no expected parameter Currency found"), ) - var uiState: SwapStateHolder by mutableStateOf( - stateBuilder.createInitialLoadingState(currency.symbol) - { /** [REDACTED_TODO_COMMENT]*/ }, - ) + var uiState: SwapStateHolder by mutableStateOf(stateBuilder.createInitialLoadingState(currency.symbol)) private set + //shows currency order (direct - swap initial to selected, reversed = selected to initial) + private var isOrderReversed = false + private val lastAmount = mutableStateOf(INITIAL_AMOUNT) private var swapRouter: SwapRouter by Delegates.notNull() - var currentScreen = SwapScreen.Main get() = swapRouter.currentScreen + init { + initTokens(currency) + } + fun setRouter(router: SwapRouter) { swapRouter = router uiState = uiState.copy( @@ -58,21 +69,18 @@ internal class SwapViewModel @Inject constructor( ) } - init { + private fun initTokens(currency: Currency) { viewModelScope.launch(dispatchers.main) { - runCatching { - withContext(dispatchers.io) { - swapInteractor.getTokensToSwap(currency.networkId) - } + runCatching(dispatchers.io) { + swapInteractor.initTokensToSwap(currency) } - .onSuccess { tokens -> - if (tokens.size > MIN_TOKENS_IN_LIST) { - tokens.firstOrNull { token -> - token.id == currency.id - }?.let { - loadQuotes(it, tokens[1], INITIAL_AMOUNT) - } - } + .onSuccess { state -> + updateTokensState(dataState = state.foundTokensState) + startLoadingQuotes( + fromToken = state.preselectTokens.fromToken, + toToken = state.preselectTokens.toToken, + amount = lastAmount.value, + ) } .onFailure { Log.e("SwapViewModel", it.message ?: it.cause.toString()) @@ -80,20 +88,38 @@ internal class SwapViewModel @Inject constructor( } } - private fun loadQuotes(fromToken: Currency, toToken: Currency, amount: String) { + private fun updateTokensState(dataState: FoundTokensState) { + uiState = stateBuilder.addTokensToState(uiState, dataState) + } + + private fun startLoadingQuotes(fromToken: Currency, toToken: Currency, amount: String) { + singleTaskScheduler.cancelTask() uiState = stateBuilder.createQuotesLoadingState(uiState, fromToken, toToken, currency.id) + singleTaskScheduler.scheduleTask( + viewModelScope, + PeriodicTask( + delay = UPDATE_DELAY, + ) { + loadQuotesInternal( + fromToken = fromToken, + toToken = toToken, + amount = amount, + ) + }, + ) + } + + private fun loadQuotesInternal(fromToken: Currency, toToken: Currency, amount: String) { viewModelScope.launch(dispatchers.main) { - runCatching { - withContext(dispatchers.io) { - swapInteractor.findBestQuote( - fromToken = fromToken, - toToken = toToken, - amount = createFromAmountWithoutOffset( - amountWithoutOffset = amount, - decimals = swapInteractor.getTokenDecimals(fromToken), - ), - ) - } + runCatching(dispatchers.io) { + swapInteractor.findBestQuote( + fromToken = fromToken, + toToken = toToken, + amount = createFromAmountWithoutOffset( + amountWithoutOffset = amount, + decimals = swapInteractor.getTokenDecimals(fromToken), + ), + ) } .onSuccess { swapState -> when (swapState) { @@ -102,13 +128,8 @@ internal class SwapViewModel @Inject constructor( uiStateHolder = uiState, quoteModel = swapState, fromToken = fromToken, - onSwapClick = { onSwapClick(toToken, swapState) }, - onGivePermissionClick = { givePermissionsToSwap(fromToken) }, ) } - is SwapState.SwapSuccess -> { - //todo implement - } is SwapState.SwapError -> { } } @@ -117,46 +138,106 @@ internal class SwapViewModel @Inject constructor( } } - private fun onSwapClick(toToken: Currency, quoteModel: SwapState.QuotesLoadedState) { + private fun createUiActions(): UiActions { + return UiActions( + onSearchEntered = { onSearchEntered(it) }, + onTokenSelected = { onTokenSelect(it) }, + onAmountChanged = { onAmountChanged(it) }, + onSwapClick = { onSwapClick() }, + onGivePermissionClick = { givePermissionsToSwap() }, + onChangeCardsClicked = { onChangeCardsClicked() }, + onBackClicked = { onSearchEntered("") }, + ) + } + + private fun onSwapClick() { + singleTaskScheduler.cancelTask() + uiState = stateBuilder.createSwapInProgressState(uiState) viewModelScope.launch(dispatchers.main) { - runCatching { - withContext(dispatchers.io) { - if (!quoteModel.isAllowedToSpend) { - swapInteractor.givePermissionToSwap(toToken) - } else { - swapInteractor.onSwap() - } - } + runCatching(dispatchers.io) { + swapInteractor.onSwap() } .onSuccess { - if (it is SwapState) { - when (it) { - is SwapState.SwapSuccess -> { - } - is SwapState.SwapError -> { - } - else -> {} + when (it) { + is SwapState.SwapError -> { } + else -> {} } } .onFailure { } } } - private fun givePermissionsToSwap(tokenToApprove: Currency) { + private fun givePermissionsToSwap() { viewModelScope.launch(dispatchers.main) { - runCatching { - withContext(dispatchers.io) { - swapInteractor.givePermissionToSwap(tokenToApprove) - } + runCatching(dispatchers.io) { + swapInteractor.givePermissionToSwap() } .onSuccess { } .onFailure { } } } + private fun onSearchEntered(searchQuery: String) { + viewModelScope.launch(dispatchers.main) { + runCatching(dispatchers.io) { + swapInteractor.onSearchToken(searchQuery) + } + .onSuccess { + updateTokensState(it) + } + .onFailure { } + } + } + + private fun onTokenSelect(id: String) { + val foundToken = swapInteractor.findTokenById(id) + if (foundToken != null) { + val fromToken: Currency + val toToken: Currency + if (isOrderReversed) { + fromToken = foundToken + toToken = currency + } else { + fromToken = currency + toToken = foundToken + } + swapRouter.openScreen(SwapScreen.Main) + startLoadingQuotes(fromToken, toToken, lastAmount.value) + } + } + + private fun onChangeCardsClicked() { + val currencies = swapInteractor.getExchangeCurrencies() + val newFromToken = currencies?.toCurrency + val newToToken = currencies?.fromCurrency + if (newFromToken != null && newToToken != null) { + isOrderReversed = !isOrderReversed + startLoadingQuotes(newFromToken, newToToken, lastAmount.value) + } + } + + private fun onAmountChanged(value: String) { + uiState = stateBuilder.updateSwapAmount(uiState, value, value) + lastAmount.value = value + amountDebouncer.debounce(DEBOUNCE_AMOUNT_DELAY, viewModelScope) { + val currencies = swapInteractor.getExchangeCurrencies() + val fromToken = currencies?.fromCurrency + val toToken = currencies?.toCurrency + if (fromToken != null && toToken != null) { + startLoadingQuotes(fromToken, toToken, lastAmount.value) + } + } + } + + override fun onCleared() { + singleTaskScheduler.cancelTask() + super.onCleared() + } + companion object { - private const val MIN_TOKENS_IN_LIST = 2 private const val INITIAL_AMOUNT = "1" + private const val UPDATE_DELAY = 10000L + private const val DEBOUNCE_AMOUNT_DELAY = 1000L } } \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt index 34f2f3d06b..ca92f3faf6 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt @@ -13,6 +13,7 @@ interface TransactionManager { amountToSend: BigDecimal, currencyToSend: Currency, feeAmount: BigDecimal, + estimatedGas: Int, destinationAddress: String, dataToSign: String, ): SendTxResult @@ -25,8 +26,6 @@ interface TransactionManager { destinationAddress: String, ): ProxyAmount - fun getNativeAddress(networkId: String): String - @Throws(IllegalStateException::class) fun getNativeTokenDecimals(networkId: String): Int diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt index 3faa50aa4c..f4c84822ed 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt @@ -12,7 +12,7 @@ interface UserWalletManager { /** * Returns all user tokens (merged from local and backend) */ - suspend fun getUserTokens(): List + suspend fun getUserTokens(networkId: String): List /** * Returns user walletId From 479d14280723ec9772ffbca44f6aa2f8a4526fe2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 18 Jan 2023 18:05:18 +0800 Subject: [PATCH 083/145] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 7 +- .../java/com/tangem/tap/TapApplication.kt | 4 +- .../java/com/tangem/tap/WaitForMigration.kt | 2 +- .../com/tangem/tap/common/DialogManager.kt | 3 +- .../com/tangem/tap/common/KeyboardObserver.kt | 3 +- .../java/com/tangem/tap/common/TestActions.kt | 3 +- .../common/analytics/AnalyticsEventsLogger.kt | 4 +- ...gemSdkError.kt => TangemSdkErrorMapper.kt} | 6 +- .../converters/BasicEventConverter.kt | 4 +- .../common/analytics/events/AnalyticsParam.kt | 5 +- .../handlers/amplitude/AmplitudeClient.kt | 2 +- .../handlers/appsFlyer/AppsFlyerClient.kt | 2 +- .../handlers/firebase/FirebaseClient.kt | 4 +- .../tangem/tap/common/compose/AutoSizeText.kt | 3 +- .../compose/{Theme.kt => NoRippleTheme.kt} | 0 .../tap/common/compose/OutlinedSpinner.kt | 3 +- .../common/compose/OutlinedTextFieldWidget.kt | 3 +- .../tap/common/compose/PinCodeWidget.kt | 4 - .../tap/common/compose/extensions/Color.kt | 20 --- .../common/compose/extensions/ImageBitmap.kt | 5 +- .../tap/common/extensions/BackupService.kt | 5 +- .../tangem/tap/common/extensions/Bitmap.kt | 2 + .../tap/common/extensions/Blockchain.kt | 12 +- .../tap/common/extensions/Navigation.kt | 18 +- .../tangem/tap/common/extensions/Specific.kt | 22 +-- .../tangem/tap/common/extensions/String.kt | 5 +- .../tangem/tap/common/extensions/TextView.kt | 12 +- .../com/tangem/tap/common/extensions/Token.kt | 14 +- .../com/tangem/tap/common/extensions/UI.kt | 56 +----- .../tap/common/extensions/WalletManager.kt | 4 +- .../common/feedback/AdditionalFeedbackInfo.kt | 7 +- .../tap/common/feedback/FeedbackData.kt | 2 + .../leapfrogWidget/TestLeapfrogFragment.kt | 2 +- .../common/qrCodeScan/ScanQrCodeActivity.kt | 11 +- ...yStore.kt => FeatureRepositoryProvider.kt} | 0 .../common/redux/NotificationsMiddleware.kt | 1 + ...lobalMidlleware.kt => GlobalMiddleware.kt} | 7 +- .../tap/common/redux/global/GlobalReducer.kt | 1 + .../navigation/FragmentShareTransition.kt | 8 +- .../redux/navigation/NavigationReducer.kt | 6 +- .../tap/common/shop/TangemShopService.kt | 1 + .../common/shop/googlepay/GooglePayService.kt | 17 +- .../common/shop/googlepay/GooglePayUtil.kt | 59 +++---- .../tap/common/shop/shopify/ShopifyService.kt | 133 +++----------- .../tap/common/shop/shopify/data/Checkout.kt | 9 +- .../common/shop/shopify/data/Collection.kt | 4 +- .../common/text/DecimalDigitsInputFilter.kt | 19 +- .../toggleWidget/RefreshBalanceWidget.kt | 3 + .../common/toggleWidget/ViewStateWidget.kt | 1 - .../tangem/tap/common/ui/SimpleAlertDialog.kt | 86 +++++----- .../com/tangem/tap/domain/PayIdManager.kt | 42 +++-- .../com/tangem/tap/domain/RatesRepository.kt | 2 +- .../com/tangem/tap/domain/TangemSdkManager.kt | 3 +- .../java/com/tangem/tap/domain/TapErrors.kt | 12 +- .../configurable/config/ConfigManager.kt | 1 + ...onfigLoaders.kt => FeaturesLocalLoader.kt} | 0 .../domain/configurable/config/JsonModels.kt | 1 + .../warningMessage/WarningMessagesManager.kt | 4 +- .../tap/domain/extensions/Blockchain.kt | 2 +- .../com/tangem/tap/domain/extensions/Card.kt | 1 + .../domain/extensions/WalletManagerFactory.kt | 9 +- .../model/builders/WalletStoreBuilder.kt | 4 +- .../converter/AddCustomTokenErrorConverter.kt | 3 +- .../SaltPayActivationErrorConverter.kt | 2 +- .../tap/domain/scanCard/ScanCardProcessor.kt | 47 ++--- .../OnboardingWalletStateConverter.kt | 1 + .../tangem/tap/domain/tasks/DerivationTask.kt | 55 ------ .../product/ResetToFactorySettingsTask.kt | 2 +- .../domain/tasks/product/ScanProductTask.kt | 6 +- .../tokens/LoadAvailableCoinsService.kt | 4 +- .../domain/tokens/OldUserTokensRepository.kt | 2 +- .../tap/domain/tokens/UserTokensRepository.kt | 2 +- .../tap/domain/tokens/models/BlockchainDao.kt | 7 +- .../domain/tokens/models/BlockchainNetwork.kt | 17 +- .../tap/domain/twins/TwinCardsWidget.kt | 11 +- .../twins/WriteProtectedIssuerDataTask.kt | 10 +- .../userWalletList/UserWalletsListManager.kt | 6 +- .../BiometricUserWalletsListManager.kt | 17 +- .../BiometricUserWalletsKeysRepository.kt | 2 +- .../domain/walletStores/WalletStoresError.kt | 5 + .../DefaultWalletAmountsRepository.kt | 49 +++--- .../DefaultWalletManagersRepository.kt | 11 +- .../storage/WalletManagerStorage.kt | 5 +- .../storage/WalletStoresStorage.kt | 5 +- .../tap/domain/walletconnect/BnbHelper.kt | 120 +++++++------ .../tap/domain/walletconnect/EthSignHelper.kt | 62 +++---- .../walletconnect/WalletConnectManager.kt | 13 +- .../WalletConnectNetworkUtils.kt | 69 ++++---- .../walletconnect/WalletConnectSdkHelper.kt | 5 +- .../tangem/tap/features/BaseStoreFragment.kt | 3 +- .../tangem/tap/features/demo/DemoHelper.kt | 10 +- ...res.kt => DemoOnboardingNoteMiddleware.kt} | 2 +- .../details/redux/DetailsMiddleware.kt | 3 +- .../features/details/redux/DetailsReducer.kt | 10 +- .../walletconnect/WalletConnectMiddleware.kt | 7 +- .../walletconnect/WalletConnectReducer.kt | 73 ++++---- .../redux/walletconnect/WalletConnectState.kt | 2 +- .../ui/appsettings/AppSettingsScreen.kt | 1 + .../ui/cardsettings/CardSettingsScreen.kt | 2 + .../ui/cardsettings/CardSettingsViewModel.kt | 3 +- .../details/ui/common/TangemSwitch.kt | 1 + .../details/ui/details/DetailsViewModel.kt | 5 +- .../details/ui/resetcard/ResetCardScreen.kt | 1 + .../ui/securitymode/SecurityModeViewModel.kt | 16 +- .../dialogs/ApproveWcSessionDialog.kt | 60 ++++--- .../dialogs/BnbTransactionDialog.kt | 100 ++++++----- .../dialogs/ClipboardOrScanQrDialog.kt | 32 ++-- .../dialogs/PersonalSignDialog.kt | 34 ++-- .../dialogs/TransactionDialog.kt | 69 ++++---- .../ui/walletconnect/dialogs/WcDialog.kt | 55 ------ .../tap/features/disclaimer/Disclaimer.kt | 1 + .../disclaimer/redux/DisclaimerReducer.kt | 6 +- .../disclaimer/redux/DisclaimerState.kt | 3 +- .../disclaimer/ui/DisclaimerFragment.kt | 1 - .../disclaimer/ui/DisclaimerWebViewClient.kt | 6 +- .../tap/features/feedback/FeedbackManager.kt | 0 .../features/home/compose/StoriesAnimation.kt | 36 ++-- .../features/home/compose/StoriesScreen.kt | 55 +++--- .../features/home/compose/content/Content.kt | 27 ++- .../compose/content/CurrenciesWeb3Content.kt | 5 +- .../compose/content/FirstStoriesContent.kt | 28 +-- .../compose/content/FloatingCardsContent.kt | 103 +++++------ .../home/compose/views/HomeButtons.kt | 2 + .../tap/features/home/redux/HomeMiddleware.kt | 33 ++-- .../tap/features/home/redux/HomeReducer.kt | 6 +- .../features/onboarding/OnboardingHelper.kt | 143 ++++++++-------- .../features/onboarding/OnboardingManager.kt | 6 +- .../onboarding/OnboardingSaltPayHelper.kt | 46 ++--- .../note/redux/OnboardingNoteMiddleware.kt | 11 +- .../note/redux/OnboardingNoteReducer.kt | 6 +- .../redux/OnboardingOtherCardsMiddleware.kt | 13 +- .../redux/OnboardingOtherCardsReducer.kt | 6 +- .../twins/redux/TwinCardsMiddleware.kt | 13 +- .../products/twins/redux/TwinCardsReducer.kt | 7 +- .../products/twins/ui/TwinsCardsFragment.kt | 7 +- .../ui/dialog/CreateWalletInterruptDialog.kt | 17 +- .../redux/OnboardingWalletMiddleware.kt | 12 +- .../wallet/redux/OnboardingWalletReducer.kt | 161 ++++++++--------- .../wallet/redux/OnboardingWalletState.kt | 4 +- .../wallet/saltPay/GnosisRegistrator.kt | 8 +- .../products/wallet/saltPay/PinFilters.kt | 4 +- .../saltPay/SaltPayActivationManager.kt | 9 +- .../wallet/saltPay/SaltPayExceptionHandler.kt | 50 +++--- .../products/wallet/saltPay/UtorgWebView.kt | 4 +- .../dialog/InterruptOnboardingDialog.kt | 22 ++- .../dialog/NoFundsForActivationDialog.kt | 40 ++--- .../saltPay/dialog/RegistrationErrorDialog.kt | 30 ++-- .../saltPay/message/SaltPayModuleMessage.kt | 33 ++-- .../redux/OnboardingSaltPayMiddleware.kt | 8 +- .../saltPay/redux/OnboardingSaltPayReducer.kt | 9 +- .../saltPay/redux/OnboardingSaltPayState.kt | 4 +- .../saltPay/ui/OnboardingSaltPayView.kt | 11 +- .../products/wallet/ui/BackupAnimator.kt | 9 +- .../wallet/ui/OnboardingWalletFragment.kt | 3 +- .../products/wallet/ui/WalletCardsWidget.kt | 16 +- .../ui/dialogs/AddMoreBackupCardsDialog.kt | 30 ++-- .../ui/dialogs/BackupInProgressDialog.kt | 26 ++- .../dialogs/ConfirmDiscardingBackupDialog.kt | 34 ++-- .../ui/dialogs/UnfinishedBackupFoundDialog.kt | 32 ++-- .../features/send/redux/SendScreenAction.kt | 44 ++--- .../redux/middlewares/RequestFeeMiddleware.kt | 30 ---- .../send/redux/middlewares/SendMiddleware.kt | 9 +- .../redux/reducers/AddressPayIdReducer.kt | 32 ++-- .../send/redux/reducers/FeeReducer.kt | 4 +- .../send/redux/reducers/ReceiptReducer.kt | 38 ++-- .../send/redux/reducers/SendScreenReducer.kt | 62 ++++--- .../reducers/TransactionExtrasReducer.kt | 10 +- .../send/redux/states/AddressPayIdState.kt | 3 +- .../features/send/redux/states/SendState.kt | 47 ++--- .../tap/features/send/ui/SendFragment.kt | 136 +++++++++------ .../send/ui/dialogs/RequestFeeErrorDialog.kt | 29 ++-- .../ui/dialogs/SendTransactionFailsDialog.kt | 38 ++-- .../stateSubscribers/SendStateSubscriber.kt | 10 +- .../tap/features/shop/redux/ShopAction.kt | 2 +- .../tap/features/shop/redux/ShopMiddleware.kt | 10 +- .../tap/features/shop/redux/ShopReducer.kt | 16 +- .../tap/features/shop/ui/ShopFragment.kt | 36 ++-- .../compose/AddCustomTokenScreen.kt | 32 ++-- .../addCustomToken/compose/FormFieldViews.kt | 36 ---- .../features/tokens/redux/TokensMiddleware.kt | 41 ++--- .../features/tokens/redux/TokensReducer.kt | 14 +- .../tap/features/tokens/redux/TokensState.kt | 20 ++- .../features/tokens/ui/AddTokensFragment.kt | 3 +- .../tokens/ui/compose/CurrenciesScreen.kt | 5 +- .../tokens/ui/compose/CurrenciesWarning.kt | 1 + .../tokens/ui/compose/ExpandedCurrencyItem.kt | 0 .../tokens/ui/compose/ListOfCurrencies.kt | 1 + .../currencyItem/CurrencyExpandedContent.kt | 1 + .../ui/compose/currencyItem/CurrencyItem.kt | 1 + .../compose/currencyItem/CurrencyItemArrow.kt | 1 + .../currencyItem/CurrencyItemHeader.kt | 2 + .../ui/compose/currencyItem/NetworkItem.kt | 2 + .../features/wallet/models/WalletWarning.kt | 6 +- .../tap/features/wallet/redux/WalletState.kt | 19 +- .../wallet/redux/middlewares/Mapper.kt | 7 +- .../middlewares/MultiWalletMiddleware.kt | 20 ++- .../redux/middlewares/WalletMiddleware.kt | 29 ++-- .../redux/middlewares/WarningsMiddleware.kt | 6 +- .../redux/reducers/MultiWalletReducer.kt | 3 +- .../redux/reducers/OnWalletLoadedReducer.kt | 13 +- .../redux/reducers/TotalBalanceOperations.kt | 35 ++-- .../wallet/redux/reducers/WalletReducer.kt | 9 +- .../tap/features/wallet/ui/BalanceWidget.kt | 6 +- .../wallet/ui/MultipleAddressUiHelper.kt | 50 +++--- .../wallet/ui/WalletDetailsFragment.kt | 3 +- .../tap/features/wallet/ui/WalletFragment.kt | 6 +- .../ui/adapters/WarningMessagesAdapter.kt | 1 + .../wallet/ui/dialogs/ScanFailsDialog.kt | 28 ++- .../ui/dialogs/SignedHashesWarningDialog.kt | 39 ++--- .../wallet/ui/dialogs/SimpleOkDialog.kt | 84 +++++---- .../wallet/ui/images/CurrencyIconView.kt | 1 + .../RoundedCornersCropTransformation.kt | 58 ------- .../tap/features/wallet/ui/test/TestWallet.kt | 160 +++++++++++++++++ .../ui/test/TestWalletDetailsActions.kt | 162 ------------------ .../wallet/ui/view/WalletDetailsButtonsRow.kt | 7 +- .../wallet/ui/wallet/SaltPayBalanceWidget.kt | 4 +- .../wallet/ui/wallet/SingleWalletView.kt | 2 - .../redux/WalletSelectorMiddleware.kt | 28 +-- .../redux/WalletSelectorReducer.kt | 1 + .../WallectSelectorScreenContent.kt | 1 + .../ui/components/WalletItem.kt | 2 +- .../ui/components/WelcomeScreenContent.kt | 1 + .../tangem/tap/network/NetworkConnectivity.kt | 23 ++- .../CurrencyExchangeManager.kt | 19 +- .../mercuryo/MercuryoService.kt | 12 +- .../tap/network/payid/PayIdVerifyApi.kt | 29 ++-- .../persistence/CardBalanceStateAdapter.kt | 2 +- .../tap/persistence/PreferencesStorage.kt | 20 ++- .../tap/proxy/TransactionManagerImpl.kt | 10 +- .../tangem/tap/proxy/UserWalletManagerImpl.kt | 13 +- .../java/com/tangem/wallet/ExampleUnitTest.kt | 17 -- .../com/tangem/common/module/ModuleMessage.kt | 2 +- config/detekt/detekt.yml | 3 +- .../tangem/core/analytics/api/ClientApi.kt | 2 +- .../{Adapters.kt => BigDecimalAdapter.kt} | 0 .../tangem/datasource/api/common/Retrofit.kt | 1 + .../{RateResponse.kt => RatesResponse.kt} | 0 .../com/tangem/core/ui/components/Buttons.kt | 47 ++--- .../com/tangem/core/ui/components/Cards.kt | 11 +- .../com/tangem/core/ui/components/Dialogs.kt | 3 +- .../{KeybosardState.kt => Keyboard.kt} | 1 + .../core/ui/components/ResizableText.kt | 1 + .../com/tangem/core/ui/components/Spacers.kt | 3 +- .../core/ui/components/SuccessScreen.kt | 6 +- .../tangem/core/ui/components/TextFields.kt | 1 + .../ui/components/appbar/AppBarWithSearch.kt | 40 ++--- .../tangem/core/ui/extensions/Blockchain.kt | 1 + .../{ButtonColor.kt => ButtonColorType.kt} | 2 +- .../ui/res/{IconColor.kt => IconColorType.kt} | 2 +- .../com/tangem/core/ui/res/TangemColors.kt | 2 +- .../com/tangem/core/ui/res/TangemDimens.kt | 1 + .../com/tangem/core/ui/res/TangemTheme.kt | 3 +- .../ui/res/{TextColor.kt => TextColorType.kt} | 2 +- .../java/com/tangem/utils/FormatExtensions.kt | 5 +- .../FeatureCoroutineExceptionHandler.kt | 22 ++- .../utils/detekt/UnusedRequiredComponent.kt | 28 +++ .../di/CoroutineDispatcherProviderModule.kt | 4 +- .../java/com/tangem/domain/DomainLayer.kt | 2 +- .../java/com/tangem/domain/common/CardDTO.kt | 15 +- .../com/tangem/domain/common/LogConfig.kt | 15 +- .../tangem/domain/common/SaltPayWorkaround.kt | 68 ++++---- .../tangem/domain/common/TapWorkarounds.kt | 31 ++-- .../com/tangem/domain/common/TwinsHelper.kt | 57 +++--- .../domain/common/extensions/Blockchain.kt | 3 + .../domain/common/form/FieldDataConverters.kt | 4 +- .../domain/common/form/FieldsValidators.kt | 33 ++-- .../tangem/domain/common/util/UserWalletId.kt | 1 + .../domain/common/util/ValueDebouncer.kt | 1 + .../addCustomToken/AddCustomTokenService.kt | 2 +- .../features/addCustomToken/CustomCurrency.kt | 15 +- .../redux/AddCustomTokenAction.kt | 7 +- .../addCustomToken/redux/AddCustomTokenHub.kt | 24 +-- .../redux/AddCustomTokenState.kt | 2 +- .../com/tangem/domain/redux/DomainStore.kt | 4 +- .../referral/ui/ParticipateBottomBlock.kt | 1 + .../referral/viewmodels/ReferralViewModel.kt | 1 + .../tangem/feature/swap/SwapRepositoryImpl.kt | 1 + .../feature/swap/domain/SwapInteractorImpl.kt | 10 +- .../feature/swap/domain/SwapRepository.kt | 1 + .../feature/swap/ui/SwapScreenContent.kt | 7 +- .../feature/swap/ui/SwapSelectTokenScreen.kt | 31 ++-- .../tangem/feature/swap/ui/TransactionCard.kt | 18 +- .../tangem/lib/crypto/TransactionManager.kt | 1 + 283 files changed, 2338 insertions(+), 2730 deletions(-) rename app/src/main/java/com/tangem/tap/common/analytics/{TangemSdkError.kt => TangemSdkErrorMapper.kt} (98%) rename app/src/main/java/com/tangem/tap/common/compose/{Theme.kt => NoRippleTheme.kt} (100%) delete mode 100644 app/src/main/java/com/tangem/tap/common/compose/extensions/Color.kt rename app/src/main/java/com/tangem/tap/common/redux/{FeatureRepositoryStore.kt => FeatureRepositoryProvider.kt} (100%) rename app/src/main/java/com/tangem/tap/common/redux/global/{GlobalMidlleware.kt => GlobalMiddleware.kt} (98%) rename app/src/main/java/com/tangem/tap/domain/configurable/config/{ConfigLoaders.kt => FeaturesLocalLoader.kt} (100%) delete mode 100644 app/src/main/java/com/tangem/tap/domain/tasks/DerivationTask.kt rename app/src/main/java/com/tangem/tap/features/demo/{DemoMiddlewares.kt => DemoOnboardingNoteMiddleware.kt} (98%) delete mode 100644 app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/WcDialog.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/tokens/ui/compose/ExpandedCurrencyItem.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/images/RoundedCornersCropTransformation.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWallet.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWalletDetailsActions.kt delete mode 100644 app/src/test/java/com/tangem/wallet/ExampleUnitTest.kt rename core/datasource/src/main/java/com/tangem/datasource/api/common/{Adapters.kt => BigDecimalAdapter.kt} (100%) rename core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/{RateResponse.kt => RatesResponse.kt} (100%) rename core/ui/src/main/java/com/tangem/core/ui/components/{KeybosardState.kt => Keyboard.kt} (98%) rename core/ui/src/main/java/com/tangem/core/ui/res/{ButtonColor.kt => ButtonColorType.kt} (89%) rename core/ui/src/main/java/com/tangem/core/ui/res/{IconColor.kt => IconColorType.kt} (92%) rename core/ui/src/main/java/com/tangem/core/ui/res/{TextColor.kt => TextColorType.kt} (92%) create mode 100644 core/utils/src/main/java/com/tangem/utils/detekt/UnusedRequiredComponent.kt diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index b300ea02c8..3249b8d1b5 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -20,8 +20,8 @@ import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.NotificationsHandler import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.common.shop.GooglePayService -import com.tangem.tap.common.shop.GooglePayService.Companion.LOAD_PAYMENT_DATA_REQUEST_CODE +import com.tangem.tap.common.shop.googlepay.GooglePayService +import com.tangem.tap.common.shop.googlepay.GooglePayService.Companion.LOAD_PAYMENT_DATA_REQUEST_CODE import com.tangem.tap.common.shop.googlepay.GooglePayUtil.createPaymentsClient import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.userWalletList.UserWalletsListManager @@ -120,7 +120,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac val isScannedBefore = store.state.globalState.scanResponse != null val isOnboardingServiceActive = store.state.globalState.onboardingState.onboardingStarted val shopOpened = store.state.shopState.total != null - if (backStackIsEmpty || (!isOnboardingServiceActive && !isScannedBefore && !shopOpened)) { + @Suppress("ComplexCondition") + if (backStackIsEmpty || !isOnboardingServiceActive && !isScannedBefore && !shopOpened) { if (userWalletsListManager.hasSavedUserWallets) { store.dispatchOnMain(WelcomeAction.HandleDeepLink(intent)) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Welcome)) diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 608ca53cbe..d05473f112 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -173,7 +173,7 @@ class TapApplication : Application(), ImageLoaderFactory { private fun initWithConfigDependency(config: Config) { shopService = TangemShopService(this, config.shopify!!) initAnalytics(this, config) - initFeedbackManager(this, preferencesStorage, config) + initFeedbackManager(this, preferencesStorage) } private fun initAnalytics(application: Application, config: Config) { @@ -196,7 +196,7 @@ class TapApplication : Application(), ImageLoaderFactory { factory.build(Analytics, buildData) } - private fun initFeedbackManager(context: Context, preferencesStorage: PreferencesStorage, config: Config) { + private fun initFeedbackManager(context: Context, preferencesStorage: PreferencesStorage) { fun initAdditionalFeedbackInfo(context: Context): AdditionalFeedbackInfo = AdditionalFeedbackInfo().apply { appVersion = try { val pInfo = context.packageManager.getPackageInfo(context.packageName, 0) diff --git a/app/src/main/java/com/tangem/tap/WaitForMigration.kt b/app/src/main/java/com/tangem/tap/WaitForMigration.kt index 536b9711eb..59c14fb064 100644 --- a/app/src/main/java/com/tangem/tap/WaitForMigration.kt +++ b/app/src/main/java/com/tangem/tap/WaitForMigration.kt @@ -4,4 +4,4 @@ package com.tangem.tap [REDACTED_AUTHOR] */ -val DELAY_SDK_DIALOG_CLOSE = 1400L \ No newline at end of file +const val DELAY_SDK_DIALOG_CLOSE = 1400L \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index 595d9b6fad..60be5dd746 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -56,6 +56,7 @@ class DialogManager : StoreSubscriber { store.unsubscribe(this) } + @Suppress("LongMethod", "ComplexMethod") override fun newState(state: GlobalState) { if (state.dialog == null) { dialog?.dismiss() @@ -73,7 +74,7 @@ class DialogManager : StoreSubscriber { is AppDialog.ScanFailsDialog -> ScanFailsDialog.create(context) is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context) is AppDialog.TestActionsDialog -> TestActionsBottomSheetDialog(state.dialog, context) - is TwinCardsAction.Wallet.ShowInterruptDialog -> CreateWalletInterruptDialog.create(state.dialog, context) + is TwinCardsAction.Wallet.ShowInterruptDialog -> CreateWalletInterruptDialog.create(context) is OnboardingDialog.InterruptOnboarding -> InterruptOnboardingDialog.create(context, state.dialog) is WalletConnectDialog.UnsupportedCard -> SimpleAlertDialog.create( diff --git a/app/src/main/java/com/tangem/tap/common/KeyboardObserver.kt b/app/src/main/java/com/tangem/tap/common/KeyboardObserver.kt index 69581a7a34..47f579ca1b 100644 --- a/app/src/main/java/com/tangem/tap/common/KeyboardObserver.kt +++ b/app/src/main/java/com/tangem/tap/common/KeyboardObserver.kt @@ -44,7 +44,7 @@ class KeyboardObserver(activity: Activity) { onKeyboardListener?.invoke(isShow) } - private fun isSoftKeyChanged() = ((lastWindowHeight - getWindowHeight()).absoluteValue) == getSoftKeyButtonHeight() + private fun isSoftKeyChanged() = (lastWindowHeight - getWindowHeight()).absoluteValue == getSoftKeyButtonHeight() private fun getSoftKeyButtonHeight(): Int { val applicationDisplayHeight = DisplayMetrics().apply { @@ -57,5 +57,4 @@ class KeyboardObserver(activity: Activity) { return realDisplayHeight - applicationDisplayHeight } - } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/TestActions.kt b/app/src/main/java/com/tangem/tap/common/TestActions.kt index 5eb1021886..b787e7a1b3 100644 --- a/app/src/main/java/com/tangem/tap/common/TestActions.kt +++ b/app/src/main/java/com/tangem/tap/common/TestActions.kt @@ -40,9 +40,10 @@ typealias TestAction = Pair Unit> class TestActionsBottomSheetDialog( private val appDialog: AppDialog.TestActionsDialog, - context: Context + context: Context, ) : BottomSheetDialog(context) { + @Suppress("MagicNumber") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventsLogger.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventsLogger.kt index 6b5a1fc3a2..39291d12f6 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventsLogger.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventsLogger.kt @@ -1,8 +1,8 @@ package com.tangem.tap.common.analytics import com.tangem.common.json.MoshiJsonConverter -import com.tangem.analytics.api.ErrorEventLogger -import com.tangem.analytics.api.EventLogger +import com.tangem.core.analytics.api.ErrorEventLogger +import com.tangem.core.analytics.api.EventLogger import timber.log.Timber class AnalyticsEventsLogger( diff --git a/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt b/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkErrorMapper.kt similarity index 98% rename from app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt rename to app/src/main/java/com/tangem/tap/common/analytics/TangemSdkErrorMapper.kt index 491ef8f683..5a157d6372 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkErrorMapper.kt @@ -2,10 +2,11 @@ package com.tangem.tap.common.analytics import com.tangem.common.core.TangemSdkError -object TangemSdk { +object TangemSdkErrorMapper { // This mapping is performed to group errors in FirebaseCrashlytics. // At the moment, the errors in Crashlytics can only be grouped by their place of creation (class and line). + @Suppress("LongMethod", "ComplexMethod") fun map(error: TangemSdkError): TangemSdkError { return when (error) { is TangemSdkError.TagLost -> TangemSdkError.TagLost() @@ -90,7 +91,8 @@ object TangemSdk { is TangemSdkError.BackupFailedEmptyWallets -> TangemSdkError.BackupFailedEmptyWallets() is TangemSdkError.BackupFailedNotEmptyWallets -> TangemSdkError.BackupFailedNotEmptyWallets() is TangemSdkError.NoActiveBackup -> TangemSdkError.NoActiveBackup() - is TangemSdkError.ResetBackupFailedHasBackupedWallets -> TangemSdkError.ResetBackupFailedHasBackupedWallets() + is TangemSdkError.ResetBackupFailedHasBackupedWallets -> + TangemSdkError.ResetBackupFailedHasBackupedWallets() is TangemSdkError.BackupServiceInvalidState -> TangemSdkError.BackupServiceInvalidState() is TangemSdkError.NoBackupCardForIndex -> TangemSdkError.NoBackupCardForIndex() is TangemSdkError.EmptyBackupCards -> TangemSdkError.EmptyBackupCards() diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt index 52fa450434..21a9dbbaf6 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt @@ -53,7 +53,9 @@ class BasicTopUpEventConverter( } } -private fun AnalyticsParam.CardBalanceState.Companion.from(walletsData: List): AnalyticsParam.CardBalanceState { +private fun AnalyticsParam.CardBalanceState.Companion.from( + walletsData: List, +): AnalyticsParam.CardBalanceState { val totalCryptoAmount = walletsData.calculateTotalCryptoAmount() return when { totalCryptoAmount.isZero() -> AnalyticsParam.CardBalanceState.Empty diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt index bad36d3f0d..2e5a925560 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt @@ -8,7 +8,10 @@ sealed class AnalyticsParam { class Currency(currency: com.tangem.tap.features.wallet.models.Currency) : CurrencyType(currency.currencySymbol) class Blockchain(blockchain: com.tangem.blockchain.common.Blockchain) : CurrencyType(blockchain.currency) class Token(token: com.tangem.blockchain.common.Token) : CurrencyType(token.symbol) - class FiatCurrency(fiatCurrency: com.tangem.tap.common.entities.FiatCurrency) : CurrencyType(fiatCurrency.symbol) + class FiatCurrency( + fiatCurrency: com.tangem.tap.common.entities.FiatCurrency, + ) : CurrencyType(fiatCurrency.symbol) + class Amount(amount: com.tangem.blockchain.common.Amount) : CurrencyType(amount.currencySymbol) } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeClient.kt index 80f8359025..ddb27d18ff 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeClient.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeClient.kt @@ -4,7 +4,7 @@ import android.app.Application import com.amplitude.api.Amplitude import com.amplitude.api.AmplitudeClient import com.tangem.common.Converter -import com.tangem.analytics.api.EventLogger +import com.tangem.core.analytics.api.EventLogger import org.json.JSONObject /** diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerClient.kt index b8271cc6af..40cee4e698 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerClient.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerClient.kt @@ -2,7 +2,7 @@ package com.tangem.tap.common.analytics.handlers.appsFlyer import android.content.Context import com.appsflyer.AppsFlyerLib -import com.tangem.analytics.api.EventLogger +import com.tangem.core.analytics.api.EventLogger /** [REDACTED_AUTHOR] diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt index 3db2ed90bc..33751b5e0a 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt @@ -6,8 +6,8 @@ import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.analytics.ktx.analytics import com.google.firebase.crashlytics.ktx.crashlytics import com.google.firebase.ktx.Firebase -import com.tangem.analytics.api.ErrorEventLogger -import com.tangem.analytics.api.EventLogger +import com.tangem.core.analytics.api.ErrorEventLogger +import com.tangem.core.analytics.api.EventLogger /** [REDACTED_AUTHOR] diff --git a/app/src/main/java/com/tangem/tap/common/compose/AutoSizeText.kt b/app/src/main/java/com/tangem/tap/common/compose/AutoSizeText.kt index 324fd3877e..414473177d 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/AutoSizeText.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/AutoSizeText.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.sp +@Suppress("MagicNumber") @Composable fun TextAutoSize( modifier: Modifier = Modifier, @@ -46,7 +47,7 @@ fun TextAutoSize( } else { readyToDraw.value = true } - } + }, ) } diff --git a/app/src/main/java/com/tangem/tap/common/compose/Theme.kt b/app/src/main/java/com/tangem/tap/common/compose/NoRippleTheme.kt similarity index 100% rename from app/src/main/java/com/tangem/tap/common/compose/Theme.kt rename to app/src/main/java/com/tangem/tap/common/compose/NoRippleTheme.kt diff --git a/app/src/main/java/com/tangem/tap/common/compose/OutlinedSpinner.kt b/app/src/main/java/com/tangem/tap/common/compose/OutlinedSpinner.kt index 3792119da9..4c2a91e791 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/OutlinedSpinner.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/OutlinedSpinner.kt @@ -17,6 +17,7 @@ import com.tangem.tap.common.extensions.ValueCallback /** [REDACTED_AUTHOR] */ +@Suppress("MagicNumber") @OptIn(ExperimentalMaterialApi::class) @Composable fun OutlinedSpinner( @@ -95,7 +96,7 @@ class ClosePopupTrigger { @Preview @Composable fun TestSpinnerPreview() { - Scaffold() { + Scaffold { OutlinedSpinner( label = "Blockchain name", itemList = listOf(Blockchain.values()), 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 deacbaf05f..ecffb1c422 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 @@ -78,6 +78,7 @@ fun OutlinedTextFieldWidget( } } +@Suppress("LongMethod", "NestedBlockDepth", "MagicNumber", "MaxLineLength") @Composable private fun OutlinedProgressTextField( modifier: Modifier = Modifier, @@ -127,7 +128,7 @@ private fun OutlinedProgressTextField( logger.log("$isNotUserInput: внешние данные ОДИНАКОВЫ с данными в поле") } else { logger.log("$isNotUserInput: внешние данные РАЗЛИЧАЮТСЯ с данными в поле") - if ((textDebouncer.emittedValue != textDebouncer.debounced) || textDebouncer.emitsCountBeforeDebounce > 0) { + if (textDebouncer.emittedValue != textDebouncer.debounced || textDebouncer.emitsCountBeforeDebounce > 0) { logger.log("$isNotUserInput: пользователь ВВОДИТ данные -> внешние данные игнорируем, ждем RECOMPOSE") } else { logger.log("$isNotUserInput: пользователь НЕ вводит данные -> пытаемся обработать внешние данные") diff --git a/app/src/main/java/com/tangem/tap/common/compose/PinCodeWidget.kt b/app/src/main/java/com/tangem/tap/common/compose/PinCodeWidget.kt index ad7bbe794b..f7dad88a0d 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/PinCodeWidget.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/PinCodeWidget.kt @@ -125,10 +125,6 @@ private fun PinElement( } } -@Composable -private fun HiddenInputField() { -} - private fun createPinSymbolsList(size: Int, text: String): List = List(size) { try { text[it].toString() diff --git a/app/src/main/java/com/tangem/tap/common/compose/extensions/Color.kt b/app/src/main/java/com/tangem/tap/common/compose/extensions/Color.kt deleted file mode 100644 index d25bc9e26b..0000000000 --- a/app/src/main/java/com/tangem/tap/common/compose/extensions/Color.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.tap.common.compose.extensions - -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.toArgb -import androidx.core.graphics.alpha -import androidx.core.graphics.blue -import androidx.core.graphics.green -import androidx.core.graphics.red - -/** -[REDACTED_AUTHOR] - */ -fun Color.toAndroidGraphicsColor(): Int { - val argb = this.toArgb() - return android.graphics.Color.argb(argb.alpha, argb.red, argb.green, argb.blue) -} - -fun Color.parse(hexColor: String): Color { - return Color(hexColor.removePrefix("#").toInt(16)) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/compose/extensions/ImageBitmap.kt b/app/src/main/java/com/tangem/tap/common/compose/extensions/ImageBitmap.kt index 1692b4c51f..99911d9e6f 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/extensions/ImageBitmap.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/extensions/ImageBitmap.kt @@ -13,7 +13,8 @@ import androidx.core.graphics.drawable.toBitmap */ @Composable fun asImageBitmap(@DrawableRes drawableId: Int): ImageBitmap { - val drawable = AppCompatResources.getDrawable(LocalContext.current, drawableId) - ?: throw NullPointerException() + val drawable = requireNotNull(AppCompatResources.getDrawable(LocalContext.current, drawableId)) { + "drawable is null" + } return drawable.toBitmap().asImageBitmap() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/BackupService.kt b/app/src/main/java/com/tangem/tap/common/extensions/BackupService.kt index abe1863c6d..140f0946b4 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/BackupService.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/BackupService.kt @@ -6,8 +6,7 @@ import com.tangem.operations.backup.BackupService /** [REDACTED_AUTHOR] */ +@Suppress("MagicNumber") fun BackupService.primaryCardIsSaltPayVisa(): Boolean { - return primaryCardId?.slice(0..3)?.let { - SaltPayWorkaround.isVisaBatchId(it) - } ?: false + return primaryCardId?.slice(0..3)?.let(SaltPayWorkaround::isVisaBatchId) ?: false } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Bitmap.kt b/app/src/main/java/com/tangem/tap/common/extensions/Bitmap.kt index e8b3891ce9..80e756c21c 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Bitmap.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Bitmap.kt @@ -4,12 +4,14 @@ import android.graphics.Bitmap import android.graphics.BitmapFactory import java.io.ByteArrayOutputStream +@Suppress("MagicNumber") fun Bitmap.toByteArray(): ByteArray { val stream = ByteArrayOutputStream() this.compress(Bitmap.CompressFormat.JPEG, 20, stream) return stream.toByteArray() } +@Suppress("MagicNumber") fun ByteArray.toBitmap(): Bitmap { return BitmapFactory.decodeByteArray(this, 0, this.size) } \ No newline at end of file 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 5e10f11f1d..133869ee22 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 @@ -5,16 +5,17 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.remove import com.tangem.wallet.R +@Suppress("ComplexMethod") @DrawableRes fun Blockchain.getGreyedOutIconRes(): Int { return when (this) { Blockchain.Arbitrum, Blockchain.ArbitrumTestnet -> R.drawable.ic_arbitrum_no_color // Blockchain.Ducatus -> R.drawable.ic_ducatus - Blockchain.Bitcoin, Blockchain.BitcoinTestnet,-> R.drawable.ic_bitcoin_no_color + Blockchain.Bitcoin, Blockchain.BitcoinTestnet -> R.drawable.ic_bitcoin_no_color Blockchain.BitcoinCash -> R.drawable.ic_bitcoin_cash_no_color Blockchain.Litecoin -> R.drawable.ic_litecoin_no_color - Blockchain.Ethereum, Blockchain.EthereumTestnet, -> R.drawable.ic_eth_no_color - Blockchain.EthereumClassic, Blockchain.EthereumClassicTestnet, -> R.drawable.ic_eth_no_color + Blockchain.Ethereum, Blockchain.EthereumTestnet -> R.drawable.ic_eth_no_color + Blockchain.EthereumClassic, Blockchain.EthereumClassicTestnet -> R.drawable.ic_eth_no_color Blockchain.RSK -> R.drawable.ic_rsk_no_color Blockchain.Cardano, Blockchain.CardanoShelley -> R.drawable.ic_cardano_no_color Blockchain.Tezos -> R.drawable.ic_tezos_no_color @@ -24,7 +25,8 @@ fun Blockchain.getGreyedOutIconRes(): Int { Blockchain.Polygon, Blockchain.PolygonTestnet -> R.drawable.ic_polygon_no_color 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.BSC, Blockchain.BSCTestnet, Blockchain.Binance, Blockchain.BinanceTestnet -> + R.drawable.ic_bsc_no_color Blockchain.Dogecoin -> R.drawable.ic_dogecoin_no_color Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.ic_tron_no_color Blockchain.Gnosis -> R.drawable.ic_gnosis_no_color @@ -49,4 +51,4 @@ fun Blockchain.getNetworkName(): String { } val Blockchain.fullNameWithoutTestnet -get() = this.fullName.remove(" Testnet") \ No newline at end of file + get() = this.fullName.remove(" Testnet") \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index eaae03f471..6cb707b071 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -1,12 +1,10 @@ package com.tangem.tap.common.extensions import android.os.Bundle -import androidx.activity.OnBackPressedCallback import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentManager -import com.tangem.common.extensions.VoidCallback import com.tangem.feature.referral.ReferralFragment import com.tangem.feature.swap.presentation.SwapFragment import com.tangem.tap.common.redux.navigation.AppScreen @@ -84,21 +82,15 @@ fun FragmentActivity.getPreviousScreen(): AppScreen? { } else { 0 } - val tag = if (indexOfLastFragment < this.supportFragmentManager.backStackEntryCount) + val tag = if (indexOfLastFragment < this.supportFragmentManager.backStackEntryCount) { this.supportFragmentManager.getBackStackEntryAt(indexOfLastFragment).name - else null + } else { + null + } return tag?.let { AppScreen.valueOf(tag) } } -fun FragmentActivity.addOnBackPressedDispatcher( - isEnabled: Boolean = true, - onBackPressed: VoidCallback, -): OnBackPressedCallback = (object : OnBackPressedCallback(isEnabled) { - override fun handleOnBackPressed() { - onBackPressed() - } -}).also { this.onBackPressedDispatcher.addCallback(it) } - +@Suppress("ComplexMethod") private fun fragmentFactory(screen: AppScreen): Fragment { return when (screen) { AppScreen.Home -> HomeFragment() 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 3f8e39c384..f3a7e1ca1c 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 @@ -13,7 +13,7 @@ import java.util.* //todo move extensions to utils 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() @@ -25,9 +25,10 @@ fun BigDecimal.toFormattedString( return df.format(this) } +@Suppress("MagicNumber") fun BigDecimal.toFormattedCurrencyString( decimals: Int, currency: String, roundingMode: RoundingMode = RoundingMode.DOWN, - limitNumberOfDecimals: Boolean = true + limitNumberOfDecimals: Boolean = true, ): String { val decimalsForRounding = if (limitNumberOfDecimals) { if (decimals > 8) 8 else decimals @@ -35,13 +36,13 @@ fun BigDecimal.toFormattedCurrencyString( decimals } val formattedAmount = this.toFormattedString( - decimals = decimalsForRounding, roundingMode = roundingMode + decimals = decimalsForRounding, roundingMode = roundingMode, ) return "$formattedAmount $currency" } fun BigDecimal.toFiatRateString( - fiatCurrencyName: String + fiatCurrencyName: String, ): String { val value = this .setScale(2, RoundingMode.HALF_UP) @@ -52,7 +53,7 @@ fun BigDecimal.toFiatRateString( fun BigDecimal.toFiatString( rateValue: BigDecimal, fiatCurrencyName: String, - formatWithSpaces: Boolean = false + formatWithSpaces: Boolean = false, ): String { val fiatValue = rateValue.multiply(this) return fiatValue.toFormattedFiatValue(fiatCurrencyName, formatWithSpaces) @@ -65,7 +66,7 @@ fun BigDecimal.toFiatValue(rateValue: BigDecimal): BigDecimal { fun BigDecimal.toFormattedFiatValue( fiatCurrencyName: String, - formatWithSpaces: Boolean = false + formatWithSpaces: Boolean = false, ): String { val fiatValue = this.setScale(2, RoundingMode.HALF_UP) .let { if (formatWithSpaces) it.formatWithSpaces() else it } @@ -82,9 +83,7 @@ fun BigDecimal.scaleToFiat(applyPrecision: Boolean = false): BigDecimal { if (this.isZero()) return this val scaledFiat = this.setScale(2, RoundingMode.DOWN) - return if (scaledFiat.isZero() && applyPrecision) this.setPrecision(1) - else scaledFiat - + return if (scaledFiat.isZero() && applyPrecision) this.setPrecision(1) else scaledFiat } fun BigDecimal.setPrecision(precision: Int, roundingMode: RoundingMode = RoundingMode.DOWN): BigDecimal { @@ -109,7 +108,7 @@ fun BigDecimal.isLessThanOrEqual(value: BigDecimal): Boolean { fun BigDecimal.formatAmountAsSpannedString( currencySymbol: String, - reminderPartSizeProportion: Float = 0.7f + reminderPartSizeProportion: Float = 0.7f, ): SpannedString { val amount = this .setScale(2, RoundingMode.HALF_UP) @@ -125,11 +124,12 @@ fun BigDecimal.formatAmountAsSpannedString( append( "$reminder $currencySymbol", RelativeSizeSpan(reminderPartSizeProportion), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE, ) } } +@Suppress("MagicNumber") fun BigDecimal.formatWithSpaces(): String { val str = this.toString() var integerStr = str.substringBefore('.') diff --git a/app/src/main/java/com/tangem/tap/common/extensions/String.kt b/app/src/main/java/com/tangem/tap/common/extensions/String.kt index 021cc3a7f2..3c4125d422 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/String.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/String.kt @@ -29,7 +29,7 @@ fun String.colorSegment( context: Context, color: Int, startIndex: Int = 0, - endIndex: Int = this.length + endIndex: Int = this.length, ): Spannable { return this.toSpannable() .also { spannable -> @@ -37,11 +37,12 @@ fun String.colorSegment( ForegroundColorSpan(ContextCompat.getColor(context, color)), startIndex, endIndex, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE, ) } } +@Suppress("MagicNumber") fun String.toQrCode(): Bitmap { val hintMap = Hashtable() hintMap[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.M // H = 30% damage diff --git a/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt b/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt index 48ee67afc7..80daecc1d9 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt @@ -23,8 +23,7 @@ fun EditText.update(text: String?) { this.setText(text) if (!isFocused || textLength == 0) return - if (cursorPosition == 0) setSelection(textLength) - else setSelection(cursorPosition) + if (cursorPosition == 0) setSelection(textLength) else setSelection(cursorPosition) } fun EditText.setOnImeActionListener(action: Int, handler: (EditText) -> Unit) { @@ -56,13 +55,4 @@ fun TextInputLayout.enableError(enable: Boolean, errorMessage: String? = null) { error = null isErrorEnabled = false } -} - -fun TextView.isEllipsized(): Boolean { - val layout = this.layout - if (layout != null) { - val lines: Int = layout.lineCount - return (lines > 0) && (layout.getEllipsisCount(lines - 1) > 0) - } - return false } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Token.kt b/app/src/main/java/com/tangem/tap/common/extensions/Token.kt index 59c918d736..7acbdfd9fb 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Token.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Token.kt @@ -6,19 +6,23 @@ import androidx.core.graphics.luminance import androidx.core.graphics.toColorInt import com.tangem.blockchain.common.Token +@Suppress("MagicNumber") @ColorInt fun Token.getColor(isTestnet: Boolean = false): Int { val defaultColor = "#C7C7CC".toColorInt() // equivalent to R.color.lightGray4 - return if (isTestnet) - defaultColor - else try { - ("#" + this.contractAddress.subSequence(2..7).toString()).toColorInt() - } catch (exception: Exception) { + return if (isTestnet) { defaultColor + } else { + try { + ("#" + this.contractAddress.subSequence(2..7).toString()).toColorInt() + } catch (exception: Exception) { + defaultColor + } } } +@Suppress("MagicNumber") @ColorInt fun Token.getTextColor(isTestnet: Boolean = false): Int = when { isTestnet -> Color.WHITE diff --git a/app/src/main/java/com/tangem/tap/common/extensions/UI.kt b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt index 9e58542ecb..b7743af30d 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/UI.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt @@ -1,16 +1,14 @@ +@file:Suppress("TooManyFunctions") + package com.tangem.tap.common.extensions import android.app.Activity -import android.content.ActivityNotFoundException import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.content.ContextWrapper import android.content.Intent -import android.content.res.Resources import android.graphics.drawable.Drawable -import android.os.Build -import android.os.Bundle import android.util.DisplayMetrics import android.util.TypedValue import android.view.View @@ -25,11 +23,6 @@ import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.fragment.app.Fragment import com.google.android.material.card.MaterialCardView -import com.tangem.common.extensions.VoidCallback - -fun Fragment.getDrawable(@DrawableRes drawableResId: Int): Drawable? { - return ContextCompat.getDrawable(requireContext(), drawableResId) -} fun Context.getDrawableCompat(@DrawableRes drawableResId: Int): Drawable? { return ContextCompat.getDrawable(this, drawableResId) @@ -62,17 +55,8 @@ fun View.getQuantityString(@PluralsRes id: Int, quantity: Int): String { return context.resources.getQuantityString(id, quantity, quantity) } -fun View.getResourceName(): String { - return try { - resources.getResourceEntryName(id) - } catch (ex: Resources.NotFoundException) { - "Not found" - } -} - fun View.show(show: Boolean, invokeBeforeStateChanged: (() -> Unit)? = null) { - return if (show) this.show(invokeBeforeStateChanged) - else this.hide(invokeBeforeStateChanged) + return if (show) this.show(invokeBeforeStateChanged) else this.hide(invokeBeforeStateChanged) } fun View.show(invokeBeforeStateChanged: (() -> Unit)? = null) { @@ -111,12 +95,6 @@ fun Context.pixelsToDp(pixels: Int): Int { .toInt() } -fun Context.dpToPixels(dp: Float): Float = - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, this.resources.displayMetrics) - -fun Context.pixelsToDp(pixels: Float): Float = - (pixels / (resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)) - tailrec fun Context?.getActivity(): Activity? = this as? Activity ?: (this as? ContextWrapper)?.baseContext?.getActivity() @@ -136,19 +114,6 @@ fun MaterialCardView.setMargins( this.layoutParams = params } -fun Activity.setSystemBarTextColor(setTextDark: Boolean) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - val flags = this.window.decorView.systemUiVisibility - // Update the SystemUiVisibility dependening on whether we want a Light or Dark theme. - this.window.decorView.systemUiVisibility = - if (setTextDark) { - flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv() - } else { - flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR - } - } -} - fun View.hideKeyboard() { val inputMethodManager = context.getSystemService(android.content.Context.INPUT_METHOD_SERVICE) as? InputMethodManager @@ -185,21 +150,6 @@ fun Fragment.shareText(text: String) { requireContext().shareText(text) } -fun Context.safeStartActivity( - intent: Intent, - options: Bundle? = null, - fallback: ((ActivityNotFoundException) -> Unit)? = null, - finally: VoidCallback? = null, -) { - try { - this.startActivity(intent, options) - } catch (ex: ActivityNotFoundException) { - fallback?.invoke(ex) - } finally { - finally?.invoke() - } -} - fun View.getString(resId: Int, vararg formatArgs: Any?): String { return context.getString(resId, *formatArgs) } diff --git a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt index cda489f536..97997ba97f 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt @@ -21,6 +21,7 @@ import timber.log.Timber /** [REDACTED_AUTHOR] */ +@Suppress("MagicNumber") suspend fun WalletManager.safeUpdate(): Result = try { val scanResponse = store.state.globalState.scanResponse @@ -72,8 +73,7 @@ fun WalletManager?.getAddressData(): AddressData? { val wallet = this?.wallet ?: return null val addressDataList = wallet.createAddressesData() - return if (addressDataList.isEmpty()) null - else addressDataList[0] + return if (addressDataList.isEmpty()) null else addressDataList[0] } fun WalletManager.Companion.stub(): T { diff --git a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt index 589597238f..c6f5bafd0a 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt @@ -48,6 +48,9 @@ class AdditionalFeedbackInfo { var fee: String = "" var token: String = "" + private val Address.name: String + get() = type.javaClass.simpleName + fun setCardInfo(data: ScanResponse) { cardId = data.card.cardId cardBlockchain = data.walletData?.blockchain ?: "" @@ -108,6 +111,7 @@ class AdditionalFeedbackInfo { } } + @Suppress("MagicNumber") private fun Wallet.formatAddressWith(with: String, mapAddress: (Address) -> String): String { return if (addresses.size == 1) { getExploreUrl(address) @@ -117,7 +121,4 @@ class AdditionalFeedbackInfo { .joinToString("\n") } } - - private val Address.name: String - get() = type.javaClass.simpleName } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackData.kt b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackData.kt index 8a426cccfc..452eb173aa 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackData.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackData.kt @@ -15,6 +15,7 @@ interface FeedbackData { fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String + @Suppress("MagicNumber") fun joinTogether(context: Context, infoHolder: AdditionalFeedbackInfo): String { return StringBuilder().apply { append(context.getString(mainMessageResId)) @@ -42,6 +43,7 @@ class ScanFailsEmail : FeedbackData { override val subjectResId: Int = R.string.feedback_subject_scan_failed override val mainMessageResId: Int = R.string.feedback_preface_scan_failed + @Suppress("MagicNumber") override fun joinTogether(context: Context, infoHolder: AdditionalFeedbackInfo): String = StringBuilder().apply { append(context.getString(mainMessageResId)) breakLine(4) diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt index 1999c35855..98a06327bb 100644 --- a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt +++ b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt @@ -17,13 +17,13 @@ class TestLeapfrogFragment : Fragment(R.layout.test_leapfrog_fragment) { private lateinit var twinsCardWidget: TwinsCardWidget private val binding: TestLeapfrogFragmentBinding by viewBinding(TestLeapfrogFragmentBinding::bind) - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val inflater = TransitionInflater.from(requireContext()) exitTransition = inflater.inflateTransition(R.transition.fade) } + @Suppress("MagicNumber") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/app/src/main/java/com/tangem/tap/common/qrCodeScan/ScanQrCodeActivity.kt b/app/src/main/java/com/tangem/tap/common/qrCodeScan/ScanQrCodeActivity.kt index da69eed9ae..4a86caa20d 100644 --- a/app/src/main/java/com/tangem/tap/common/qrCodeScan/ScanQrCodeActivity.kt +++ b/app/src/main/java/com/tangem/tap/common/qrCodeScan/ScanQrCodeActivity.kt @@ -16,11 +16,6 @@ import me.dm7.barcodescanner.zxing.ZXingScannerView [REDACTED_AUTHOR] */ class ScanQrCodeActivity : AppCompatActivity(), ZXingScannerView.ResultHandler { - companion object { - val SCAN_QR_REQUEST_CODE = 1001 - val SCAN_RESULT = "scanResult" - } - private lateinit var mScannerView: ZXingScannerView override fun onCreate(state: Bundle?) { @@ -29,7 +24,6 @@ class ScanQrCodeActivity : AppCompatActivity(), ZXingScannerView.ResultHandler { setContentView(mScannerView) if (!permissionIsGranted()) requestPermission() - } override fun onResume() { @@ -66,4 +60,9 @@ class ScanQrCodeActivity : AppCompatActivity(), ZXingScannerView.ResultHandler { private fun requestPermission() { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), PERMISSION_REQUEST_CODE) } + + companion object { + const val SCAN_QR_REQUEST_CODE = 1001 + const val SCAN_RESULT = "scanResult" + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/FeatureRepositoryStore.kt b/app/src/main/java/com/tangem/tap/common/redux/FeatureRepositoryProvider.kt similarity index 100% rename from app/src/main/java/com/tangem/tap/common/redux/FeatureRepositoryStore.kt rename to app/src/main/java/com/tangem/tap/common/redux/FeatureRepositoryProvider.kt diff --git a/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt index a32545d675..4a7cb7b8f8 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt @@ -88,6 +88,7 @@ val notificationsMiddleware: Middleware = { dispatch, state -> } } +@Suppress("ComplexMethod") private fun handleNotificationAction(action: Action) { if (action is Debug && !BuildConfig.DEBUG) return 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/GlobalMiddleware.kt similarity index 98% rename from app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt rename to app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt index 46ea27926e..c6c51659e2 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/GlobalMiddleware.kt @@ -30,10 +30,8 @@ import org.rekotlin.DispatchFunction import org.rekotlin.Middleware import java.util.* -class GlobalMiddleware { - companion object { - val handler = globalMiddlewareHandler - } +object GlobalMiddleware { + val handler = globalMiddlewareHandler } private val globalMiddlewareHandler: Middleware = { dispatch, appState -> @@ -45,6 +43,7 @@ private val globalMiddlewareHandler: Middleware = { dispatch, appState } } +@Suppress("LongMethod", "ComplexMethod") private fun handleAction(action: Action, appState: () -> AppState?, dispatch: DispatchFunction) { when (action) { is GlobalAction.ScanFailsCounter.ChooseBehavior -> { 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 4dc8390a9c..07bf342432 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 @@ -9,6 +9,7 @@ import com.tangem.tap.preferencesStorage import com.tangem.tap.proxy.AppStateHolder import org.rekotlin.Action +@Suppress("LongMethod", "ComplexMethod") fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolder): GlobalState { if (action !is GlobalAction) return state.globalState diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/FragmentShareTransition.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/FragmentShareTransition.kt index 69547d020f..ab7aa07259 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/FragmentShareTransition.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/FragmentShareTransition.kt @@ -10,7 +10,7 @@ import java.lang.ref.WeakReference data class FragmentShareTransition( val shareElements: List, val enterTransitionSet: TransitionSet, - val exitTransitionSet: TransitionSet + val exitTransitionSet: TransitionSet, ) /** @@ -24,12 +24,12 @@ class ShareElement(view: View, name: String? = null) { init { name?.let { view.transitionName = it } elementName = view.transitionName - ?: throw UnsupportedOperationException("ShareElement require the name") + ?: throw UnsupportedOperationException("ShareElement require the name") wView = WeakReference(view) } companion object { - val imvFrontCard = "imv_front_card" - val imvBackCard = "imv_back_card" + const val imvFrontCard = "imv_front_card" + const val imvBackCard = "imv_back_card" } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt index b6f7522586..e338a7fb4c 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt @@ -4,10 +4,8 @@ import com.tangem.tap.common.extensions.getPreviousScreen import com.tangem.tap.common.redux.AppState import org.rekotlin.Action -class NavigationReducer { - companion object { - fun reduce(action: Action, state: AppState): NavigationState = internalReduce(action, state) - } +object NavigationReducer { + fun reduce(action: Action, state: AppState): NavigationState = internalReduce(action, state) } private fun internalReduce(action: Action, state: AppState): NavigationState { diff --git a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt index 47951bfb84..fb010875c5 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt @@ -10,6 +10,7 @@ import com.tangem.tap.common.extensions.filterNotNull import com.tangem.tap.common.shop.data.ProductType import com.tangem.tap.common.shop.data.TangemProduct import com.tangem.tap.common.shop.data.TotalSum +import com.tangem.tap.common.shop.googlepay.GooglePayService import com.tangem.tap.common.shop.shopify.ShopifyService import com.tangem.tap.common.shop.shopify.ShopifyShop import com.tangem.tap.common.shop.shopify.data.CheckoutItem diff --git a/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayService.kt b/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayService.kt index 13c3913ebe..37201242d9 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayService.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayService.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.common.shop +package com.tangem.tap.common.shop.googlepay import android.app.Activity import android.app.Activity.RESULT_CANCELED @@ -6,9 +6,12 @@ import android.app.Activity.RESULT_OK import android.content.Intent import android.util.Log import com.google.android.gms.common.api.ApiException -import com.google.android.gms.wallet.* +import com.google.android.gms.wallet.AutoResolveHelper +import com.google.android.gms.wallet.IsReadyToPayRequest +import com.google.android.gms.wallet.PaymentData +import com.google.android.gms.wallet.PaymentDataRequest +import com.google.android.gms.wallet.PaymentsClient import com.tangem.common.core.TangemSdkError -import com.tangem.tap.common.shop.googlepay.GooglePayUtil import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.json.JSONException @@ -48,7 +51,7 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a totalPriceCents, currencyCode = currencyCode, countryCode = "RU", - merchantID = merchantID + merchantID = merchantID, ) if (paymentDataRequestJson == null) { Timber.e("RequestPayment: can't fetch payment data request") @@ -57,7 +60,7 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a val request = PaymentDataRequest.fromJson(paymentDataRequestJson.toString()) AutoResolveHelper.resolveTask( - paymentsClient.loadPaymentData(request), activity, LOAD_PAYMENT_DATA_REQUEST_CODE + paymentsClient.loadPaymentData(request), activity, LOAD_PAYMENT_DATA_REQUEST_CODE, ) } @@ -81,7 +84,6 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a } else { Result.failure(Exception("$statusCode")) } - } else -> Result.failure(Exception("Unknown Status")) } @@ -117,7 +119,6 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a .getString("token") return GooglePayResponse(address, token) - } catch (e: JSONException) { Log.e("handlePaymentSuccess", "Error: " + e.toString()) } @@ -144,5 +145,5 @@ data class Address( val address3: String, val locality: String, val administrativeArea: String, - val sortingCode: String + val sortingCode: String, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayUtil.kt b/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayUtil.kt index 610ab10c9d..c27680eef3 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayUtil.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayUtil.kt @@ -14,20 +14,7 @@ object GooglePayUtil { put("apiVersionMinor", 0) } - private fun gatewayTokenizationSpecification(merchantID: String): JSONObject { - return JSONObject().apply { - put("type", "PAYMENT_GATEWAY") - put( - "parameters", JSONObject( - mapOf( - "gateway" to "shopify", - "gatewayMerchantId" to merchantID - ) - ) - ) - } - } - + @Suppress("MagicNumber") private val allowedCardNetworks = JSONArray( listOf( "AMEX", @@ -35,17 +22,34 @@ object GooglePayUtil { "INTERAC", "JCB", "MASTERCARD", - "VISA" - ) + "VISA", + ), ) private val allowedCardAuthMethods = JSONArray( listOf( "PAN_ONLY", - "CRYPTOGRAM_3DS" - ) + "CRYPTOGRAM_3DS", + ), ) + private val merchantInfo: JSONObject = JSONObject().put("merchantName", "Example Merchant") + + private fun gatewayTokenizationSpecification(merchantID: String): JSONObject { + return JSONObject().apply { + put("type", "PAYMENT_GATEWAY") + put( + "parameters", + JSONObject( + mapOf( + "gateway" to "shopify", + "gatewayMerchantId" to merchantID, + ), + ), + ) + } + } + private fun baseCardPaymentMethod(): JSONObject { return JSONObject().apply { @@ -53,9 +57,12 @@ object GooglePayUtil { put("allowedAuthMethods", allowedCardAuthMethods) put("allowedCardNetworks", allowedCardNetworks) put("billingAddressRequired", true) - put("billingAddressParameters", JSONObject().apply { - put("format", "FULL") - }) + put( + "billingAddressParameters", + JSONObject().apply { + put("format", "FULL") + }, + ) } put("type", "CARD") @@ -83,7 +90,6 @@ object GooglePayUtil { baseRequest.apply { put("allowedPaymentMethods", JSONArray().put(baseCardPaymentMethod())) } - } catch (e: JSONException) { null } @@ -92,7 +98,7 @@ object GooglePayUtil { private fun getTransactionInfo( price: String, countryCode: String, - currencyCode: String + currencyCode: String, ): JSONObject { return JSONObject().apply { put("totalPrice", price) @@ -102,15 +108,11 @@ object GooglePayUtil { } } - private val merchantInfo: JSONObject = - JSONObject().put("merchantName", "Example Merchant") - - fun getPaymentDataRequest( price: String, countryCode: String, currencyCode: String, - merchantID: String + merchantID: String, ): JSONObject? { try { return baseRequest.apply { @@ -133,5 +135,4 @@ object GooglePayUtil { } } - const val PAYMENTS_ENVIRONMENT = WalletConstants.ENVIRONMENT_TEST \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/shop/shopify/ShopifyService.kt b/app/src/main/java/com/tangem/tap/common/shop/shopify/ShopifyService.kt index a33fb82b84..4998c89da9 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/shopify/ShopifyService.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/shopify/ShopifyService.kt @@ -16,11 +16,10 @@ import java.util.concurrent.TimeUnit import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine - +@Suppress("LargeClass") class ShopifyService(private val application: Application, val shop: ShopifyShop) { val client: GraphClient by lazy { initClient() } - suspend fun getShopName(): Result { val query = query { rootQuery: QueryRootQuery -> rootQuery @@ -40,16 +39,15 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop } } + @Suppress("MagicNumber") suspend fun getProducts(collectionTitleFilter: String? = null): Result> { val filter = collectionTitleFilter?.let { "title:\"$it\"" } val query = query { rootQuery: QueryRootQuery -> - rootQuery - .collections( - { arg -> arg.first(250).query(filter) }, - ) { collectionConnectionQuery -> - collectionConnectionQuery.collectionFieldsFragment() - } + rootQuery.collections( + { arg -> arg.first(250).query(filter) }, + CollectionConnectionQuery::collectionFieldsFragment, + ) } return when (val result = queryAsync(query)) { is GraphCallResult.Success -> { @@ -78,7 +76,7 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop } } val retryHandler = RetryHandler.build( - 1, TimeUnit.SECONDS + 1, TimeUnit.SECONDS, ) { this.retryWhen { result -> when (result) { @@ -100,7 +98,6 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop } else { Result.failure(ShopifyError.Unknown) } - } is GraphCallResult.Failure -> { Result.failure(result.error) @@ -110,10 +107,9 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop suspend fun createCheckout( checkoutItems: List, - checkoutID: ID? = null + checkoutID: ID? = null, ): Result { - val storefrontLineItems: MutableList = checkoutItems .map { CheckoutLineItemInput(it.quantity, it.id) }.toMutableList() @@ -121,13 +117,13 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop mutation { mutationQuery: MutationQuery -> mutationQuery .checkoutLineItemsReplace( - storefrontLineItems, checkoutID + storefrontLineItems, checkoutID, ) { payloadQuery: CheckoutLineItemsReplacePayloadQuery -> payloadQuery .checkout { checkoutQuery: CheckoutQuery -> checkoutQuery.checkoutFieldsFragment() } - .userErrors() { userErrorQuery: CheckoutUserErrorQuery -> + .userErrors { userErrorQuery: CheckoutUserErrorQuery -> userErrorQuery .field() .message() @@ -137,18 +133,18 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop } else { val input = CheckoutCreateInput() .setLineItemsInput( - Input.value(storefrontLineItems) + Input.value(storefrontLineItems), ) mutation { mutationQuery: MutationQuery -> mutationQuery .checkoutCreate( - input + input, ) { payloadQuery: CheckoutCreatePayloadQuery -> payloadQuery .checkout { checkoutQuery: CheckoutQuery -> checkoutQuery.checkoutFieldsFragment() } - .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery -> + .checkoutUserErrors { userErrorQuery: CheckoutUserErrorQuery -> userErrorQuery .field() .message() @@ -163,13 +159,13 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop val query = mutation { mutationQuery: MutationQuery -> mutationQuery .checkoutDiscountCodeApplyV2( - discountCode, checkoutID + discountCode, checkoutID, ) { payloadQuery: CheckoutDiscountCodeApplyV2PayloadQuery -> payloadQuery .checkout { checkoutQuery: CheckoutQuery -> checkoutQuery.checkoutFieldsFragment() } - .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery -> + .checkoutUserErrors { userErrorQuery: CheckoutUserErrorQuery -> userErrorQuery .field() .message() @@ -183,44 +179,9 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop val query = mutation { mutationQuery: MutationQuery -> mutationQuery .checkoutDiscountCodeRemove( - checkoutID + checkoutID, ) { payloadQuery: CheckoutDiscountCodeRemovePayloadQuery -> payloadQuery - .checkout { checkoutQuery: CheckoutQuery -> - checkoutQuery.checkoutFieldsFragment() - } - .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery -> - userErrorQuery - .field() - .message() - } - } - } - return runCheckoutMutation(query) - } - - suspend fun updateAddress( - address: MailingAddress, - checkoutID: ID, - waitForShippingRates: Boolean - ): Result { - val input = MailingAddressInput() - .setAddress1(address.address1) - .setAddress2(address.address2) - .setCity(address.city) - .setCountry(address.country) - .setFirstName(address.firstName) - .setLastName(address.lastName) - .setPhone(address.phone) - .setProvince(address.province) - .setZip(address.zip) - - val query = mutation { mutationQuery: MutationQuery -> - mutationQuery - .checkoutShippingAddressUpdateV2( - input, checkoutID - ) { shippingAddressUpdatePayloadQuery: CheckoutShippingAddressUpdateV2PayloadQuery -> - shippingAddressUpdatePayloadQuery .checkout { checkoutQuery: CheckoutQuery -> checkoutQuery.checkoutFieldsFragment() } @@ -231,59 +192,18 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop } } } - - return runCheckoutMutation(query) - } - - suspend fun updateEmail(email: String?, checkoutID: ID): Result { - val query = mutation { mutationQuery: MutationQuery -> - mutationQuery - .checkoutEmailUpdateV2( - checkoutID, email - ) { emailUpdatePayloadQuery: CheckoutEmailUpdateV2PayloadQuery -> - emailUpdatePayloadQuery - .checkout { checkoutQuery: CheckoutQuery -> - checkoutQuery.checkoutFieldsFragment() - } - .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery -> - userErrorQuery - .field() - .message() - } - } - } - return runCheckoutMutation(query) - } - - suspend fun updateShippingRate(handle: String?, checkoutID: ID): Result { - val query = mutation { mutationQuery: MutationQuery -> - mutationQuery - .checkoutShippingLineUpdate( - checkoutID, handle - ) { shippingLineUpdatePayloadQuery: CheckoutShippingLineUpdatePayloadQuery -> - shippingLineUpdatePayloadQuery - .checkout { checkoutQuery: CheckoutQuery -> - checkoutQuery.checkoutFieldsFragment() - } - .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery -> - userErrorQuery - .field() - .message() - } - } - } return runCheckoutMutation(query) } suspend fun completeWithTokenizedPayment( payment: TokenizedPaymentInputV3, - checkoutID: ID + checkoutID: ID, ): Result { val query = mutation { mutationQuery: MutationQuery -> mutationQuery .checkoutCompleteWithTokenizedPaymentV3( - checkoutID, payment + checkoutID, payment, ) { payloadQuery: CheckoutCompleteWithTokenizedPaymentV3PayloadQuery -> payloadQuery .payment { paymentQuery: PaymentQuery -> @@ -295,7 +215,7 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop checkoutQuery .ready() } - .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery -> + .checkoutUserErrors { userErrorQuery: CheckoutUserErrorQuery -> userErrorQuery .field() .message() @@ -305,10 +225,6 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop return runCheckoutMutation(query) } - fun startGooglePaySession() { -// PaySession() - } - private suspend fun runCheckoutMutation(mutation: MutationQuery): Result { return when (val result = mutationQueryAsync(mutation)) { is GraphCallResult.Success -> { @@ -322,13 +238,13 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop Result.success(checkout) } - is GraphCallResult.Failure -> Result.failure(result.error) + is GraphCallResult.Failure -> Result.failure(result.error) } } private suspend fun queryAsync( query: QueryRootQuery, - retryHandler: RetryHandler + retryHandler: RetryHandler, ): GraphCallResult = withContext(Dispatchers.IO) { suspendCoroutine { continuation -> @@ -358,12 +274,11 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop } } - private fun initClient(): GraphClient { return GraphClient.build( application, shop.domain, - shop.storefrontApiKey + shop.storefrontApiKey, ) { // httpCache(application.filesDir) { // cacheMaxSizeBytes = (1024 * 1024 * 10) @@ -371,13 +286,9 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop // HttpCachePolicy.Default.CACHE_FIRST.expireAfter(20, TimeUnit.MINUTES) // } } - } } - sealed class ShopifyError : Throwable() { object Unknown : ShopifyError() - object GooglePayFailed : ShopifyError() - class UserError(val errorMessage: String) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Checkout.kt b/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Checkout.kt index 849c1f33c3..7565db2447 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Checkout.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Checkout.kt @@ -2,23 +2,24 @@ package com.tangem.tap.common.shop.shopify.data import com.shopify.buy3.Storefront +@Suppress("LongMethod", "MagicNumber") fun Storefront.CheckoutQuery.checkoutFieldsFragment() { - // id() ready() webUrl() currencyCode() lineItemsSubtotalPrice { it.amount() } totalPriceV2 { it.currencyCode() - it.amount() } + it.amount() + } lineItems({ arg -> arg.first(250) }) { it.edges { it.node { // it.id() it.title() it.quantity() - it.variant() { - it.priceV2() { it.amount() } + it.variant { + it.priceV2 { it.amount() } } } } diff --git a/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Collection.kt b/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Collection.kt index 572a7b68c7..1527fc9558 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Collection.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Collection.kt @@ -2,13 +2,15 @@ package com.tangem.tap.common.shop.shopify.data import com.shopify.buy3.Storefront +@Suppress("MagicNumber") fun Storefront.CollectionConnectionQuery.collectionFieldsFragment() { edges { collectionEdgeQuery -> collectionEdgeQuery .node { collectionQuery -> collectionQuery .title() - .products({ arg -> arg.first(250) } + .products( + { arg -> arg.first(250) }, ) { productConnectionQuery -> productConnectionQuery .edges { productEdgeQuery -> diff --git a/app/src/main/java/com/tangem/tap/common/text/DecimalDigitsInputFilter.kt b/app/src/main/java/com/tangem/tap/common/text/DecimalDigitsInputFilter.kt index 689095c534..067bcfb5df 100644 --- a/app/src/main/java/com/tangem/tap/common/text/DecimalDigitsInputFilter.kt +++ b/app/src/main/java/com/tangem/tap/common/text/DecimalDigitsInputFilter.kt @@ -8,13 +8,22 @@ import java.util.regex.Pattern [REDACTED_AUTHOR] */ class DecimalDigitsInputFilter( - digitsBeforeDecimal: Int, - digitsAfterDecimal: Int, - private val decimalSeparator: String + digitsBeforeDecimal: Int, + digitsAfterDecimal: Int, + private val decimalSeparator: String, ) : InputFilter { - private val pattern: Pattern = Pattern.compile("(([1-9]{1}[0-9]{0,${digitsBeforeDecimal - 1}})?||[0]{1})((\\$decimalSeparator[0-9]{0,$digitsAfterDecimal})?)||(\\$decimalSeparator)?") + @Suppress("MaxLineLength") + private val pattern: Pattern = + Pattern.compile("(([1-9]{1}[0-9]{0,${digitsBeforeDecimal - 1}})?||[0]{1})((\\$decimalSeparator[0-9]{0,$digitsAfterDecimal})?)||(\\$decimalSeparator)?") - override fun filter(source: CharSequence, sourceStart: Int, sourceEnd: Int, destination: Spanned, destinationStart: Int, destinationEnd: Int): CharSequence? { + override fun filter( + source: CharSequence, + sourceStart: Int, + sourceEnd: Int, + destination: Spanned, + destinationStart: Int, + destinationEnd: Int, + ): CharSequence? { val destString = destination.toString() val prefix = destString.substring(0, destinationStart) val suffix = destString.substring(destinationEnd, destString.length) diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt index ca3bb64dbf..101c40a0d2 100644 --- a/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt +++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt @@ -17,6 +17,7 @@ import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ +@Suppress("MagicNumber") class RefreshBalanceWidget( private val root: ViewGroup, ) : ViewStateWidget { @@ -57,6 +58,7 @@ class RefreshBalanceWidget( viewSwitcher.showNext() } + @Suppress("MagicNumber") private fun animateState(state: ProgressState) { when (state) { ProgressState.Done, ProgressState.Error -> { @@ -94,6 +96,7 @@ class RefreshBalanceWidget( private fun getState(): ProgressState = if (isArrowRefreshActive()) ProgressState.Done else ProgressState.Loading } +@Suppress("MagicNumber") class ShowAnimation : AnimationSet(true) { init { addAnimation( diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt index 48ba0a7291..10b6a1f7a6 100644 --- a/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt +++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt @@ -2,7 +2,6 @@ package com.tangem.tap.common.toggleWidget import android.view.View - /** [REDACTED_AUTHOR] */ diff --git a/app/src/main/java/com/tangem/tap/common/ui/SimpleAlertDialog.kt b/app/src/main/java/com/tangem/tap/common/ui/SimpleAlertDialog.kt index 8cd6e7e5ba..0eaa8e2bf2 100644 --- a/app/src/main/java/com/tangem/tap/common/ui/SimpleAlertDialog.kt +++ b/app/src/main/java/com/tangem/tap/common/ui/SimpleAlertDialog.kt @@ -6,54 +6,50 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.store import com.tangem.wallet.R -class SimpleAlertDialog { - companion object { - fun create( - titleRes: Int? = null, - messageRes: Int? = null, - title: String? = null, - message: String? = null, - primaryButtonRes: Int = R.string.common_ok, - context: Context, - ): AlertDialog { - return SimpleCancelableAlertDialog.create( - titleRes = titleRes, - messageRes = messageRes, - title = title, - message = message, - primaryButtonRes = primaryButtonRes, - secondaryButtonRes = null, - context = context - ) - } +object SimpleAlertDialog { + fun create( + titleRes: Int? = null, + messageRes: Int? = null, + title: String? = null, + message: String? = null, + primaryButtonRes: Int = R.string.common_ok, + context: Context, + ): AlertDialog { + return SimpleCancelableAlertDialog.create( + titleRes = titleRes, + messageRes = messageRes, + title = title, + message = message, + primaryButtonRes = primaryButtonRes, + secondaryButtonRes = null, + context = context, + ) } } -class SimpleCancelableAlertDialog { - companion object { - fun create( - titleRes: Int? = null, - messageRes: Int? = null, - title: String? = null, - message: String? = null, - primaryButtonRes: Int = R.string.common_ok, - secondaryButtonRes: Int? = R.string.common_cancel, - primaryButtonAction: () -> Unit = {}, - secondaryButtonAction: () -> Unit = {}, - context: Context, - ): AlertDialog { +object SimpleCancelableAlertDialog { + fun create( + titleRes: Int? = null, + messageRes: Int? = null, + title: String? = null, + message: String? = null, + primaryButtonRes: Int = R.string.common_ok, + secondaryButtonRes: Int? = R.string.common_cancel, + primaryButtonAction: () -> Unit = {}, + secondaryButtonAction: () -> Unit = {}, + context: Context, + ): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(titleRes?.let { context.getString(it) } ?: title ) - setMessage(messageRes?.let { context.getString(it) } ?: message) - setPositiveButton(context.getText(primaryButtonRes)) { _, _ -> primaryButtonAction() } - if (secondaryButtonRes != null) { - setNegativeButton(context.getText(secondaryButtonRes)) { _, _ -> secondaryButtonAction()} - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - }.create() - } + return AlertDialog.Builder(context).apply { + setTitle(titleRes?.let { context.getString(it) } ?: title) + setMessage(messageRes?.let { context.getString(it) } ?: message) + setPositiveButton(context.getText(primaryButtonRes)) { _, _ -> primaryButtonAction() } + if (secondaryButtonRes != null) { + setNegativeButton(context.getText(secondaryButtonRes)) { _, _ -> secondaryButtonAction() } + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt b/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt index cfd5c2b5c2..f99f01f923 100644 --- a/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt @@ -10,7 +10,11 @@ import java.util.* class PayIdManager { - suspend fun verifyPayId(payId: String, blockchain: Blockchain): Result = withContext(Dispatchers.IO) { + @Suppress("MagicNumber") + suspend fun verifyPayId( + payId: String, + blockchain: Blockchain, + ): Result = withContext(Dispatchers.IO) { val splitPayId = payId.split("\$") val user = splitPayId[0] val baseUrl = "https://${splitPayId[1]}/" @@ -19,28 +23,30 @@ class PayIdManager { private fun Blockchain.getPayIdNetwork(): String { return when (this) { - Blockchain.XRP -> "XRPL" - Blockchain.RSK -> "RSK" - else -> this.currency - }.toLowerCase() + Blockchain.XRP -> "XRPL" + Blockchain.RSK -> "RSK" + else -> this.currency + }.lowercase(Locale.getDefault()) } companion object { - val payIdRegExp = "^[a-z0-9!#@%&*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#@%&*+/=?^_`{|}~-]+)*\\\$(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z-]*[a-z0-9])?|(?:[0-9]{1,3}\\.){3}[0-9]{1,3})\$".toRegex() + @Suppress("MaxLineLength") + val payIdRegExp = + "^[a-z0-9!#@%&*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#@%&*+/=?^_`{|}~-]+)*\\\$(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z-]*[a-z0-9])?|(?:[0-9]{1,3}\\.){3}[0-9]{1,3})\$".toRegex() val payIdSupported: EnumSet = EnumSet.of( - Blockchain.XRP, - Blockchain.Ethereum, - Blockchain.Bitcoin, - Blockchain.Litecoin, - Blockchain.Stellar, - Blockchain.Cardano, - Blockchain.CardanoShelley, - Blockchain.Ducatus, - Blockchain.BitcoinCash, - Blockchain.Binance, - Blockchain.RSK, - Blockchain.Tezos + Blockchain.XRP, + Blockchain.Ethereum, + Blockchain.Bitcoin, + Blockchain.Litecoin, + Blockchain.Stellar, + Blockchain.Cardano, + Blockchain.CardanoShelley, + Blockchain.Ducatus, + Blockchain.BitcoinCash, + Blockchain.Binance, + Blockchain.RSK, + Blockchain.Tezos, ) fun isPayId(value: String?): Boolean = value?.contains(payIdRegExp) ?: false diff --git a/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt b/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt index 10c14744b2..3d2a5764e2 100644 --- a/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt @@ -42,7 +42,7 @@ class RatesRepository( } .onFailure { Result.Failure(it) } - throw IllegalStateException("Unreachable code because runCatching must return result") + error("Unreachable code because runCatching must return result") } private fun handleFiatRatesResult(rates: Map?>): Result.Success { diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index cf8a13681c..09a23afbf3 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -21,6 +21,7 @@ import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.hdWallet.DerivationPath import com.tangem.common.map import com.tangem.common.usersCode.UserCodeRepository +import com.tangem.core.analytics.Analytics import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.operations.CommandResponse @@ -30,7 +31,6 @@ import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask import com.tangem.operations.pins.CheckUserCodesCommand import com.tangem.operations.pins.CheckUserCodesResponse import com.tangem.operations.pins.SetUserCodeCommand -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask import com.tangem.tap.domain.tasks.product.CreateProductWalletTask @@ -211,6 +211,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co return withContext(Dispatchers.Main) { result } } + @Suppress("MagicNumber") fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse?) { tangemSdk.config.cardIdDisplayFormat = when { scanResponse == null -> CardIdDisplayFormat.Full diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt index 72d70b4bb1..b0ee231286 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt @@ -17,21 +17,20 @@ interface MultiMessageError : TapErrors { sealed class TapError( @StringRes val messageResource: Int, - override val args: List? = null + override val args: List? = null, ) : Throwable(), TapErrors, ArgError { object UnknownError : TapError(R.string.send_error_unknown) open class CustomError(val customMessage: String) : TapError(R.string.common_custom_string, listOf(customMessage)) object ScanCardError : TapError(R.string.scan_card_error) - object PayIdAlreadyCreated : TapError(R.string.wallet_create_payid_error_already_created) - object PayIdCreatingError : TapError(R.string.wallet_create_payid_error_message) - object PayIdEmptyField : TapError(R.string.wallet_create_payid_empty) object UnknownBlockchain : TapError(R.string.wallet_error_unsupported_blockchain_subtitle) object NoInternetConnection : TapError(R.string.wallet_notification_no_internet) - object InsufficientBalance : TapError(R.string.send_error_insufficient_balance) object BlockchainInternalError : TapError(R.string.send_error_blockchain_internal) object AmountExceedsBalance : TapError(R.string.send_validation_amount_exceeds_balance) - data class AmountLowerExistentialDeposit(override val args: List) : TapError(R.string.send_error_minimum_balance_format) + data class AmountLowerExistentialDeposit( + override val args: List, + ) : TapError(R.string.send_error_minimum_balance_format) + object FeeExceedsBalance : TapError(R.string.send_validation_invalid_fee) object TotalExceedsBalance : TapError(R.string.send_validation_invalid_total) object InvalidAmountValue : TapError(R.string.send_validation_invalid_amount) @@ -48,7 +47,6 @@ sealed class TapError( object CreationError : CustomError("Can't create wallet manager") class NoAccountError(amountToCreateAccount: String) : CustomError(amountToCreateAccount) class InternalError(message: String) : CustomError(message) - object BlockchainIsUnreachable : TapError(R.string.wallet_balance_blockchain_unreachable) object BlockchainIsUnreachableTryLater : TapError(R.string.wallet_balance_blockchain_unreachable_try_later) } diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt index 989d16359e..d6d05ac254 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt @@ -77,6 +77,7 @@ class ConfigManager { ) } + @Suppress("LongMethod") private fun setupKey(configValues: ConfigValueModel?) { val values = configValues ?: return config = config.copy( diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigLoaders.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/FeaturesLocalLoader.kt similarity index 100% rename from app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigLoaders.kt rename to app/src/main/java/com/tangem/tap/domain/configurable/config/FeaturesLocalLoader.kt diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt index 77a1f0d6cc..586ba0c63b 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt @@ -15,6 +15,7 @@ class FeatureModel( val isCreatingTwinCardsAllowed: Boolean, ) +@Suppress("LongParameterList") class ConfigValueModel( val coinMarketCapKey: String, val mercuryoWidgetId: String, diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt index f75a4922ce..93b80c5ca4 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt @@ -70,6 +70,8 @@ class WarningMessagesManager { } companion object { + const val REMAINING_SIGNATURES_WARNING = 10 + fun devCardWarning(): WarningMessage = WarningMessage( "", "", @@ -171,7 +173,5 @@ class WarningMessagesManager { R.string.alert_demo_message, WarningMessage.Origin.Local, ) - - const val REMAINING_SIGNATURES_WARNING = 10 } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt index 673ef1787b..9adb6b24aa 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt @@ -5,7 +5,7 @@ import com.tangem.blockchain.common.Token import com.tangem.common.card.EllipticCurve import java.math.BigDecimal - +@Suppress("MagicNumber") fun Blockchain.amountToCreateAccount(token: Token? = null): BigDecimal? { return when (this) { Blockchain.Stellar -> if (token?.symbol == NODL) BigDecimal(1.5) else BigDecimal.ONE diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt index 486a9f6b89..b321c5f5c3 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt @@ -37,6 +37,7 @@ val CardDTO.isTangemWallet: Boolean && firmwareVersion >= FirmwareVersion.MultiWalletAvailable && !isSaltPay +@Suppress("UnnecessaryParentheses") fun CardDTO.hasSignedHashes(): Boolean { return wallets.any { (it.totalSignedHashes ?: 0) > 0 } } diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt index cfa69de062..a41280542c 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt @@ -45,7 +45,7 @@ fun WalletManagerFactory.makeWalletManagerForApp( curve = wallet.curve, ) } - scanResponse.card.isHdWalletAllowedByApp && (seedKey != null && derivationParams != null) -> { + scanResponse.card.isHdWalletAllowedByApp && seedKey != null && derivationParams != null -> { val derivedKeys = scanResponse.derivedKeys[wallet.publicKey.toMapKey()] val derivationPath = when (derivationParams) { is DerivationParams.Default -> blockchain.derivationPath(derivationParams.style) @@ -143,12 +143,13 @@ fun WalletManagerFactory.makeSaltPayWalletManager( scanResponse: ScanResponse, ): EthereumWalletManager { val blockchain = scanResponse.getBlockchain() - if (blockchain != Blockchain.SaltPay) - throw IllegalArgumentException("WalletManager for the SaltPay can be created based only on Blockchain.SaltPay") + if (blockchain != Blockchain.SaltPay) { + error("WalletManager for the SaltPay can be created based only on Blockchain.SaltPay") + } val token = SaltPayWorkaround.tokenFrom(blockchain) val cardWallet = scanResponse.card.wallets.firstOrNull().guard { - throw NullPointerException("SaltPay card must have one wallet at least") + error("SaltPay card must have one wallet at least") } return makeWalletManager( diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt index e76a291668..ada8c6896e 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt @@ -57,7 +57,7 @@ private class BlockchainNetworkWalletStoreBuilderImpl( userWalletId = userWalletId, blockchain = blockchainNetwork.blockchain, derivationPath = blockchainNetwork.derivationPath?.let { DerivationPath(it) }, - walletsData = (listOf(blockchainWalletData) + tokensWalletsData), + walletsData = listOf(blockchainWalletData) + tokensWalletsData, walletRent = null, walletManager = walletManager, blockchainNetwork = blockchainNetwork, @@ -79,7 +79,7 @@ private class WalletMangerWalletStoreBuilderImpl( userWalletId = userWalletId, blockchain = wallet.blockchain, derivationPath = wallet.publicKey.derivationPath, - walletsData = (listOf(blockchainWalletData) + listOfNotNull(tokenWalletsData)), + walletsData = listOf(blockchainWalletData) + listOfNotNull(tokenWalletsData), walletRent = null, walletManager = walletManager, blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager), diff --git a/app/src/main/java/com/tangem/tap/domain/moduleMessage/domain/converter/AddCustomTokenErrorConverter.kt b/app/src/main/java/com/tangem/tap/domain/moduleMessage/domain/converter/AddCustomTokenErrorConverter.kt index 66e41dc48e..daa0901b14 100644 --- a/app/src/main/java/com/tangem/tap/domain/moduleMessage/domain/converter/AddCustomTokenErrorConverter.kt +++ b/app/src/main/java/com/tangem/tap/domain/moduleMessage/domain/converter/AddCustomTokenErrorConverter.kt @@ -15,8 +15,9 @@ internal class AddCustomTokenErrorConverter( private val context: Context, ) : ModuleMessageConverter { + @Suppress("MagicNumber") override fun convert(message: DomainModuleError): ConvertedMessage? { - val customTokenError = (message as? AddCustomTokenError) ?: throw UnsupportedOperationException() + val customTokenError = message as? AddCustomTokenError ?: throw UnsupportedOperationException() val rawMessage = when (customTokenError) { AddCustomTokenError.Warning.PotentialScamToken -> R.string.custom_token_validation_error_not_found diff --git a/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayActivationErrorConverter.kt b/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayActivationErrorConverter.kt index 65de384d44..1771a2db47 100644 --- a/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayActivationErrorConverter.kt +++ b/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayActivationErrorConverter.kt @@ -14,7 +14,7 @@ internal class SaltPayActivationErrorConverter( ) : ModuleMessageConverter { override fun convert(message: SaltPayActivationError): ConvertedDialogMessage? { - val saltPayError = (message as? SaltPayActivationError) ?: throw UnsupportedOperationException() + val saltPayError = message as? SaltPayActivationError ?: throw UnsupportedOperationException() val dialogMessage = when (saltPayError) { SaltPayActivationError.NoGas -> { diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt index 7b33c2f9fc..60cfd3b5f9 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt @@ -6,12 +6,12 @@ import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.common.services.Result +import com.tangem.core.analytics.Analytics import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext import com.tangem.operations.backup.BackupService import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.backupService -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.IntroductionProcess import com.tangem.tap.common.analytics.paramsInterceptor.BatchIdParamsInterceptor import com.tangem.tap.common.extensions.dispatchDialogShow @@ -141,31 +141,34 @@ object ScanCardProcessor { store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer)) if (disclaimer.isAccepted()) { - nextHandler((scanResponse)) - } else scope.launch { - delay(DELAY_SDK_DIALOG_CLOSE) - disclaimerWillShow() - dispatchOnMain( - DisclaimerAction.Show( - fromScreen = AppScreen.Home, - callback = DisclaimerCallback( - onAccept = { - scope.launch(Dispatchers.Main) { - nextHandler(scanResponse) - } - }, - onDismiss = { - scope.launch(Dispatchers.Main) { - onProgressStateChange(false) - onFailure(TangemSdkError.UserCancelled()) - } - }, + nextHandler(scanResponse) + } else { + scope.launch { + delay(DELAY_SDK_DIALOG_CLOSE) + disclaimerWillShow() + dispatchOnMain( + DisclaimerAction.Show( + fromScreen = AppScreen.Home, + callback = DisclaimerCallback( + onAccept = { + scope.launch(Dispatchers.Main) { + nextHandler(scanResponse) + } + }, + onDismiss = { + scope.launch(Dispatchers.Main) { + onProgressStateChange(false) + onFailure(TangemSdkError.UserCancelled()) + } + }, + ), ), - ), - ) + ) + } } } + @Suppress("LongMethod", "MagicNumber") private suspend inline fun onScanSuccess( scanResponse: ScanResponse, crossinline onProgressStateChange: suspend (showProgress: Boolean) -> Unit, diff --git a/app/src/main/java/com/tangem/tap/domain/statePrinter/OnboardingWalletStateConverter.kt b/app/src/main/java/com/tangem/tap/domain/statePrinter/OnboardingWalletStateConverter.kt index 858dc8b5d1..baccb34947 100644 --- a/app/src/main/java/com/tangem/tap/domain/statePrinter/OnboardingWalletStateConverter.kt +++ b/app/src/main/java/com/tangem/tap/domain/statePrinter/OnboardingWalletStateConverter.kt @@ -56,6 +56,7 @@ class BackupStepAdapter { } } + @Suppress("MagicNumber") @FromJson fun fromJson(json: String): BackupStep { return when (json) { diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/DerivationTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/DerivationTask.kt deleted file mode 100644 index 3c471f4bc4..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/tasks/DerivationTask.kt +++ /dev/null @@ -1,55 +0,0 @@ -//package com.tangem.tap.domain.tasks -// -//import com.tangem.common.CompletionResult -//import com.tangem.common.core.CardSession -//import com.tangem.common.core.CardSessionRunnable -//import com.tangem.common.core.CompletionCallback -//import com.tangem.common.hdWallet.DerivationPath -//import com.tangem.common.hdWallet.ExtendedPublicKey -//import com.tangem.operations.CommandResponse -//import com.tangem.operations.derivation.DeriveWalletPublicKeysTask -//import com.tangem.tap.common.extensions.ByteArrayKey -// -//class ExtendedPublicKeyList( -// items: Collection -//): ArrayList(items), CommandResponse -// -// -//class DerivationTaskResponse( -// val entries: Map -//): CommandResponse -// -//class DerivationTask( -// private val derivations: Map> -//) : CardSessionRunnable { -// -// val response: MutableMap = mutableMapOf() -// -// override fun run(session: CardSession, callback: CompletionCallback) { -// derive(keys = derivations.keys.toList(), index = 0, session = session, callback = callback) -// } -// -// private fun derive( -// keys: List, -// index: Int, -// session: CardSession, -// callback: CompletionCallback -// ) { -// if (index == keys.count()) { -// callback(CompletionResult.Success(DerivationTaskResponse(response.toMap()))) -// return -// } -// -// val key = keys[index] -// val paths = derivations[key]!! -// DeriveWalletPublicKeysTask(key.bytes, paths).run(session) { result -> -// when (result) { -// is CompletionResult.Success -> { -// response[key] = result.data[key] -// derive(keys = keys, index = index + 1, session = session, callback = callback) -// } -// is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) -// } -// } -// } -//} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetToFactorySettingsTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetToFactorySettingsTask.kt index 444a7b3971..bc11f4be74 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetToFactorySettingsTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetToFactorySettingsTask.kt @@ -8,7 +8,7 @@ import com.tangem.common.extensions.guard import com.tangem.operations.backup.ResetBackupCommand import com.tangem.operations.wallet.PurgeWalletCommand -class ResetToFactorySettingsTask() : CardSessionRunnable { +class ResetToFactorySettingsTask : CardSessionRunnable { override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { deleteWallets(session, callback) diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index b04226d1d6..080659c97b 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -170,7 +170,8 @@ private class ScanWalletProcessor( ) { val activationInProgress = preferencesStorage.usedCardsPrefStorage.isActivationInProgress(card.cardId) - if ((card.backupStatus == CardDTO.BackupStatus.NoBackup && card.wallets.isNotEmpty()) + @Suppress("ComplexCondition") + if (card.backupStatus == CardDTO.BackupStatus.NoBackup && card.wallets.isNotEmpty() && (activationInProgress || card.isSaltPay) ) { StartPrimaryCardLinkingTask().run(session) { linkingResult -> @@ -266,7 +267,7 @@ private class ScanWalletProcessor( } if (additionalBlockchainsToDerive != null) { blockchainsToDerive.addAll( - additionalBlockchainsToDerive.map { BlockchainNetwork(it, card) } + additionalBlockchainsToDerive.map { BlockchainNetwork(it, card) }, ) } if (!card.useOldStyleDerivation) { @@ -307,6 +308,7 @@ private class ScanWalletProcessor( } } +@Suppress("MagicNumber") private class ScanTwinProcessor : ProductCommandProcessor { override fun proceed( card: CardDTO, diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt index 179d3a22ea..90a720055c 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt @@ -43,7 +43,7 @@ class LoadAvailableCoinsService( LoadedCoins( currencies = data.coins.map { Currency.fromCoinResponse(it, data.imageHost) }, moreAvailable = data.total > offset + LOAD_PER_PAGE, - ) + ), ) } is Result.Failure -> { @@ -70,7 +70,7 @@ class LoadAvailableCoinsService( .onSuccess { return@withContext Result.Success(it) } .onFailure { return@withContext Result.Failure(it) } - throw IllegalStateException("Unreachable code because runCatching must return result") + error("Unreachable code because runCatching must return result") } } diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt index 96d98f91a5..a4706680b7 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt @@ -119,7 +119,7 @@ class OldUserTokensRepository( .onSuccess { return@map it.coins.firstOrNull()?.id } .onFailure { return@map null } - throw IllegalStateException("Unreachable code because runCatching must return result") + error("Unreachable code because runCatching must return result") } .mapIndexedNotNull { index, id -> if (id == null) null else tokens[index].contractAddress to id diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index b8b585e1ee..7d01846702 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -49,7 +49,7 @@ class UserTokensRepository( return@withContext handleGetUserTokensFailure(card = card, userId = userId, error = it) } - throw IllegalStateException("Unreachable code because runCatching must return result") + error("Unreachable code because runCatching must return result") } // TODO("After adding DI") replace with CoroutineDispatcherProvider diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/models/BlockchainDao.kt b/app/src/main/java/com/tangem/tap/domain/tokens/models/BlockchainDao.kt index 73eef228ad..4417004531 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/models/BlockchainDao.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/models/BlockchainDao.kt @@ -10,14 +10,13 @@ data class BlockchainDao( @Json(name = "key") val name: String, @Json(name = "testnet") - val isTestNet: Boolean + val isTestNet: Boolean, ) { @Deprecated("The method is used only for migration from older versions of the app") fun toBlockchain(): Blockchain { val blockchain = Blockchain.values().find { it.name.lowercase() == name.lowercase() } - ?: throw Exception("Invalid BlockchainDao") - return if (!isTestNet) blockchain else blockchain.getTestnetVersion() - ?: throw Exception("Invalid BlockchainDao") + ?: error("Blockchain is null") + return if (!isTestNet) blockchain else blockchain.getTestnetVersion() ?: error("Invalid BlockchainDao") } companion object { diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/models/BlockchainNetwork.kt b/app/src/main/java/com/tangem/tap/domain/tokens/models/BlockchainNetwork.kt index 3b9558c303..0be009ce5a 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/models/BlockchainNetwork.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/models/BlockchainNetwork.kt @@ -12,15 +12,16 @@ import com.tangem.domain.common.TapWorkarounds.derivationStyle data class BlockchainNetwork( val blockchain: Blockchain, val derivationPath: String?, - val tokens: List + val tokens: List, ) { - constructor( - blockchain: Blockchain, - card: CardDTO, - ) : this( + constructor(blockchain: Blockchain, card: CardDTO) : this( blockchain = blockchain, - derivationPath = if (card.settings.isHDWalletAllowed) blockchain.derivationPath(card.derivationStyle)?.rawPath else null, + derivationPath = if (card.settings.isHDWalletAllowed) { + blockchain.derivationPath(card.derivationStyle)?.rawPath + } else { + null + }, tokens = emptyList(), ) @@ -43,7 +44,7 @@ data class BlockchainNetwork( } override fun hashCode(): Int = calculateHashCode( - blockchain.hashCode(), derivationPath?.hashCode() ?: 0 + blockchain.hashCode(), derivationPath?.hashCode() ?: 0, ) companion object { @@ -51,7 +52,7 @@ data class BlockchainNetwork( return BlockchainNetwork( walletManager.wallet.blockchain, walletManager.wallet.publicKey.derivationPath?.rawPath, - walletManager.cardTokens.toList() + walletManager.cardTokens.toList(), ) } } diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt index 36d3b037ff..95d1d76fdd 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt @@ -11,14 +11,13 @@ import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapView import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapViewState import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget - /** [REDACTED_AUTHOR] */ class TwinsCardWidget( val leapfrogWidget: LeapfrogWidget, private val deviceScaleFactor: Float = 1f, - val getTopOfAnchorViewForActivateState: () -> Float + val getTopOfAnchorViewForActivateState: () -> Float, ) { var state: TwinCardsWidgetState? = null @@ -37,7 +36,7 @@ class TwinsCardWidget( val animator = createAnimator(animate) animator.playTogether( createAnimator(TwinCardNumber.First, createWelcomeProperties(TwinCardNumber.First)), - createAnimator(TwinCardNumber.Second, createWelcomeProperties(TwinCardNumber.Second)) + createAnimator(TwinCardNumber.Second, createWelcomeProperties(TwinCardNumber.Second)), ) animator.doOnEnd { onEnd() } leapfrogWidget.fold(animate) { animator.start() } @@ -50,7 +49,7 @@ class TwinsCardWidget( val animator = createAnimator(animate) animator.playTogether( createAnimator(TwinCardNumber.First, createLeapfrogProperties(TwinCardNumber.First)), - createAnimator(TwinCardNumber.Second, createLeapfrogProperties(TwinCardNumber.Second)) + createAnimator(TwinCardNumber.Second, createLeapfrogProperties(TwinCardNumber.Second)), ) animator.doOnEnd { leapfrogWidget.initViews() @@ -66,7 +65,7 @@ class TwinsCardWidget( val animator = createAnimator(animate) animator.playTogether( createAnimator(TwinCardNumber.First, createActivateProperties(TwinCardNumber.First)), - createAnimator(TwinCardNumber.Second, createActivateProperties(TwinCardNumber.Second)) + createAnimator(TwinCardNumber.Second, createActivateProperties(TwinCardNumber.Second)), ) animator.doOnEnd { onEnd() } animator.start() @@ -85,6 +84,7 @@ class TwinsCardWidget( return animator } + @Suppress("MagicNumber") private fun createWelcomeProperties(cardNumber: TwinCardNumber): TwinsCardProperties { return when (cardNumber) { TwinCardNumber.First -> { @@ -118,6 +118,7 @@ class TwinsCardWidget( ) } + @Suppress("MagicNumber") private fun createActivateProperties(cardNumber: TwinCardNumber): TwinsCardProperties { val topOfAnchorView = getTopOfAnchorViewForActivateState() val twinProperties = TwinsCardProperties.from(getLeapViewByCardNumber(cardNumber).state) diff --git a/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt index 31d35c4a06..4b86581b80 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt @@ -34,11 +34,14 @@ class WriteProtectedIssuerDataTask( is CompletionResult.Success -> { writeIssuerData( twinPublicKey, issuerKeys, signResult.data.signature, - readResult.data, session, callback + readResult.data, session, callback, ) } - is CompletionResult.Failure -> callback(CompletionResult.Failure( - readResult.error)) + is CompletionResult.Failure -> callback( + CompletionResult.Failure( + readResult.error, + ), + ) } } } @@ -47,6 +50,7 @@ class WriteProtectedIssuerDataTask( } } + @Suppress("LongParameterList") private fun writeIssuerData( twinPublicKey: ByteArray, issuerKeys: KeyPair, cardSignature: ByteArray, readResponse: ReadIssuerDataResponse, diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt index 9e7619e7c5..fd85a066ba 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt @@ -21,10 +21,10 @@ interface UserWalletsListManager { /** * Save provided user wallet and set it as selected * @param userWallet [UserWallet] to save - * @param canOverride If false, then terminate with [UserWalletListError.WalletAlreadySaved] when user tries to save an - * already saved card + * @param canOverride If false, then terminate with [UserWalletListError.WalletAlreadySaved] when user tries + * to save an already saved card * @return [CompletionResult] of operation - * */ + */ suspend fun save(userWallet: UserWallet, canOverride: Boolean = false): CompletionResult /** diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index db1db2ebbd..3f8cdb5221 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -219,13 +219,18 @@ internal class BiometricUserWalletsListManager( private suspend fun loadModels(): CompletionResult { return getSavedUserWallets() .map { userWallets -> - if (userWallets.isNotEmpty()) state.update { prevState -> - val wallets = (userWallets + prevState.userWallets).distinctBy { it.walletId } + if (userWallets.isNotEmpty()) { + state.update { prevState -> + val wallets = (userWallets + prevState.userWallets).distinctBy { it.walletId } - prevState.copy( - userWallets = wallets, - selectedUserWalletId = findOrSetSelectedUserWalletId(prevState.selectedUserWalletId, wallets), - ) + prevState.copy( + userWallets = wallets, + selectedUserWalletId = findOrSetSelectedUserWalletId( + prevSelectedWalletId = prevState.selectedUserWalletId, + userWallets = wallets, + ), + ) + } } } .doOnFailure { error -> diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt index 11e558535d..5f72d40ab7 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt @@ -134,7 +134,7 @@ internal class BiometricUserWalletsKeysRepository( } private suspend fun deleteUserWalletsIds(userWalletsIds: List) { - val remainingIds = (getUserWalletsIds() - userWalletsIds.toSet()) + val remainingIds = getUserWalletsIds() - userWalletsIds.toSet() withContext(Dispatchers.IO) { secureStorage.store(remainingIds.encode(), StorageKey.UserWalletIds.name) diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresError.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresError.kt index de8c07b6d2..826c158a8b 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresError.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresError.kt @@ -11,6 +11,7 @@ sealed class WalletStoresError(code: Int) : TangemError(code) { override val message: String? get() = customMessage + @Suppress("MagicNumber") class FetchFiatRatesError( currencies: List, override val cause: Throwable?, @@ -18,18 +19,22 @@ sealed class WalletStoresError(code: Int) : TangemError(code) { override var customMessage: String = "Failed to fetch fiat rates for currencies $currencies" } + @Suppress("MagicNumber") class UnknownBlockchain : WalletStoresError(60012) { override var customMessage: String = "Unknown blockchain" } + @Suppress("MagicNumber") object NoInternetConnection : WalletStoresError(60013) { override var customMessage: String = "No internet connection" } + @Suppress("MagicNumber") class WalletManagerNotCreated(blockchain: Blockchain) : WalletStoresError(60014) { override var customMessage: String = "Wallet manager can not be created for $blockchain" } + @Suppress("MagicNumber") class UpdateWalletManagerError( blockchain: Blockchain, override val cause: Throwable, diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt index a9cb80f66e..4cf796096a 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt @@ -48,6 +48,7 @@ import kotlinx.coroutines.withContext import timber.log.Timber import java.math.BigDecimal +@Suppress("LargeClass") internal class DefaultWalletAmountsRepository( private val tangemTechApi: TangemTechApi, private val dispatchers: CoroutineDispatcherProvider, @@ -59,13 +60,16 @@ internal class DefaultWalletAmountsRepository( userWallets: List, fiatCurrency: FiatCurrency, ): CompletionResult { - return if (userWallets.isEmpty()) CompletionResult.Success(Unit) - else withContext(Dispatchers.Default) { - awaitAll( - async { fetchAmountsForUserWallets(userWallets) }, - async { fetchFiatRates(userWallets, walletStores = null, fiatCurrency) }, - ) - .fold() + return if (userWallets.isEmpty()) { + CompletionResult.Success(Unit) + } else { + withContext(Dispatchers.Default) { + awaitAll( + async { fetchAmountsForUserWallets(userWallets) }, + async { fetchFiatRates(userWallets, walletStores = null, fiatCurrency) }, + ) + .fold() + } } } @@ -81,16 +85,19 @@ internal class DefaultWalletAmountsRepository( userWallet: UserWallet, fiatCurrency: FiatCurrency, ): CompletionResult { - return if (walletStores.isEmpty()) CompletionResult.Success(Unit) - else withContext(Dispatchers.Default) { - val userWalletId = userWallet.walletId - val scanResponse = userWallet.scanResponse + return if (walletStores.isEmpty()) { + CompletionResult.Success(Unit) + } else { + withContext(Dispatchers.Default) { + val userWalletId = userWallet.walletId + val scanResponse = userWallet.scanResponse - awaitAll( - async { fetchAmountForWalletStores(userWalletId, scanResponse, walletStores) }, - async { fetchFiatRates(listOf(userWallet), walletStores, fiatCurrency) }, - ) - .fold() + awaitAll( + async { fetchAmountForWalletStores(userWalletId, scanResponse, walletStores) }, + async { fetchFiatRates(listOf(userWallet), walletStores, fiatCurrency) }, + ) + .fold() + } } } @@ -139,7 +146,7 @@ internal class DefaultWalletAmountsRepository( return@withContext CompletionResult.Failure(error) } - throw IllegalStateException("Unreachable code because runCatching must return result") + error("Unreachable code because runCatching must return result") } } @@ -168,7 +175,7 @@ internal class DefaultWalletAmountsRepository( ): CompletionResult = coroutineScope { walletStores.map { walletStore -> async { - // TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository] + //TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository] val walletManager = walletStore.walletManager fetchAmountsForWalletStore(userWalletId, scanResponse, walletStore, walletManager) } @@ -408,8 +415,10 @@ internal class DefaultWalletAmountsRepository( val error = WalletStoresError.NoInternetConnection Timber.e(error) CompletionResult.Failure(error) - } else withContext(Dispatchers.IO) { - catching { block() } + } else { + withContext(Dispatchers.IO) { + catching { block() } + } } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt index 6355f2f6d6..788958565a 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt @@ -38,7 +38,9 @@ internal class DefaultWalletManagersRepository( return findOrMakeInternal(userWallet, blockchainNetwork) } - override suspend fun findOrMakeSingleCurrencyWalletManager(userWallet: UserWallet): CompletionResult { + override suspend fun findOrMakeSingleCurrencyWalletManager( + userWallet: UserWallet, + ): CompletionResult { return findOrMakeInternal(userWallet, blockchainNetwork = null) } @@ -180,8 +182,11 @@ internal class DefaultWalletManagersRepository( blockchain: Blockchain?, ): WalletManager? { return walletManagersStorage.getAll().first()[userWalletId]?.let { userWalletManagers -> - if (blockchain == null) userWalletManagers.firstOrNull() - else userWalletManagers.find { it.wallet.blockchain == blockchain } + if (blockchain == null) { + userWalletManagers.firstOrNull() + } else { + userWalletManagers.find { it.wallet.blockchain == blockchain } + } } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt index 3646e38f6a..866711e543 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt @@ -11,8 +11,8 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock internal object WalletManagerStorage { - private val managers = - MutableSharedFlow>>(replay = 1) + private val managers = MutableSharedFlow>>(replay = 1) + private val mutex = Mutex() init { managers.tryEmit(hashMapOf()) @@ -22,7 +22,6 @@ internal object WalletManagerStorage { return managers.asSharedFlow() } - private val mutex = Mutex() suspend fun update( f: suspend (HashMap>) -> HashMap>, ) { diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt index 93cdfb508f..932abce88b 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt @@ -11,8 +11,8 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock internal object WalletStoresStorage { - private val stores = - MutableSharedFlow>>(replay = 1) + private val stores = MutableSharedFlow>>(replay = 1) + private val mutex = Mutex() init { stores.tryEmit(hashMapOf()) @@ -22,7 +22,6 @@ internal object WalletStoresStorage { return stores.asSharedFlow() } - private val mutex = Mutex() suspend fun update( f: suspend (HashMap>) -> HashMap>, ) { diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt index d35cc7c2e0..5dec1d0850 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt @@ -12,69 +12,65 @@ import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder import com.trustwallet.walletconnect.models.binance.tradeOrderSerializer import timber.log.Timber -class BnbHelper { - companion object { +object BnbHelper { + fun createMessageData(order: WCBinanceTransferOrder): BinanceMessageData.Transfer { + val input = order.msgs.first().inputs.first() + val output = order.msgs.first().inputs.first() - fun createMessageData(order: WCBinanceTransferOrder): BinanceMessageData.Transfer { - val input = order.msgs.first().inputs.first() - val output = order.msgs.first().inputs.first() + val currency = + input.coins.firstOrNull()?.denom ?: Blockchain.Binance + val amount = input.coins + .mapNotNull { if (it.denom == currency) it.amount else null } + .sum() + .toBigDecimal() + .movePointLeft(Blockchain.Binance.decimals()) + .stripZeroPlainString() - val currency = - input.coins.firstOrNull()?.denom ?: Blockchain.Binance - val amount = input.coins - .mapNotNull { if (it.denom == currency) it.amount else null } - .sum() - .toBigDecimal() - .movePointLeft(Blockchain.Binance.decimals()) - .stripZeroPlainString() - - val gson = GsonBuilder() - .registerTypeAdapter(tradeOrderSerializer) - .serializeNulls() - .create() - - return BinanceMessageData.Transfer( - outputAddress = output.address, - amount = amount, - address = input.address, - data = gson.toJson(order).toByteArray().calculateSha256() - ) - } - - fun createMessageData(order: WCBinanceTradeOrder): BinanceMessageData.Trade { - val address = order.msgs.first().sender - - val tradeData = order.msgs.map { - val price = it.price.toBigDecimal() - .movePointLeft(Blockchain.Binance.decimals()) - .stripTrailingZeros() - val quantity = it.quantity.toBigDecimal() - .movePointLeft(Blockchain.Binance.decimals()) - .stripTrailingZeros() - - val amount = price * quantity - val symbol = it.symbol.substringBefore("-") - - TradeData( - price = "$price ${Blockchain.Binance.currency}", - quantity = "$quantity $symbol", - amount = "$amount ${Blockchain.Binance.currency}", - symbol = symbol - ) - } - val gson = GsonBuilder() - .registerTypeAdapter(tradeOrderSerializer) - .serializeNulls() - .create() - val serialized = gson.toJson(order) - Timber.d(serialized) - - return BinanceMessageData.Trade( - tradeData = tradeData, - address = address, - data = serialized.toByteArray().calculateSha256() - ) - } + val gson = GsonBuilder() + .registerTypeAdapter(tradeOrderSerializer) + .serializeNulls() + .create() + return BinanceMessageData.Transfer( + outputAddress = output.address, + amount = amount, + address = input.address, + data = gson.toJson(order).toByteArray().calculateSha256(), + ) } -} + + fun createMessageData(order: WCBinanceTradeOrder): BinanceMessageData.Trade { + val address = order.msgs.first().sender + + val tradeData = order.msgs.map { + val price = it.price.toBigDecimal() + .movePointLeft(Blockchain.Binance.decimals()) + .stripTrailingZeros() + val quantity = it.quantity.toBigDecimal() + .movePointLeft(Blockchain.Binance.decimals()) + .stripTrailingZeros() + + val amount = price * quantity + val symbol = it.symbol.substringBefore("-") + + TradeData( + price = "$price ${Blockchain.Binance.currency}", + quantity = "$quantity $symbol", + amount = "$amount ${Blockchain.Binance.currency}", + symbol = symbol, + ) + } + val gson = GsonBuilder() + .registerTypeAdapter(tradeOrderSerializer) + .serializeNulls() + .create() + val serialized = gson.toJson(order) + Timber.d(serialized) + + return BinanceMessageData.Trade( + tradeData = tradeData, + address = address, + data = serialized.toByteArray().calculateSha256(), + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/EthSignHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/EthSignHelper.kt index b366229775..a302d44747 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/EthSignHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/EthSignHelper.kt @@ -9,50 +9,28 @@ import com.google.gson.JsonParser import com.google.gson.annotations.SerializedName import com.tangem.blockchain.common.Blockchain import com.trustwallet.walletconnect.JSONRPC_VERSION -import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage -class EthSignHelper { - companion object { - private val gson: Gson by lazy { - GsonBuilder() - .setPrettyPrinting() - .serializeNulls() - .create() - } +object EthSignHelper { + private val gson: Gson by lazy { + GsonBuilder() + .setPrettyPrinting() + .serializeNulls() + .create() + } - fun parseCustomRequest(data: String): CustomJsonRpcRequest { - return gson.fromJson(data) - } - - fun tryToParseEthTypedMessage(request: CustomJsonRpcRequest): WCEthereumSignMessage? { - return if (request.method == WCMethodExtended.ETH_SIGN_TYPE_DATA_V4) { - WCEthereumSignMessage( - listOf( - request.params[0].asString, - request.params[1].asString, - ), - WCEthereumSignMessage.WCSignType.TYPED_MESSAGE - ) - } else { - null - } - } - - fun tryToParseEthTypedMessageString(message: String): String? { - return try { - val messageString = message - .replace("\\", "") - .removePrefix("\"") - .removeSuffix("\"") - val messageJson = JsonParser().parse(messageString) - val filteredMap = gson.fromJson>(messageJson) - .filterKeys { it == "domain" || it == "message"} - - gson.toJson(filteredMap) - } catch (exception: Exception) { - null - } + fun tryToParseEthTypedMessageString(message: String): String? { + return try { + val messageString = message + .replace("\\", "") + .removePrefix("\"") + .removeSuffix("\"") + val messageJson = JsonParser().parse(messageString) + val filteredMap = gson.fromJson>(messageJson) + .filterKeys { it == "domain" || it == "message" } + gson.toJson(filteredMap) + } catch (exception: Exception) { + null } } } @@ -61,7 +39,7 @@ data class CustomJsonRpcRequest( val id: Long, val jsonrpc: String = JSONRPC_VERSION, val method: WCMethodExtended?, - val params: JsonArray + val params: JsonArray, ) { fun blockchainFromChainId(): Blockchain? { diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt index 33850fc1c6..6b96fc0f6a 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt @@ -3,8 +3,8 @@ package com.tangem.tap.domain.walletconnect import com.tangem.blockchain.common.Blockchain import com.tangem.common.card.EllipticCurve import com.tangem.common.extensions.guard -import com.tangem.domain.common.ScanResponse import com.tangem.core.analytics.Analytics +import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.analytics.events.WalletConnect import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.global.GlobalAction @@ -43,6 +43,7 @@ import java.util.* import java.util.concurrent.TimeUnit import kotlin.collections.set +@Suppress("LargeClass") class WalletConnectManager { private var cardId: String? = null @@ -126,6 +127,7 @@ class WalletConnectManager { } } + @Suppress("MagicNumber") private fun setupConnectionTimeoutCheck(session: WCSession) { scope.launch { delay(20_000) @@ -345,6 +347,7 @@ class WalletConnectManager { } } + @Suppress("LongMethod", "ComplexMethod") fun setListeners(client: WCClient) { client.onSessionRequest = { id: Long, peer: WCPeerMeta -> Timber.d("OnSessionRequest: $peer") @@ -490,12 +493,11 @@ class WalletConnectManager { } companion object { - private val tangemPeerMeta = - WCPeerMeta(name = "Tangem Wallet", url = "https://tangem.com") + const val WC_SCHEME = "wc" + + private val tangemPeerMeta = WCPeerMeta(name = "Tangem Wallet", url = "https://tangem.com") fun isCorrectWcUri(string: String): Boolean = WCSession.from(string) != null - - const val WC_SCHEME = "wc" } } @@ -521,6 +523,7 @@ data class WalletConnectActiveData( } } +@Suppress("MagicNumber") class RetryInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val request: Request = chain.request() diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt index d186cad24f..19e2e44e42 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt @@ -3,46 +3,39 @@ package com.tangem.tap.domain.walletconnect import com.tangem.blockchain.common.Blockchain import com.trustwallet.walletconnect.models.WCPeerMeta -class WalletConnectNetworkUtils { - - companion object { - - fun parseBlockchain( - chainId: Int?, - peer: WCPeerMeta, - ): Blockchain? { - return when { - peer.url.contains("pancakeswap.finance") -> { - Blockchain.BSC - } - peer.url.contains("optimism") -> { - Blockchain.Optimism - } - chainId != null -> { - Blockchain.fromChainId(chainId) - } - peer.url.contains("matic.network") || peer.name == "Polygon" -> { - Blockchain.Polygon - } - peer.url.contains("binance.org") || peer.name.contains("Binance") -> { - if (peer.icons.firstOrNull()?.contains("testnet") == true) { - Blockchain.BinanceTestnet - } else { - Blockchain.Binance - } - } - peer.name.contains("BSC") -> { - Blockchain.BSC - } - peer.url.contains("honeyswap.1hive.eth.limo") -> { - // Check if something's changed after this bug report: - // https://github.com/1Hive/honeyswap-interface/issues/83 - Blockchain.Gnosis - } - else -> { - Blockchain.Ethereum +object WalletConnectNetworkUtils { + fun parseBlockchain(chainId: Int?, peer: WCPeerMeta): Blockchain? { + return when { + peer.url.contains("pancakeswap.finance") -> { + Blockchain.BSC + } + peer.url.contains("optimism") -> { + Blockchain.Optimism + } + chainId != null -> { + Blockchain.fromChainId(chainId) + } + peer.url.contains("matic.network") || peer.name == "Polygon" -> { + Blockchain.Polygon + } + peer.url.contains("binance.org") || peer.name.contains("Binance") -> { + if (peer.icons.firstOrNull()?.contains("testnet") == true) { + Blockchain.BinanceTestnet + } else { + Blockchain.Binance } } + peer.name.contains("BSC") -> { + Blockchain.BSC + } + peer.url.contains("honeyswap.1hive.eth.limo") -> { + // Check if something's changed after this bug report: + // https://github.com/1Hive/honeyswap-interface/issues/83 + Blockchain.Gnosis + } + else -> { + Blockchain.Ethereum + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index 2ad236bd6c..6448911980 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -22,9 +22,9 @@ import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toDecompressedPublicKey import com.tangem.common.extensions.toHexString +import com.tangem.core.analytics.Analytics import com.tangem.crypto.CryptoUtils import com.tangem.operations.sign.SignHashCommand -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.WalletConnect import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.extensions.toFormattedString @@ -48,6 +48,7 @@ import java.math.BigDecimal class WalletConnectSdkHelper { + @Suppress("MagicNumber") suspend fun prepareTransactionData( transaction: WCEthereumTransaction, session: WalletConnectSession, @@ -101,7 +102,7 @@ class WalletConnectSdkHelper { gasAmount = fee.toFormattedString(decimals), totalAmount = total.toFormattedString(decimals), balance = balance.toFormattedString(decimals), - isEnoughFundsToSend = (balance - total) >= BigDecimal.ZERO, + isEnoughFundsToSend = balance - total >= BigDecimal.ZERO, session = session.session, id = id, type = type, diff --git a/app/src/main/java/com/tangem/tap/features/BaseStoreFragment.kt b/app/src/main/java/com/tangem/tap/features/BaseStoreFragment.kt index b4f486a1a2..e295c9f100 100644 --- a/app/src/main/java/com/tangem/tap/features/BaseStoreFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/BaseStoreFragment.kt @@ -8,9 +8,10 @@ import org.rekotlin.StoreSubscriber */ abstract class BaseStoreFragment(layoutId: Int) : BaseFragment(layoutId) { - abstract fun subscribeToStore() protected val storeSubscribersList = mutableListOf>() + abstract fun subscribeToStore() + override fun onStart() { super.onStart() subscribeToStore() diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt index a5823818dd..ec0f95eece 100644 --- a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt @@ -82,6 +82,7 @@ object DemoHelper { } } +@Suppress("LargeClass") class DemoConfig { val demoBlockchains = listOf( @@ -114,6 +115,7 @@ class DemoConfig { return (releaseDemoCardIds + testDemoCardIds).distinct() } + @Suppress("ClassOrdering") private val releaseDemoCardIds = mutableListOf( // === Not from the Google Sheet table === "FB10000000000196", // Note BTC @@ -476,20 +478,19 @@ class DemoConfig { "AB02000000058187", ) + @Suppress("ClassOrdering") private val testDemoCardIds = listOf( "FB20000000000186", // Note ETH "FB10000000000196", // Note BTC "FB30000000000176", // Wallet ) + @Suppress("ClassOrdering") private val debugTestDemoCardIds = listOf( ) } -class DemoTransactionSender( - private val walletManager: WalletManager, - private val sender: TransactionSender = walletManager as TransactionSender -) : TransactionSender { +class DemoTransactionSender(private val walletManager: WalletManager) : TransactionSender { override suspend fun getFee(amount: Amount, destination: String): Result> { val blockchain = walletManager.wallet.blockchain @@ -502,6 +503,7 @@ class DemoTransactionSender( ) } + @Suppress("MagicNumber") override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult { val dataToSign = randomString(32).toByteArray() val signerResponse = signer.sign( diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoMiddlewares.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoOnboardingNoteMiddleware.kt similarity index 98% rename from app/src/main/java/com/tangem/tap/features/demo/DemoMiddlewares.kt rename to app/src/main/java/com/tangem/tap/features/demo/DemoOnboardingNoteMiddleware.kt index 87af003060..9b391b22df 100644 --- a/app/src/main/java/com/tangem/tap/features/demo/DemoMiddlewares.kt +++ b/app/src/main/java/com/tangem/tap/features/demo/DemoOnboardingNoteMiddleware.kt @@ -39,7 +39,7 @@ internal class DemoOnboardingNoteMiddleware : DemoMiddleware { currency = Currency.Blockchain(walletManager.wallet.blockchain, null), state = ProgressState.Done, error = null, - criticalError = null + criticalError = null, ) walletManager.wallet.setAmount(balanceAmount) 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 f6e2c4b7c4..602bfa3378 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 @@ -5,8 +5,8 @@ import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.common.flatMap -import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.core.analytics.Analytics +import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.extensions.dispatchDialogShow @@ -146,6 +146,7 @@ class DetailsMiddleware { } class ManageSecurityMiddleware { + @Suppress("ComplexMethod") fun handle(action: DetailsAction.ManageSecurity) { when (action) { is DetailsAction.ManageSecurity.OpenSecurity -> { 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 44b5e293cb..9b6814f90e 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 @@ -15,10 +15,8 @@ import com.tangem.tap.tangemSdkManager import org.rekotlin.Action import java.util.* -class DetailsReducer { - companion object { - fun reduce(action: Action, state: AppState): DetailsState = internalReduce(action, state) - } +object DetailsReducer { + fun reduce(action: Action, state: AppState): DetailsState = internalReduce(action, state) } private fun internalReduce(action: Action, state: AppState): DetailsState { @@ -104,8 +102,7 @@ private fun prepareSecurityOptions(card: CardDTO): ManageSecurityState { private fun isResetToFactoryAllowedByCard(card: CardDTO): Boolean { val notAllowedByAnyWallet = card.wallets.any { it.settings.isPermanent } val notAllowedByCard = notAllowedByAnyWallet || - (card.isWalletDataSupported && (!card.isTangemNote && !card.settings.isBackupAllowed)) || - card.isSaltPay + card.isWalletDataSupported && !card.isTangemNote && !card.settings.isBackupAllowed || card.isSaltPay return !notAllowedByCard } @@ -188,6 +185,7 @@ private fun prepareAllowedSecurityOptions( return allowedSecurityOptions } +@Suppress("MagicNumber") private fun CardDTO.toCardInfo(): CardInfo { val cardId = this.cardId.chunked(4).joinToString(separator = " ") val issuer = this.issuer.name diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index 7818f367b3..958d119c37 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -40,6 +40,7 @@ class WalletConnectMiddleware { } } + @Suppress("ComplexMethod", "LongMethod") private fun handle(state: () -> AppState?, action: Action) { if (DemoHelper.tryHandle(state, action)) return @@ -49,10 +50,8 @@ class WalletConnectMiddleware { walletConnectManager.restoreSessions(action.scanResponse) } is WalletConnectAction.HandleDeepLink -> { - if (!action.wcUri.isNullOrBlank()) { - if (WalletConnectManager.isCorrectWcUri(action.wcUri)) { - store.dispatchOnMain(WalletConnectAction.OpenSession(action.wcUri)) - } + if (!action.wcUri.isNullOrBlank() && WalletConnectManager.isCorrectWcUri(action.wcUri)) { + store.dispatchOnMain(WalletConnectAction.OpenSession(action.wcUri)) } } is WalletConnectAction.StartWalletConnect -> { diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt index e4cc76254c..811a934f3d 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt @@ -2,47 +2,44 @@ package com.tangem.tap.features.details.redux.walletconnect import org.rekotlin.Action -class WalletConnectReducer { - companion object { - fun reduce( - action: Action, state: WalletConnectState, - ): WalletConnectState { - if (action !is WalletConnectAction) return state +object WalletConnectReducer { + fun reduce( + action: Action, state: WalletConnectState, + ): WalletConnectState { + if (action !is WalletConnectAction) return state - return when (action) { - is WalletConnectAction.ResetState -> return WalletConnectState() - is WalletConnectAction.ApproveSession.Success -> { - state.copy( - loading = false, - sessions = state.sessions + action.session, - ) - } - is WalletConnectAction.OpenSession -> { - state.copy(loading = true) - } - is WalletConnectAction.SetNewSessionData -> { - state.copy(newSessionData = action.newSession) - } - is WalletConnectAction.SetSessionsRestored -> - WalletConnectState(sessions = action.sessions) - is WalletConnectAction.RemoveSession -> { - val sessions = - state.sessions.filterNot { it.session.toUri() == action.session.toUri() } - state.copy(sessions = sessions) - } - is WalletConnectAction.UnsupportedCard -> state.copy(loading = false) - is WalletConnectAction.RefuseOpeningSession -> state.copy(loading = false) - is WalletConnectAction.OpeningSessionTimeout -> state.copy(loading = false) - is WalletConnectAction.FailureEstablishingSession -> state.copy(loading = false) - is WalletConnectAction.UpdateBlockchain -> state.copy( - sessions = state.sessions - .filterNot { it.peerId == action.updatedSession.peerId } - + action.updatedSession + return when (action) { + is WalletConnectAction.ResetState -> return WalletConnectState() + is WalletConnectAction.ApproveSession.Success -> { + state.copy( + loading = false, + sessions = state.sessions + action.session, ) - - else -> state } - } + is WalletConnectAction.OpenSession -> { + state.copy(loading = true) + } + is WalletConnectAction.SetNewSessionData -> { + state.copy(newSessionData = action.newSession) + } + is WalletConnectAction.SetSessionsRestored -> + WalletConnectState(sessions = action.sessions) + is WalletConnectAction.RemoveSession -> { + val sessions = + state.sessions.filterNot { it.session.toUri() == action.session.toUri() } + state.copy(sessions = sessions) + } + is WalletConnectAction.UnsupportedCard -> state.copy(loading = false) + is WalletConnectAction.RefuseOpeningSession -> state.copy(loading = false) + is WalletConnectAction.OpeningSessionTimeout -> state.copy(loading = false) + is WalletConnectAction.FailureEstablishingSession -> state.copy(loading = false) + is WalletConnectAction.UpdateBlockchain -> state.copy( + sessions = state.sessions + .filterNot { it.peerId == action.updatedSession.peerId } + + action.updatedSession, + ) + else -> state + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt index 97afb372a2..53e37c8d80 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt @@ -74,7 +74,7 @@ data class WalletForSession( } override fun hashCode(): Int { - var result = (walletPublicKey?.contentHashCode() ?: 0) + var result = walletPublicKey?.contentHashCode() ?: 0 result = 31 * result + (derivedPublicKey?.contentHashCode() ?: 0) result = 31 * result + (derivationPath?.hashCode() ?: 0) result = 31 * result + isTestNet.hashCode() diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt index b072be7b74..ad15a780b8 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt @@ -89,6 +89,7 @@ private fun AppSettings( } } +@Suppress("LongMethod") @Composable private fun AppSettingsElement( modifier: Modifier = Modifier, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt index 0b1063c381..7a0092384a 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt @@ -51,6 +51,7 @@ fun CardSettingsScreen( ) } +@Suppress("MagicNumber") @Composable fun CardSettingsReadCard( onScanCardClick: () -> Unit, @@ -114,6 +115,7 @@ fun CardSettingsReadCard( } } +@Suppress("ComplexMethod") @Composable fun CardSettings( state: CardSettingsScreenState, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt index 969b3e4408..1f5353c548 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt @@ -1,14 +1,13 @@ package com.tangem.tap.features.details.ui.cardsettings +import com.tangem.core.analytics.Analytics import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.domain.common.getTwinCardIdForUser -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.details.redux.CardSettingsState import com.tangem.tap.features.details.redux.DetailsAction -import com.tangem.wallet.R import org.rekotlin.Store class CardSettingsViewModel(private val store: Store) { diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt index d511348af9..ce5be61078 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.tangem.wallet.R +@Suppress("MagicNumber") @Composable fun TangemSwitch( modifier: Modifier = Modifier, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt index 6165dc5c39..cf2e38f02b 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt @@ -1,8 +1,7 @@ package com.tangem.tap.features.details.ui.details -import com.tangem.domain.common.TapWorkarounds.isSaltPay -import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.core.analytics.Analytics +import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.feedback.FeedbackEmail import com.tangem.tap.common.feedback.SupportInfo @@ -21,6 +20,8 @@ import com.tangem.wallet.BuildConfig import org.rekotlin.Store class DetailsViewModel(private val store: Store) { + + @Suppress("ComplexMethod") fun updateState(state: DetailsState): DetailsScreenState { val settings = SettingsElement.values().mapNotNull { when (it) { diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt index 02095d290a..0f57b021f1 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt @@ -42,6 +42,7 @@ fun ResetCardScreen(state: ResetCardScreenState, onBackPressed: () -> Unit) { ) } +@Suppress("LongMethod", "MagicNumber") @Composable fun ResetCardView(state: ResetCardScreenState) { Column( diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt index b25277864d..a0666ff19d 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt @@ -9,13 +9,15 @@ import org.rekotlin.Store class SecurityModeViewModel(val store: Store) { fun updateState(state: ManageSecurityState?): SecurityModeScreenState { - if (state == null) return SecurityModeScreenState( - availableOptions = emptyList(), - selectedSecurityMode = SecurityOption.LongTap, - isSaveChangesEnabled = false, - onNewModeSelected = {}, - onSaveChangesClicked = {}, - ) + if (state == null) { + return SecurityModeScreenState( + availableOptions = emptyList(), + selectedSecurityMode = SecurityOption.LongTap, + isSaveChangesEnabled = false, + onNewModeSelected = {}, + onSaveChangesClicked = {}, + ) + } return SecurityModeScreenState( availableOptions = state.allowedOptions.toList(), selectedSecurityMode = state.selectedOption, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt index da8e03c3b8..aba68641df 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt @@ -9,38 +9,36 @@ import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession import com.tangem.tap.store import com.tangem.wallet.R -class ApproveWcSessionDialog { - companion object { - fun create(session: WalletConnectSession, networks: List, context: Context): AlertDialog { - val sessionBlockchain = requireNotNull(session.wallet.blockchain) { "session network is null" } - val message = context.getString( - R.string.wallet_connect_request_session_start, - session.peerMeta.name, - sessionBlockchain.fullName, - session.peerMeta.url, - ) - return AlertDialog.Builder(context).apply { - setTitle(context.getString(R.string.wallet_connect_title)) - setMessage(message) - setPositiveButton(context.getText(R.string.common_start)) { _, _ -> +object ApproveWcSessionDialog { + fun create(session: WalletConnectSession, networks: List, context: Context): AlertDialog { + val sessionBlockchain = requireNotNull(session.wallet.blockchain) { "session network is null" } + val message = context.getString( + R.string.wallet_connect_request_session_start, + session.peerMeta.name, + sessionBlockchain.fullName, + session.peerMeta.url, + ) + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.wallet_connect_title)) + setMessage(message) + setPositiveButton(context.getText(R.string.common_start)) { _, _ -> + store.dispatch(GlobalAction.HideDialog) + store.dispatch(WalletConnectAction.ChooseNetwork(sessionBlockchain)) + } + if (networks.size > 1) { + setNeutralButton(context.getText(R.string.wallet_connect_select_network)) { _, _ -> store.dispatch(GlobalAction.HideDialog) - store.dispatch(WalletConnectAction.ChooseNetwork(sessionBlockchain)) + store.dispatch(WalletConnectAction.SelectNetwork(session = session, networks = networks)) } - if (networks.size > 1) { - setNeutralButton(context.getText(R.string.wallet_connect_select_network)) { _, _ -> - store.dispatch(GlobalAction.HideDialog) - store.dispatch(WalletConnectAction.SelectNetwork(session = session, networks = networks)) - } - } - setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> - store.dispatch(GlobalAction.HideDialog) - store.dispatch(WalletConnectAction.FailureEstablishingSession(session.session)) - } - setOnCancelListener { - store.dispatch(GlobalAction.HideDialog) - store.dispatch(WalletConnectAction.FailureEstablishingSession(session.session)) - } - }.create() - } + } + setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> + store.dispatch(GlobalAction.HideDialog) + store.dispatch(WalletConnectAction.FailureEstablishingSession(session.session)) + } + setOnCancelListener { + store.dispatch(GlobalAction.HideDialog) + store.dispatch(WalletConnectAction.FailureEstablishingSession(session.session)) + } + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/BnbTransactionDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/BnbTransactionDialog.kt index 8de826231f..f39ae20dde 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/BnbTransactionDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/BnbTransactionDialog.kt @@ -9,59 +9,57 @@ import com.tangem.tap.store import com.tangem.wallet.R import com.trustwallet.walletconnect.models.session.WCSession -class BnbTransactionDialog { - companion object { - fun create( - data: BinanceMessageData, - session: WCSession, - sessionId: Long, - dAppName: String, - context: Context, - ): AlertDialog { +object BnbTransactionDialog { + fun create( + data: BinanceMessageData, + session: WCSession, + sessionId: Long, + dAppName: String, + context: Context, + ): AlertDialog { - val message = when (data) { - is BinanceMessageData.Trade -> data.tradeData.map { - context.getString( - R.string.wallet_connect_bnb_trade_order_message, - it.symbol, - it.price, - it.quantity, - it.amount, - ) - }.joinToString(separator = "\n\n") - is BinanceMessageData.Transfer -> context.getString( - R.string.wallet_connect_bnb_transaction_message, - data.address, - data.outputAddress, - data.amount, + val message = when (data) { + is BinanceMessageData.Trade -> data.tradeData.map { + context.getString( + R.string.wallet_connect_bnb_trade_order_message, + it.symbol, + it.price, + it.quantity, + it.amount, + ) + }.joinToString(separator = "\n\n") + is BinanceMessageData.Transfer -> context.getString( + R.string.wallet_connect_bnb_transaction_message, + data.address, + data.outputAddress, + data.amount, + ) + } + + val fullMessage = context.getString( + R.string.wallet_connect_bnb_sign_message, + dAppName, message, + ) + val positiveButtonTitle = context.getText(R.string.common_sign) + + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.wallet_connect_title)) + setMessage(fullMessage) + setPositiveButton(positiveButtonTitle) { _, _ -> + store.dispatch( + WalletConnectAction.BinanceTransaction.Sign( + id = sessionId, + data = data.data, + sessionData = session, + ), ) } - - val fullMessage = context.getString( - R.string.wallet_connect_bnb_sign_message, - dAppName, message, - ) - val positiveButtonTitle = context.getText(R.string.common_sign) - - return AlertDialog.Builder(context).apply { - setTitle(context.getString(R.string.wallet_connect_title)) - setMessage(fullMessage) - setPositiveButton(positiveButtonTitle) { _, _ -> - store.dispatch( - WalletConnectAction.BinanceTransaction.Sign( - id = sessionId, - data = data.data, - sessionData = session, - ), - ) - } - setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> - store.dispatch(WalletConnectAction.RejectRequest(session, sessionId)) - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - }.create() - } + setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> + store.dispatch(WalletConnectAction.RejectRequest(session, sessionId)) + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt index 0213971713..d1962be5e4 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt @@ -9,22 +9,20 @@ import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.store import com.tangem.wallet.R -class ClipboardOrScanQrDialog { - companion object { - fun create(wcUri: String, context: Context): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(context.getString(R.string.wallet_connect_title)) - setMessage(context.getText(R.string.wallet_connect_clipboard_alert)) - setPositiveButton(context.getText(R.string.wallet_connect_paste_from_clipboard)) { _, _ -> - store.dispatch(WalletConnectAction.OpenSession(wcUri)) - } - setNegativeButton(context.getText(R.string.wallet_connect_scan_new_code)) { _, _ -> - store.dispatch(NavigationAction.NavigateTo(AppScreen.QrScan)) - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - }.create() - } +object ClipboardOrScanQrDialog { + fun create(wcUri: String, context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.wallet_connect_title)) + setMessage(context.getText(R.string.wallet_connect_clipboard_alert)) + setPositiveButton(context.getText(R.string.wallet_connect_paste_from_clipboard)) { _, _ -> + store.dispatch(WalletConnectAction.OpenSession(wcUri)) + } + setNegativeButton(context.getText(R.string.wallet_connect_scan_new_code)) { _, _ -> + store.dispatch(NavigationAction.NavigateTo(AppScreen.QrScan)) + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/PersonalSignDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/PersonalSignDialog.kt index c78549f273..5f6bf384ed 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/PersonalSignDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/PersonalSignDialog.kt @@ -8,24 +8,22 @@ import com.tangem.tap.store import com.tangem.wallet.R import com.trustwallet.walletconnect.models.session.WCSession -class PersonalSignDialog { - companion object { - fun create(data: PersonalSignDialogData, context: Context): AlertDialog { - val message = context.getString(R.string.wallet_connect_alert_sign_message, data.message) - return AlertDialog.Builder(context).apply { - setTitle(context.getString(R.string.wallet_connect_title)) - setMessage(message) - setPositiveButton(context.getText(R.string.common_sign)) { _, _ -> - store.dispatch(WalletConnectAction.SignMessage(data.session)) - } - setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> - store.dispatch(WalletConnectAction.RejectRequest(data.session, data.id)) - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - }.create() - } +object PersonalSignDialog { + fun create(data: PersonalSignDialogData, context: Context): AlertDialog { + val message = context.getString(R.string.wallet_connect_alert_sign_message, data.message) + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.wallet_connect_title)) + setMessage(message) + setPositiveButton(context.getText(R.string.common_sign)) { _, _ -> + store.dispatch(WalletConnectAction.SignMessage(data.session)) + } + setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> + store.dispatch(WalletConnectAction.RejectRequest(data.session, data.id)) + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + }.create() } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt index cd45c1c9be..4d502f7275 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt @@ -9,45 +9,40 @@ import com.tangem.tap.store import com.tangem.wallet.R import com.trustwallet.walletconnect.models.session.WCSession -class TransactionDialog { - companion object { - fun create( - data: TransactionRequestDialogData, - context: Context, - ): AlertDialog { - val message = context.getString( - R.string.wallet_connect_create_tx_message, - data.dAppName, - data.dAppUrl, - data.amount, - data.gasAmount, - data.totalAmount, - data.balance, - ) +object TransactionDialog { + fun create(data: TransactionRequestDialogData, context: Context): AlertDialog { + val message = context.getString( + R.string.wallet_connect_create_tx_message, + data.dAppName, + data.dAppUrl, + data.amount, + data.gasAmount, + data.totalAmount, + data.balance, + ) - val positiveButtonTitle = when (data.type) { - WcTransactionType.EthSignTransaction -> context.getText(R.string.common_sign) - WcTransactionType.EthSendTransaction -> context.getText(R.string.common_sign_and_send) - } - return AlertDialog.Builder(context).apply { - setTitle(context.getString(R.string.wallet_connect_title)) - setMessage(message) - setPositiveButton(positiveButtonTitle) { _, _ -> - if (data.isEnoughFundsToSend) { - store.dispatch(WalletConnectAction.SendTransaction(data.session)) - } else { - store.dispatch(WalletConnectAction.RejectRequest(data.session, data.id)) - store.dispatch(WalletConnectAction.NotEnoughFunds) - } - } - setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> - store.dispatch(WalletConnectAction.RejectRequest(data.session, data.id)) - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - }.create() + val positiveButtonTitle = when (data.type) { + WcTransactionType.EthSignTransaction -> context.getText(R.string.common_sign) + WcTransactionType.EthSendTransaction -> context.getText(R.string.common_sign_and_send) } + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.wallet_connect_title)) + setMessage(message) + setPositiveButton(positiveButtonTitle) { _, _ -> + if (data.isEnoughFundsToSend) { + store.dispatch(WalletConnectAction.SendTransaction(data.session)) + } else { + store.dispatch(WalletConnectAction.RejectRequest(data.session, data.id)) + store.dispatch(WalletConnectAction.NotEnoughFunds) + } + } + setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> + store.dispatch(WalletConnectAction.RejectRequest(data.session, data.id)) + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + }.create() } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/WcDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/WcDialog.kt deleted file mode 100644 index f71c14a348..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/WcDialog.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.tangem.tap.features.details.ui.walletconnect.dialogs - -import android.content.Context -import androidx.appcompat.app.AlertDialog - -class WcDialog { - companion object { - fun create(dialogData: DialogData, context: Context): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(dialogData.title) - setMessage(dialogData.message) - setPositiveButton(dialogData.positiveButton.title) { _, _ -> - dialogData.positiveButton.action() - } - setNegativeButton(dialogData.negativeButton.title) { _, _ -> - dialogData.negativeButton.action() - } - setOnDismissListener { dialogData.onDismissAction() } - }.create() - } - } -} - -data class DialogData( - val title: String, - val message: String, - val positiveButton: ButtonData, - val negativeButton: ButtonData, - val onDismissAction: () -> Unit, -) - -data class ButtonData( - val title: String, - val action: () -> Unit, -) - -//class WcDialogMessageBuilder(val messageRes: Int, val data: DialogMessageData? = null) { -// fun build(context: Context): String { -// when (data) { -// null -> context.getString(messageRes) -// } -// } -//} - -interface DialogMessageData - -data class WcTransactionDialogMessageData( - val dAppName: String, - val dAppUrl: String, - val amount: String, - val gasAmount: String, - val totalAmount: String, - val balance: String, - val isEnoughFundsToSend: Boolean, -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/Disclaimer.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/Disclaimer.kt index 1f72592557..27dd88ee13 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/Disclaimer.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/Disclaimer.kt @@ -51,6 +51,7 @@ class Start2CoinDisclaimer(dataProvider: DisclaimerDataProvider) : BaseDisclaime override fun getUri(): Uri = Uri.parse("$baseUrl/" + filename(dataProvider.getLanguage(), getRegion())) override fun getPreferenceKey(): String = "start2Coin_tos_accepted_${getRegion()}" + @Suppress("ComplexMethod") private fun filename(languageCode: String, regionCode: String?): String { return when { languageCode == "fr" && regionCode == "ch" -> "start2coin-fr-ch-tangem.html" diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt index 456818fe70..bef592e97e 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt @@ -3,10 +3,8 @@ package com.tangem.tap.features.disclaimer.redux import com.tangem.tap.common.redux.AppState import org.rekotlin.Action -class DisclaimerReducer { - companion object { - fun reduce(action: Action, state: AppState): DisclaimerState = internalReduce(action, state) - } +object DisclaimerReducer { + fun reduce(action: Action, state: AppState): DisclaimerState = internalReduce(action, state) } private fun internalReduce(action: Action, state: AppState): DisclaimerState { diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt index 99db90b3d8..b610475974 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt @@ -12,8 +12,7 @@ data class DisclaimerState( val showedFromScreen: AppScreen = AppScreen.Home, val callback: DisclaimerCallback? = null, val progressState: ProgressState? = null, -) : StateType { -} +) : StateType data class DisclaimerCallback( val onAccept: VoidCallback? = null, diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt index 506b8beae9..e19be8e926 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt @@ -5,7 +5,6 @@ import android.view.View import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding import com.tangem.core.ui.fragments.setStatusBarColor -import com.tangem.tap.common.extensions.configureSettings import com.tangem.tap.common.extensions.beginDelayedTransition import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerWebViewClient.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerWebViewClient.kt index c1a024fc0c..4e07832feb 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerWebViewClient.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerWebViewClient.kt @@ -59,10 +59,14 @@ class DisclaimerWebViewClient : WebViewClient() { super.onReceivedHttpError(view, resourceRequest, errorResponse) ifNotNull(resourceRequest, errorResponse) { request, response -> - if (request.url?.toString() != loadingUrl || response.statusCode < 400) return + if (request.url?.toString() != loadingUrl || response.statusCode < RESPONSE_USER_ERROR_STATUS_CODE) return if (progressState != ProgressState.Done) return progressState = ProgressState.Error } } + + companion object { + private const val RESPONSE_USER_ERROR_STATUS_CODE = 400 + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt b/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesAnimation.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesAnimation.kt index 58f9356349..76ef1a643e 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesAnimation.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesAnimation.kt @@ -22,6 +22,9 @@ import androidx.compose.ui.unit.dp import com.tangem.tap.common.compose.extensions.AnimatedValue import com.tangem.tap.common.compose.extensions.toAnimatable +private const val SCALE_SWITCH_BARRIER = 1.15f + +@Suppress("LongParameterList") @Composable fun HorizontalSlidingImage( painter: Painter, @@ -39,7 +42,7 @@ fun HorizontalSlidingImage( .requiredWidth(itemSize.width) .requiredHeight(itemSize.height) .graphicsLayer( - translationX = translateX.toAnimatable(isPaused = paused, duration = duration).value + translationX = translateX.toAnimatable(isPaused = paused, duration = duration).value, ), alignment = Alignment.TopStart, contentScale = ContentScale.FillBounds, @@ -54,7 +57,7 @@ fun StoriesTextAnimation( slideInDelay: Int = 200, slideDistance: Dp = 60.dp, label: String = "", - content: @Composable (Modifier) -> Unit + content: @Composable (Modifier) -> Unit, ) { val isLaunched = remember { mutableStateOf(false) } val transition = updateTransition(targetState = isLaunched.value, label = label) @@ -64,10 +67,10 @@ fun StoriesTextAnimation( tween( durationMillis = slideInDuration, delayMillis = slideInDelay, - easing = FastOutSlowInEasing + easing = FastOutSlowInEasing, ) }, - label = "Slide in" + label = "Slide in", ) { value -> if (value) 0.dp else slideDistance } val alpha = transition.animateFloat( @@ -75,16 +78,16 @@ fun StoriesTextAnimation( tween( durationMillis = slideInDuration * 2, delayMillis = slideInDelay, - easing = FastOutSlowInEasing + easing = FastOutSlowInEasing, ) }, - label = "Visibility" + label = "Visibility", ) { value -> if (value) 1f else 0f } content( Modifier .absoluteOffset(y = offsetY.value) - .alpha(alpha.value) + .alpha(alpha.value), ) LaunchedEffect(Unit) { isLaunched.value = true } @@ -95,9 +98,8 @@ fun StoriesBottomImageAnimation( initialScale: Float = 2.5f, firstStepDuration: Int, totalDuration: Int, - content: @Composable (Modifier) -> Unit + content: @Composable (Modifier) -> Unit, ) { - val scaleSwitchBarrier = 1.15f val secondStepDuration = totalDuration - firstStepDuration val isFirstStepLaunched = remember { mutableStateOf(false) } @@ -105,7 +107,7 @@ fun StoriesBottomImageAnimation( val firstTransition = updateTransition( targetState = isFirstStepLaunched.value, - label = "Image appearing" + label = "Image appearing", ) val firstScaleStep = firstTransition.animateFloat( transitionSpec = { @@ -114,12 +116,12 @@ fun StoriesBottomImageAnimation( easing = FastOutLinearInEasing, ) }, - label = "Appearing scale" - ) { value -> if (value) scaleSwitchBarrier else initialScale } + label = "Appearing scale", + ) { value -> if (value) SCALE_SWITCH_BARRIER else initialScale } val secondTransition = updateTransition( targetState = isSecondStepLaunched.value, - label = "Image slow outgoing" + label = "Image slow outgoing", ) val secondScaleStep = secondTransition.animateFloat( transitionSpec = { @@ -128,15 +130,15 @@ fun StoriesBottomImageAnimation( easing = LinearEasing, ) }, - label = "Outgoing scale" - ) { value -> if (value) 1f else scaleSwitchBarrier } + label = "Outgoing scale", + ) { value -> if (value) 1f else SCALE_SWITCH_BARRIER } val fadeIn = firstTransition.animateFloat( transitionSpec = { tween(durationMillis = 400) }, - label = "Fade in on start" + label = "Fade in on start", ) { value -> if (value) 1f else 0f } - if (firstScaleStep.value == scaleSwitchBarrier) { + if (firstScaleStep.value == SCALE_SWITCH_BARRIER) { isSecondStepLaunched.value = true } diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt index a459a4d701..2add8bf97a 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt @@ -1,3 +1,5 @@ +@file:Suppress("MagicNumber") + package com.tangem.tap.features.home.compose import androidx.compose.foundation.Image @@ -52,6 +54,9 @@ import com.tangem.tap.features.home.redux.HomeState import com.tangem.wallet.R import kotlin.math.max +private const val STEPS = 6 + +@Suppress("LongMethod", "ComplexMethod") @Composable fun StoriesScreen( homeState: MutableState = mutableStateOf(HomeState()), @@ -59,7 +64,6 @@ fun StoriesScreen( onShopButtonClick: () -> Unit, onSearchTokensClick: () -> Unit, ) { - val steps = 6 val currentStep = remember { mutableStateOf(1) } val systemUiController = rememberSystemUiController() @@ -69,7 +73,7 @@ fun StoriesScreen( currentStep.value = max(1, currentStep.value - 1) } val goToNextScreen = { - currentStep.value = if (currentStep.value < steps) currentStep.value + 1 else 1 + currentStep.value = if (currentStep.value < STEPS) currentStep.value + 1 else 1 } val isPressed = remember { mutableStateOf(false) } @@ -149,7 +153,7 @@ fun StoriesScreen( verticalArrangement = Arrangement.Center, ) { StoriesProgressBar( - steps = steps, + steps = STEPS, currentStep = currentStep.value, stepDuration = currentStep.duration(), paused = isPaused, @@ -181,27 +185,29 @@ fun StoriesScreen( .align(Alignment.BottomCenter) .fillMaxWidth(), ) { - if (currentStep.value == 4) Button( - onClick = onSearchTokensClick, - modifier = Modifier - .fillMaxWidth() - .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) - .height(48.dp), - colors = ButtonDefaults.textButtonColors( - backgroundColor = Color.White, - contentColor = Color(0xFF080C10), - ), - ) { - Image( - painter = painterResource(id = R.drawable.ic_search), - contentDescription = null, - ) - Text( - text = stringResource(id = R.string.search_tokens_title), - fontWeight = FontWeight.Medium, - fontSize = 16.sp, - textAlign = TextAlign.Center, - ) + if (currentStep.value == 4) { + Button( + onClick = onSearchTokensClick, + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .height(48.dp), + colors = ButtonDefaults.textButtonColors( + backgroundColor = Color.White, + contentColor = Color(0xFF080C10), + ), + ) { + Image( + painter = painterResource(id = R.drawable.ic_search), + contentDescription = null, + ) + Text( + text = stringResource(id = R.string.search_tokens_title), + fontWeight = FontWeight.Medium, + fontSize = 16.sp, + textAlign = TextAlign.Center, + ) + } } HomeButtons( modifier = Modifier @@ -216,6 +222,7 @@ fun StoriesScreen( } } +@Suppress("MagicNumber") private fun MutableState.duration(): Int = when (this.value) { 1 -> 8000 else -> 6000 diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt index e8a6b447f7..5ff3317d22 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt @@ -51,7 +51,7 @@ fun StoriesRevolutionaryWallet(stepDuration: Int) { isDarkBackground = true, ) } - } + }, ) } @@ -76,7 +76,7 @@ fun StoriesUltraSecureBackup(isPaused: Boolean, stepDuration: Int) { }, bottomContent = { FloatingCardsContent(isPaused, stepDuration) - } + }, ) } @@ -92,7 +92,7 @@ fun StoriesCurrencies(isPaused: Boolean, stepDuration: Int) { }, bottomContent = { StoriesCurrenciesContent(paused = isPaused, duration = stepDuration) - } + }, ) } @@ -108,7 +108,7 @@ fun StoriesWeb3(isPaused: Boolean, stepDuration: Int) { }, bottomContent = { StoriesWeb3Content(paused = isPaused, duration = stepDuration) - } + }, ) } @@ -133,7 +133,7 @@ fun StoriesWalletForEveryone(stepDuration: Int) { isDarkBackground = true, ) } - } + }, ) } @@ -146,7 +146,7 @@ private fun SplitContent( modifier = Modifier .fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Top + verticalArrangement = Arrangement.Top, ) { topContent() bottomContent() @@ -171,6 +171,7 @@ private fun TopContent( SpacerH32() } +@Suppress("MagicNumber") @Composable private fun StoriesTitleText( text: String, @@ -187,15 +188,14 @@ private fun StoriesTitleText( fontSize = 32.sp, fontWeight = FontWeight.SemiBold, color = if (isDarkBackground) Color.White else Color(0xFF090E13), - textAlign = TextAlign.Center + textAlign = TextAlign.Center, ) } } +@Suppress("MagicNumber") @Composable -private fun StoriesSubtitleText( - subtitleText: AnnotatedString, -) { +private fun StoriesSubtitleText(subtitleText: AnnotatedString) { val color = Color(0xFFA6AAAD) StoriesTextAnimation( @@ -209,7 +209,7 @@ private fun StoriesSubtitleText( text = subtitleText, fontSize = 20.sp, color = color, - textAlign = TextAlign.Center + textAlign = TextAlign.Center, ) } } @@ -224,7 +224,7 @@ private fun StoriesImage( painter = painterResource(id = drawableResId), contentDescription = null, contentScale = if (isDarkBackground) ContentScale.Inside else ContentScale.FillWidth, - modifier = modifier.fillMaxWidth() + modifier = modifier.fillMaxWidth(), ) } @@ -233,7 +233,6 @@ private fun String.annotated(): AnnotatedString { return buildAnnotatedString { append(source) } } - @Preview @Composable private fun RevolutionaryWalletPreview() { @@ -252,14 +251,12 @@ private fun CurrenciesPreview() { StoriesCurrencies(false, 6000) } - @Preview @Composable private fun Web3Preview() { StoriesWeb3(false, 6000) } - @Preview @Composable private fun WalletForEveryonePreview() { diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/CurrenciesWeb3Content.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/CurrenciesWeb3Content.kt index 85ec581b07..0f4bae7cff 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/content/CurrenciesWeb3Content.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/CurrenciesWeb3Content.kt @@ -53,7 +53,7 @@ fun StoriesCurrenciesContent( val chessOffset = if (index.isEven()) 0.dp else scaledItemSize.halfHeight() val animateFrom = chessOffset - moveItemToStartOfScreen - val animateTo = 50.dp - (50.dp * index * decreaseRate) + val animateTo = 50.dp - 50.dp * index * decreaseRate HorizontalSlidingImage( paused = paused, @@ -70,6 +70,7 @@ fun StoriesCurrenciesContent( } } +@Suppress("MagicNumber") @Composable fun StoriesWeb3Content( paused: Boolean, @@ -99,7 +100,7 @@ fun StoriesWeb3Content( val chessOffset = if (index.isEven()) 0.dp else scaledItemSize.width / 3 val animateFrom = chessOffset - moveItemToStartOfScreen - val animateTo = 70.dp - (70.dp * index * decreaseRate) + val animateTo = 70.dp - 70.dp * index * decreaseRate HorizontalSlidingImage( paused = paused, diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt index 393642f976..3a7b6da393 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt @@ -5,8 +5,17 @@ import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.tween import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.* -import androidx.compose.runtime.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -25,10 +34,11 @@ import com.tangem.tap.features.home.compose.StoriesBottomImageAnimation import com.tangem.tap.features.home.compose.StoriesTextAnimation import com.tangem.wallet.R +@Suppress("LongMethod", "ComplexMethod", "MagicNumber") @Composable fun FirstStoriesContent( isPaused: Boolean, duration: Int = 8_000, - hideContent: (Boolean) -> Unit + hideContent: (Boolean) -> Unit, ) { val screenState = remember { mutableStateOf(StartingScreenState.INIT) } val progress = remember { Animatable(0f) } @@ -41,8 +51,8 @@ fun FirstStoriesContent( targetValue = 2f, animationSpec = tween( durationMillis = duration, - easing = LinearEasing - ) + easing = LinearEasing, + ), ) } } @@ -80,7 +90,7 @@ fun FirstStoriesContent( .padding(start = 20.dp, end = 20.dp, bottom = 100.dp), text = textId?.let { stringResource(textId) } ?: "", textStyle = style, - fontSizeRange = FontSizeRange(20.sp, 60.sp) + fontSizeRange = FontSizeRange(20.sp, 60.sp), ) } else { Column( @@ -88,7 +98,7 @@ fun FirstStoriesContent( ) { Box( modifier = Modifier - .weight(0.7f) + .weight(0.7f), ) { if (screenState.isMeetTangemDisplaying()) { StoriesTextAnimation( @@ -100,10 +110,9 @@ fun FirstStoriesContent( .alpha(if (screenState.value == StartingScreenState.SHOW_CARD) 0f else 1f), text = textId?.let { stringResource(textId) } ?: "", textStyle = style, - fontSizeRange = FontSizeRange(30.sp, 50.sp) + fontSizeRange = FontSizeRange(30.sp, 50.sp), ) } - } } Box( @@ -155,7 +164,6 @@ private fun MutableState.isMeetTangemDisplaying(): Boolean return this.value == StartingScreenState.MEET_TANGEM } - @Preview @Composable fun FirstStoriesPreview() { diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/FloatingCardsContent.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/FloatingCardsContent.kt index 8528982857..16e9044852 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/content/FloatingCardsContent.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/FloatingCardsContent.kt @@ -26,7 +26,7 @@ fun FloatingCardsContent( FloatingCard.second(), FloatingCard.third(), ) - Box() { + Box { cards.forEach { floatingCard -> FloatingCard.Item( isPaused = isPaused, @@ -47,57 +47,58 @@ private data class CardValues( val scale: AnimatedValue = AnimatedValue(1f, 1f), ) -private class FloatingCard { - companion object { +private object FloatingCard { - @Composable - fun Item( - isPaused: Boolean, - stepDuration: Int, - imageBitmap: ImageBitmap, - cardValues: CardValues, - ) { - Image( - bitmap = imageBitmap, - contentDescription = "Floating Tangem card", - modifier = Modifier - .graphicsLayer( - translationX = cardValues.translateX.toAnimatable(isPaused, stepDuration).value, - translationY = cardValues.translateY.toAnimatable(isPaused, stepDuration).value, - rotationX = cardValues.rotationX.toAnimatable(isPaused, stepDuration).value, - rotationY = cardValues.rotationY.toAnimatable(isPaused, stepDuration).value, - rotationZ = cardValues.rotationZ.toAnimatable(isPaused, stepDuration).value, - scaleX = cardValues.scale.toAnimatable(isPaused, stepDuration).value, - scaleY = cardValues.scale.toAnimatable(isPaused, stepDuration).value, - ), - ) - } - - fun first(): CardValues = CardValues( - translateX = -400f to -350f, - translateY = 30f to 32f, - rotationX = 10f to 15f, - rotationY = 15f to 15f, - rotationZ = 40f to 27f, - scale = 0.6f to 0.6f, - ) - - fun second(): CardValues = CardValues( - translateX = 350f to 300f, - translateY = -70f to 0f, - rotationX = 30f to 48f, - rotationY = 0f to 5f, - rotationZ = -34f to -42f, - scale = 0.47f to 0.35f, - ) - - fun third(): CardValues = CardValues( - translateX = 320f to 250f, - translateY = 500f to 500f, - rotationX = 0f to 3f, - rotationY = 10f to 10f, - rotationZ = -45f to -30f, - scale = 0.6f to 0.75f, + @Composable + fun Item( + isPaused: Boolean, + stepDuration: Int, + imageBitmap: ImageBitmap, + cardValues: CardValues, + ) { + Image( + bitmap = imageBitmap, + contentDescription = "Floating Tangem card", + modifier = Modifier + .graphicsLayer( + translationX = cardValues.translateX.toAnimatable(isPaused, stepDuration).value, + translationY = cardValues.translateY.toAnimatable(isPaused, stepDuration).value, + rotationX = cardValues.rotationX.toAnimatable(isPaused, stepDuration).value, + rotationY = cardValues.rotationY.toAnimatable(isPaused, stepDuration).value, + rotationZ = cardValues.rotationZ.toAnimatable(isPaused, stepDuration).value, + scaleX = cardValues.scale.toAnimatable(isPaused, stepDuration).value, + scaleY = cardValues.scale.toAnimatable(isPaused, stepDuration).value, + ), ) } + + @Suppress("MagicNumber") + fun first(): CardValues = CardValues( + translateX = -400f to -350f, + translateY = 30f to 32f, + rotationX = 10f to 15f, + rotationY = 15f to 15f, + rotationZ = 40f to 27f, + scale = 0.6f to 0.6f, + ) + + @Suppress("MagicNumber") + fun second(): CardValues = CardValues( + translateX = 350f to 300f, + translateY = -70f to 0f, + rotationX = 30f to 48f, + rotationY = 0f to 5f, + rotationZ = -34f to -42f, + scale = 0.47f to 0.35f, + ) + + @Suppress("MagicNumber") + fun third(): CardValues = CardValues( + translateX = 320f to 250f, + translateY = 500f to 500f, + rotationX = 0f to 3f, + rotationY = 10f to 10f, + rotationZ = -45f to -30f, + scale = 0.6f to 0.75f, + ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt index cea593e6fe..5779309916 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.unit.sp import com.tangem.core.ui.components.SpacerS8 import com.tangem.wallet.R +@Suppress("MagicNumber") @Composable fun HomeButtons( modifier: Modifier = Modifier, @@ -82,6 +83,7 @@ fun HomeButtons( } } +@Suppress("LongParameterList") @Composable fun ProgressButton( modifier: Modifier, diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 167b7ec607..0e98babe31 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -17,7 +17,7 @@ import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.scanCard.ScanCardProcessor import com.tangem.tap.features.home.BELARUS_COUNTRY_CODE import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE -import com.tangem.tap.features.home.redux.HomeMiddleware.Companion.BUY_WALLET_URL +import com.tangem.tap.features.home.redux.HomeMiddleware.BUY_WALLET_URL import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.tap.preferencesStorage import com.tangem.tap.scope @@ -30,12 +30,10 @@ import org.rekotlin.Action import org.rekotlin.Middleware import timber.log.Timber -class HomeMiddleware { - companion object { - val handler = homeMiddleware +object HomeMiddleware { + val handler = homeMiddleware - const val BUY_WALLET_URL = "https://tangem.com/ru/resellers/" - } + const val BUY_WALLET_URL = "https://tangem.com/ru/resellers/" } private val homeMiddleware: Middleware = { _, _ -> @@ -57,7 +55,7 @@ private fun handleHomeAction(action: Action) { is HomeAction.ShouldScanCardOnResume -> { if (action.shouldScanCard) { store.dispatch(HomeAction.ShouldScanCardOnResume(false)) - postUiDelayBg(700) { store.dispatch(HomeAction.ReadCard) } + postUiDelayBg(ms = 700) { store.dispatch(HomeAction.ReadCard) } } } is HomeAction.ReadCard -> readCard() @@ -94,18 +92,15 @@ private fun readCard() = scope.launch { scope.launch { if (preferencesStorage.shouldSaveUserWallets) { val userWallet = UserWalletBuilder(scanResponse).build() ?: return@launch - userWalletsListManager.save(userWallet) - .doOnFailure { error -> - Timber.e(error, "Unable to save user wallet") - store.onCardScanned(scanResponse) - } - .doOnSuccess { - scope.launch { store.onUserWalletSelected(userWallet) } - } - .doOnResult { - changeButtonState(ButtonState.ENABLED) - store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) - } + userWalletsListManager.save(userWallet).doOnFailure { error -> + Timber.e(error, "Unable to save user wallet") + store.onCardScanned(scanResponse) + }.doOnSuccess { + scope.launch { store.onUserWalletSelected(userWallet) } + }.doOnResult { + changeButtonState(ButtonState.ENABLED) + store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) + } } else { store.onCardScanned(scanResponse) changeButtonState(ButtonState.ENABLED) diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt index e731e02945..a398a92c9c 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt @@ -3,10 +3,8 @@ package com.tangem.tap.features.home.redux import com.tangem.tap.common.redux.AppState import org.rekotlin.Action -class HomeReducer { - companion object { - fun reduce(action: Action, state: AppState): HomeState = internalReduce(action, state) - } +object HomeReducer { + fun reduce(action: Action, state: AppState): HomeState = internalReduce(action, state) } private fun internalReduce(action: Action, state: AppState): HomeState { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index cb01879840..a92129dbd9 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -17,82 +17,79 @@ import kotlinx.coroutines.launch /** [REDACTED_AUTHOR] */ -class OnboardingHelper { - companion object { - - fun isOnboardingCase(response: ScanResponse): Boolean { - val cardInfoStorage = preferencesStorage.usedCardsPrefStorage - val cardId = response.card.cardId - return when { - response.isTangemTwins() -> { - if (!response.twinsIsTwinned()) { - true - } else { - cardInfoStorage.isActivationInProgress(cardId) - } - } - response.card.wallets.isNotEmpty() -> cardInfoStorage.isActivationInProgress(cardId) - else -> true - } - } - - fun whereToNavigate(scanResponse: ScanResponse): AppScreen { - return when (scanResponse.productType) { - ProductType.Note -> AppScreen.OnboardingNote - ProductType.Wallet -> if (scanResponse.card.settings.isBackupAllowed) { - AppScreen.OnboardingWallet +object OnboardingHelper { + fun isOnboardingCase(response: ScanResponse): Boolean { + val cardInfoStorage = preferencesStorage.usedCardsPrefStorage + val cardId = response.card.cardId + return when { + response.isTangemTwins() -> { + if (!response.twinsIsTwinned()) { + true } else { - AppScreen.OnboardingOther - } - ProductType.Twins -> AppScreen.OnboardingTwins - ProductType.SaltPay -> AppScreen.OnboardingWallet - ProductType.Start2Coin -> throw java.lang.UnsupportedOperationException( - "Onboarding for Start2Coin cards is not supported" - ) - } - } - - fun trySaveWalletAndNavigateToWalletScreen( - scanResponse: ScanResponse, - accessCode: String? = null, - backupCardsIds: List? = null, - ) { - when { - // When should save user wallets, then save card without navigate to save wallet screen - preferencesStorage.shouldSaveUserWallets -> scope.launch { - store.dispatchOnMain( - SaveWalletAction.ProvideBackupInfo( - scanResponse = scanResponse, - accessCode = accessCode, - backupCardsIds = backupCardsIds?.toSet(), - ), - ) - store.dispatchOnMain(SaveWalletAction.Save) - } - // When should not save user wallets but device has biometry and save wallet screen has not been shown, - // then open save wallet screen - tangemSdkManager.canUseBiometry && - preferencesStorage.shouldShowSaveUserWalletScreen -> scope.launch { - store.onCardScanned(scanResponse) - - delay(timeMillis = 1_200) - - store.dispatchOnMain( - SaveWalletAction.ProvideBackupInfo( - scanResponse = scanResponse, - accessCode = accessCode, - backupCardsIds = backupCardsIds?.toSet(), - ), - ) - store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.SaveWallet)) - } - // If device has no biometry and save wallet screen has been shown, then go through old scenario - else -> scope.launch { - store.onCardScanned(scanResponse) + cardInfoStorage.isActivationInProgress(cardId) } } - - store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) + response.card.wallets.isNotEmpty() -> cardInfoStorage.isActivationInProgress(cardId) + else -> true } } + + fun whereToNavigate(scanResponse: ScanResponse): AppScreen { + return when (scanResponse.productType) { + ProductType.Note -> AppScreen.OnboardingNote + ProductType.Wallet -> if (scanResponse.card.settings.isBackupAllowed) { + AppScreen.OnboardingWallet + } else { + AppScreen.OnboardingOther + } + ProductType.Twins -> AppScreen.OnboardingTwins + ProductType.SaltPay -> AppScreen.OnboardingWallet + ProductType.Start2Coin -> throw java.lang.UnsupportedOperationException( + "Onboarding for Start2Coin cards is not supported", + ) + } + } + + fun trySaveWalletAndNavigateToWalletScreen( + scanResponse: ScanResponse, + accessCode: String? = null, + backupCardsIds: List? = null, + ) { + when { + // When should save user wallets, then save card without navigate to save wallet screen + preferencesStorage.shouldSaveUserWallets -> scope.launch { + store.dispatchOnMain( + SaveWalletAction.ProvideBackupInfo( + scanResponse = scanResponse, + accessCode = accessCode, + backupCardsIds = backupCardsIds?.toSet(), + ), + ) + store.dispatchOnMain(SaveWalletAction.Save) + } + // When should not save user wallets but device has biometry and save wallet screen has not been shown, + // then open save wallet screen + tangemSdkManager.canUseBiometry && + preferencesStorage.shouldShowSaveUserWalletScreen -> scope.launch { + store.onCardScanned(scanResponse) + + delay(timeMillis = 1_200) + + store.dispatchOnMain( + SaveWalletAction.ProvideBackupInfo( + scanResponse = scanResponse, + accessCode = accessCode, + backupCardsIds = backupCardsIds?.toSet(), + ), + ) + store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.SaveWallet)) + } + // If device has no biometry and save wallet screen has been shown, then go through old scenario + else -> scope.launch { + store.onCardScanned(scanResponse) + } + } + + store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt index 853044464b..dcc026a041 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt @@ -55,7 +55,7 @@ class OnboardingManager( } } is Result.Failure -> { - val error = (result.error as? TapError) ?: TapError.UnknownError + val error = result.error as? TapError ?: TapError.UnknownError when (error) { is TapError.WalletManager.NoAccountError -> OnboardingWalletBalance.error(error) // NoInternetConnection, WalletManager.InternalError @@ -100,11 +100,11 @@ data class OnboardingWalletBalance( val criticalError: TapError? = null, ) { - fun balanceIsToppedUp(): Boolean = value.isPositive() || hasIncomingTransaction - val amountToCreateAccount: String? get() = if (error is TapError.WalletManager.NoAccountError) error.customMessage else null + fun balanceIsToppedUp(): Boolean = value.isPositive() || hasIncomingTransaction + companion object { fun error(error: TapError): OnboardingWalletBalance = OnboardingWalletBalance( state = ProgressState.Error, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt index 209fc789eb..b9aa35f096 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt @@ -12,39 +12,23 @@ import com.tangem.tap.preferencesStorage /** [REDACTED_AUTHOR] */ -class OnboardingSaltPayHelper { - companion object { +object OnboardingSaltPayHelper { + suspend fun isOnboardingCase(scanResponse: ScanResponse, manager: SaltPayActivationManager): Result { + return try { + var updatedStep = manager.update(SaltPayActivationStep.None, null).successOr { return it } - suspend fun isOnboardingCase( - scanResponse: ScanResponse, - manager: SaltPayActivationManager, - ): Result { - return try { - var updatedStep = manager.update(SaltPayActivationStep.None, null).successOr { return it } - - val cardStorage = preferencesStorage.usedCardsPrefStorage - val activationIsFinished = cardStorage.isActivationFinished(scanResponse.card.cardId) - if (updatedStep == SaltPayActivationStep.Success && activationIsFinished) { - updatedStep = SaltPayActivationStep.Finished - } - val isActivationCase = updatedStep != SaltPayActivationStep.Finished - val isBackupCase = scanResponse.card.backupStatus?.isActive == false - Result.Success(isActivationCase || isBackupCase) - } catch (ex: Exception) { - Result.Failure(ex) - } catch (error: SaltPayActivationError) { - Result.Failure(error) + val cardStorage = preferencesStorage.usedCardsPrefStorage + val activationIsFinished = cardStorage.isActivationFinished(scanResponse.card.cardId) + if (updatedStep == SaltPayActivationStep.Success && activationIsFinished) { + updatedStep = SaltPayActivationStep.Finished } + val isActivationCase = updatedStep != SaltPayActivationStep.Finished + val isBackupCase = scanResponse.card.backupStatus?.isActive == false + Result.Success(isActivationCase || isBackupCase) + } catch (ex: Exception) { + Result.Failure(ex) + } catch (error: SaltPayActivationError) { + Result.Failure(error) } - - fun testProceedToOnboarding( - scanResponse: ScanResponse, - manager: SaltPayActivationManager, - ): Result = Result.Success(true) - - fun testProceedToMainScreen( - scanResponse: ScanResponse, - manager: SaltPayActivationManager, - ): Result = Result.Success(false) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt index bd7193cedc..4538cb5145 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt @@ -2,9 +2,9 @@ package com.tangem.tap.features.onboarding.products.note.redux import com.tangem.common.CompletionResult import com.tangem.common.extensions.guard +import com.tangem.core.analytics.Analytics import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchDebugErrorNotification @@ -36,10 +36,8 @@ import org.rekotlin.Action import org.rekotlin.DispatchFunction import org.rekotlin.Middleware -class OnboardingNoteMiddleware { - companion object { - val handler = onboardingNoteMiddleware - } +object OnboardingNoteMiddleware { + val handler = onboardingNoteMiddleware } private val onboardingNoteMiddleware: Middleware = { dispatch, state -> @@ -51,6 +49,7 @@ private val onboardingNoteMiddleware: Middleware = { dispatch, state - } } +@Suppress("LongMethod", "ComplexMethod", "MagicNumber") private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch: DispatchFunction) { if (action !is OnboardingNoteAction) return if (DemoHelper.tryHandle(appState, action)) return @@ -80,7 +79,7 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch noteState.walletBalance.balanceIsToppedUp() -> OnboardingNoteStep.Done else -> OnboardingNoteStep.TopUpWallet } - store.dispatch((OnboardingNoteAction.SetStepOfScreen(step))) + store.dispatch(OnboardingNoteAction.SetStepOfScreen(step)) } is OnboardingNoteAction.SetStepOfScreen -> { when (action.step) { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt index 7626922560..5552fc8b50 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt @@ -4,10 +4,8 @@ import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import org.rekotlin.Action -class OnboardingNoteReducer { - companion object { - fun reduce(action: Action, state: AppState): OnboardingNoteState = internalReduce(action, state) - } +object OnboardingNoteReducer { + fun reduce(action: Action, state: AppState): OnboardingNoteState = internalReduce(action, state) } private fun internalReduce(action: Action, appState: AppState): OnboardingNoteState { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt index e340caec6b..98f5ce5df2 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt @@ -2,9 +2,9 @@ package com.tangem.tap.features.onboarding.products.otherCards.redux import com.tangem.blockchain.common.Blockchain import com.tangem.common.CompletionResult +import com.tangem.core.analytics.Analytics import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.postUi import com.tangem.tap.common.redux.AppState @@ -20,10 +20,8 @@ import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.Middleware -class OnboardingOtherCardsMiddleware { - companion object { - val handler = onboardingOtherCardsMiddleware - } +object OnboardingOtherCardsMiddleware { + val handler = onboardingOtherCardsMiddleware } private val onboardingOtherCardsMiddleware: Middleware = { dispatch, _ -> @@ -35,6 +33,7 @@ private val onboardingOtherCardsMiddleware: Middleware = { dispatch, _ } } +@Suppress("LongMethod", "ComplexMethod", "MagicNumber") private fun handleOtherCardsAction(action: Action) { if (action !is OnboardingOtherCardsAction) return val globalState = store.state.globalState @@ -59,7 +58,7 @@ private fun handleOtherCardsAction(action: Action) { card.wallets.isEmpty() -> OnboardingOtherCardsStep.CreateWallet else -> OnboardingOtherCardsStep.Done } - store.dispatch((OnboardingOtherCardsAction.SetStepOfScreen(step))) + store.dispatch(OnboardingOtherCardsAction.SetStepOfScreen(step)) } is OnboardingOtherCardsAction.SetStepOfScreen -> { when (action.step) { @@ -71,7 +70,7 @@ private fun handleOtherCardsAction(action: Action) { onboardingManager.activationFinished(card.cardId) postUi(200) { store.dispatch(OnboardingOtherCardsAction.Confetti.Show) } } - else -> {} + else -> Unit } } is OnboardingOtherCardsAction.CreateWallet -> { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt index e8dc2ae111..3dae886ef6 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt @@ -4,10 +4,8 @@ import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import org.rekotlin.Action -class OnboardingOtherCardsReducer { - companion object { - fun reduce(action: Action, state: AppState): OnboardingOtherCardsState = internalReduce(action, state) - } +object OnboardingOtherCardsReducer { + fun reduce(action: Action, state: AppState): OnboardingOtherCardsState = internalReduce(action, state) } private fun internalReduce(action: Action, appState: AppState): OnboardingOtherCardsState { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index f89ce801df..3a636728f0 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -4,10 +4,10 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.extensions.Result import com.tangem.common.CompletionResult import com.tangem.common.extensions.guard +import com.tangem.core.analytics.Analytics import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchDialogShow @@ -39,10 +39,8 @@ import org.rekotlin.Action import org.rekotlin.DispatchFunction import org.rekotlin.Middleware -class TwinCardsMiddleware { - companion object { - val handler = twinsWalletMiddleware - } +object TwinCardsMiddleware { + val handler = twinsWalletMiddleware } private val twinsWalletMiddleware: Middleware = { dispatch, state -> @@ -54,6 +52,7 @@ private val twinsWalletMiddleware: Middleware = { dispatch, state -> } } +@Suppress("LongMethod", "ComplexMethod", "MagicNumber") private fun handle(action: Action, dispatch: DispatchFunction) { val action = action as? TwinCardsAction ?: return @@ -111,7 +110,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { twinCardsState.walletBalance.balanceIsToppedUp() -> TwinCardsStep.Done else -> TwinCardsStep.TopUpWallet } - store.dispatch((TwinCardsAction.SetStepOfScreen(step))) + store.dispatch(TwinCardsAction.SetStepOfScreen(step)) } else { store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Welcome)) } @@ -266,7 +265,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { scope.launch { val loadedBalance = onboardingManager?.updateBalance(walletManager) ?: return@launch - loadedBalance.criticalError?.let { store.dispatchErrorNotification(it) } + loadedBalance.criticalError?.let(store::dispatchErrorNotification) delay(if (isLoadedBefore) 0 else 300) withMainContext { store.dispatch(TwinCardsAction.Balance.Set(loadedBalance)) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsReducer.kt index d501a484a6..878176c323 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsReducer.kt @@ -4,12 +4,11 @@ import com.tangem.domain.common.getTwinCardNumber import com.tangem.tap.common.redux.AppState import org.rekotlin.Action -class TwinCardsReducer { - companion object { - fun reduce(action: Action, state: AppState): TwinCardsState = internalReduce(action, state) - } +object TwinCardsReducer { + fun reduce(action: Action, state: AppState): TwinCardsState = internalReduce(action, state) } +@Suppress("ComplexMethod") private fun internalReduce(action: Action, state: AppState): TwinCardsState { if (action !is TwinCardsAction) return state.twinCardsState diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt index e3867c6b8d..aa307a448d 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt @@ -13,12 +13,12 @@ import coil.load import com.tangem.Message import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.VoidCallback +import com.tangem.core.analytics.Analytics import com.tangem.core.ui.fragments.setStatusBarColor import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TwinCardNumber import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget import com.tangem.tap.common.AndroidAssetReader -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.beginDelayedTransition import com.tangem.tap.common.extensions.getDrawableCompat @@ -40,6 +40,7 @@ import com.tangem.tap.store import com.tangem.wallet.R import com.tangem.wallet.databinding.LayoutOnboardingContainerTopBinding +@Suppress("LargeClass") class TwinsCardsFragment : BaseOnboardingFragment() { private val mainBinding by lazy { binding.vMain } @@ -64,6 +65,7 @@ class TwinsCardsFragment : BaseOnboardingFragment() { } } + @Suppress("MagicNumber") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) addBackPressHandler(this) @@ -409,8 +411,7 @@ class TwinsCardsFragment : BaseOnboardingFragment() { TwinCardsAction.Wallet.HandleOnBackPressed { should, popAction -> store.dispatch(TwinCardsAction.Confetti.Hide) showConfetti(false) - if (should) switchToCard(TwinCardNumber.First, true, popAction) - else popAction() + if (should) switchToCard(TwinCardNumber.First, true, popAction) else popAction() }, ) } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/dialog/CreateWalletInterruptDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/dialog/CreateWalletInterruptDialog.kt index 62d98c1f69..72a7ae7f2e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/dialog/CreateWalletInterruptDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/dialog/CreateWalletInterruptDialog.kt @@ -4,21 +4,18 @@ import android.content.Context import androidx.appcompat.app.AlertDialog import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.tangem.tap.common.extensions.dispatchDialogHide -import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.store import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ -class CreateWalletInterruptDialog { - companion object { - fun create(state: TwinCardsAction.Wallet.ShowInterruptDialog, context: Context): AlertDialog { - return MaterialAlertDialogBuilder(context) - .setMessage(R.string.onboarding_twin_exit_warning) - .setPositiveButton(R.string.warning_button_ok) { _, _ -> } - .setOnDismissListener { store.dispatchDialogHide() } - .create() - } +object CreateWalletInterruptDialog { + fun create(context: Context): AlertDialog { + return MaterialAlertDialogBuilder(context) + .setMessage(R.string.onboarding_twin_exit_warning) + .setPositiveButton(R.string.warning_button_ok) { _, _ -> } + .setOnDismissListener { store.dispatchDialogHide() } + .create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index eeb074dc0c..b4daef5268 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -4,13 +4,13 @@ import android.net.Uri import com.tangem.blockchain.common.Blockchain import com.tangem.common.CompletionResult import com.tangem.common.extensions.ifNotNull +import com.tangem.core.analytics.Analytics import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.extensions.withMainContext import com.tangem.operations.backup.BackupService import com.tangem.tap.backupService -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain @@ -37,10 +37,8 @@ import org.rekotlin.Action import org.rekotlin.DispatchFunction import org.rekotlin.Middleware -class OnboardingWalletMiddleware { - companion object { - val handler = onboardingWalletMiddleware - } +object OnboardingWalletMiddleware { + val handler = onboardingWalletMiddleware } private val onboardingWalletMiddleware: Middleware = { dispatch, state -> @@ -52,6 +50,7 @@ private val onboardingWalletMiddleware: Middleware = { dispatch, state } } +@Suppress("LongMethod", "ComplexMethod") private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: DispatchFunction) { if (action !is OnboardingWalletAction) return @@ -218,6 +217,7 @@ class BackupMiddleware { } } +@Suppress("LongMethod", "ComplexMethod", "MagicNumber") private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) { if (DemoHelper.tryHandle(appState, action)) return val globalState = appState()?.globalState ?: return @@ -396,7 +396,7 @@ private fun initSaltPayOnBackupFinishedIfNeeded( onboardingWalletState: OnboardingWalletState, ) { if (onboardingWalletState.isSaltPay && onboardingWalletState.onboardingSaltPayState == null) { - if (scanResponse == null) throw IllegalArgumentException() + if (scanResponse == null) error("scanning response is null") val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse) store.dispatchOnMain(OnboardingSaltPayAction.SetDependencies(manager, config)) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt index 2e50716048..ffc422f1a3 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt @@ -7,10 +7,8 @@ import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.Onboardi import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayReducer import org.rekotlin.Action -class OnboardingWalletReducer { - companion object { - fun reduce(action: Action, state: AppState): OnboardingWalletState = internalReduce(action, state) - } +object OnboardingWalletReducer { + fun reduce(action: Action, state: AppState): OnboardingWalletState = internalReduce(action, state) } private fun internalReduce(action: Action, appState: AppState): OnboardingWalletState { @@ -36,103 +34,78 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingWallet } } -private class ReducerForGlobalAction { - companion object { - fun reduce(action: GlobalAction.Onboarding, state: OnboardingWalletState): OnboardingWalletState { - return when (action) { - is GlobalAction.Onboarding.Start -> { - OnboardingWalletState( - backupState = state.backupState.copy( - maxBackupCards = if (action.scanResponse.isSaltPay()) 1 else 2, - canSkipBackup = if (action.scanResponse.isSaltPay()) false else action.canSkipBackup, - ), - isSaltPay = action.scanResponse.isSaltPay(), - ) - } - is GlobalAction.Onboarding.StartForUnfinishedBackup -> { - OnboardingWalletState( - backupState = state.backupState.copy( - maxBackupCards = action.addedBackupCardsCount, - canSkipBackup = false, - ), - isSaltPay = false, - ) - } - else -> state +private object ReducerForGlobalAction { + fun reduce(action: GlobalAction.Onboarding, state: OnboardingWalletState): OnboardingWalletState { + return when (action) { + is GlobalAction.Onboarding.Start -> { + OnboardingWalletState( + backupState = state.backupState.copy( + maxBackupCards = if (action.scanResponse.isSaltPay()) 1 else 2, + canSkipBackup = if (action.scanResponse.isSaltPay()) false else action.canSkipBackup, + ), + isSaltPay = action.scanResponse.isSaltPay(), + ) } + is GlobalAction.Onboarding.StartForUnfinishedBackup -> { + OnboardingWalletState( + backupState = state.backupState.copy( + maxBackupCards = action.addedBackupCardsCount, + canSkipBackup = false, + ), + isSaltPay = false, + ) + } + else -> state } } } -private class BackupReducer { - companion object { - fun reduce( - action: BackupAction, - state: BackupState, - isSaltPay: Boolean, - ): BackupState { - - return when (action) { - is BackupAction.IntroduceBackup -> BackupState( - backupStep = BackupStep.InitBackup, - maxBackupCards = if (isSaltPay) 1 else 2, - canSkipBackup = state.canSkipBackup, +private object BackupReducer { + @Suppress("ComplexMethod") + fun reduce(action: BackupAction, state: BackupState, isSaltPay: Boolean): BackupState { + return when (action) { + is BackupAction.IntroduceBackup -> BackupState( + backupStep = BackupStep.InitBackup, + maxBackupCards = if (isSaltPay) 1 else 2, + canSkipBackup = state.canSkipBackup, + ) + BackupAction.StartAddingPrimaryCard -> state.copy(backupStep = BackupStep.ScanOriginCard) + BackupAction.StartAddingBackupCards -> state.copy(backupStep = BackupStep.AddBackupCards) + BackupAction.AddBackupCard.Success -> state.copy(backupCardsNumber = state.backupCardsNumber + 1) + BackupAction.ShowAccessCodeInfoScreen -> state.copy(backupStep = BackupStep.SetAccessCode) + BackupAction.ShowEnterAccessCodeScreen -> state.copy( + backupStep = BackupStep.EnterAccessCode, + accessCodeError = null, + ) + is BackupAction.SaveFirstAccessCode -> state.copy( + backupStep = BackupStep.ReenterAccessCode, + accessCode = action.accessCode, + ) + is BackupAction.SetAccessCodeError -> state.copy(accessCodeError = action.error) + is BackupAction.PrepareToWritePrimaryCard -> if (state.primaryCardId == null) { + state.copy( + primaryCardId = backupService.primaryCardId, + backupCardIds = backupService.backupCardIds, + backupCardsNumber = backupService.backupCardIds.size, + backupStep = BackupStep.WritePrimaryCard, ) - BackupAction.StartAddingPrimaryCard -> state.copy(backupStep = BackupStep.ScanOriginCard) - BackupAction.StartAddingBackupCards -> { - state.copy(backupStep = BackupStep.AddBackupCards) - } - BackupAction.AddBackupCard.Success -> { - state.copy(backupCardsNumber = state.backupCardsNumber + 1) - } - BackupAction.ShowAccessCodeInfoScreen -> { - state.copy(backupStep = BackupStep.SetAccessCode) - } - BackupAction.ShowEnterAccessCodeScreen -> { - state.copy(backupStep = BackupStep.EnterAccessCode, accessCodeError = null) - } - is BackupAction.SaveFirstAccessCode -> { - state.copy( - backupStep = BackupStep.ReenterAccessCode, - accessCode = action.accessCode, - ) - } - is BackupAction.SetAccessCodeError -> { - state.copy(accessCodeError = action.error) - } - is BackupAction.PrepareToWritePrimaryCard -> { - if (state.primaryCardId == null) { - state.copy( - primaryCardId = backupService.primaryCardId, - backupCardIds = backupService.backupCardIds, - backupCardsNumber = backupService.backupCardIds.size, - backupStep = BackupStep.WritePrimaryCard, - ) - } else { - state.copy(backupStep = BackupStep.WritePrimaryCard) - } - } - is BackupAction.PrepareToWriteBackupCard -> { - if (state.primaryCardId == null) { - state.copy( - primaryCardId = backupService.primaryCardId, - backupCardIds = backupService.backupCardIds, - backupCardsNumber = backupService.backupCardIds.size, - backupStep = BackupStep.WriteBackupCard(action.cardNumber), - ) - } else { - state.copy(backupStep = BackupStep.WriteBackupCard(action.cardNumber)) - } - } - is BackupAction.FinishBackup -> { - state.copy(backupStep = BackupStep.Finished) - } - BackupAction.OnAccessCodeDialogClosed -> { - state.copy(backupStep = BackupStep.AddBackupCards) - } - BackupAction.DiscardBackup -> BackupState() - else -> state + } else { + state.copy(backupStep = BackupStep.WritePrimaryCard) } + is BackupAction.PrepareToWriteBackupCard -> if (state.primaryCardId == null) { + state.copy( + primaryCardId = backupService.primaryCardId, + backupCardIds = backupService.backupCardIds, + backupCardsNumber = backupService.backupCardIds.size, + backupStep = BackupStep.WriteBackupCard(action.cardNumber), + ) + } else { + state.copy(backupStep = BackupStep.WriteBackupCard(action.cardNumber)) + } + is BackupAction.FinishBackup -> state.copy(backupStep = BackupStep.Finished) + BackupAction.OnAccessCodeDialogClosed -> state.copy(backupStep = BackupStep.AddBackupCards) + BackupAction.DiscardBackup -> BackupState() + else -> state } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt index 23da378819..f678ff716e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt @@ -31,11 +31,13 @@ data class OnboardingWalletState( else -> null } + @Suppress("MagicNumber") fun getMaxProgress(): Int = when { isSaltPay -> 12 else -> 6 } + @Suppress("ComplexMethod", "MagicNumber") fun getProgressStep(): Int { return when { step == OnboardingWalletStep.CreateWallet -> 1 @@ -106,7 +108,7 @@ sealed class BackupStep { object Finished : BackupStep() } -sealed class OnboardingDialog: StateDialog { +sealed class OnboardingDialog : StateDialog { data class InterruptOnboarding(val onOk: VoidCallback) : BackupDialog() } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt index 6e0f223452..3a17f07fb7 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt @@ -159,11 +159,15 @@ class GnosisRegistrator( return Result.Success(Unit) } + @Suppress("MagicNumber") private fun Result>.extractFeeAmount(): Result { return when (this) { is Result.Success -> { - if (this.data.size != 3) Result.Failure(BlockchainSdkError.FailedToLoadFee) - else Result.Success(this.data[1]) + if (this.data.size != 3) { + Result.Failure(BlockchainSdkError.FailedToLoadFee) + } else { + Result.Success(this.data[1]) + } } is Result.Failure -> this } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/PinFilters.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/PinFilters.kt index 15a4137ec0..fd359c25a4 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/PinFilters.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/PinFilters.kt @@ -5,9 +5,9 @@ import com.tangem.common.Filter /** [REDACTED_AUTHOR] */ -abstract class PinFilter : Filter +interface PinFilter : Filter -class AllSymbolsTheSameFilter : PinFilter() { +class AllSymbolsTheSameFilter : PinFilter { override fun filter(value: String): Boolean { if (value.isEmpty()) return false diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt index 9085764bf1..2e75c77dce 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt @@ -29,6 +29,7 @@ import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPa import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.math.BigDecimal +import com.tangem.blockchain.extensions.Result as BlockchainResult /** [REDACTED_AUTHOR] @@ -50,12 +51,12 @@ class SaltPayActivationManager( suspend fun checkHasGas(): Result { return when (val hasGasResult = gnosisRegistrator.checkHasGas()) { - is com.tangem.blockchain.extensions.Result.Success -> if (hasGasResult.data) { + is BlockchainResult.Success -> if (hasGasResult.data) { Result.Success(Unit) } else { Result.Failure(SaltPayActivationError.NoGas) } - is com.tangem.blockchain.extensions.Result.Failure -> Result.Failure(hasGasResult.error as BlockchainSdkError) + is BlockchainResult.Failure -> Result.Failure(hasGasResult.error as BlockchainSdkError) } } @@ -92,7 +93,7 @@ class SaltPayActivationManager( if (response.results.isEmpty()) throw SaltPayActivationError.EmptyResponse val item = response.results[0] - if (item.error != null) throw Exception(response.makeErrorMessage()) + if (item.error != null) throw IllegalStateException(response.makeErrorMessage()) Result.Success(item) } catch (ex: Exception) { @@ -117,7 +118,7 @@ class SaltPayActivationManager( suspend fun sendTransactions( signedTransactions: List, - ): com.tangem.blockchain.extensions.Result> { + ): BlockchainResult> { return gnosisRegistrator.sendTransactions(signedTransactions) } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt index 3b4ed41a05..cf34a759d4 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt @@ -11,35 +11,33 @@ import com.tangem.tap.store /** [REDACTED_AUTHOR] */ -class SaltPayExceptionHandler { - companion object { - fun handle(throwable: Throwable) { - when (throwable) { - is SaltPayActivationError -> { - val dialog = when (throwable) { - is SaltPayActivationError.NoGas -> SaltPayDialog.Activation.NoGas - else -> SaltPayDialog.Activation.OnError(throwable) - } - store.dispatchDialogShow(dialog) +object SaltPayExceptionHandler { + fun handle(throwable: Throwable) { + when (throwable) { + is SaltPayActivationError -> { + val dialog = when (throwable) { + is SaltPayActivationError.NoGas -> SaltPayDialog.Activation.NoGas + else -> SaltPayDialog.Activation.OnError(throwable) } - is TangemSdkError -> { - when (throwable) { - is TangemSdkError.NetworkError -> { - val message = (throwable).customMessage - store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message)) - } - else -> { - // do nothing - } + store.dispatchDialogShow(dialog) + } + is TangemSdkError -> { + when (throwable) { + is TangemSdkError.NetworkError -> { + val message = throwable.customMessage + store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message)) + } + else -> { + // do nothing } } - is BlockchainSdkError -> { - store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(throwable.customMessage)) - } - else -> { - val message = throwable.localizedMessage ?: "SaltPay unknown error" - store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message)) - } + } + is BlockchainSdkError -> { + store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(throwable.customMessage)) + } + else -> { + val message = throwable.localizedMessage ?: "SaltPay unknown error" + store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message)) } } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/UtorgWebView.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/UtorgWebView.kt index b773adcb70..fccce0450e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/UtorgWebView.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/UtorgWebView.kt @@ -64,7 +64,9 @@ class UtorgWebChromeClient( if (!readyToShowFileChooser()) return false val type: String - if (fileChooserParams != null && fileChooserParams.acceptTypes != null && fileChooserParams.acceptTypes.isNotEmpty()) { + if (fileChooserParams != null && fileChooserParams.acceptTypes != null + && fileChooserParams.acceptTypes.isNotEmpty() + ) { type = if (!TextUtils.isEmpty(fileChooserParams.acceptTypes[0])) fileChooserParams.acceptTypes[0] else "*/*" this.filePathCallback = filePathCallback } else { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/InterruptOnboardingDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/InterruptOnboardingDialog.kt index 1636675ae1..35a99173c3 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/InterruptOnboardingDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/InterruptOnboardingDialog.kt @@ -11,17 +11,15 @@ import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ -class InterruptOnboardingDialog { - companion object { - fun create(context: Context, dialog: OnboardingDialog.InterruptOnboarding): Dialog { - return AlertDialog.Builder(context).apply { - setTitle(context.getString(R.string.onboarding_exit_alert_title)) - setMessage(context.getString(R.string.onboarding_exit_alert_message)) - setPositiveButton(R.string.common_ok) { _, _ -> dialog.onOk() } - setNegativeButton(R.string.common_cancel) { _, _ -> } - setOnDismissListener { store.dispatchDialogHide() } - setCancelable(false) - }.create() - } +object InterruptOnboardingDialog { + fun create(context: Context, dialog: OnboardingDialog.InterruptOnboarding): Dialog { + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.onboarding_exit_alert_title)) + setMessage(context.getString(R.string.onboarding_exit_alert_message)) + setPositiveButton(R.string.common_ok) { _, _ -> dialog.onOk() } + setNegativeButton(R.string.common_cancel) { _, _ -> } + setOnDismissListener { store.dispatchDialogHide() } + setCancelable(false) + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt index 579f34d614..175e53ae38 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt @@ -13,27 +13,25 @@ import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ -class NoFundsForActivationDialog { - companion object { - fun create(context: Context): Dialog { - return AlertDialog.Builder(context).apply { - setTitle(R.string.saltpay_error_no_gas_title) - setMessage(R.string.saltpay_error_no_gas_message) - //TODO: SaltPay: change onboarding_supplement_button_kyc_waiting -> to appropriate string - setPositiveButton(R.string.onboarding_supplement_button_kyc_waiting) { _, _ -> - val config = store.state.globalState.configManager?.config?.saltPayConfig?.zendesk.guard { - store.dispatchDebugErrorNotification("SaltPayConfig not initialized") - return@setPositiveButton - } - store.dispatch(GlobalAction.OpenChat(SupportInfo(), config)) +object NoFundsForActivationDialog { + fun create(context: Context): Dialog { + return AlertDialog.Builder(context).apply { + setTitle(R.string.saltpay_error_no_gas_title) + setMessage(R.string.saltpay_error_no_gas_message) + //TODO: SaltPay: change onboarding_supplement_button_kyc_waiting -> to appropriate string + setPositiveButton(R.string.onboarding_supplement_button_kyc_waiting) { _, _ -> + val config = store.state.globalState.configManager?.config?.saltPayConfig?.zendesk.guard { + store.dispatchDebugErrorNotification("SaltPayConfig not initialized") + return@setPositiveButton } - setNegativeButton(R.string.common_cancel) { _, _ -> - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - setCancelable(false) - }.create() - } + store.dispatch(GlobalAction.OpenChat(SupportInfo(), config)) + } + setNegativeButton(R.string.common_cancel) { _, _ -> + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + setCancelable(false) + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/RegistrationErrorDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/RegistrationErrorDialog.kt index 831d097783..f3c184157f 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/RegistrationErrorDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/RegistrationErrorDialog.kt @@ -12,22 +12,20 @@ import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ -class RegistrationErrorDialog { - companion object { - fun create(context: Context, dialog: SaltPayDialog.Activation.OnError): Dialog { - val convertedMessage = SaltPayErrorConverter(context).convert(dialog.error) as ConvertedDialogMessage +object RegistrationErrorDialog { + fun create(context: Context, dialog: SaltPayDialog.Activation.OnError): Dialog { + val convertedMessage = SaltPayErrorConverter(context).convert(dialog.error) as ConvertedDialogMessage - return AlertDialog.Builder(context).apply { - setTitle(convertedMessage.title) - setMessage(convertedMessage.message) - setPositiveButton(R.string.common_ok) { _, _ -> - store.dispatch(GlobalAction.HideDialog) - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - setCancelable(false) - }.create() - } + return AlertDialog.Builder(context).apply { + setTitle(convertedMessage.title) + setMessage(convertedMessage.message) + setPositiveButton(R.string.common_ok) { _, _ -> + store.dispatch(GlobalAction.HideDialog) + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + setCancelable(false) + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/message/SaltPayModuleMessage.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/message/SaltPayModuleMessage.kt index 6f2a5de7ad..a8e4a95885 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/message/SaltPayModuleMessage.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/message/SaltPayModuleMessage.kt @@ -28,27 +28,26 @@ sealed class SaltPayActivationError( subCode = ERROR_CODE_REGISTRATION + subCode, message = message ?: EMPTY_MESSAGE, ) { - object Unknown : SaltPayActivationError(4) - object Empty : SaltPayActivationError(4) + object Unknown : SaltPayActivationError(subCode = 4) + object Empty : SaltPayActivationError(subCode = 4) // gnosis - object FailedToMakeTxData : SaltPayActivationError(1) - object FailedToSendTx : SaltPayActivationError(2) + object FailedToMakeTxData : SaltPayActivationError(subCode = 1) + object FailedToSendTx : SaltPayActivationError(subCode = 2) - object NeedPin : SaltPayActivationError(3) - object NoGas : SaltPayActivationError(5) // - object EmptyResponse : SaltPayActivationError(6) - object CardNotFound : SaltPayActivationError(8) - class CardDisabled(message: String?) : SaltPayActivationError(9, message) - class CardNotPassed(message: String?) : SaltPayActivationError(10, message) - object EmptyDynamicAttestResponse : SaltPayActivationError(11) - object EmptyBackupCardScanned : SaltPayActivationError(12) - object WeakPin : SaltPayActivationError(13) + object NeedPin : SaltPayActivationError(subCode = 3) + object NoGas : SaltPayActivationError(subCode = 5) + object EmptyResponse : SaltPayActivationError(subCode = 6) + object CardNotFound : SaltPayActivationError(subCode = 8) + class CardDisabled(message: String?) : SaltPayActivationError(subCode = 9, message = message) + class CardNotPassed(message: String?) : SaltPayActivationError(subCode = 10, message = message) + object EmptyDynamicAttestResponse : SaltPayActivationError(subCode = 11) + object EmptyBackupCardScanned : SaltPayActivationError(subCode = 12) + object WeakPin : SaltPayActivationError(subCode = 13) - object FailedToGetFundsToClaim : SaltPayActivationError(14) - object NoFundsToClaim : SaltPayActivationError(15) - object ClaimTransactionFailed : SaltPayActivationError(16) - object NotClaimed : SaltPayActivationError(17) + object FailedToGetFundsToClaim : SaltPayActivationError(subCode = 14) + object NoFundsToClaim : SaltPayActivationError(subCode = 15) + object ClaimTransactionFailed : SaltPayActivationError(subCode = 16) companion object { const val EMPTY_MESSAGE = "" diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt index 81d7ad4138..a97cb49580 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt @@ -39,10 +39,8 @@ import timber.log.Timber /** [REDACTED_AUTHOR] */ -class OnboardingSaltPayMiddleware { - companion object { - val handler = onboardingSaltPayMiddleware - } +object OnboardingSaltPayMiddleware { + val handler = onboardingSaltPayMiddleware } private val onboardingSaltPayMiddleware: Middleware = { dispatch, state -> @@ -54,6 +52,7 @@ private val onboardingSaltPayMiddleware: Middleware = { dispatch, stat } } +@Suppress("LongMethod", "ComplexMethod") private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> AppState?) { if (DemoHelper.tryHandle(appState, anyAction)) return val action = anyAction as? OnboardingSaltPayAction ?: return @@ -292,6 +291,7 @@ suspend fun SaltPayActivationManager.update( return Result.Success(newStep) } +@Suppress("ComplexMethod") private fun determineStep( currentStep: SaltPayActivationStep, amountToClaim: Amount?, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt index 71570fd52e..9d4e8774c0 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt @@ -6,14 +6,13 @@ import org.rekotlin.Action /** [REDACTED_AUTHOR] */ -class OnboardingSaltPayReducer { - companion object { - fun reduce(action: Action, state: OnboardingWalletState): OnboardingWalletState { - return internalReduce(action, state) - } +object OnboardingSaltPayReducer { + fun reduce(action: Action, state: OnboardingWalletState): OnboardingWalletState { + return internalReduce(action, state) } } +@Suppress("ComplexMethod") private fun internalReduce(anyAction: Action, onboardingWalletState: OnboardingWalletState): OnboardingWalletState { val action = anyAction as? OnboardingSaltPayAction ?: return onboardingWalletState diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt index e1317622a4..e530450920 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt @@ -4,9 +4,9 @@ import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.common.extensions.guard +import com.tangem.datasource.api.paymentology.PaymentologyApiService import com.tangem.domain.common.SaltPayWorkaround import com.tangem.domain.common.ScanResponse -import com.tangem.datasource.api.paymentology.PaymentologyApiService import com.tangem.tap.common.toggleWidget.WidgetState import com.tangem.tap.domain.extensions.makeSaltPayWalletManager import com.tangem.tap.features.onboarding.products.wallet.saltPay.GnosisRegistrator @@ -37,8 +37,6 @@ data class OnboardingSaltPayState( val mainButtonState: WidgetState get() = if (inProgress) ProgressState.Loading else ProgressState.Done - fun readyToClaim(): Boolean = amountToClaim != null - val pinLength: Int = 4 companion object { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt index cfefd6b722..16f76fa654 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt @@ -63,7 +63,9 @@ internal class OnboardingSaltPayView( private val toolbar: MaterialToolbar by lazy { walletFragment.binding.toolbar } private val claimBinding: LayoutOnboardingMainBinding by lazy { walletFragment.bindingSaltPay.claim } private val topContainer: LayoutOnboardingContainerTopBinding by lazy { claimBinding.onboardingTopContainer } - private val actionContainer: LayoutOnboardingContainerBottomBinding by lazy { claimBinding.onboardingActionContainer } + private val actionContainer: LayoutOnboardingContainerBottomBinding by lazy { + claimBinding.onboardingActionContainer + } private val btnRefreshBalanceWidget by lazy { RefreshBalanceWidget(claimBinding.onboardingTopContainer.onboardingWalletContainer) @@ -147,8 +149,8 @@ internal class OnboardingSaltPayView( SaltPayActivationStep.Claim -> handleClaim(state) SaltPayActivationStep.ClaimInProgress -> handleClaim(state) SaltPayActivationStep.ClaimSuccess -> handleClaim(state) - SaltPayActivationStep.Success -> handleSuccess(state) - else -> {} + SaltPayActivationStep.Success -> handleSuccess() + else -> Unit } progressButton?.changeState(state.mainButtonState) } @@ -252,6 +254,7 @@ internal class OnboardingSaltPayView( progressButton = SaltPayProgressButton(root) } + @Suppress("LongMethod") private fun handleClaim(state: OnboardingSaltPayState) = with(walletFragment.bindingSaltPay) { toolbar.title = getText(R.string.onboarding_getting_started) val btnMain = actionContainer.btnContainer.findViewById(R.id.btn_main_action) @@ -325,7 +328,7 @@ internal class OnboardingSaltPayView( } } - private fun handleSuccess(state: OnboardingSaltPayState) = with(walletFragment.binding) { + private fun handleSuccess() = with(walletFragment.binding) { walletFragment.bindingSaltPay.onboardingSaltpayContainer.hide() onboardingWalletContainer.show() walletFragment.showSuccess() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt index 6453fdca92..368cb6a3a3 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt @@ -34,6 +34,8 @@ class WalletBackupAnimator( } private var currentStep = -1 + private var firstBackupCardAnimated = false + private var secondBackupCardAnimated = false override fun updateBackupState(backupState: BackupState) { } @@ -64,6 +66,7 @@ class WalletBackupAnimator( cardsWidget.toFolded() } + @Suppress("MagicNumber") override fun showAddBackupCards(state: BackupState, backupCardsCounts: Int) { when (backupCardsCounts) { 0 -> { @@ -101,6 +104,7 @@ class WalletBackupAnimator( currentStep = STEP_ADD_BACKUP_CARDS } + @Suppress("MagicNumber") override fun showWritePrimaryCard(state: BackupState) { currentStep = STEP_WRITE_PRIMARY_CARD @@ -112,9 +116,7 @@ class WalletBackupAnimator( ) } - private var firstBackupCardAnimated = false - private var secondBackupCardAnimated = false - + @Suppress("MagicNumber") override fun showWriteBackupCard(state: BackupState, backupCard: Int) { when (viewState) { State.TwoCards -> { @@ -244,6 +246,7 @@ class TestBackupAnimation( init(binding, this) } + @Suppress("MagicNumber") fun setStep(step: Int, onStepUpdate: (Int) -> Unit = {}) { steps = step when (steps) { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt index f07087c1fb..4086da388e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt @@ -18,9 +18,9 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.tabs.TabLayoutMediator import com.tangem.common.CardIdFormatter import com.tangem.common.core.CardIdDisplayFormat +import com.tangem.core.analytics.Analytics import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.PropertyCalculator -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.inflate @@ -46,6 +46,7 @@ import com.tangem.wallet.databinding.LayoutOnboardingSaltpayBinding import com.tangem.wallet.databinding.ViewOnboardingProgressBinding import org.rekotlin.StoreSubscriber +@Suppress("LargeClass", "MagicNumber") class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_wallet), StoreSubscriber, FragmentOnBackPressedHandler { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt index 7d4831c434..7385c4d025 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt @@ -88,6 +88,7 @@ class WalletCardsWidget( return getCardTypesTypes().map { createCardAnimator(it, propertyFactory(it)) } } + @Suppress("MagicNumber") private fun getCardTypesTypes(): List = when (leapfrogWidget.getViewsCount()) { 2 -> listOf(BackupCardType.ORIGIN, BackupCardType.FIRST_BACKUP) 3 -> listOf(BackupCardType.ORIGIN, BackupCardType.FIRST_BACKUP, BackupCardType.SECOND_BACKUP) @@ -114,6 +115,7 @@ class WalletCardsWidget( return animator } + @Suppress("MagicNumber") private fun createWelcomeProperties(cardType: BackupCardType): CardProperties { return when (cardType) { BackupCardType.ORIGIN -> CardProperties( @@ -140,6 +142,7 @@ class WalletCardsWidget( } } + @Suppress("MagicNumber") private fun createFanProperties(cardType: BackupCardType): CardProperties { return when (cardType) { BackupCardType.ORIGIN -> CardProperties( @@ -166,6 +169,7 @@ class WalletCardsWidget( } } + @Suppress("MagicNumber") private fun createLeapfrogProperties(cardType: BackupCardType): CardProperties { return when (cardType) { BackupCardType.ORIGIN -> CardProperties( @@ -192,17 +196,6 @@ class WalletCardsWidget( } } - private fun createActivateProperties(cardType: BackupCardType): CardProperties { - val topOfAnchorView = getTopOfAnchorViewForActivateState() - val twinProperties = CardProperties.from(getLeapViewByCard(cardType).state) - return twinProperties.copy( - xTranslation = twinProperties.xTranslation, - yTranslation = twinProperties.yTranslation - topOfAnchorView, - rotation = 0f, - scale = twinProperties.scale - 0.4f, - ) - } - @Throws(UnsupportedOperationException::class) fun getOriginCardView(): ImageView { return getLeapViewByCard(BackupCardType.ORIGIN).view as ImageView @@ -218,6 +211,7 @@ class WalletCardsWidget( return getLeapViewByCard(BackupCardType.SECOND_BACKUP).view as ImageView } + @Suppress("MagicNumber") @Throws(UnsupportedOperationException::class) private fun getLeapViewByCard(cardType: BackupCardType): LeapView { fun getByIndex(index: Int): LeapView { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AddMoreBackupCardsDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AddMoreBackupCardsDialog.kt index d98e9907b9..950a2eb59e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AddMoreBackupCardsDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AddMoreBackupCardsDialog.kt @@ -7,22 +7,20 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction import com.tangem.tap.store import com.tangem.wallet.R -class AddMoreBackupCardsDialog { - companion object { - fun create(context: Context): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(R.string.common_warning) - setMessage(R.string.onboarding_alert_message_not_max_backup_cards_added) - setPositiveButton(R.string.common_continue) { _, _ -> - store.dispatch(BackupAction.ShowAccessCodeInfoScreen) - } - setNegativeButton(R.string.common_cancel) { _, _ -> +object AddMoreBackupCardsDialog { + fun create(context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(R.string.common_warning) + setMessage(R.string.onboarding_alert_message_not_max_backup_cards_added) + setPositiveButton(R.string.common_continue) { _, _ -> + store.dispatch(BackupAction.ShowAccessCodeInfoScreen) + } + setNegativeButton(R.string.common_cancel) { _, _ -> - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - }.create() - } + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BackupInProgressDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BackupInProgressDialog.kt index e4fa7525b5..ed4e32a0a4 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BackupInProgressDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BackupInProgressDialog.kt @@ -6,19 +6,17 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.store import com.tangem.wallet.R -class BackupInProgressDialog { - companion object { - fun create(context: Context): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(R.string.common_warning) - setMessage(R.string.onboarding_backup_exit_warning) - setPositiveButton(R.string.warning_button_ok) { _, _ -> - store.dispatch(GlobalAction.HideDialog) - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - }.create() - } +object BackupInProgressDialog { + fun create(context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(R.string.common_warning) + setMessage(R.string.onboarding_backup_exit_warning) + setPositiveButton(R.string.warning_button_ok) { _, _ -> + store.dispatch(GlobalAction.HideDialog) + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/ConfirmDiscardingBackupDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/ConfirmDiscardingBackupDialog.kt index 4d35050937..557c955bb1 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/ConfirmDiscardingBackupDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/ConfirmDiscardingBackupDialog.kt @@ -7,23 +7,21 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction import com.tangem.tap.store import com.tangem.wallet.R -class ConfirmDiscardingBackupDialog { - companion object { - fun create(context: Context): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(R.string.welcome_interrupted_backup_discard_title) - setMessage(R.string.welcome_interrupted_backup_discard_message) - setPositiveButton(R.string.welcome_interrupted_backup_discard_resume) { _, _ -> - store.dispatch(BackupAction.ResumeFoundUnfinishedBackup) - } - setNegativeButton(R.string.welcome_interrupted_backup_discard_discard) { _, _ -> - store.dispatch(BackupAction.DiscardSavedBackup) - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - setCancelable(false) - }.create() - } +object ConfirmDiscardingBackupDialog { + fun create(context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(R.string.welcome_interrupted_backup_discard_title) + setMessage(R.string.welcome_interrupted_backup_discard_message) + setPositiveButton(R.string.welcome_interrupted_backup_discard_resume) { _, _ -> + store.dispatch(BackupAction.ResumeFoundUnfinishedBackup) + } + setNegativeButton(R.string.welcome_interrupted_backup_discard_discard) { _, _ -> + store.dispatch(BackupAction.DiscardSavedBackup) + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + setCancelable(false) + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/UnfinishedBackupFoundDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/UnfinishedBackupFoundDialog.kt index d13afaa088..6576a36fce 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/UnfinishedBackupFoundDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/UnfinishedBackupFoundDialog.kt @@ -8,22 +8,20 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog import com.tangem.tap.store import com.tangem.wallet.R -class UnfinishedBackupFoundDialog { - companion object { - fun create(context: Context): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(R.string.common_warning) - setMessage(R.string.welcome_interrupted_backup_alert_message) - setPositiveButton(R.string.welcome_interrupted_backup_alert_resume) { _, _ -> - store.dispatch(GlobalAction.HideDialog) - store.dispatch(BackupAction.ResumeFoundUnfinishedBackup) - } - setNegativeButton(R.string.welcome_interrupted_backup_alert_discard) { _, _ -> - store.dispatch(GlobalAction.HideDialog) - store.dispatch(GlobalAction.ShowDialog(BackupDialog.ConfirmDiscardingBackup)) - } - setCancelable(false) - }.create() - } +object UnfinishedBackupFoundDialog { + fun create(context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(R.string.common_warning) + setMessage(R.string.welcome_interrupted_backup_alert_message) + setPositiveButton(R.string.welcome_interrupted_backup_alert_resume) { _, _ -> + store.dispatch(GlobalAction.HideDialog) + store.dispatch(BackupAction.ResumeFoundUnfinishedBackup) + } + setNegativeButton(R.string.welcome_interrupted_backup_alert_discard) { _, _ -> + store.dispatch(GlobalAction.HideDialog) + store.dispatch(GlobalAction.ShowDialog(BackupDialog.ConfirmDiscardingBackup)) + } + setCancelable(false) + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt index 41b3f9d34a..64a01578f1 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt @@ -26,11 +26,11 @@ interface SendScreenActionUi : SendScreenAction object ReleaseSendState : Action data class PrepareSendScreen( - val coinAmount: Amount?, - val coinRate: BigDecimal?, - val walletManager: WalletManager?, - val tokenAmount: Amount? = null, - val tokenRate: BigDecimal? = null + val coinAmount: Amount?, + val coinRate: BigDecimal?, + val walletManager: WalletManager?, + val tokenAmount: Amount? = null, + val tokenRate: BigDecimal? = null, ) : SendScreenAction // Address or PayId @@ -44,18 +44,17 @@ sealed class AddressPayIdActionUi : SendScreenActionUi { data class ChangePayIdState(val sendingToPayIdEnabled: Boolean) : AddressPayIdActionUi() } - sealed class TransactionExtrasAction : SendScreenActionUi { data class Prepare( - val blockchain: Blockchain, - val walletAddress: String, - val xrpTag: String?, + val blockchain: Blockchain, + val walletAddress: String, + val xrpTag: String?, ) : TransactionExtrasAction() object Release : TransactionExtrasAction() sealed class XlmMemo : TransactionExtrasAction() { - // data class ChangeSelectedMemo(val memoType: XlmMemoType) : XlmMemo() + // data class ChangeSelectedMemo(val memoType: XlmMemoType) : XlmMemo() data class HandleUserInput(val data: String) : XlmMemo() } @@ -81,7 +80,11 @@ sealed class AddressPayIdVerifyAction : SendScreenAction { sealed class PayIdVerification : AddressPayIdVerifyAction() { data class SetPayIdError(val error: Error?) : PayIdVerification() - data class SetPayIdWalletAddress(val payId: String, val payIdWalletAddress: String, val isUserInput: Boolean) : PayIdVerification() + data class SetPayIdWalletAddress( + val payId: String, + val payIdWalletAddress: String, + val isUserInput: Boolean, + ) : PayIdVerification() } sealed class AddressVerification : AddressPayIdVerifyAction() { @@ -121,9 +124,9 @@ sealed class FeeAction : SendScreenAction { } data class ChangeLayoutVisibility( - val main: Boolean? = null, - val controls: Boolean? = null, - val chipGroup: Boolean? = null, + val main: Boolean? = null, + val controls: Boolean? = null, + val chipGroup: Boolean? = null, ) : FeeAction() } @@ -147,14 +150,14 @@ sealed class SendAction : SendScreenAction { sealed class Dialog : SendAction(), StateDialog { data class TezosWarningDialog( - val reduceCallback: () -> Unit, - val sendAllCallback: () -> Unit, - val reduceAmount: BigDecimal, + val reduceCallback: () -> Unit, + val sendAllCallback: () -> Unit, + val reduceAmount: BigDecimal, ) : Dialog() sealed class SendTransactionFails : Dialog() { - data class CardSdkError(val error: TangemSdkError): Dialog() - data class BlockchainSdkError(val error: com.tangem.blockchain.common.BlockchainSdkError): Dialog() + data class CardSdkError(val error: TangemSdkError) : Dialog() + data class BlockchainSdkError(val error: com.tangem.blockchain.common.BlockchainSdkError) : Dialog() } data class RequestFeeError( @@ -173,7 +176,6 @@ sealed class SendAction : SendScreenAction { data class SendSpecificTransaction( val sendAmount: String, val destinationAddress: String, - val transactionId: String + val transactionId: String, ) : SendAction() - } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt index f73c2c3241..8a0b096c4c 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt @@ -1,7 +1,6 @@ package com.tangem.tap.features.send.redux.middlewares import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.blockchain.common.TransactionSender import com.tangem.blockchain.extensions.Result @@ -82,33 +81,4 @@ class RequestFeeMiddleware { } } } -} - -class FeeMock { - companion object { - suspend fun getFee(blockchain: Blockchain): List { - return feeStandard(blockchain) -// return feeSingle(blockchain) -// return feeStellar(blockchain) -// return feeZero(blockchain) - } - - suspend fun feeStellar(blockchain: Blockchain): List = listOf(Amount(0.0001.toBigDecimal(), blockchain)) - - suspend fun feeZero(blockchain: Blockchain): List = listOf(Amount(BigDecimal.ZERO, blockchain)) - - suspend fun feeStandard(blockchain: Blockchain): List = listOf( - Amount(0.001500.toBigDecimal(), blockchain), - Amount(0.0030.toBigDecimal(), blockchain), - Amount(0.0045001.toBigDecimal(), blockchain), - ) - - suspend fun feeStandardBig(blockchain: Blockchain): List = listOf( - Amount(1.76.toBigDecimal(), blockchain), - Amount(2.30.toBigDecimal(), blockchain), - Amount(3.45.toBigDecimal(), blockchain), - ) - - suspend fun feeSingle(blockchain: Blockchain): List = listOf(Amount(0.0015.toBigDecimal(), blockchain)) - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 7467b27720..45801327ea 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -13,11 +13,11 @@ import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.extensions.SimpleResult import com.tangem.common.core.TangemSdkError import com.tangem.common.services.Result +import com.tangem.core.analytics.Analytics import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.dispatchDialogShow @@ -145,6 +145,7 @@ private fun verifyAndSendTransaction( } } +@Suppress("LongParameterList", "LongMethod", "ComplexMethod") private fun sendTransaction( action: SendActionUi.SendAmountToRecipient, walletManager: WalletManager, @@ -235,7 +236,7 @@ private fun sendTransaction( } scope.launch(Dispatchers.IO) { updateWallet(walletManager) - delay(11000) // more than 10000 to avoid throttling + delay(timeMillis = 11000) // more than 10000 to avoid throttling updateWallet(walletManager) } } @@ -246,13 +247,13 @@ private fun sendTransaction( feeAmount = feeAmount, destinationAddress = destinationAddress, ) - val error = (sendResult.error as? BlockchainSdkError) ?: return@withMainContext + val error = sendResult.error as? BlockchainSdkError ?: return@withMainContext Analytics.send(Token.Send.TransactionSent(currencyType, error)) when (error) { is BlockchainSdkError.WrappedTangemError -> { - val tangemSdkError = (error.tangemError as? TangemSdkError) ?: return@withMainContext + val tangemSdkError = error.tangemError as? TangemSdkError ?: return@withMainContext if (tangemSdkError is TangemSdkError.UserCancelled) return@withMainContext dispatch(SendAction.Dialog.SendTransactionFails.CardSdkError(tangemSdkError)) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt index faf6f1b4b0..ddacac0df8 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt @@ -19,7 +19,11 @@ class AddressPayIdReducer : SendInternalReducer { else -> sendState } - private fun handleUiAction(action: AddressPayIdActionUi, sendState: SendState, state: AddressPayIdState): SendState { + private fun handleUiAction( + action: AddressPayIdActionUi, + sendState: SendState, + state: AddressPayIdState, + ): SendState { val result = when (action) { is AddressPayIdActionUi.HandleUserInput -> state is AddressPayIdActionUi.SetTruncateHandler -> state.copy(truncateHandler = action.handler) @@ -35,24 +39,28 @@ class AddressPayIdReducer : SendInternalReducer { return updateLastState(sendState.copy(addressPayIdState = result), result) } - private fun handleAction(action: AddressPayIdVerifyAction, sendState: SendState, state: AddressPayIdState): SendState { + private fun handleAction( + action: AddressPayIdVerifyAction, + sendState: SendState, + state: AddressPayIdState, + ): SendState { val result = when (action) { is PayIdVerification.SetPayIdWalletAddress -> { state.copy( - viewFieldValue = InputViewValue(action.payId, action.isUserInput), - normalFieldValue = action.payId, - truncatedFieldValue = state.truncate(action.payId), - destinationWalletAddress = action.payIdWalletAddress, - error = null + viewFieldValue = InputViewValue(action.payId, action.isUserInput), + normalFieldValue = action.payId, + truncatedFieldValue = state.truncate(action.payId), + destinationWalletAddress = action.payIdWalletAddress, + error = null, ) } is AddressVerification.SetWalletAddress -> { state.copy( - viewFieldValue = InputViewValue(action.address, action.isUserInput), - normalFieldValue = action.address, - truncatedFieldValue = state.truncate(action.address), - destinationWalletAddress = action.address, - error = null + viewFieldValue = InputViewValue(action.address, action.isUserInput), + normalFieldValue = action.address, + truncatedFieldValue = state.truncate(action.address), + destinationWalletAddress = action.address, + error = null, ) } is AddressPayIdVerifyAction.ChangePasteBtnEnableState -> state.copy(pasteIsEnabled = action.isEnabled) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt index 3238c3b372..d0548132b9 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt @@ -21,7 +21,9 @@ class FeeReducer : SendInternalReducer { private fun handleUiAction(action: FeeActionUi, sendState: SendState, state: FeeState): SendState { val result = when (action) { - is FeeActionUi.ToggleControlsVisibility -> state.copy(controlsLayoutIsVisible = !state.controlsLayoutIsVisible) + is FeeActionUi.ToggleControlsVisibility -> { + state.copy(controlsLayoutIsVisible = !state.controlsLayoutIsVisible) + } is FeeActionUi.ChangeSelectedFee -> { val currentFee = createValueOfFeeAmount(action.feeType, state.feeList) state.copy( diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt index 4d4154218c..3f403ea92c 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt @@ -37,13 +37,12 @@ class ReceiptReducer : SendInternalReducer { this.feeState = sendState.feeState return when (action) { - is RefreshReceipt -> handleRefresh(action, sendState.receiptState) + is RefreshReceipt -> handleRefresh(sendState.receiptState) else -> sendState } } - - private fun handleRefresh(action: RefreshReceipt, state: ReceiptState): SendState { + private fun handleRefresh(state: ReceiptState): SendState { val wallet = sendState.walletManager?.wallet ?: return sendState val layoutType = determineLayoutType(amountState.mainCurrency.type, amountState.typeOfAmount) @@ -55,7 +54,7 @@ class ReceiptReducer : SendInternalReducer { fiat = createFiatType(symbols, showBlank), crypto = createCryptoType(symbols, showBlank), tokenFiat = createTokenFiatType(symbols, showBlank), - tokenCrypto = createTokenCryptoType(symbols, showBlank) + tokenCrypto = createTokenCryptoType(symbols, showBlank), ) return updateLastState(sendState.copy(receiptState = result), result) } @@ -76,7 +75,7 @@ class ReceiptReducer : SendInternalReducer { feeFiat = feeFiat, totalFiat = totalFiat, willSentCrypto = amountState.amountToSendCrypto.stripZeroPlainString(), - symbols = symbols + symbols = symbols, ) } else { val totalAmountCrypto = amountState.amountToSendCrypto.plus(feeCrypto) @@ -87,7 +86,7 @@ class ReceiptReducer : SendInternalReducer { feeFiat = feeFiat, totalFiat = totalFiat, willSentCrypto = totalAmountCrypto.stripZeroPlainString(), - symbols = symbols + symbols = symbols, ) } } @@ -106,7 +105,7 @@ class ReceiptReducer : SendInternalReducer { totalCrypto = amountState.amountToSendCrypto.stripZeroPlainString(), feeFiat = feeFiat, willSentFiat = convertToFiatPrecision(amountState.amountToSendCrypto), - symbols = symbols + symbols = symbols, ) } else { val totalCrypto = amountState.amountToSendCrypto.plus(feeCrypto) @@ -116,7 +115,7 @@ class ReceiptReducer : SendInternalReducer { totalCrypto = totalCrypto.stripZeroPlainString(), feeFiat = feeFiat, willSentFiat = convertToFiatPrecision(totalCrypto), - symbols = symbols + symbols = symbols, ) } } @@ -140,7 +139,7 @@ class ReceiptReducer : SendInternalReducer { totalFiat = totalFiat.scaleToFiat(true).stripZeroPlainString(), willSentToken = tokensToSend.stripZeroPlainString(), willSentFeeCoin = feeCoin.stripZeroPlainString(), - symbols = symbols + symbols = symbols, ) } else { ReceiptTokenFiat( @@ -149,7 +148,7 @@ class ReceiptReducer : SendInternalReducer { totalFiat = UNKNOWN_AMOUNT_SIGN, willSentToken = tokensToSend.stripZeroPlainString(), willSentFeeCoin = feeCoin.stripZeroPlainString(), - symbols = symbols + symbols = symbols, ) } } @@ -170,17 +169,16 @@ class ReceiptReducer : SendInternalReducer { amountToken = tokensToSend.stripZeroPlainString(), feeCoin = feeCoin.stripZeroPlainString().addPrecisionSign(), totalFiat = totalFiat.scaleToFiat(true).stripZeroPlainString(), - symbols = symbols + symbols = symbols, ) } else { ReceiptTokenCrypto( amountToken = tokensToSend.stripZeroPlainString(), feeCoin = feeCoin.stripZeroPlainString().addPrecisionSign(), totalFiat = UNKNOWN_AMOUNT_SIGN, - symbols = symbols + symbols = symbols, ) } - } private fun determineSymbols(wallet: Wallet, amountType: AmountType): ReceiptSymbols { @@ -190,7 +188,7 @@ class ReceiptReducer : SendInternalReducer { token = when (amountType) { is AmountType.Token -> amountType.token.symbol else -> null - } + }, ) } @@ -211,9 +209,15 @@ class ReceiptReducer : SendInternalReducer { private fun convertToFiatPrecision(value: BigDecimal, isToken: Boolean = false): String { return when { - !isToken && sendState.coinIsConvertible() -> sendState.coinConverter!!.toFiatWithPrecision(value).stripZeroPlainString() - isToken && sendState.tokenIsConvertible() -> sendState.tokenConverter!!.toFiatWithPrecision(value).stripZeroPlainString() - else -> UNKNOWN_AMOUNT_SIGN + !isToken && sendState.coinIsConvertible() -> { + sendState.coinConverter!!.toFiatWithPrecision(value).stripZeroPlainString() + } + isToken && sendState.tokenIsConvertible() -> { + sendState.tokenConverter!!.toFiatWithPrecision(value).stripZeroPlainString() + } + else -> { + UNKNOWN_AMOUNT_SIGN + } } } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt index d44a27f0d1..ba22cc2af8 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt @@ -28,25 +28,23 @@ interface SendInternalReducer { fun handle(action: SendScreenAction, sendState: SendState): SendState } -class SendScreenReducer { - companion object { - fun reduce(incomingAction: Action, sendState: SendState): SendState { - if (incomingAction is ReleaseSendState) return SendState() - val action = incomingAction as? SendScreenAction ?: return sendState +object SendScreenReducer { + fun reduce(incomingAction: Action, sendState: SendState): SendState { + if (incomingAction is ReleaseSendState) return SendState() + val action = incomingAction as? SendScreenAction ?: return sendState - val reducer: SendInternalReducer = when (action) { - is PrepareSendScreen -> PrepareSendScreenStatesReducer() - is AddressPayIdActionUi, is AddressPayIdVerifyAction -> AddressPayIdReducer() - is TransactionExtrasAction -> TransactionExtrasReducer() - is AmountActionUi, is AmountAction -> AmountReducer() - is FeeActionUi, is FeeAction -> FeeReducer() - is ReceiptAction -> ReceiptReducer() - is SendAction -> SendReducer() - else -> EmptyReducer() - } - - return reducer.handle(action, sendState) + val reducer: SendInternalReducer = when (action) { + is PrepareSendScreen -> PrepareSendScreenStatesReducer() + is AddressPayIdActionUi, is AddressPayIdVerifyAction -> AddressPayIdReducer() + is TransactionExtrasAction -> TransactionExtrasReducer() + is AmountActionUi, is AmountAction -> AmountReducer() + is FeeActionUi, is FeeAction -> FeeReducer() + is ReceiptAction -> ReceiptReducer() + is SendAction -> SendReducer() + else -> EmptyReducer() } + + return reducer.handle(action, sendState) } } @@ -70,19 +68,19 @@ private class SendReducer : SendInternalReducer { } private fun handleSendSpecificTransactionAction( - action: SendAction.SendSpecificTransaction, state: SendState + action: SendAction.SendSpecificTransaction, state: SendState, ): SendState { return state.copy( externalTransactionData = ExternalTransactionData(action.sendAmount, action.destinationAddress, action.transactionId), feeState = state.feeState.copy( - includeFeeSwitcherIsEnabled = false + includeFeeSwitcherIsEnabled = false, ), amountState = state.amountState.copy( - inputIsEnabled = false + inputIsEnabled = false, ), addressPayIdState = state.addressPayIdState.copy( - inputIsEnabled = false + inputIsEnabled = false, ), ) } @@ -100,17 +98,17 @@ private class PrepareSendScreenStatesReducer : SendInternalReducer { val decimals = amountToExtract.decimals return sendState.copy( - walletManager = walletManager, - coinConverter = action.coinRate?.let { CurrencyConverter(it, decimals) }, - tokenConverter = action.tokenRate?.let { CurrencyConverter(it, decimals) }, - amountState = sendState.amountState.copy( - amountToExtract = amountToExtract, - typeOfAmount = amountToExtract.type, - balanceCrypto = amountToExtract.value ?: BigDecimal.ZERO - ), - feeState = sendState.feeState.copy( - includeFeeSwitcherIsEnabled = amountToExtract.type == AmountType.Coin - ) + walletManager = walletManager, + coinConverter = action.coinRate?.let { CurrencyConverter(it, decimals) }, + tokenConverter = action.tokenRate?.let { CurrencyConverter(it, decimals) }, + amountState = sendState.amountState.copy( + amountToExtract = amountToExtract, + typeOfAmount = amountToExtract.type, + balanceCrypto = amountToExtract.value ?: BigDecimal.ZERO, + ), + feeState = sendState.feeState.copy( + includeFeeSwitcherIsEnabled = amountToExtract.type == AmountType.Coin, + ), ) } } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt index 71000abec1..5265a3c789 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt @@ -24,7 +24,7 @@ class TransactionExtrasReducer : SendInternalReducer { override fun handle(action: SendScreenAction, sendState: SendState): SendState { return when (action) { is Prepare -> handleInitialization(action, sendState) - Release -> handleRelease(action, sendState) + Release -> handleRelease(sendState) is XlmMemo -> handleXlmMemo(action, sendState, sendState.transactionExtrasState) is BinanceMemo -> handleBinanceMemo(action, sendState, sendState.transactionExtrasState) is XrpDestinationTag -> handleXrpTag(action, sendState, sendState.transactionExtrasState) @@ -43,8 +43,10 @@ class TransactionExtrasReducer : SendInternalReducer { if (tag == null) { TransactionExtrasState(xrpDestinationTag = XrpDestinationTagState()) } else { - TransactionExtrasState(xrpDestinationTag = XrpDestinationTagState( - InputViewValue("$tag", false), tag) + TransactionExtrasState( + xrpDestinationTag = XrpDestinationTagState( + InputViewValue("$tag", false), tag, + ), ) } } else { @@ -58,7 +60,7 @@ class TransactionExtrasReducer : SendInternalReducer { return updateLastState(sendState.copy(transactionExtrasState = result), result) } - private fun handleRelease(action: SendScreenAction, sendState: SendState): SendState { + private fun handleRelease(sendState: SendState): SendState { val result = TransactionExtrasState() return updateLastState(sendState.copy(transactionExtrasState = result), result) } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt index 30fe37f60a..6d0b9bee8a 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt @@ -59,6 +59,7 @@ data class XlmMemoState( else -> XlmMemoType.TEXT } + @Suppress("MagicNumber") fun isAssignableValue(value: String): Boolean = when (determineMemoType(value)) { XlmMemoType.TEXT -> { // from org.stellar.sdk.MemoText @@ -67,7 +68,7 @@ data class XlmMemoState( XlmMemoType.ID -> { try { // from com.tangem.blockchain.blockchains.stellar.StellarMemo.toStellarSdkMemo - value.toBigInteger() in BigInteger.ZERO..(Long.MAX_VALUE.toBigInteger() * 2.toBigInteger()) + value.toBigInteger() in BigInteger.ZERO..Long.MAX_VALUE.toBigInteger() * 2.toBigInteger() } catch (ex: NumberFormatException) { false } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt index 98fbfa47e1..6e7d3596d6 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt @@ -30,19 +30,19 @@ enum class StateId { interface SendScreenState : StateType, IdStateHolder data class SendState( - val walletManager: WalletManager? = null, - val coinConverter: CurrencyConverter? = null, - val tokenConverter: CurrencyConverter? = null, - val lastChangedStates: LinkedHashSet = linkedSetOf(), - val addressPayIdState: AddressPayIdState = AddressPayIdState(), - val transactionExtrasState: TransactionExtrasState = TransactionExtrasState(), - val amountState: AmountState = AmountState(), - val feeState: FeeState = FeeState(), - val receiptState: ReceiptState = ReceiptState(), - val sendWarningsList: List = listOf(), - val sendButtonState: IndeterminateProgressButton = IndeterminateProgressButton(ButtonState.DISABLED), - val dialog: StateDialog? = null, - val externalTransactionData: ExternalTransactionData? = null + val walletManager: WalletManager? = null, + val coinConverter: CurrencyConverter? = null, + val tokenConverter: CurrencyConverter? = null, + val lastChangedStates: LinkedHashSet = linkedSetOf(), + val addressPayIdState: AddressPayIdState = AddressPayIdState(), + val transactionExtrasState: TransactionExtrasState = TransactionExtrasState(), + val amountState: AmountState = AmountState(), + val feeState: FeeState = FeeState(), + val receiptState: ReceiptState = ReceiptState(), + val sendWarningsList: List = listOf(), + val sendButtonState: IndeterminateProgressButton = IndeterminateProgressButton(ButtonState.DISABLED), + val dialog: StateDialog? = null, + val externalTransactionData: ExternalTransactionData? = null, ) : SendScreenState { override val stateId: StateId = StateId.SEND_SCREEN @@ -114,7 +114,7 @@ data class SendState( fun isReadyToRequestFee(): Boolean = addressPayIdIsReady() && amountIsReady() fun isReadyToSend(): Boolean = addressPayIdIsReady() && amountIsReady() && - store.state.sendState.feeState.isReady() + store.state.sendState.feeState.isReady() } } @@ -144,10 +144,13 @@ data class AmountState( fun isCoinAmount(): Boolean = typeOfAmount == AmountType.Coin fun createMainCurrency(type: MainCurrencyType, canSwitched: Boolean): MainCurrency { - return if (!canSwitched) MainCurrency(type, amountToExtract?.currencySymbol ?: "NONE", false) - else when (type) { - MainCurrencyType.FIAT -> MainCurrency(type, store.state.globalState.appCurrency.code) - MainCurrencyType.CRYPTO -> MainCurrency(type, amountToExtract?.currencySymbol ?: "NONE") + return if (!canSwitched) { + MainCurrency(type, amountToExtract?.currencySymbol ?: "NONE", false) + } else { + when (type) { + MainCurrencyType.FIAT -> MainCurrency(type, store.state.globalState.appCurrency.code) + MainCurrencyType.CRYPTO -> MainCurrency(type, amountToExtract?.currencySymbol ?: "NONE") + } } } @@ -166,9 +169,9 @@ enum class MainCurrencyType { } data class MainCurrency( - val type: MainCurrencyType, - val currencySymbol: String, - val isEnabled: Boolean = true + val type: MainCurrencyType, + val currencySymbol: String, + val isEnabled: Boolean = true, ) data class ExternalTransactionData( @@ -176,5 +179,5 @@ data class ExternalTransactionData( val destinationAddress: String, val transactionId: String, val canAmountBeModified: Boolean = false, - val canDestinationAddressBeModified: Boolean = false + val canDestinationAddressBeModified: Boolean = false, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt index 5c5a0b924d..55b1211cfb 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt @@ -1,3 +1,5 @@ +@file:Suppress("MagicNumber") + package com.tangem.tap.features.send.ui import android.content.Context @@ -14,9 +16,9 @@ import androidx.recyclerview.widget.RecyclerView import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.material.textfield.TextInputEditText import com.tangem.Message +import com.tangem.core.analytics.Analytics import com.tangem.tangem_sdk_new.extensions.hideSoftKeyboard import com.tangem.tap.common.KeyboardObserver -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.extensions.getFromClipboard @@ -30,10 +32,25 @@ import com.tangem.tap.common.toggleWidget.IndeterminateProgressButtonWidget import com.tangem.tap.common.toggleWidget.ViewStateWidget import com.tangem.tap.features.BaseStoreFragment import com.tangem.tap.features.addBackPressHandler -import com.tangem.tap.features.send.redux.* -import com.tangem.tap.features.send.redux.AddressPayIdActionUi.* -import com.tangem.tap.features.send.redux.AmountActionUi.* -import com.tangem.tap.features.send.redux.FeeActionUi.* +import com.tangem.tap.features.send.redux.AddressPayIdActionUi +import com.tangem.tap.features.send.redux.AddressPayIdActionUi.CheckClipboard +import com.tangem.tap.features.send.redux.AddressPayIdActionUi.PasteAddressPayId +import com.tangem.tap.features.send.redux.AddressPayIdActionUi.SetTruncateHandler +import com.tangem.tap.features.send.redux.AddressPayIdActionUi.TruncateOrRestore +import com.tangem.tap.features.send.redux.AmountAction +import com.tangem.tap.features.send.redux.AmountActionUi +import com.tangem.tap.features.send.redux.AmountActionUi.CheckAmountToSend +import com.tangem.tap.features.send.redux.AmountActionUi.SetMainCurrency +import com.tangem.tap.features.send.redux.AmountActionUi.SetMaxAmount +import com.tangem.tap.features.send.redux.AmountActionUi.ToggleMainCurrency +import com.tangem.tap.features.send.redux.FeeActionUi.ChangeIncludeFee +import com.tangem.tap.features.send.redux.FeeActionUi.ChangeSelectedFee +import com.tangem.tap.features.send.redux.FeeActionUi.ToggleControlsVisibility +import com.tangem.tap.features.send.redux.ReceiptAction +import com.tangem.tap.features.send.redux.ReleaseSendState +import com.tangem.tap.features.send.redux.SendAction +import com.tangem.tap.features.send.redux.SendActionUi +import com.tangem.tap.features.send.redux.TransactionExtrasAction import com.tangem.tap.features.send.redux.states.FeeType import com.tangem.tap.features.send.redux.states.MainCurrencyType import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber @@ -45,7 +62,12 @@ import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentSendBinding import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import java.text.DecimalFormatSymbols /** @@ -85,13 +107,16 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { private fun initSendButtonStates() = with(binding) { btnSend.setOnClickListener { - store.dispatch(SendActionUi.SendAmountToRecipient( - Message(getString(R.string.initial_message_sign_header)) - )) + store.dispatch( + SendActionUi.SendAmountToRecipient( + Message(getString(R.string.initial_message_sign_header)), + ), + ) } sendBtn = IndeterminateProgressButtonWidget(btnSend, progress) } + @Suppress("MagicNumber") private fun setupAddressOrPayIdLayout() = with(binding.lSendAddressPayid) { store.dispatch(SetTruncateHandler { etAddressOrPayId.truncateMiddleWith(it, "...") }) store.dispatch(CheckClipboard(requireContext().getFromClipboard()?.toString())) @@ -100,12 +125,12 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { store.dispatch(TruncateOrRestore(!hasFocus)) } etAddressOrPayId.inputtedTextAsFlow() - .debounce(400) - .filter { store.state.sendState.addressPayIdState.viewFieldValue.value != it } - .onEach { - store.dispatch(AddressPayIdActionUi.HandleUserInput(it)) - } - .launchIn(mainScope) + .debounce(400) + .filter { store.state.sendState.addressPayIdState.viewFieldValue.value != it } + .onEach { + store.dispatch(AddressPayIdActionUi.HandleUserInput(it)) + } + .launchIn(mainScope) imvPaste.setOnClickListener { Analytics.send(Token.Send.ButtonPaste()) @@ -116,29 +141,29 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { Analytics.send(Token.Send.ButtonQRCode()) startActivityForResult( Intent(requireContext(), ScanQrCodeActivity::class.java), - ScanQrCodeActivity.SCAN_QR_REQUEST_CODE + ScanQrCodeActivity.SCAN_QR_REQUEST_CODE, ) } } private fun setupTransactionExtrasLayout() = with(binding.lSendAddressPayid) { etXlmMemo.inputtedTextAsFlow() - .debounce(400) - .filter { - val info = store.state.sendState.transactionExtrasState - info.xlmMemo?.viewFieldValue?.value != it - } - .onEach { store.dispatch(TransactionExtrasAction.XlmMemo.HandleUserInput(it)) } - .launchIn(mainScope) + .debounce(400) + .filter { + val info = store.state.sendState.transactionExtrasState + info.xlmMemo?.viewFieldValue?.value != it + } + .onEach { store.dispatch(TransactionExtrasAction.XlmMemo.HandleUserInput(it)) } + .launchIn(mainScope) etDestinationTag.inputtedTextAsFlow() - .debounce(400) - .filter { - val info = store.state.sendState.transactionExtrasState - info.xrpDestinationTag?.viewFieldValue?.value != it - } - .onEach { store.dispatch(TransactionExtrasAction.XrpDestinationTag.HandleUserInput(it)) } - .launchIn(mainScope) + .debounce(400) + .filter { + val info = store.state.sendState.transactionExtrasState + info.xrpDestinationTag?.viewFieldValue?.value != it + } + .onEach { store.dispatch(TransactionExtrasAction.XrpDestinationTag.HandleUserInput(it)) } + .launchIn(mainScope) etBinanceMemo.inputtedTextAsFlow() .debounce(400) @@ -159,10 +184,13 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { // Delayed launch is needed in order for the UI to be drawn and to process the sent events. // If do not use the delay, then etAmount error field is not displayed when // inserting an incorrect amount by shareUri - binding.lSendAddressPayid.imvQrCode.postDelayed({ - store.dispatch(PasteAddressPayId(scannedCode)) - store.dispatch(TruncateOrRestore(!binding.lSendAddressPayid.etAddressOrPayId.isFocused)) - }, 200) + binding.lSendAddressPayid.imvQrCode.postDelayed( + { + store.dispatch(PasteAddressPayId(scannedCode)) + store.dispatch(TruncateOrRestore(!binding.lSendAddressPayid.etAddressOrPayId.isFocused)) + }, + 200, + ) } private fun setupAmountLayout() { @@ -221,10 +249,10 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { } etAmountToSend.inputtedTextAsFlow() - .debounce(400) - .filter { store.state.sendState.amountState.viewAmountValue.value != it && it.isNotEmpty() } - .onEach { store.dispatch(AmountActionUi.HandleUserInput(it)) } - .launchIn(mainScope) + .debounce(400) + .filter { store.state.sendState.amountState.viewAmountValue.value != it && it.isNotEmpty() } + .onEach { store.dispatch(AmountActionUi.HandleUserInput(it)) } + .launchIn(mainScope) etAmountToSend.setOnImeActionListener(EditorInfo.IME_ACTION_DONE) { it.hideSoftKeyboard() @@ -287,7 +315,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { store.dispatch(NavigationAction.PopBackTo()) } else { store.dispatch( - WalletAction.TradeCryptoAction.FinishSelling(externalTransactionData.transactionId) + WalletAction.TradeCryptoAction.FinishSelling(externalTransactionData.transactionId), ) } } @@ -309,24 +337,22 @@ fun EditText.inputtedTextAsFlow(): Flow = callbackFlow { awaitClose { removeTextChangedListener(watcher) } } -class FeeUiHelper { - companion object { - fun toId(fee: FeeType): Int { - return when (fee) { - FeeType.SINGLE -> View.NO_ID - FeeType.LOW -> R.id.chipLow - FeeType.NORMAL -> R.id.chipNormal - FeeType.PRIORITY -> R.id.chipPriority - } +object FeeUiHelper { + fun toId(fee: FeeType): Int { + return when (fee) { + FeeType.SINGLE -> View.NO_ID + FeeType.LOW -> R.id.chipLow + FeeType.NORMAL -> R.id.chipNormal + FeeType.PRIORITY -> R.id.chipPriority } + } - fun toType(id: Int): FeeType { - return when (id) { - R.id.chipLow -> FeeType.LOW - R.id.chipNormal -> FeeType.NORMAL - R.id.chipPriority -> FeeType.PRIORITY - else -> FeeType.NORMAL - } + fun toType(id: Int): FeeType { + return when (id) { + R.id.chipLow -> FeeType.LOW + R.id.chipNormal -> FeeType.NORMAL + R.id.chipPriority -> FeeType.PRIORITY + else -> FeeType.NORMAL } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/RequestFeeErrorDialog.kt b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/RequestFeeErrorDialog.kt index 353989e76c..8da2d25445 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/RequestFeeErrorDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/RequestFeeErrorDialog.kt @@ -11,22 +11,19 @@ import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ -class RequestFeeErrorDialog { +object RequestFeeErrorDialog { + fun create(context: Context, dialog: SendAction.Dialog.RequestFeeError): AlertDialog { + val errorMessage = dialog.error.customMessage - companion object { - fun create(context: Context, dialog: SendAction.Dialog.RequestFeeError): AlertDialog { - val errorMessage = dialog.error.customMessage - - return AlertDialog.Builder(context).apply { - setTitle(R.string.common_fee_error) - setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, errorMessage)) - setNegativeButton(R.string.alert_button_send_feedback) { _, _ -> - store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage))) - } - setPositiveButton(R.string.common_retry) { _, _ -> dialog.onRetry() } - setNeutralButton(R.string.common_cancel) { _, _ -> } - setOnDismissListener { store.dispatch(SendAction.Dialog.Hide) } - }.create() - } + return AlertDialog.Builder(context).apply { + setTitle(R.string.common_fee_error) + setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, errorMessage)) + setNegativeButton(R.string.alert_button_send_feedback) { _, _ -> + store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage))) + } + setPositiveButton(R.string.common_retry) { _, _ -> dialog.onRetry() } + setNeutralButton(R.string.common_cancel) { _, _ -> } + setOnDismissListener { store.dispatch(SendAction.Dialog.Hide) } + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt index 0e35552e43..600e7a99d7 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt @@ -16,28 +16,26 @@ import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ -class SendTransactionFailsDialog { - companion object { - fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails.CardSdkError): AlertDialog { - return create(context, dialog.error.localizedDescription(context)) - } +object SendTransactionFailsDialog { + fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails.CardSdkError): AlertDialog { + return create(context, dialog.error.localizedDescription(context)) + } - fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails.BlockchainSdkError): AlertDialog { - val errorConverter = BlockchainSdkErrorConverter(context) - return create(context, errorConverter.convert(dialog.error)) - } + fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails.BlockchainSdkError): AlertDialog { + val errorConverter = BlockchainSdkErrorConverter(context) + return create(context, errorConverter.convert(dialog.error)) + } - private fun create(context: Context, errorMessage: String): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(R.string.alert_failed_to_send_transaction_title) - setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, errorMessage)) - setNeutralButton(R.string.alert_button_send_feedback) { _, _ -> - store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage))) - } - setPositiveButton(R.string.common_cancel) { _, _ -> } - setOnDismissListener { store.dispatch(SendAction.Dialog.Hide) } - }.create() - } + private fun create(context: Context, errorMessage: String): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(R.string.alert_failed_to_send_transaction_title) + setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, errorMessage)) + setNeutralButton(R.string.alert_button_send_feedback) { _, _ -> + store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage))) + } + setPositiveButton(R.string.common_cancel) { _, _ -> } + setOnDismissListener { store.dispatch(SendAction.Dialog.Hide) } + }.create() } } diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt index 78bff6c996..6226a2b1a9 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt @@ -69,6 +69,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : } } + @Suppress("ComplexMethod") private fun handleTransactionExtrasState(fg: SendFragment, infoState: TransactionExtrasState) = with(fg.binding.lSendAddressPayid) { @@ -220,7 +221,11 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : tilAmountToSend.enableError(false) } - val filter = DecimalDigitsInputFilter(12, state.maxLengthOfAmount, state.decimalSeparator) + val filter = DecimalDigitsInputFilter( + digitsBeforeDecimal = 12, + digitsAfterDecimal = state.maxLengthOfAmount, + decimalSeparator = state.decimalSeparator, + ) etAmountToSend.filters = arrayOf(filter) val amountToSend = state.viewAmountValue if (!amountToSend.isFromUserInput) etAmountToSend.update(amountToSend.value) @@ -249,8 +254,8 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : tvAmountCurrency.setTextColor(fg.getColor(textColor)) } + @Suppress("MagicNumber") private fun handleFeeState(fg: SendFragment, state: FeeState) = with(fg.binding.clNetworkFee) { -// chipGroup.fitChipsByGroupWidth() fg.view?.findViewById(R.id.clNetworkFee)?.show(state.mainLayoutIsVisible) flExpandCollapse.imvExpandCollapse.rotation = if (state.controlsLayoutIsVisible) 0f else 180f llFeeControlsContainer.show(state.controlsLayoutIsVisible) @@ -265,6 +270,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : if (chipGroup.checkedChipId != chipId && chipId != View.NO_ID) chipGroup.check(chipId) } + @Suppress("LongMethod", "ComplexMethod") private fun handleReceiptState( fg: SendFragment, state: ReceiptState, diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt index 27a4a5b9e5..219753c469 100644 --- a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt +++ b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt @@ -2,9 +2,9 @@ package com.tangem.tap.features.shop.redux import android.content.Intent import com.tangem.tap.common.redux.NotificationAction -import com.tangem.tap.common.shop.GooglePayService import com.tangem.tap.common.shop.data.ProductType import com.tangem.tap.common.shop.data.TangemProduct +import com.tangem.tap.common.shop.googlepay.GooglePayService import com.tangem.wallet.R import org.rekotlin.Action diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt index 79f2e10a6c..22073f9236 100644 --- a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt @@ -25,6 +25,7 @@ class ShopMiddleware { } } +@Suppress("LongMethod", "ComplexMethod") private fun handle(action: Action) { val shopState = store.state.shopState @@ -49,8 +50,8 @@ private fun handle(action: Action) { store.dispatchOnMain( ShopAction.ApplyPromoCode.Success( promoCode = products.first { it.type == shopState.selectedProduct }.appliedDiscount, - products = products - ) + products = products, + ), ) } result.onFailure { store.dispatchOnMain(ShopAction.ApplyPromoCode.InvalidPromoCode) } @@ -76,7 +77,7 @@ private fun handle(action: Action) { val result = shopService.handleGooglePayResult( action.resultCode, action.data, - shopState.selectedProduct + shopState.selectedProduct, ) result.onSuccess { store.dispatchOnMain(ShopAction.BuyWithGooglePay.Success) @@ -85,7 +86,6 @@ private fun handle(action: Action) { store.dispatchOnMain(ShopAction.BuyWithGooglePay.Failure(it)) } } - } ShopAction.LoadProducts -> { scope.launch { @@ -94,7 +94,7 @@ private fun handle(action: Action) { onFailure = { Timber.e(it) store.dispatchOnMain(ShopAction.LoadProducts.Failure) - } + }, ) } } diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt index 001c48a46f..633afe5727 100644 --- a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt @@ -2,12 +2,11 @@ package com.tangem.tap.features.shop.redux import org.rekotlin.Action -class ShopReducer { - companion object { - fun reduce(action: Action, state: ShopState): ShopState = internalReduce(action, state) - } +object ShopReducer { + fun reduce(action: Action, state: ShopState): ShopState = internalReduce(action, state) } +@Suppress("ComplexMethod") private fun internalReduce(action: Action, state: ShopState): ShopState { if (action !is ShopAction) return state @@ -15,7 +14,7 @@ private fun internalReduce(action: Action, state: ShopState): ShopState { return when (action) { is ShopAction.ApplyPromoCode -> state.copy( promoCode = action.promoCode, - promoCodeLoading = true + promoCodeLoading = true, ) ShopAction.BuyWithGooglePay -> state ShopAction.LoadProducts -> state @@ -26,15 +25,15 @@ private fun internalReduce(action: Action, state: ShopState): ShopState { } ShopAction.StartWebCheckout -> state ShopAction.ApplyPromoCode.InvalidPromoCode -> state.copy( - promoCode = null, promoCodeLoading = false + promoCode = null, promoCodeLoading = false, ) is ShopAction.ApplyPromoCode.Success -> { state.copy( promoCode = action.promoCode, availableProducts = action.products, - promoCodeLoading = false + promoCodeLoading = false, - ) + ) } is ShopAction.SelectProduct -> { state.copy( @@ -49,7 +48,6 @@ private fun internalReduce(action: Action, state: ShopState): ShopState { } ShopAction.CheckIfGooglePayAvailable.Success -> { state.copy(isGooglePayAvailable = false) // TODO: change when we add support for GPay - } is ShopAction.BuyWithGooglePay.Failure -> { state diff --git a/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt b/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt index 1f32a5305e..dd0110b605 100644 --- a/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt @@ -10,9 +10,9 @@ import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager import androidx.activity.OnBackPressedCallback import by.kirich1409.viewbindingdelegate.viewBinding +import com.tangem.core.analytics.Analytics import com.tangem.tap.common.GlobalLayoutStateHandler import com.tangem.tap.common.KeyboardObserver -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Shop import com.tangem.tap.common.extensions.show import com.tangem.tap.common.redux.navigation.NavigationAction @@ -25,10 +25,10 @@ import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentShopBinding import org.rekotlin.StoreSubscriber - class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber { private val binding: FragmentShopBinding by viewBinding(FragmentShopBinding::bind) + private var cardTranslationY = 70f lateinit var keyboardObserver: KeyboardObserver @@ -45,12 +45,15 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< super.onCreate(savedInstanceState) Analytics.send(Shop.ScreenOpened()) - activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - store.dispatch(NavigationAction.PopBackTo()) - store.dispatch(ShopAction.ResetState) - } - }) + activity?.onBackPressedDispatcher?.addCallback( + this, + object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + store.dispatch(NavigationAction.PopBackTo()) + store.dispatch(ShopAction.ResetState) + } + }, + ) } override fun onDestroyView() { @@ -76,7 +79,7 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< } } - private var cardTranslationY = 70f + @Suppress("MagicNumber") private fun setupCardsImages() { GlobalLayoutStateHandler(binding.imvSecond).apply { onStateChanged = { @@ -94,7 +97,6 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< detach() } } - } private fun setupProductSelection() = with(binding) { @@ -147,12 +149,14 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< if (show) imvThird.show() imvThird.animate() .translationY(translationY) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - super.onAnimationEnd(animation) - imvThird.show(show) - } - }) + .setListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + super.onAnimationEnd(animation) + imvThird.show(show) + } + }, + ) } private fun handlePriceState(state: ShopState) = with(binding) { 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 cfa3c7a5b8..fe5f66f086 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 @@ -63,11 +63,6 @@ import com.tangem.tap.features.tokens.addCustomToken.compose.test.TestCasesList import com.tangem.wallet.R import kotlinx.coroutines.launch -/** -[REDACTED_AUTHOR] - */ -private class AddCustomTokenScreen // for simple search - @OptIn(ExperimentalMaterialApi::class) @Composable fun AddCustomTokenScreen( @@ -77,7 +72,7 @@ fun AddCustomTokenScreen( val selectedTestCase = remember { mutableStateOf(TestCase.ContractAddress) } val bottomSheetScaffoldState = rememberBottomSheetScaffoldState( - bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed) + bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed), ) val coroutineScope = rememberCoroutineScope() val toggleBottomSheet = { coroutineScope.launch { bottomSheetScaffoldState.toggle() } } @@ -146,7 +141,7 @@ private fun ScreenContent( Column( modifier = Modifier .fillMaxWidth() - .padding(16.dp) + .padding(16.dp), ) { FormFields(state, closePopupTrigger) } @@ -197,7 +192,7 @@ fun Warnings(warnings: List) { AddCustomTokenWarning( modifier = modifier.fillMaxWidth(), warning = item, - converter = warningConverter + converter = warningConverter, ) } } @@ -208,22 +203,15 @@ private fun AddButton(state: MutableState) { AddCustomTokenFab( modifier = Modifier .widthIn(210.dp, 280.dp), - isEnabled = state.value.screenState.addButton.isEnabled + isEnabled = state.value.screenState.addButton.isEnabled, ) { domainStore.dispatch(AddCustomTokenAction.OnAddCustomTokenClicked) } } +@Suppress("MagicNumber") @Composable -private fun AddCustomTokenFab( - modifier: Modifier = Modifier, - isEnabled: Boolean = true, - onClick: () -> Unit -) { +private fun AddCustomTokenFab(modifier: Modifier = Modifier, isEnabled: Boolean = true, onClick: () -> Unit) { val contentColor = Color.White - val backgroundColor = if (isEnabled) { - Color(0xFF1ACE80) - } else { - Color(0xFFB9E6D3) - } + val backgroundColor = if (isEnabled) Color(0xFF1ACE80) else Color(0xFFB9E6D3) val elevation = if (!isEnabled) { FloatingActionButtonDefaults.elevation(0.dp, 0.dp) } else { @@ -253,19 +241,19 @@ data class ScreenFieldData( val field: DataField<*>, val error: AddCustomTokenError?, val errorConverter: ModuleMessageConverter, - val viewState: ViewStates.TokenField + val viewState: ViewStates.TokenField, ) { companion object { fun fromState( field: DataField<*>, state: AddCustomTokenState, - errorConverter: ModuleMessageConverter + errorConverter: ModuleMessageConverter, ): ScreenFieldData { return ScreenFieldData( field = field, error = state.getError(field.id), errorConverter = errorConverter, - viewState = selectField(field.id, state.screenState) + viewState = selectField(field.id, state.screenState), ) } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/FormFieldViews.kt b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/FormFieldViews.kt index bc4c0ae9c6..7a14ca2d85 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/FormFieldViews.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/FormFieldViews.kt @@ -2,10 +2,8 @@ package com.tangem.tap.features.tokens.addCustomToken.compose import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.SpacerH8 import com.tangem.domain.common.form.Field import com.tangem.domain.features.addCustomToken.TokenBlockchainField @@ -20,12 +18,9 @@ import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTo import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState import com.tangem.domain.redux.domainStore import com.tangem.tap.common.compose.BlockchainSpinner -import com.tangem.tap.common.compose.ClearButton import com.tangem.tap.common.compose.ClosePopupTrigger import com.tangem.tap.common.compose.OutlinedTextFieldWidget -import com.tangem.tap.common.compose.PasteButton import com.tangem.tap.common.compose.TitleSubtitle -import com.tangem.tap.common.extensions.getFromClipboard import com.tangem.wallet.R /** @@ -52,31 +47,6 @@ fun TokenContractAddressView(screenFieldData: ScreenFieldData) { SpacerH8() } -@Composable -private fun PasteClearButton( - showFirst: Boolean, -) { - if (showFirst) { - val context = LocalContext.current - PasteButton( - onClick = { - context.getFromClipboard()?.let { - val fieldData = Field.Data(it.toString(), false) - domainStore.dispatch(OnTokenContractAddressChanged(fieldData)) - } - - } - ) - } else { - ClearButton( - onClick = { - val fieldData = Field.Data("", false) - domainStore.dispatch(OnTokenContractAddressChanged(fieldData)) - } - ) - } -} - @Composable fun TokenNameView(screenFieldData: ScreenFieldData) { if (!screenFieldData.viewState.isVisible) return @@ -178,10 +148,4 @@ fun TokenDerivationPathView( closePopupTrigger = closePopupTrigger, ) { domainStore.dispatch(OnTokenDerivationPathChanged(Field.Data(it, true))) } SpacerH8() -} - -@Preview -@Composable -fun TestTokenContractAddressView() { - } \ No newline at end of file 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 26b1e9c376..7c513d06de 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 @@ -10,6 +10,7 @@ import com.tangem.common.extensions.toMapKey import com.tangem.common.flatMap import com.tangem.common.hdWallet.DerivationPath import com.tangem.common.services.Result +import com.tangem.core.analytics.Analytics import com.tangem.domain.DomainWrapped import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.derivationStyle @@ -21,7 +22,6 @@ import com.tangem.domain.redux.domainStore import com.tangem.operations.derivation.ExtendedPublicKeysMap import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.assetReader -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.ManageTokens import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchOnMain @@ -45,6 +45,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Middleware +@Suppress("LargeClass") class TokensMiddleware { val tokensMiddleware: Middleware = { _, _ -> @@ -96,11 +97,11 @@ class TokensMiddleware { isTestcard, supportedBlockchains, tokensState.pageToLoad, - tokensState.searchInput + tokensState.searchInput, ) } else { loadCoinsService.getSupportedTokens( - isTestcard, supportedBlockchains, 0, newSearchInput.ifBlank { null } + isTestcard, supportedBlockchains, 0, newSearchInput.ifBlank { null }, ) } when (loadCoinsResult) { @@ -143,10 +144,11 @@ class TokensMiddleware { convertToCurrencies( blockchains = blockchainsToRemove, tokens = tokensToRemove, - derivationStyle = derivationStyle - ) + derivationStyle = derivationStyle, + ), ) + @Suppress("ComplexCondition") if (tokensToAdd.isEmpty() && tokensToRemove.isEmpty() && blockchainsToAdd.isEmpty() && blockchainsToRemove.isEmpty() ) { @@ -158,7 +160,7 @@ class TokensMiddleware { val currencyList = convertToCurrencies( blockchains = blockchainsToAdd, tokens = tokensToAdd, - derivationStyle = derivationStyle + derivationStyle = derivationStyle, ) if (scanResponse.supportsHdWallet()) { deriveMissingBlockchains(scanResponse, currencyList) { @@ -174,7 +176,7 @@ class TokensMiddleware { private fun convertToCurrencies( blockchains: List, tokens: List, - derivationStyle: DerivationStyle? + derivationStyle: DerivationStyle?, ): List { return blockchains.map { Currency.Blockchain(it, it.derivationPath(derivationStyle)?.rawPath) @@ -182,7 +184,7 @@ class TokensMiddleware { Currency.Token( it.token, it.blockchain, - it.blockchain.derivationPath(derivationStyle)?.rawPath + it.blockchain.derivationPath(derivationStyle)?.rawPath, ) } } @@ -194,7 +196,7 @@ class TokensMiddleware { ) { val derivationDataList = listOfNotNull( getDerivations(EllipticCurve.Secp256k1, scanResponse, currencyList), - getDerivations(EllipticCurve.Ed25519, scanResponse, currencyList) + getDerivations(EllipticCurve.Ed25519, scanResponse, currencyList), ) val derivations = derivationDataList.associate { it.derivations } if (derivations.isEmpty()) { @@ -267,29 +269,24 @@ class TokensMiddleware { return DerivationData( derivations = mapKeyOfWalletPublicKey to toDerive, alreadyDerivedKeys = alreadyDerivedKeys, - mapKeyOfWalletPublicKey = mapKeyOfWalletPublicKey + mapKeyOfWalletPublicKey = mapKeyOfWalletPublicKey, ) } private class DerivationData( val derivations: Pair>, val alreadyDerivedKeys: ExtendedPublicKeysMap, - val mapKeyOfWalletPublicKey: ByteArrayKey + val mapKeyOfWalletPublicKey: ByteArrayKey, ) - private fun submitAdd( - scanResponse: ScanResponse, - currencyList: List, - ) { + private fun submitAdd(scanResponse: ScanResponse, currencyList: List) { val selectedUserWallet = userWalletsListManager.selectedUserWalletSync if (selectedUserWallet != null) { scope.launch { userWalletsListManager.update( userWalletId = selectedUserWallet.walletId, update = { userWallet -> - userWallet.copy( - scanResponse = scanResponse, - ) + userWallet.copy(scanResponse = scanResponse) }, ) .flatMap { updatedUserWallet -> @@ -307,7 +304,6 @@ class TokensMiddleware { when (currency) { is Currency.Blockchain -> { val derivationPath = currency.derivationPath?.let { DerivationPath(it) } - val derivationParams = derivationStyle?.let { when (derivationPath) { null -> DerivationParams.Default(derivationStyle) @@ -319,9 +315,8 @@ class TokensMiddleware { blockchain = currency.blockchain, derivationParams = derivationParams, ) ?: return@mapIndexedNotNull null - val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager) WalletAction.MultiWallet.AddBlockchain( - blockchain = blockchainNetwork, + blockchain = BlockchainNetwork.fromWalletManager(walletManager), walletManager = walletManager, save = index == currencyList.lastIndex, ) @@ -394,12 +389,12 @@ class TokensMiddleware { when (it) { is Currency.Blockchain -> DomainWrapped.Currency.Blockchain( it.blockchain, - it.derivationPath + it.derivationPath, ) is Currency.Token -> DomainWrapped.Currency.Token( it.token, it.blockchain, - it.derivationPath + it.derivationPath, ) } } 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 007a1e9711..336d8a7982 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 @@ -3,10 +3,8 @@ package com.tangem.tap.features.tokens.redux import com.tangem.tap.common.redux.AppState import org.rekotlin.Action -class TokensReducer { - companion object { - fun reduce(action: Action, state: AppState): TokensState = internalReduce(action, state) - } +object TokensReducer { + fun reduce(action: Action, state: AppState): TokensState = internalReduce(action, state) } private fun internalReduce(action: Action, state: AppState): TokensState { @@ -23,7 +21,7 @@ private fun internalReduce(action: Action, state: AppState): TokensState { } tokensState.copy( scanResponse = action.scanResponse, - loadCoinsState = loadingState + loadCoinsState = loadingState, ) } is TokensAction.LoadCurrencies.Success -> { @@ -31,7 +29,7 @@ private fun internalReduce(action: Action, state: AppState): TokensState { currencies = tokensState.currencies + action.currencies, needToLoadMore = action.loadMore, pageToLoad = tokensState.pageToLoad + 1, - loadCoinsState = LoadCoinsState.LOADED + loadCoinsState = LoadCoinsState.LOADED, ) } is TokensAction.SetAddedCurrencies -> { @@ -40,7 +38,7 @@ private fun internalReduce(action: Action, state: AppState): TokensState { addedBlockchains = action.wallets.toNonCustomBlockchains(action.derivationStyle), addedTokens = action.wallets.toNonCustomTokensWithBlockchains(action.derivationStyle), addedWallets = action.wallets, - derivationStyle = action.derivationStyle + derivationStyle = action.derivationStyle, ) } is TokensAction.AllowToAddTokens -> { @@ -52,7 +50,7 @@ private fun internalReduce(action: Action, state: AppState): TokensState { needToLoadMore = true, currencies = emptyList(), pageToLoad = 0, - loadCoinsState = LoadCoinsState.LOADING + loadCoinsState = LoadCoinsState.LOADING, ) } else -> tokensState 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 ab1b3731a5..5c4f723467 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 @@ -9,6 +9,7 @@ import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.tap.domain.tokens.Currency import com.tangem.tap.features.wallet.redux.WalletData import org.rekotlin.StateType +import com.tangem.tap.features.wallet.models.Currency.Token as CurrencyToken data class TokensState( val addedWallets: List = emptyList(), @@ -32,18 +33,18 @@ data class TokensState( typealias ContractAddress = String fun List.toTokensContractAddresses(): List { - return mapNotNull { (it.currency as? com.tangem.tap.features.wallet.models.Currency.Token)?.token?.contractAddress }.distinct() + return mapNotNull { (it.currency as? CurrencyToken)?.token?.contractAddress }.distinct() } fun List.toNonCustomTokens(derivationStyle: DerivationStyle?): List { return filter { !it.currency.isCustomCurrency(derivationStyle) } - .mapNotNull { (it.currency as? com.tangem.tap.features.wallet.models.Currency.Token)?.token } + .mapNotNull { (it.currency as? CurrencyToken)?.token } .distinct() } fun List.toNonCustomTokensWithBlockchains(derivationStyle: DerivationStyle?): List { return mapNotNull { - if (it.currency !is com.tangem.tap.features.wallet.models.Currency.Token) return@mapNotNull null + if (it.currency !is CurrencyToken) return@mapNotNull null if (it.currency.isCustomCurrency(derivationStyle)) return@mapNotNull null TokenWithBlockchain(it.currency.token, it.currency.blockchain) }.distinct() @@ -61,17 +62,18 @@ fun List.toNonCustomBlockchains(derivationStyle: DerivationStyle?): data class TokenWithBlockchain( val token: Token, - val blockchain: Blockchain + val blockchain: Blockchain, ) fun List.filter(supportedBlockchains: Set?): List { if (supportedBlockchains == null) return this return map { - it.copy(contracts = - it.contracts.filter { - supportedBlockchains.contains(it.blockchain) && - (it.blockchain.canHandleTokens() || it.address == null) - } + it.copy( + contracts = + it.contracts.filter { + supportedBlockchains.contains(it.blockchain) && + (it.blockchain.canHandleTokens() || it.address == null) + }, ) }.filterNot { it.contracts.isNullOrEmpty() && !supportedBlockchains.contains(Blockchain.fromNetworkId(it.id)) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt index 499ed8e2f5..9f9288ea99 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt @@ -103,6 +103,7 @@ class AddTokensFragment : BaseFragment(R.layout.fragment_add_tokens), StoreSubsc super.onDestroy() } + @Suppress("MagicNumber") override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.menu_popular_tokens, menu) @@ -168,5 +169,5 @@ fun SearchView.inputtedTextAsFlow(): Flow = callbackFlow { } }, ) - awaitClose { (watcher) } + awaitClose { watcher } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt index 612f5d413a..371e343dda 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt @@ -1,3 +1,5 @@ +@file:Suppress("MagicNumber") + package com.tangem.tap.features.tokens.ui.compose import androidx.compose.animation.AnimatedVisibility @@ -26,12 +28,12 @@ import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.tangem.blockchain.common.Blockchain +import com.tangem.core.analytics.Analytics import com.tangem.core.ui.components.Keyboard import com.tangem.core.ui.components.SystemBarsEffect import com.tangem.core.ui.components.keyboardAsState import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation import com.tangem.domain.common.extensions.fromNetworkId -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.ManageTokens import com.tangem.tap.common.compose.extensions.addAndNotify @@ -48,6 +50,7 @@ import com.tangem.tap.features.wallet.redux.models.WalletDialog import com.tangem.tap.store import com.tangem.wallet.R +@Suppress("LongMethod") @Composable fun CurrenciesScreen( tokensState: MutableState = mutableStateOf(store.state.tokensState), diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesWarning.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesWarning.kt index b5c11c60b8..d102ebda9f 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesWarning.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesWarning.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.tangem.wallet.R +@Suppress("MagicNumber") @Composable fun CurrenciesWarning() { Box( 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 deleted file mode 100644 index e69de29bb2..0000000000 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 613f741c55..175d3a765a 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 @@ -19,6 +19,7 @@ import com.tangem.tap.features.tokens.redux.ContractAddress import com.tangem.tap.features.tokens.redux.TokenWithBlockchain import com.tangem.tap.features.tokens.ui.compose.currencyItem.CurrencyItem +@Suppress("LongParameterList") @Composable fun ListOfCurrencies( header: @Composable () -> Unit, diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyExpandedContent.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyExpandedContent.kt index c4b1b21abe..0757c05100 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyExpandedContent.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyExpandedContent.kt @@ -15,6 +15,7 @@ import com.tangem.tap.domain.tokens.Currency import com.tangem.tap.features.tokens.redux.ContractAddress import com.tangem.tap.features.tokens.redux.TokenWithBlockchain +@Suppress("LongParameterList") @Composable fun CurrencyExpandedContent( currency: Currency, diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItem.kt index 6758c71e82..2a3c458e2e 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItem.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItem.kt @@ -7,6 +7,7 @@ import com.tangem.tap.domain.tokens.Currency import com.tangem.tap.features.tokens.redux.ContractAddress import com.tangem.tap.features.tokens.redux.TokenWithBlockchain +@Suppress("LongParameterList") @Composable fun CurrencyItem( currency: Currency, diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemArrow.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemArrow.kt index 4be02043d5..7c4ebd56e8 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemArrow.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemArrow.kt @@ -36,6 +36,7 @@ fun CurrencyItemArrow( } } +@Suppress("MagicNumber") @Composable private fun MiddleArrowView(rowHeight: Dp) { Box { diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemHeader.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemHeader.kt index 32c6c7b329..c081298112 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemHeader.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemHeader.kt @@ -40,6 +40,7 @@ import com.tangem.tap.features.tokens.ui.compose.CurrencyPlaceholderIcon import com.tangem.tap.features.tokens.ui.compose.fullName import com.tangem.wallet.R +@Suppress("LongMethod", "MagicNumber") @Composable fun CurrencyItemHeader( currency: Currency, @@ -166,6 +167,7 @@ private fun NetworksRow( } } +@Suppress("MagicNumber") @Composable private fun BlockchainNetworkItem( blockchain: Blockchain?, diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/NetworkItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/NetworkItem.kt index 494cbc8553..93ed42ffe7 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/NetworkItem.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/NetworkItem.kt @@ -41,6 +41,7 @@ import com.tangem.tap.features.tokens.redux.ContractAddress import com.tangem.tap.features.tokens.redux.TokenWithBlockchain import com.tangem.tap.features.tokens.ui.compose.CurrencyPlaceholderIcon +@Suppress("LongParameterList", "LongMethod", "MagicNumber") @OptIn(ExperimentalFoundationApi::class) @Composable fun NetworkItem( @@ -158,6 +159,7 @@ fun NetworkItem( } } +@Suppress("MagicNumber") @Composable private fun prepareNetworkNameSpannableText( blockchain: Blockchain, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt index e2709d2bb9..caafcd99c0 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt @@ -8,9 +8,9 @@ sealed class WalletWarning( val edStringValueWithSymbol: String, ) : WalletWarning(1) - data class TransactionInProgress(val currencyName: String) : WalletWarning(10) - data class BalanceNotEnoughForFee(val blockchainFullName: String) : WalletWarning(30) - data class Rent(val walletRent: WalletRent) : WalletWarning(40) + data class TransactionInProgress(val currencyName: String) : WalletWarning(showingPosition = 10) + data class BalanceNotEnoughForFee(val blockchainFullName: String) : WalletWarning(showingPosition = 30) + data class Rent(val walletRent: WalletRent) : WalletWarning(showingPosition = 40) } data class WalletWarningDescription( diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index b803175cf8..0e42de22a6 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -110,7 +110,7 @@ data class WalletState( if (currency == null) return null return walletsStores.firstOrNull { it.blockchainNetwork.derivationPath == currency.derivationPath && - (it.blockchainNetwork.blockchain == currency.blockchain) + it.blockchainNetwork.blockchain == currency.blockchain } } @@ -125,7 +125,7 @@ data class WalletState( if (blockchainNetwork == null) return null return walletsStores.firstOrNull { it.blockchainNetwork.derivationPath == blockchainNetwork.derivationPath && - (it.blockchainNetwork.blockchain == blockchainNetwork.blockchain) + it.blockchainNetwork.blockchain == blockchainNetwork.blockchain } } @@ -134,13 +134,6 @@ data class WalletState( return getWalletStore(currency)?.walletsData?.firstOrNull { it.currency == currency } } - private fun isPrimaryCurrency(walletData: WalletData): Boolean { - return (walletData.currency is Currency.Blockchain && - walletData.currency.blockchain == store.state.walletState.primaryBlockchain) - || (walletData.currency is Currency.Token && - walletData.currency.token == store.state.walletState.primaryToken) - } - fun replaceWalletStoreInWalletsStores(wallet: WalletStore?): List { if (wallet == null) return walletsStores var changed = false @@ -231,9 +224,9 @@ data class WalletState( fiatCurrency = store.state.globalState.appCurrency, ), ) - } else this.copy( - totalBalance = null, - ) + } else { + this.copy(totalBalance = null) + } } private fun updateProgressState(): WalletState { @@ -377,7 +370,7 @@ data class WalletData( if (!currency.isToken()) return val blockchainFullName = currency.blockchain.fullName - if ((blockchainAmountIsEmpty() && !tokenAmountIsEmpty())) { + if (blockchainAmountIsEmpty() && !tokenAmountIsEmpty()) { walletWarnings.add(WalletWarning.BalanceNotEnoughForFee(blockchainFullName)) } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt index 75b79e6a61..a9b2f6fb7c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt @@ -54,6 +54,7 @@ internal fun WalletStoreModel.mapToReduxModel( ) } +@Suppress("LongMethod", "ComplexMethod") private fun List.mapToReduxModel( isMultiWalletAllowed: Boolean, walletRent: WalletStoreModel.WalletRent?, @@ -93,14 +94,16 @@ private fun List.mapToReduxModel( walletRent = walletRent?.let { WalletRent( minRentValue = "${it.rent.stripZeroPlainString()} ${currency.blockchain.currency}", - rentExemptValue = "${it.exemptionAmount.stripZeroPlainString()} ${currency.blockchain.currency}", + rentExemptValue = "${it.exemptionAmount.stripZeroPlainString()} " + + currency.blockchain.currency, ) }, currencyData = BalanceWidgetData( status = when (status) { is WalletDataModel.Loading -> BalanceStatus.Loading is WalletDataModel.NoAccount -> BalanceStatus.NoAccount - is WalletDataModel.SameCurrencyTransactionInProgress -> BalanceStatus.SameCurrencyTransactionInProgress + is WalletDataModel.SameCurrencyTransactionInProgress -> + BalanceStatus.SameCurrencyTransactionInProgress is WalletDataModel.TransactionInProgress -> BalanceStatus.TransactionInProgress is WalletDataModel.Unreachable -> BalanceStatus.Unreachable is WalletDataModel.MissedDerivation -> BalanceStatus.MissedDerivation 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 8a3aaa65c7..10b3fff7cf 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 @@ -5,8 +5,8 @@ import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.WalletManager import com.tangem.common.doOnSuccess import com.tangem.common.extensions.guard -import com.tangem.domain.common.extensions.withMainContext import com.tangem.core.analytics.Analytics +import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Token.ButtonRemoveToken @@ -42,6 +42,7 @@ import kotlinx.coroutines.withContext import java.math.BigDecimal class MultiWalletMiddleware { + @Suppress("LongMethod", "ComplexMethod") fun handle( action: WalletAction.MultiWallet, walletState: WalletState?, globalState: GlobalState?, ) { @@ -129,11 +130,13 @@ class MultiWalletMiddleware { } is WalletAction.MultiWallet.RemoveWallet -> { val selectedUserWallet = userWalletsListManager.selectedUserWalletSync - if (selectedUserWallet != null) scope.launch { - walletCurrenciesManager.removeCurrency( - userWallet = selectedUserWallet, - currencyToRemove = action.currency, - ) + if (selectedUserWallet != null) { + scope.launch { + walletCurrenciesManager.removeCurrency( + userWallet = selectedUserWallet, + currencyToRemove = action.currency, + ) + } } else { val currency = action.currency val card = globalState.scanResponse?.card.guard { @@ -144,8 +147,9 @@ class MultiWalletMiddleware { var currencies = walletState?.currencies ?: emptyList() currencies = currencies.filterNot { it == currency } if (currency.isBlockchain()) { - currencies - .filter { it.blockchain == currency.blockchain && it.derivationPath == currency.derivationPath } + currencies.filter { + it.blockchain == currency.blockchain && it.derivationPath == currency.derivationPath + } } scope.launch { userTokensRepository.saveUserTokens(card, currencies) } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index 6142599a97..4ff0929fd3 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -65,6 +65,7 @@ import org.rekotlin.Middleware import timber.log.Timber import java.math.BigDecimal +@Suppress("LargeClass") class WalletMiddleware { private val tradeCryptoMiddleware = TradeCryptoMiddleware() private val warningsMiddleware = WarningsMiddleware() @@ -89,6 +90,7 @@ class WalletMiddleware { } } + @Suppress("LongMethod", "ComplexMethod") private fun handleAction(state: () -> AppState?, action: Action, dispatch: DispatchFunction) { if (DemoHelper.tryHandle(state, action)) return @@ -117,17 +119,15 @@ class WalletMiddleware { is WalletAction.LoadWallet.Success -> { checkForRentWarning(walletState.getWalletManager(action.blockchain)) val coinAmount = action.wallet.amounts[AmountType.Coin]?.value - if (coinAmount != null && !coinAmount.isZero()) { - if (walletState.getWalletData(action.blockchain) == null) { - store.dispatch( - WalletAction.MultiWallet.AddBlockchain( - action.blockchain, - null, - true, - ), - ) - store.dispatch(WalletAction.LoadWallet.Success(action.wallet, action.blockchain)) - } + if (coinAmount?.isZero() == false && walletState.getWalletData(action.blockchain) == null) { + store.dispatch( + WalletAction.MultiWallet.AddBlockchain( + blockchain = action.blockchain, + walletManager = null, + save = true, + ), + ) + store.dispatch(WalletAction.LoadWallet.Success(action.wallet, action.blockchain)) } store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) warningsMiddleware.tryToShowAppRatingWarning(action.wallet) @@ -402,7 +402,8 @@ class WalletMiddleware { } } } else { - if ((amounts?.size ?: 0) > 1) { + val amountsSize = amounts?.size ?: 0 + if (amountsSize > 1) { WalletAction.DialogAction.ChooseCurrency(amounts) } else { val amountToSend = amounts?.first() @@ -468,8 +469,8 @@ class WalletMiddleware { dispatchOnMain( WalletAction.SetWalletRent( wallet = walletManager.wallet, - minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency"), - rentExempt = ("${rentExempt.stripZeroPlainString()} $currency"), + minRent = "${rentProvider.rentAmount().stripZeroPlainString()} $currency", + rentExempt = "${rentExempt.stripZeroPlainString()} $currency", ), ) } else { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt index b2ef29f6b3..4f0307c06f 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt @@ -98,10 +98,8 @@ class WarningsMiddleware { } else if (!preferencesStorage.usedCardsPrefStorage.wasScanned(card.cardId)) { checkIfWarningNeeded(scanResponse)?.let { warning -> addWarningMessage(warning) } } - if (card.firmwareVersion.type == FirmwareVersion.FirmwareType.Release) { - if (!globalState.cardVerifiedOnline) { - addWarningMessage(WarningMessagesManager.onlineVerificationFailed()) - } + if (card.firmwareVersion.type == FirmwareVersion.FirmwareType.Release && !globalState.cardVerifiedOnline) { + addWarningMessage(WarningMessagesManager.onlineVerificationFailed()) } if (scanResponse.isDemoCard()) { addWarningMessage(WarningMessagesManager.demoCardWarning()) 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 afb523a5ca..93bae202ee 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 @@ -33,13 +33,14 @@ import com.tangem.wallet.R import java.math.BigDecimal class MultiWalletReducer { + @Suppress("LongMethod", "ComplexMethod") fun reduce(action: WalletAction.MultiWallet, state: WalletState): WalletState { return when (action) { is WalletAction.MultiWallet.AddBlockchains -> { val walletStores: List = action.blockchains.map { blockchain -> val walletManager = action.walletManagers.firstOrNull { it.wallet.blockchain == blockchain.blockchain && - (it.wallet.publicKey.derivationPath?.rawPath == blockchain.derivationPath) + it.wallet.publicKey.derivationPath?.rawPath == blockchain.derivationPath } val wallet = walletManager?.wallet val cardToken = if (!state.isMultiwalletAllowed) { 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 68e6080883..dc01666309 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 @@ -34,10 +34,11 @@ class OnWalletLoadedReducer { } } + @Suppress("LongMethod") private fun onMultiWalletLoaded( wallet: Wallet, blockchainNetwork: BlockchainNetwork, - walletState: WalletState + walletState: WalletState, ): WalletState { val walletData = walletState.getWalletData(blockchainNetwork) ?: return walletState @@ -45,7 +46,7 @@ class OnWalletLoadedReducer { val coinAmountValue = wallet.amounts[AmountType.Coin]?.value val formattedAmount = coinAmountValue?.toFormattedCurrencyString( wallet.blockchain.decimals(), - wallet.blockchain.currency + wallet.blockchain.currency, ) val pendingTransactions = wallet.getPendingTransactions() @@ -98,7 +99,7 @@ class OnWalletLoadedReducer { amount = tokenAmountValue, amountFormatted = tokenAmountValue?.toFormattedCurrencyString( token.decimals, - token.symbol + token.symbol, ), fiatAmount = tokenFiatAmount, fiatAmountFormatted = tokenFiatAmountFormatted, @@ -141,7 +142,7 @@ class OnWalletLoadedReducer { val amount = wallet.amounts[AmountType.Coin]?.value val formattedAmount = amount?.toFormattedCurrencyString( wallet.blockchain.decimals(), - wallet.blockchain.currency + wallet.blockchain.currency, ) val fiatAmount = walletState.primaryWallet?.fiatRate?.let { amount?.toFiatValue(it) } val fiatAmountFormatted = fiatAmount?.toFormattedFiatValue(fiatCurrencyName) ?: UNKNOWN_AMOUNT_SIGN @@ -162,7 +163,7 @@ class OnWalletLoadedReducer { amount = amount, amountFormatted = formattedAmount, fiatAmount = fiatAmount, - fiatAmountFormatted = fiatAmountFormatted + fiatAmountFormatted = fiatAmountFormatted, ), pendingTransactions = pendingTransactions.removeUnknownTransactions(), mainButton = WalletMainButton.SendButton(sendButtonEnabled), @@ -171,7 +172,7 @@ class OnWalletLoadedReducer { val updatedStore = walletState.getWalletStore(walletData?.currency)?.updateWallets(wallets) return walletState.updateWalletStore(updatedStore).copy( - state = ProgressState.Done, error = null + state = ProgressState.Done, error = null, ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt index 08a7def4bd..1e15f2460a 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt @@ -25,22 +25,25 @@ fun List.calculateTotalCryptoAmount(): BigDecimal { private fun List.mapToProgressState(): List { return this.map { wallet -> - if (wallet.fiatRate == null) ProgressState.Error - else when (wallet.currencyData.status) { - BalanceStatus.Refreshing -> ProgressState.Refreshing - BalanceStatus.VerifiedOnline, - BalanceStatus.SameCurrencyTransactionInProgress, - BalanceStatus.TransactionInProgress, - BalanceStatus.NoAccount, - -> ProgressState.Done - BalanceStatus.Unreachable, - BalanceStatus.EmptyCard, - BalanceStatus.UnknownBlockchain, - BalanceStatus.MissedDerivation, - -> ProgressState.Error - BalanceStatus.Loading, - null, - -> ProgressState.Loading + if (wallet.fiatRate == null) { + ProgressState.Error + } else { + when (wallet.currencyData.status) { + BalanceStatus.Refreshing -> ProgressState.Refreshing + BalanceStatus.VerifiedOnline, + BalanceStatus.SameCurrencyTransactionInProgress, + BalanceStatus.TransactionInProgress, + BalanceStatus.NoAccount, + -> ProgressState.Done + BalanceStatus.Unreachable, + BalanceStatus.EmptyCard, + BalanceStatus.UnknownBlockchain, + BalanceStatus.MissedDerivation, + -> ProgressState.Error + BalanceStatus.Loading, + null, + -> ProgressState.Loading + } } } } 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 2505702c20..3b6e95d27c 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 @@ -39,13 +39,12 @@ import org.rekotlin.Action import timber.log.Timber import java.math.BigDecimal -class WalletReducer { - companion object { - fun reduce(action: Action, state: AppState, appStateHolder: AppStateHolder): WalletState = - internalReduce(action, state, appStateHolder) - } +object WalletReducer { + fun reduce(action: Action, state: AppState, appStateHolder: AppStateHolder): WalletState = + internalReduce(action, state, appStateHolder) } +@Suppress("LongMethod", "ComplexMethod") private fun internalReduce(action: Action, state: AppState, appStateHolder: AppStateHolder): WalletState { val multiWalletReducer = MultiWalletReducer() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt index 025e3cc9ee..f4f1874710 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt @@ -31,7 +31,7 @@ data class BalanceWidgetData( val fiatAmountFormatted: String? = null, val token: TokenData? = null, val amountToCreateAccount: String? = null, - val errorMessage: String? = null + val errorMessage: String? = null, ) data class TokenData( @@ -44,7 +44,6 @@ data class TokenData( val fiatRate: BigDecimal? = null, ) - class BalanceWidget( private val binding: CardBalanceBinding, private val fragment: WalletFragment, @@ -52,6 +51,7 @@ class BalanceWidget( private val isTwinCard: Boolean, ) { + @Suppress("LongMethod", "ComplexMethod") fun setup() { when (data.status) { @@ -130,7 +130,7 @@ class BalanceWidget( tvErrorDescriptions.text = fragment.getString( R.string.no_account_generic, - data.amountToCreateAccount, data.currencySymbol + data.amountToCreateAccount, data.currencySymbol, ) } BalanceStatus.UnknownBlockchain -> with(binding.lBalanceError) { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt index ecbad2650a..67e10ca9a6 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt @@ -7,36 +7,34 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.address.AddressType import com.tangem.wallet.R -class MultipleAddressUiHelper { - companion object { - fun typeToId(type: AddressType): Int { - return when (type) { - is BitcoinAddressType.Legacy -> R.id.chip_legacy - is BitcoinAddressType.Segwit -> R.id.chip_default - is CardanoAddressType.Byron -> R.id.chip_legacy - is CardanoAddressType.Shelley -> R.id.chip_default - else -> View.NO_ID - } +object MultipleAddressUiHelper { + fun typeToId(type: AddressType): Int { + return when (type) { + is BitcoinAddressType.Legacy -> R.id.chip_legacy + is BitcoinAddressType.Segwit -> R.id.chip_default + is CardanoAddressType.Byron -> R.id.chip_legacy + is CardanoAddressType.Shelley -> R.id.chip_default + else -> View.NO_ID } + } - fun idToType(id: Int, blockchain: Blockchain?): AddressType? { - return when (id) { - R.id.chip_default -> { - when (blockchain) { - Blockchain.Bitcoin, Blockchain.BitcoinTestnet, Blockchain.Litecoin -> BitcoinAddressType.Segwit - Blockchain.CardanoShelley -> CardanoAddressType.Shelley - else -> null - } + fun idToType(id: Int, blockchain: Blockchain?): AddressType? { + return when (id) { + R.id.chip_default -> { + when (blockchain) { + Blockchain.Bitcoin, Blockchain.BitcoinTestnet, Blockchain.Litecoin -> BitcoinAddressType.Segwit + Blockchain.CardanoShelley -> CardanoAddressType.Shelley + else -> null } - R.id.chip_legacy -> { - when (blockchain) { - Blockchain.Bitcoin, Blockchain.BitcoinTestnet, Blockchain.Litecoin -> BitcoinAddressType.Legacy - Blockchain.CardanoShelley -> CardanoAddressType.Byron - else -> null - } - } - else -> null } + R.id.chip_legacy -> { + when (blockchain) { + Blockchain.Bitcoin, Blockchain.BitcoinTestnet, Blockchain.Litecoin -> BitcoinAddressType.Legacy + Blockchain.CardanoShelley -> CardanoAddressType.Byron + else -> null + } + } + else -> null } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index 630d934f4b..a35ba51df9 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -59,6 +59,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.rekotlin.StoreSubscriber +@Suppress("LargeClass", "MagicNumber") class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreSubscriber { @@ -256,8 +257,6 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), rowButtons.updateButtonsVisibility( exchangeServiceFeatureOn = isExchangeServiceFeatureOn, - buyAllowed = selectedWallet.isAvailableToBuy, - sellAllowed = selectedWallet.isAvailableToSell, sendAllowed = selectedWallet.mainButton.enabled, ) rowButtons.onTradeClick = { 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 7c622df177..3873d90c69 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 @@ -17,11 +17,11 @@ import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding import coil.load import coil.size.Scale +import com.tangem.core.analytics.Analytics import com.tangem.core.ui.fragments.setStatusBarColor import com.tangem.core.ui.utils.OneTouchClickListener import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.tap.MainActivity -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.converters.BasicSignInEventConverter import com.tangem.tap.common.analytics.converters.BasicTopUpEventConverter import com.tangem.tap.common.analytics.events.MainScreen @@ -135,6 +135,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber { + isSaltPay && walletView !is SaltPaySingleWalletView -> { walletView = SaltPaySingleWalletView() walletView.changeWalletView(this, binding) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt index 774bfc2aee..1aca923c9d 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt @@ -75,6 +75,7 @@ class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerVi binding.warningCardAction.setCardBackgroundColor(binding.root.getColor(color)) } + @Suppress("LongMethod") private fun setupControlButtons(warning: WarningMessage) = when (warning.type) { WarningMessage.Type.Permanent, WarningMessage.Type.TestCard -> { binding.groupControlsTemporary.hide() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt index 3251bce185..f1081006c2 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt @@ -13,21 +13,17 @@ import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ -class ScanFailsDialog { - - companion object { - fun create(context: Context): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(context.getString(R.string.common_warning)) - setMessage(R.string.alert_troubleshooting_scan_card_title) - setPositiveButton(R.string.alert_button_request_support) { _, _ -> - Analytics.send(IntroductionProcess.ButtonRequestSupport()) - store.dispatch(GlobalAction.SendEmail(ScanFailsEmail())) - } - setNeutralButton(R.string.common_cancel) { _, _ -> } - setOnDismissListener { store.dispatchDialogHide() } - }.create() - } +object ScanFailsDialog { + fun create(context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.common_warning)) + setMessage(R.string.alert_troubleshooting_scan_card_title) + setPositiveButton(R.string.alert_button_request_support) { _, _ -> + Analytics.send(IntroductionProcess.ButtonRequestSupport()) + store.dispatch(GlobalAction.SendEmail(ScanFailsEmail())) + } + setNeutralButton(R.string.common_cancel) { _, _ -> } + setOnDismissListener { store.dispatchDialogHide() } + }.create() } - } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SignedHashesWarningDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SignedHashesWarningDialog.kt index c8db3c3d23..b419a42b81 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SignedHashesWarningDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SignedHashesWarningDialog.kt @@ -8,26 +8,23 @@ import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.store import com.tangem.wallet.R -class SignedHashesWarningDialog { - companion object { - fun create(context: Context): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(context.getString(R.string.warning_important_security_info)) - setMessage(R.string.alert_signed_hashes_message) - setPositiveButton(R.string.common_understand) { _, _ -> - store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId) - store.dispatch( - GlobalAction.HideWarningMessage( - WarningMessagesManager.signedHashesMultiWalletWarning() - ) - ) - } - setNegativeButton(R.string.common_cancel) { _, _ -> } - setOnDismissListener { - store.dispatch(WalletAction.DialogAction.Hide) - } - }.create() - } +object SignedHashesWarningDialog { + fun create(context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.warning_important_security_info)) + setMessage(R.string.alert_signed_hashes_message) + setPositiveButton(R.string.common_understand) { _, _ -> + store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId) + store.dispatch( + GlobalAction.HideWarningMessage( + WarningMessagesManager.signedHashesMultiWalletWarning(), + ), + ) + } + setNegativeButton(R.string.common_cancel) { _, _ -> } + setOnDismissListener { + store.dispatch(WalletAction.DialogAction.Hide) + } + }.create() } - } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SimpleOkDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SimpleOkDialog.kt index d52c26763e..dc44583536 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SimpleOkDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SimpleOkDialog.kt @@ -10,49 +10,47 @@ import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ -class SimpleOkDialog { +object SimpleOkDialog { - companion object { - fun create(dialog: AppDialog.SimpleOkDialog, context: Context): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(dialog.header) - setMessage(dialog.message) - setPositiveButton(R.string.common_ok) { _, _ -> } - setOnDismissListener { - store.dispatchDialogHide() - dialog.onOk?.invoke() - } - }.create() - } - - fun create(dialog: AppDialog.SimpleOkDialogRes, context: Context): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(context.getString(dialog.headerId)) - setMessage(dialog.messageId) - setPositiveButton(R.string.common_ok) { _, _ -> } - setOnDismissListener { - store.dispatchDialogHide() - dialog.onOk?.invoke() - } - }.create() - } - - fun create(dialog: AppDialog.SimpleOkErrorDialog, context: Context): AlertDialog = create( - dialog = AppDialog.SimpleOkDialog( - header = context.getString(R.string.common_error), - message = dialog.message, - onOk = dialog.onOk, - ), - context = context, - ) - - fun create(dialog: AppDialog.SimpleOkWarningDialog, context: Context): AlertDialog = create( - dialog = AppDialog.SimpleOkDialog( - header = context.getString(R.string.common_warning), - message = dialog.message, - onOk = dialog.onOk, - ), - context = context, - ) + fun create(dialog: AppDialog.SimpleOkDialog, context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(dialog.header) + setMessage(dialog.message) + setPositiveButton(R.string.common_ok) { _, _ -> } + setOnDismissListener { + store.dispatchDialogHide() + dialog.onOk?.invoke() + } + }.create() } + + fun create(dialog: AppDialog.SimpleOkDialogRes, context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(context.getString(dialog.headerId)) + setMessage(dialog.messageId) + setPositiveButton(R.string.common_ok) { _, _ -> } + setOnDismissListener { + store.dispatchDialogHide() + dialog.onOk?.invoke() + } + }.create() + } + + fun create(dialog: AppDialog.SimpleOkErrorDialog, context: Context): AlertDialog = create( + dialog = AppDialog.SimpleOkDialog( + header = context.getString(R.string.common_error), + message = dialog.message, + onOk = dialog.onOk, + ), + context = context, + ) + + fun create(dialog: AppDialog.SimpleOkWarningDialog, context: Context): AlertDialog = create( + dialog = AppDialog.SimpleOkDialog( + header = context.getString(R.string.common_warning), + message = dialog.message, + onOk = dialog.onOk, + ), + context = context, + ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt index b6d34873df..d05ac08cc9 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt @@ -13,6 +13,7 @@ import com.tangem.tap.features.wallet.models.Currency import com.tangem.wallet.databinding.ViewCurrencyIconBinding import kotlin.math.roundToInt +@Suppress("MagicNumber") class CurrencyIconView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/RoundedCornersCropTransformation.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/RoundedCornersCropTransformation.kt deleted file mode 100644 index 7a9b0a3015..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/RoundedCornersCropTransformation.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.tangem.tap.features.wallet.ui.images - -import android.graphics.Bitmap -import android.graphics.BitmapShader -import android.graphics.Matrix -import android.graphics.Paint -import android.graphics.RectF -import android.graphics.Shader -import androidx.annotation.Px -import androidx.core.graphics.applyCanvas -import coil.size.Size -import coil.size.pxOrElse -import coil.transform.Transformation - -class RoundedCornersCropTransformation( - @Px val radiusPx: Float, -) : Transformation { - override val cacheKey: String = "${javaClass.name}-$radiusPx" - - override suspend fun transform(input: Bitmap, size: Size): Bitmap { - val outputWidth = size.width.pxOrElse { Int.MAX_VALUE } - val outputHeight = size.height.pxOrElse { Int.MAX_VALUE } - val outputSize = minOf(outputWidth, outputHeight) - val output = Bitmap.createBitmap(outputSize, outputSize, Bitmap.Config.ARGB_8888) - - val rect = RectF( - /* left = */ 0f, - /* top = */ 0f, - /* right = */ outputWidth.toFloat(), - /* bottom = */ outputHeight.toFloat() - ) - val matrix = Matrix().apply { - setTranslate( - /* dx = */ (outputWidth - input.width) * .5f, - /* dy = */ (outputHeight - input.height) * .5f - ) - } - val bitmapShader = BitmapShader( - /* bitmap = */ input, - /* tileX = */ Shader.TileMode.DECAL, - /* tileY = */ Shader.TileMode.DECAL - ).apply { - setLocalMatrix(matrix) - } - val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG).apply { - shader = bitmapShader - } - - return output.applyCanvas { - drawRoundRect( - /* rect = */ rect, - /* rx = */ radiusPx, - /* ry = */ radiusPx, - /* paint = */ paint - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWallet.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWallet.kt new file mode 100644 index 0000000000..5b3dab3e8b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWallet.kt @@ -0,0 +1,160 @@ +package com.tangem.tap.features.wallet.ui.test + +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.Token +import com.tangem.blockchain.common.WalletManager +import com.tangem.common.extensions.guard +import com.tangem.tap.common.TestAction +import com.tangem.tap.common.TestActions +import com.tangem.tap.common.extensions.dispatchDebugErrorNotification +import com.tangem.tap.domain.tokens.models.BlockchainNetwork +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.store +import java.math.BigDecimal +import kotlin.random.Random + +/** +[REDACTED_AUTHOR] + */ +object TestWallet { + fun solanaRentExemptWarning(): List { + val checker = SolanaRentWarningActionEmitter() + return listOf( + "BALANCE = 0.0" to { checker.setZeroBalance() }, + "BALANCE < 0.00089088" to { checker.setLessThanRentExempt() }, + "BALANCE > 0.00089088" to { checker.setMoreThanRentExempt() }, + "BALANCE = 0.00089087" to { checker.setLessThanRentExemptByOne() }, + "BALANCE = 0.00089088 (rent exempt)" to { checker.setForRentExemptBarrier() }, + "BALANCE = 0.00089089" to { checker.setMoreThanRentExemptByOne() }, + ) + } + + fun getBlockchainBalanceActions(blockchainNetwork: BlockchainNetwork): List { + return getBlockchainBalanceActions( + getWalletManager(blockchainNetwork), + blockchainNetwork.blockchain.decimals(), + ) + } + + fun getTokenBalanceAction(blockchainNetwork: BlockchainNetwork, token: Token): List { + return getTokenBalanceAction(getWalletManager(blockchainNetwork), token) + } + + fun getBlockchainBalanceActions(walletManager: WalletManager?, decimals: Int): List { + val minValue = BigDecimal.ONE.movePointLeft(decimals) + val averageValue = BigDecimal.ONE.movePointRight(decimals) + .divide(BigDecimal(2)).movePointLeft(decimals) + val maxValue = BigDecimal(2).pow(32) + + return listOf( + "0.0" to { setBalance(walletManager, BigDecimal.ZERO) }, + "Min value" to { setBalance(walletManager, minValue) }, + "Average value" to { setBalance(walletManager, averageValue) }, + "Max value" to { setBalance(walletManager, maxValue) }, + "1234567890123.01234567890123" to { setBalance(walletManager, BigDecimal.ZERO) }, + ) + } + + fun getTokenBalanceAction(walletManager: WalletManager?, token: Token): List { + val minValue = BigDecimal.ONE.movePointLeft(token.decimals) + val averageValue = BigDecimal.ONE.movePointRight(token.decimals) + .divide(BigDecimal(2)).movePointLeft(token.decimals) + val maxValue = BigDecimal(2).pow(32) + + return listOf( + "0.0" to { setTokenBalance(walletManager, BigDecimal.ZERO, token) }, + "Min value" to { setTokenBalance(walletManager, minValue, token) }, + "Average value" to { setTokenBalance(walletManager, averageValue, token) }, + "Max value" to { setTokenBalance(walletManager, maxValue, token) }, + "1234567890123.01234567890123" to { setTokenBalance(walletManager, BigDecimal.ZERO, token) }, + ) + } + + fun setBalance(walletManager: WalletManager?, value: BigDecimal) { + val manager = walletManager.guard { + store.dispatchDebugErrorNotification("WalletManager not found") + return + } + val amount = manager.wallet.amounts[AmountType.Coin] ?: Amount(manager.wallet.blockchain) + setBalance(manager, amount, value) + } + + private fun setTokenBalance(walletManager: WalletManager?, value: BigDecimal, token: Token? = null) { + val manager = walletManager.guard { + store.dispatchDebugErrorNotification("WalletManager not found") + return + } + val token = token.guard { + store.dispatchDebugErrorNotification("Token not found") + return + } + + val amount = manager.wallet.amounts[AmountType.Token(token)] ?: Amount(token) + setBalance(manager, amount, value) + } + + private fun setBalance(walletManager: WalletManager, amount: Amount, value: BigDecimal) { + TestActions.testAmountInjectionForWalletManagerEnabled = true + walletManager.wallet.setAmount(amount.copy(value = value)) + store.dispatch(WalletAction.LoadData) + } + + private fun getWalletManager(blockchainNetwork: BlockchainNetwork): WalletManager? { + return store.state.walletState.getWalletManager(blockchainNetwork) + } +} + +private class SolanaRentWarningActionEmitter { + + private val zero = BigDecimal.ZERO + private val one = BigDecimal(0.00000001) + private val rentExemptBarrier = BigDecimal(0.00089088) + private val lessThanRentExemptByOne = rentExemptBarrier.minus(one) + private val moreThanRentExemptByOne = rentExemptBarrier.plus(one) + private val moreThanRentExempt = rentExemptBarrier.plus(Random.nextDouble().toBigDecimal()) + private val lessThanRentExempt = rentExemptBarrier + .minus(Random.nextDouble(0.0, rentExemptBarrier.minus(one).toDouble()).toBigDecimal()) + + fun setZeroBalance() { + setBalance(zero) + } + + fun setForRentExemptBarrier() { + setBalance(rentExemptBarrier) + } + + fun setLessThanRentExemptByOne() { + setBalance(lessThanRentExemptByOne) + } + + fun setMoreThanRentExemptByOne() { + setBalance(moreThanRentExemptByOne) + } + + fun setMoreThanRentExempt() { + setBalance(moreThanRentExempt) + } + + fun setLessThanRentExempt() { + setBalance(lessThanRentExempt) + } + + private fun setBalance(value: BigDecimal) { + val amount = Amount(getBlockchainNetwork().blockchain).copy(value = value) + getWalletManager().apply { + TestActions.testAmountInjectionForWalletManagerEnabled = true + wallet.setAmount(amount) + } + store.dispatch(WalletAction.LoadData) + } + + private fun getWalletManager(): WalletManager { + return store.state.walletState.getWalletManager(getBlockchainNetwork())!! + } + + private fun getBlockchainNetwork(): BlockchainNetwork { + val currency = store.state.walletState.selectedWalletData!!.currency + return BlockchainNetwork(currency.blockchain, currency.derivationPath, listOf()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWalletDetailsActions.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWalletDetailsActions.kt deleted file mode 100644 index 857cdd2631..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWalletDetailsActions.kt +++ /dev/null @@ -1,162 +0,0 @@ -package com.tangem.tap.features.wallet.ui.test - -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.Token -import com.tangem.blockchain.common.WalletManager -import com.tangem.common.extensions.guard -import com.tangem.tap.common.TestAction -import com.tangem.tap.common.TestActions -import com.tangem.tap.common.extensions.dispatchDebugErrorNotification -import com.tangem.tap.domain.tokens.models.BlockchainNetwork -import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.store -import java.math.BigDecimal -import kotlin.random.Random - -/** -[REDACTED_AUTHOR] - */ -class TestWallet { - companion object { - fun solanaRentExemptWarning(): List { - val checker = SolanaRentWarningActionEmitter() - return listOf( - "BALANCE = 0.0" to { checker.setZeroBalance() }, - "BALANCE < 0.00089088" to { checker.setLessThanRentExempt() }, - "BALANCE > 0.00089088" to { checker.setMoreThanRentExempt() }, - "BALANCE = 0.00089087" to { checker.setLessThanRentExemptByOne() }, - "BALANCE = 0.00089088 (rent exempt)" to { checker.setForRentExemptBarrier() }, - "BALANCE = 0.00089089" to { checker.setMoreThanRentExemptByOne() }, - ) - } - - fun getBlockchainBalanceActions(blockchainNetwork: BlockchainNetwork): List { - return getBlockchainBalanceActions( - getWalletManager(blockchainNetwork), - blockchainNetwork.blockchain.decimals(), - ) - } - - fun getTokenBalanceAction(blockchainNetwork: BlockchainNetwork, token: Token): List { - return getTokenBalanceAction(getWalletManager(blockchainNetwork), token) - } - - fun getBlockchainBalanceActions(walletManager: WalletManager?, decimals: Int): List { - val minValue = BigDecimal.ONE.movePointLeft(decimals) - val averageValue = BigDecimal.ONE.movePointRight(decimals) - .divide(BigDecimal(2)).movePointLeft(decimals) - val maxValue = BigDecimal(2).pow(32) - - return listOf( - "0.0" to { setBalance(walletManager, BigDecimal.ZERO) }, - "Min value" to { setBalance(walletManager, minValue) }, - "Average value" to { setBalance(walletManager, averageValue) }, - "Max value" to { setBalance(walletManager, maxValue) }, - "1234567890123.01234567890123" to { setBalance(walletManager, BigDecimal.ZERO) }, - ) - } - - fun getTokenBalanceAction(walletManager: WalletManager?, token: Token): List { - val minValue = BigDecimal.ONE.movePointLeft(token.decimals) - val averageValue = BigDecimal.ONE.movePointRight(token.decimals) - .divide(BigDecimal(2)).movePointLeft(token.decimals) - val maxValue = BigDecimal(2).pow(32) - - return listOf( - "0.0" to { setTokenBalance(walletManager, BigDecimal.ZERO, token) }, - "Min value" to { setTokenBalance(walletManager, minValue, token) }, - "Average value" to { setTokenBalance(walletManager, averageValue, token) }, - "Max value" to { setTokenBalance(walletManager, maxValue, token) }, - "1234567890123.01234567890123" to { setTokenBalance(walletManager, BigDecimal.ZERO, token) }, - ) - } - - fun setBalance(walletManager: WalletManager?, value: BigDecimal) { - val manager = walletManager.guard { - store.dispatchDebugErrorNotification("WalletManager not found") - return - } - val amount = manager.wallet.amounts[AmountType.Coin] ?: Amount(manager.wallet.blockchain) - setBalance(manager, amount, value) - } - - private fun setTokenBalance(walletManager: WalletManager?, value: BigDecimal, token: Token? = null) { - val manager = walletManager.guard { - store.dispatchDebugErrorNotification("WalletManager not found") - return - } - val token = token.guard { - store.dispatchDebugErrorNotification("Token not found") - return - } - - val amount = manager.wallet.amounts[AmountType.Token(token)] ?: Amount(token) - setBalance(manager, amount, value) - } - - private fun setBalance(walletManager: WalletManager, amount: Amount, value: BigDecimal) { - TestActions.testAmountInjectionForWalletManagerEnabled = true - walletManager.wallet.setAmount(amount.copy(value = value)) - store.dispatch(WalletAction.LoadData) - } - - private fun getWalletManager(blockchainNetwork: BlockchainNetwork): WalletManager? { - return store.state.walletState.getWalletManager(blockchainNetwork) - } - } -} - -private class SolanaRentWarningActionEmitter { - - private val zero = BigDecimal.ZERO - private val one = BigDecimal(0.00000001) - private val rentExemptBarrier = BigDecimal(0.00089088) - private val lessThanRentExemptByOne = rentExemptBarrier.minus(one) - private val moreThanRentExemptByOne = rentExemptBarrier.plus(one) - private val moreThanRentExempt = rentExemptBarrier.plus(Random.nextDouble().toBigDecimal()) - private val lessThanRentExempt = rentExemptBarrier - .minus(Random.nextDouble(0.0, rentExemptBarrier.minus(one).toDouble()).toBigDecimal()) - - fun setZeroBalance() { - setBalance(zero) - } - - fun setForRentExemptBarrier() { - setBalance(rentExemptBarrier) - } - - fun setLessThanRentExemptByOne() { - setBalance(lessThanRentExemptByOne) - } - - fun setMoreThanRentExemptByOne() { - setBalance(moreThanRentExemptByOne) - } - - fun setMoreThanRentExempt() { - setBalance(moreThanRentExempt) - } - - fun setLessThanRentExempt() { - setBalance(lessThanRentExempt) - } - - private fun setBalance(value: BigDecimal) { - val amount = Amount(getBlockchainNetwork().blockchain).copy(value = value) - getWalletManager().apply { - TestActions.testAmountInjectionForWalletManagerEnabled = true - wallet.setAmount(amount) - } - store.dispatch(WalletAction.LoadData) - } - - private fun getWalletManager(): WalletManager { - return store.state.walletState.getWalletManager(getBlockchainNetwork())!! - } - - private fun getBlockchainNetwork(): BlockchainNetwork { - val currency = store.state.walletState.selectedWalletData!!.currency - return BlockchainNetwork(currency.blockchain, currency.derivationPath, listOf()) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/view/WalletDetailsButtonsRow.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/view/WalletDetailsButtonsRow.kt index 5c73fa7531..8c18434a94 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/view/WalletDetailsButtonsRow.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/view/WalletDetailsButtonsRow.kt @@ -33,12 +33,7 @@ internal class WalletDetailsButtonsRow @JvmOverloads constructor( } } - fun updateButtonsVisibility( - exchangeServiceFeatureOn: Boolean, - buyAllowed: Boolean, - sellAllowed: Boolean, - sendAllowed: Boolean, - ) = with(binding) { + fun updateButtonsVisibility(exchangeServiceFeatureOn: Boolean, sendAllowed: Boolean) = with(binding) { containerExchangeButtons.isVisible = exchangeServiceFeatureOn btnBuy.hide() btnSell.hide() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPayBalanceWidget.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPayBalanceWidget.kt index 49fcb89c16..3741a4c59d 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPayBalanceWidget.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPayBalanceWidget.kt @@ -24,7 +24,7 @@ class SaltPayBalanceWidget( private val binding: LayoutSingleWalletBalanceBinding, private val data: SaltPayBalanceWidgetData, ) { - fun setup(): Unit = with(binding) { + fun setup() = with(binding) { if (data.state == ProgressState.Loading) { veilBalance.veil() veilBalanceCrypto.veil() @@ -45,7 +45,7 @@ class SaltPayBalanceWidget( actionDebouncer(WalletAction.LoadFiatRate()) } else { veilBalance.unVeil() - tvBalance.text = data.fiatAmount?.formatAmountAsSpannedString( + tvBalance.text = data.fiatAmount.formatAmountAsSpannedString( currencySymbol = data.fiatCurrency?.symbol ?: "", ) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index 1e96cb230b..6b53c12857 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -130,8 +130,6 @@ class SingleWalletView : WalletView() { ) { rowButtons.updateButtonsVisibility( exchangeServiceFeatureOn = isExchangeServiceFeatureEnabled, - buyAllowed = walletData.isAvailableToBuy, - sellAllowed = walletData.isAvailableToSell, sendAllowed = walletData.mainButton.enabled, ) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index 5b15d07266..98abb38ab3 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -99,21 +99,23 @@ internal class WalletSelectorMiddleware { updatedWalletStores: Map>, state: WalletSelectorState, ) { - if (updatedWalletStores.isNotEmpty()) scope.launch(Dispatchers.Default) { - state.wallets - .associateWith { updatedWalletStores[it.id] } - .forEach { (wallet, walletStores) -> - val isWalletTokensEmpty = (wallet.type as? UserWalletModel.Type.MultiCurrency)?.tokensCount == 0 - val updatedWallet = if (walletStores == null && isWalletTokensEmpty) { - wallet.copy(fiatBalance = TotalFiatBalance.Loaded(BigDecimal.ZERO)) - } else { - wallet.updateWalletStoresAndCalculateFiatBalance(walletStores.orEmpty()) - } + if (updatedWalletStores.isNotEmpty()) { + scope.launch(Dispatchers.Default) { + state.wallets + .associateWith { updatedWalletStores[it.id] } + .forEach { (wallet, walletStores) -> + val isWalletTokensEmpty = (wallet.type as? UserWalletModel.Type.MultiCurrency)?.tokensCount == 0 + val updatedWallet = if (walletStores == null && isWalletTokensEmpty) { + wallet.copy(fiatBalance = TotalFiatBalance.Loaded(BigDecimal.ZERO)) + } else { + wallet.updateWalletStoresAndCalculateFiatBalance(walletStores.orEmpty()) + } - if (wallet != updatedWallet) { - store.dispatchOnMain(WalletSelectorAction.BalanceLoaded(updatedWallet)) + if (wallet != updatedWallet) { + store.dispatchOnMain(WalletSelectorAction.BalanceLoaded(updatedWallet)) + } } - } + } } } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt index 9a8ba16eae..d0d0dffab7 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt @@ -13,6 +13,7 @@ internal object WalletSelectorReducer { } else state.walletSelectorState } + @Suppress("ComplexMethod") private fun internalReduce(action: WalletSelectorAction, state: WalletSelectorState): WalletSelectorState { return when (action) { is WalletSelectorAction.UserWalletsLoaded -> state.copy( diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt index 23ea8c4c9e..ab21b44b36 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt @@ -37,6 +37,7 @@ import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.features.walletSelector.ui.WalletSelectorScreenState import com.tangem.wallet.R +@Suppress("LongParameterList") @OptIn(ExperimentalFoundationApi::class) @Composable internal fun WalletSelectorScreenContent( diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt index 3b6f021dba..c98912667b 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt @@ -327,7 +327,7 @@ private fun SelectedWalletBadge( contentAlignment = Alignment.Center, ) { Icon( - modifier = Modifier.padding(all = (0.5).dp), + modifier = Modifier.padding(all = 0.5.dp), painter = painterResource(id = R.drawable.ic_check_circle_18), tint = TangemTheme.colors.icon.accent, contentDescription = null, diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt index 709c151963..de0ccac450 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt @@ -24,6 +24,7 @@ import com.tangem.core.ui.components.SpacerHMax import com.tangem.core.ui.res.TangemTheme import com.tangem.wallet.R +@Suppress("LongMethod") @Composable internal fun WelcomeScreenContent( modifier: Modifier = Modifier, diff --git a/app/src/main/java/com/tangem/tap/network/NetworkConnectivity.kt b/app/src/main/java/com/tangem/tap/network/NetworkConnectivity.kt index 00c2f87e19..71d0ed9206 100644 --- a/app/src/main/java/com/tangem/tap/network/NetworkConnectivity.kt +++ b/app/src/main/java/com/tangem/tap/network/NetworkConnectivity.kt @@ -15,8 +15,8 @@ import java.lang.ref.WeakReference [REDACTED_AUTHOR] */ class NetworkConnectivity( - private val store: Store<*>, - context: Context + private val store: Store<*>, + context: Context, ) { private val wContext: WeakReference = WeakReference(context) @@ -34,19 +34,16 @@ class NetworkConnectivity( store.dispatch(NetworkStateChanged(isOnlineOrConnecting())) } - fun isOnlineOrConnecting(): Boolean { val connectivityManager = getConnectivityManager() ?: return false - return if (Build.VERSION.SDK_INT >= 23) { - val capabilities = - connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) - (capabilities != null) && - (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || - capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || - capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) || - capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) - + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) + capabilities != null && + (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) || + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) } else { val networkInfo = connectivityManager.activeNetworkInfo networkInfo != null && networkInfo.isConnectedOrConnecting @@ -55,7 +52,7 @@ class NetworkConnectivity( private fun getConnectivityManager(): ConnectivityManager? { return wContext.get()?.applicationContext - ?.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager + ?.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager } companion object { diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt index 3f61d82801..671c45af11 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt @@ -59,7 +59,7 @@ class CurrencyExchangeManager( blockchain, cryptoCurrencyName, fiatCurrencyName, - walletAddress + walletAddress, ) } @@ -86,7 +86,6 @@ class CurrencyExchangeManager( } } -@Suppress("unused") suspend fun CurrencyExchangeManager.buyErc20TestnetTokens( card: CardDTO, walletManager: EthereumWalletManager, @@ -97,13 +96,15 @@ suspend fun CurrencyExchangeManager.buyErc20TestnetTokens( val amountToSend = Amount(walletManager.wallet.blockchain) val destinationAddress = token.contractAddress - val feeResult = walletManager.getFee(amountToSend, - destinationAddress) as? Result.Success ?: return + val feeResult = + walletManager.getFee( + amountToSend, + destinationAddress, + ) as? Result.Success ?: return val fee = feeResult.data[0] - if ((walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO) < fee.value) { - return - } + val coinValue = walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO + if (coinValue < fee.value) return val transaction = walletManager.createTransaction(amountToSend, fee, destinationAddress) @@ -116,8 +117,8 @@ suspend fun CurrencyExchangeManager.buyErc20TestnetTokens( GlobalAction.UpdateWalletSignedHashes( walletSignedHashes = signResponse.totalSignedHashes, walletPublicKey = walletManager.wallet.publicKey.seedKey, - remainingSignatures = signResponse.remainingSignatures - ) + remainingSignatures = signResponse.remainingSignatures, + ), ) } walletManager.send(transaction, signer) diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt index 9dd2b9746f..b326ccbb79 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt @@ -33,11 +33,12 @@ class MercuryoService( override fun featureIsSwitchedOn(): Boolean = true + @Suppress("NestedBlockDepth") override suspend fun update() { when (val result = performRequest { api.currencies(apiVersion) }) { is Result.Success -> { val response = result.data - if (response.status == 200) { + if (response.status == RESPONSE_SUCCESS_STATUS_CODE) { // all currencies which can be bought val currenciesAvailableToBy = response.data.crypto // tokens which can be bought only from specific blockchain network @@ -108,7 +109,7 @@ class MercuryoService( blockchain: Blockchain, cryptoCurrencyName: CryptoCurrencyName, fiatCurrencyName: String, - walletAddress: String + walletAddress: String, ): String { if (action == CurrencyExchangeManager.Action.Sell) throw UnsupportedOperationException() @@ -131,10 +132,9 @@ class MercuryoService( return (address + secret).calculateSha512().toHexString().lowercase() } - override fun getSellCryptoReceiptUrl( action: CurrencyExchangeManager.Action, - transactionId: String + transactionId: String, ): String? = null private fun blockchainFromCurrencyName(currencyName: String): Blockchain? = when (currencyName) { @@ -143,4 +143,8 @@ class MercuryoService( "ADA" -> Blockchain.CardanoShelley else -> Blockchain.values().find { it.currency.lowercase() == currencyName.lowercase() } } + + companion object { + private const val RESPONSE_SUCCESS_STATUS_CODE = 200 + } } diff --git a/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyApi.kt b/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyApi.kt index a40231aaf5..8566c89a86 100644 --- a/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyApi.kt +++ b/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyApi.kt @@ -11,34 +11,31 @@ import retrofit2.http.Path interface PayIdVerifyApi { @GET("{user}") suspend fun verifyAddress( - @Path("user") user: String, - @Header("Accept") acceptNetworkHeader: String, - @Header("PayID-Version") payIdVersion: String = "1.0" + @Path("user") user: String, + @Header("Accept") acceptNetworkHeader: String, + @Header("PayID-Version") payIdVersion: String = "1.0", ): VerifyPayIdResponse } - @JsonClass(generateAdapter = true) data class VerifyPayIdResponse( - val addresses: List = mutableListOf(), - val payId: String? = null, + val addresses: List = mutableListOf(), + val payId: String? = null, ) { - fun getAddressDetails(): PayIdAddressDetails? { - return if (addresses.isEmpty()) null - else addresses[0].addressDetails - } + fun getAddressDetails(): PayIdAddressDetails? = + if (addresses.isNotEmpty()) addresses[0].addressDetails else null } @JsonClass(generateAdapter = true) data class PayIdAddress( - var paymentNetwork: String, - var environment: String, - var addressDetailsType: String, - var addressDetails: PayIdAddressDetails + var paymentNetwork: String, + var environment: String, + var addressDetailsType: String, + var addressDetails: PayIdAddressDetails, ) @JsonClass(generateAdapter = true) data class PayIdAddressDetails( - var address: String, - var tag: String? = null + var address: String, + var tag: String? = null, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/persistence/CardBalanceStateAdapter.kt b/app/src/main/java/com/tangem/tap/persistence/CardBalanceStateAdapter.kt index fe16a0c017..0385071ff3 100644 --- a/app/src/main/java/com/tangem/tap/persistence/CardBalanceStateAdapter.kt +++ b/app/src/main/java/com/tangem/tap/persistence/CardBalanceStateAdapter.kt @@ -14,7 +14,7 @@ class CardBalanceStateAdapter { return when (json) { AnalyticsParam.CardBalanceState.Empty.value -> AnalyticsParam.CardBalanceState.Empty AnalyticsParam.CardBalanceState.Full.value -> AnalyticsParam.CardBalanceState.Full - else -> throw IllegalArgumentException() + else -> error("CardBalanceState not found") } } } \ 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 d662cdb29d..a6ff52c139 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -101,11 +101,7 @@ class AppRatingLaunchObserver( private val preferences: SharedPreferences, private val launchCounts: Int, ) { - private val K_SHOW_RATING_AT_LAUNCH_COUNT = "showRatingDialogAtLaunchCount" - private val K_FUNDS_FOUND_DATE = "fundsFoundDate" - private val K_USER_WAS_INTERACT_WITH_RATING = "userWasInteractWithRating" - private val FUNDS_FOUND_DATE_UNDEFINED = -1L private val deferShowing = 20 private val firstShowing = 3 private var fundsFoundDate: Calendar? = null @@ -127,6 +123,7 @@ class AppRatingLaunchObserver( } } + @Suppress("MagicNumber") fun isReadyToShow(): Boolean { val fundsDate = fundsFoundDate ?: return false @@ -144,6 +141,7 @@ class AppRatingLaunchObserver( updateNextShowing(launchCounts + deferShowing) } + @Suppress("MagicNumber") fun setNeverToShow() { updateNextShowing(999999999) } @@ -155,6 +153,16 @@ class AppRatingLaunchObserver( editor.apply() } - private fun userWasInteractWithRating(): Boolean = preferences.getBoolean(K_USER_WAS_INTERACT_WITH_RATING, false) - private fun getCounterOfNextShowing(): Int = preferences.getInt(K_SHOW_RATING_AT_LAUNCH_COUNT, firstShowing) + private fun userWasInteractWithRating(): Boolean = + preferences.getBoolean(K_USER_WAS_INTERACT_WITH_RATING, false) + + private fun getCounterOfNextShowing(): Int = + preferences.getInt(K_SHOW_RATING_AT_LAUNCH_COUNT, firstShowing) + + companion object { + private const val K_SHOW_RATING_AT_LAUNCH_COUNT = "showRatingDialogAtLaunchCount" + private const val K_FUNDS_FOUND_DATE = "fundsFoundDate" + private const val K_USER_WAS_INTERACT_WITH_RATING = "userWasInteractWithRating" + private const val FUNDS_FOUND_DATE_UNDEFINED = -1L + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt index ba55c7532b..b0889ba759 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -97,15 +97,12 @@ class TransactionManagerImpl( private fun handleSendResult(result: SimpleResult): SendTxResult { when (result) { - is SimpleResult.Success -> { - return SendTxResult.Success - } + is SimpleResult.Success -> return SendTxResult.Success is SimpleResult.Failure -> { - val error = (result.error as? BlockchainSdkError) ?: return SendTxResult.UnknownError() + val error = result.error as? BlockchainSdkError ?: return SendTxResult.UnknownError() when (error) { is BlockchainSdkError.WrappedTangemError -> { - val tangemSdkError = (error.tangemError as? TangemSdkError) - ?: return SendTxResult.UnknownError() + val tangemSdkError = error.tangemError as? TangemSdkError ?: return SendTxResult.UnknownError() if (tangemSdkError is TangemSdkError.UserCancelled) return SendTxResult.UserCancelledError return SendTxResult.TangemSdkError(tangemSdkError.code, tangemSdkError.cause) } @@ -202,6 +199,5 @@ class TransactionManagerImpl( companion object { private const val HEX_PREFIX = "0x" - private const val DEFAULT_GAS_LIMIT = 300000L } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt index 95818b9980..c29c2e92af 100644 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -82,11 +82,7 @@ class UserWalletManagerImpl( override fun addToken(currency: Currency) { val action = if (currency is NativeToken) { val card = appStateHolder.getActualCard() - if (card != null) { - addNativeTokenToWalletAction(currency, card) - } else { - throw IllegalStateException("card not found") - } + if (card != null) addNativeTokenToWalletAction(currency, card) else error("Card not found") } else { addNonNativeTokenToWalletAction(currency as NonNativeToken) } @@ -184,12 +180,15 @@ class UserWalletManagerImpl( } private fun createDerivationParams(derivationStyle: DerivationStyle?): DerivationParams? { - return derivationStyle?.let { DerivationParams.Default(derivationStyle) } //todo clarify if its need to add Custom + //todo clarify if its need to add Custom + return derivationStyle?.let { DerivationParams.Default(derivationStyle) } } private fun getActualWalletManager(blockchain: Blockchain): WalletManager { val card = requireNotNull(appStateHolder.getActualCard()) { "card not found" } val blockchainNetwork = BlockchainNetwork(blockchain, card) - return requireNotNull(appStateHolder.walletState?.getWalletManager(blockchainNetwork)) { "no wallet manager found" } + return requireNotNull(appStateHolder.walletState?.getWalletManager(blockchainNetwork)) { + "No wallet manager found" + } } } \ No newline at end of file diff --git a/app/src/test/java/com/tangem/wallet/ExampleUnitTest.kt b/app/src/test/java/com/tangem/wallet/ExampleUnitTest.kt deleted file mode 100644 index ead7134648..0000000000 --- a/app/src/test/java/com/tangem/wallet/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.tangem.wallet - -import org.junit.Assert.assertEquals -import org.junit.Test - - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} \ 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 index c8632c9597..511ffa8d99 100644 --- a/common/src/main/java/com/tangem/common/module/ModuleMessage.kt +++ b/common/src/main/java/com/tangem/common/module/ModuleMessage.kt @@ -11,7 +11,7 @@ interface ModuleMessage * @property message the error description * @property data any data that can help in the part where this error is being handled */ -abstract class ModuleError() : Throwable(), ModuleMessage { +abstract class ModuleError : Throwable(), ModuleMessage { abstract val code: Int abstract override val message: String abstract val data: Any? diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index 306dcd59df..3022bed25f 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -613,10 +613,11 @@ style: active: true UnusedPrivateClass: active: true + ignoreAnnotated: ['UnusedRequiredComponent'] UnusedPrivateMember: active: true allowedNames: '(_|ignored|expected|serialVersionUID)' - ignoreAnnotated: ['Preview'] + ignoreAnnotated: ['Preview', 'UnusedRequiredComponent'] UseAnyOrNoneInsteadOfFind: active: true UseArrayLiteralsInAnnotations: diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/api/ClientApi.kt b/core/analytics/src/main/java/com/tangem/core/analytics/api/ClientApi.kt index d4a9b22842..0989dfaad4 100644 --- a/core/analytics/src/main/java/com/tangem/core/analytics/api/ClientApi.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/api/ClientApi.kt @@ -1,4 +1,4 @@ -package com.tangem.analytics.api +package com.tangem.core.analytics.api /** [REDACTED_AUTHOR] diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/Adapters.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/BigDecimalAdapter.kt similarity index 100% rename from core/datasource/src/main/java/com/tangem/datasource/api/common/Adapters.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/common/BigDecimalAdapter.kt diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/Retrofit.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/Retrofit.kt index 86b4ec51f4..166036783c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/Retrofit.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/Retrofit.kt @@ -6,6 +6,7 @@ import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import java.util.concurrent.TimeUnit +@Suppress("MagicNumber") @Deprecated("Create and provide by DI") fun createRetrofitInstance( baseUrl: String, diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/RateResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/RatesResponse.kt similarity index 100% rename from core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/RateResponse.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/RatesResponse.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt index 57ea5a799d..c3b2ef1f2c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt @@ -232,6 +232,7 @@ fun SecondaryButtonIconLeft( } // region Defaults +@Suppress("LongParameterList") @Composable private fun TangemButton( modifier: Modifier = Modifier, @@ -286,29 +287,31 @@ private fun ButtonContent( ) } - if (showProgress) Box( - modifier = Modifier.wrapContentSize(), - ) { - CircularProgressIndicator( - modifier = Modifier - .align(Alignment.Center) - .size(TangemTheme.dimens.size24), - color = colors.contentColor(enabled = enabled).value, - strokeWidth = TangemTheme.dimens.size4, - ) - } else Row { - if (buttonIcon is TangemButtonIcon.Left) { - icon(buttonIcon.painter) - Spacer(modifier = Modifier.width(TangemTheme.dimens.size8)) + if (showProgress) { + Box(modifier = Modifier.wrapContentSize()) { + CircularProgressIndicator( + modifier = Modifier + .align(Alignment.Center) + .size(TangemTheme.dimens.size24), + color = colors.contentColor(enabled = enabled).value, + strokeWidth = TangemTheme.dimens.size4, + ) } - Text( - text = text, - style = TangemTypography.button, - color = colors.contentColor(enabled = enabled).value, - ) - if (buttonIcon is TangemButtonIcon.Right) { - Spacer(modifier = Modifier.width(TangemTheme.dimens.size8)) - icon(buttonIcon.painter) + } else { + Row { + if (buttonIcon is TangemButtonIcon.Left) { + icon(buttonIcon.painter) + Spacer(modifier = Modifier.width(TangemTheme.dimens.size8)) + } + Text( + text = text, + style = TangemTypography.button, + color = colors.contentColor(enabled = enabled).value, + ) + if (buttonIcon is TangemButtonIcon.Right) { + Spacer(modifier = Modifier.width(TangemTheme.dimens.size8)) + icon(buttonIcon.painter) + } } } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt index 0362d05890..db9c45339f 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt @@ -142,7 +142,8 @@ fun SmallInfoCardWithWarning( * @param icon icon to the left * @param additionalContent shown at the right edge of the card * - * @see Figma component */ @Composable @@ -177,7 +178,8 @@ fun CardWithIcon( * @param icon icon to the left * @param additionalContent shown at the right edge of the card * - * @see Figma component */ @Composable @@ -290,6 +292,7 @@ private fun CardInfoBox( // region Preview +@Suppress("MagicNumber") @Composable private fun CardsPreview() { Column(modifier = Modifier.fillMaxWidth()) { @@ -300,8 +303,8 @@ private fun CardsPreview() { SmallInfoCardWithWarning( startText = "Balance", endText = "0.4405434 BTC", - warningText = - "Not enough funds for fee on your Polygon wallet to create a transaction. Top up your Polygon wallet first.", + warningText = "Not enough funds for fee on your Polygon wallet to create a transaction. " + + "Top up your Polygon wallet first.", ) SpacerH32() diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt index b9f323d670..5afa4620fa 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt @@ -102,7 +102,8 @@ private fun SimpleOkDialogPreview() = SimpleOkDialog( @Composable private fun BasicDialogPreview() = BasicDialog( - message = "All protected passwords will be deleted from the secure storage, you must enter the wallet password to work with the app", + message = "All protected passwords will be deleted from the secure storage, you must enter the wallet password " + + "to work with the app", title = "Attention", confirmButton = DialogButton {}, dismissButton = DialogButton {}, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/KeybosardState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Keyboard.kt similarity index 98% rename from core/ui/src/main/java/com/tangem/core/ui/components/KeybosardState.kt rename to core/ui/src/main/java/com/tangem/core/ui/components/Keyboard.kt index 69d24e6980..a7ae0f67b7 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/KeybosardState.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Keyboard.kt @@ -18,6 +18,7 @@ sealed class Keyboard { /** * Allows to subscribe to a soft keyboard to detect when it's open/closed */ +@Suppress("MagicNumber") @Composable fun keyboardAsState(): State { val keyboardState: MutableState = remember { mutableStateOf(Keyboard.Closed) } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/ResizableText.kt b/core/ui/src/main/java/com/tangem/core/ui/components/ResizableText.kt index 4b7fe2c1ac..9067f17fd4 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/ResizableText.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/ResizableText.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.sp +@Suppress("MagicNumber") @Composable fun ResizableText( text: String, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt index b333d58fdf..15ba170e20 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt @@ -1,4 +1,4 @@ -@file:Suppress("unused") +@file:Suppress("TooManyFunctions") package com.tangem.core.ui.components @@ -88,6 +88,7 @@ fun ColumnScope.SpacerHMax(modifier: Modifier = Modifier) { } // TODO: Refactor with context receivers +@Suppress("MagicNumber") @Composable fun ColumnScope.SpacerHHalf(modifier: Modifier = Modifier) { Spacer(modifier = modifier.weight(.5f)) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/SuccessScreen.kt b/core/ui/src/main/java/com/tangem/core/ui/components/SuccessScreen.kt index c87490b133..ce91939f5e 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/SuccessScreen.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/SuccessScreen.kt @@ -40,7 +40,8 @@ import com.tangem.core.ui.res.TangemTheme * @param onSecondaryButtonClick action on clicking secondary button * @param onButtonClick action on clicking "Done" button * - * @see Figma component */ @Composable @@ -113,8 +114,7 @@ fun SuccessImage( Box( modifier = Modifier .background( - color = (resultColor) - .copy(alpha = 0.2f), + color = resultColor.copy(alpha = 0.2f), shape = CircleShape, ), contentAlignment = Alignment.Center, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt b/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt index 1ac3d8e677..a0459b8dc4 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt @@ -79,6 +79,7 @@ fun OutlineTextField( } // region Defaults +@Suppress("LongMethod") @Composable private fun TangemTextField( modifier: Modifier = Modifier, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithSearch.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithSearch.kt index 37b0acc905..4d147b65fc 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithSearch.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithSearch.kt @@ -48,7 +48,8 @@ import com.tangem.core.ui.res.TangemTheme * @param onSearchChanged action when search is modified * @param onSearchDisplayClosed action when search is closed * - * @see Figma component */ @Composable @@ -89,15 +90,6 @@ fun ExpandableSearchView( } } -@Composable -private fun SearchIcon(iconTint: Color) { - Icon( - painter = painterResource(id = R.drawable.ic_search_24), - contentDescription = null, - tint = iconTint, - ) -} - @Composable private fun CollapsedSearchView( modifier: Modifier = Modifier, @@ -151,16 +143,13 @@ private fun ExpandedSearchView( tint: Color = TangemTheme.colors.background.primary, ) { val focusManager = LocalFocusManager.current - val textFieldFocusRequester = remember { FocusRequester() } SideEffect { textFieldFocusRequester.requestFocus() } - var textFieldValue by remember { - mutableStateOf(TextFieldValue("", TextRange("".length))) - } + var textFieldValue by remember { mutableStateOf(TextFieldValue("", TextRange("".length))) } Row( modifier = modifier @@ -194,22 +183,17 @@ private fun ExpandedSearchView( placeholder = { Text(text = placeholderSearchText) }, - keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Done, - ), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions( - onDone = { - focusManager.clearFocus() - }, + onDone = { focusManager.clearFocus() }, + ), + colors = TangemTextFieldsDefault.defaultTextFieldColors.copy( + placeholderColor = TangemTheme.colors.text.disabled, + cursorColor = TangemTheme.colors.text.tertiary, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent, ), - colors = TangemTextFieldsDefault.defaultTextFieldColors - .copy( - placeholderColor = TangemTheme.colors.text.disabled, - cursorColor = TangemTheme.colors.text.tertiary, - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent, - disabledIndicatorColor = Color.Transparent, - ), ) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/Blockchain.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/Blockchain.kt index 052f2f26b6..200c7a08ed 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/Blockchain.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/Blockchain.kt @@ -3,6 +3,7 @@ package com.tangem.core.ui.extensions import androidx.annotation.DrawableRes import com.tangem.core.ui.R +@Suppress("ComplexMethod") @DrawableRes fun getActiveIconRes(blockchainId: String): Int { return when (blockchainId) { diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/ButtonColor.kt b/core/ui/src/main/java/com/tangem/core/ui/res/ButtonColorType.kt similarity index 89% rename from core/ui/src/main/java/com/tangem/core/ui/res/ButtonColor.kt rename to core/ui/src/main/java/com/tangem/core/ui/res/ButtonColorType.kt index 427be16fe5..5eb3a3fcff 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/ButtonColor.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/ButtonColorType.kt @@ -21,4 +21,4 @@ enum class ButtonColorType(val lightColor: Color, val darkColor: Color) { } @Composable -fun Colors.buttonColor(type: ButtonColorType): Color = if (isSystemInDarkTheme) type.darkColor else type.lightColor \ No newline at end of file +fun Colors.buttonColor(type: ButtonColorType): Color = if (IS_SYSTEM_IN_DARK_THEME) type.darkColor else type.lightColor \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/IconColor.kt b/core/ui/src/main/java/com/tangem/core/ui/res/IconColorType.kt similarity index 92% rename from core/ui/src/main/java/com/tangem/core/ui/res/IconColor.kt rename to core/ui/src/main/java/com/tangem/core/ui/res/IconColorType.kt index 11dab52a7b..9fa83581b6 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/IconColor.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/IconColorType.kt @@ -28,4 +28,4 @@ enum class IconColorType(val lightColor: Color, val darkColor: Color) { } @Composable -fun Colors.iconColor(type: IconColorType): Color = if (isSystemInDarkTheme) type.darkColor else type.lightColor \ No newline at end of file +fun Colors.iconColor(type: IconColorType): Color = if (IS_SYSTEM_IN_DARK_THEME) type.darkColor else type.lightColor \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt index 371e70d22e..8dc45fd453 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt @@ -6,7 +6,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.graphics.Color -@Suppress("unused", "MemberVisibilityCanBePrivate") +@Suppress("LongParameterList") @Stable class TangemColors internal constructor( text: Text, diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt index c97f04e9f0..fee6cc5fe9 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Immutable import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +@Suppress("ConstructorParameterNaming") @Immutable data class TangemDimens internal constructor( // region Elevation diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt index 8ff81a2640..8239973f6a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt @@ -11,7 +11,8 @@ import androidx.compose.runtime.Stable import androidx.compose.runtime.remember import androidx.compose.runtime.staticCompositionLocalOf -internal val isSystemInDarkTheme: Boolean = false // TODO: use isSystemInDarkTheme() for automatic color detection +// TODO: use isSystemInDarkTheme() for automatic color detection +internal const val IS_SYSTEM_IN_DARK_THEME: Boolean = false @Composable fun TangemTheme( diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TextColor.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TextColorType.kt similarity index 92% rename from core/ui/src/main/java/com/tangem/core/ui/res/TextColor.kt rename to core/ui/src/main/java/com/tangem/core/ui/res/TextColorType.kt index 0fce11d9e7..5b726844aa 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TextColor.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TextColorType.kt @@ -28,4 +28,4 @@ enum class TextColorType(val lightColor: Color, val darkColor: Color) { } @Composable -fun Colors.textColor(type: TextColorType): Color = if (isSystemInDarkTheme) type.darkColor else type.lightColor \ No newline at end of file +fun Colors.textColor(type: TextColorType): Color = if (IS_SYSTEM_IN_DARK_THEME) type.darkColor else type.lightColor \ No newline at end of file diff --git a/core/utils/src/main/java/com/tangem/utils/FormatExtensions.kt b/core/utils/src/main/java/com/tangem/utils/FormatExtensions.kt index 46b5621758..261e6811de 100644 --- a/core/utils/src/main/java/com/tangem/utils/FormatExtensions.kt +++ b/core/utils/src/main/java/com/tangem/utils/FormatExtensions.kt @@ -24,7 +24,7 @@ fun BigDecimal.toFormattedString( fun BigDecimal.toFiatString( rateValue: BigDecimal, fiatCurrencyName: String, - formatWithSpaces: Boolean = false + formatWithSpaces: Boolean = false, ): String { val fiatValue = rateValue.multiply(this) return fiatValue.toFormattedFiatValue(fiatCurrencyName, formatWithSpaces) @@ -32,13 +32,14 @@ fun BigDecimal.toFiatString( fun BigDecimal.toFormattedFiatValue( fiatCurrencyName: String, - formatWithSpaces: Boolean = false + formatWithSpaces: Boolean = false, ): String { val fiatValue = this.setScale(2, RoundingMode.HALF_UP) .let { if (formatWithSpaces) it.formatWithSpaces() else it } return " ${fiatValue}  $fiatCurrencyName" } +@Suppress("MagicNumber") fun BigDecimal.formatWithSpaces(): String { val str = this.toString() var integerStr = str.substringBefore('.') diff --git a/core/utils/src/main/java/com/tangem/utils/coroutines/FeatureCoroutineExceptionHandler.kt b/core/utils/src/main/java/com/tangem/utils/coroutines/FeatureCoroutineExceptionHandler.kt index 71cea0fb21..6075186448 100644 --- a/core/utils/src/main/java/com/tangem/utils/coroutines/FeatureCoroutineExceptionHandler.kt +++ b/core/utils/src/main/java/com/tangem/utils/coroutines/FeatureCoroutineExceptionHandler.kt @@ -9,19 +9,17 @@ import java.util.logging.Logger /** [REDACTED_AUTHOR] */ -class FeatureCoroutineExceptionHandler { +object FeatureCoroutineExceptionHandler { // add an external logger (FbAnalytics) for handling errors - companion object { - fun create(from: String): CoroutineExceptionHandler = CoroutineExceptionHandler { _, throwable -> - val sw = StringWriter() - throwable.printStackTrace(PrintWriter(sw)) - val exceptionAsString: String = sw.toString() - //it delegates logging to android Logger, cause cant use timber in java module - Logger.getLogger("CoroutineExceptHandler").log( - Level.INFO, "CoroutineException: from: $from, exception: $exceptionAsString", - ) - throw throwable - } + fun create(from: String): CoroutineExceptionHandler = CoroutineExceptionHandler { _, throwable -> + val sw = StringWriter() + throwable.printStackTrace(PrintWriter(sw)) + val exceptionAsString: String = sw.toString() + //it delegates logging to android Logger, cause cant use timber in java module + Logger.getLogger("CoroutineExceptHandler").log( + Level.INFO, "CoroutineException: from: $from, exception: $exceptionAsString", + ) + throw throwable } } \ No newline at end of file diff --git a/core/utils/src/main/java/com/tangem/utils/detekt/UnusedRequiredComponent.kt b/core/utils/src/main/java/com/tangem/utils/detekt/UnusedRequiredComponent.kt new file mode 100644 index 0000000000..05e6016541 --- /dev/null +++ b/core/utils/src/main/java/com/tangem/utils/detekt/UnusedRequiredComponent.kt @@ -0,0 +1,28 @@ +package com.tangem.utils.detekt + +/** + * Annotation for an unused component that should be ignored by Detekt + * + * @property description description of why I shouldn't remove it + * +[REDACTED_AUTHOR] + */ +@Target( + AnnotationTarget.CLASS, + AnnotationTarget.ANNOTATION_CLASS, + AnnotationTarget.TYPE_PARAMETER, + AnnotationTarget.PROPERTY, + AnnotationTarget.FIELD, + AnnotationTarget.LOCAL_VARIABLE, + AnnotationTarget.VALUE_PARAMETER, + AnnotationTarget.CONSTRUCTOR, + AnnotationTarget.FUNCTION, + AnnotationTarget.PROPERTY_GETTER, + AnnotationTarget.PROPERTY_SETTER, + AnnotationTarget.TYPE, + AnnotationTarget.EXPRESSION, + AnnotationTarget.FILE, + AnnotationTarget.TYPEALIAS +) +@Retention(AnnotationRetention.SOURCE) +annotation class UnusedRequiredComponent(val description: String) diff --git a/core/utils/src/main/java/com/tangem/utils/di/CoroutineDispatcherProviderModule.kt b/core/utils/src/main/java/com/tangem/utils/di/CoroutineDispatcherProviderModule.kt index df85354292..6dd1011462 100644 --- a/core/utils/src/main/java/com/tangem/utils/di/CoroutineDispatcherProviderModule.kt +++ b/core/utils/src/main/java/com/tangem/utils/di/CoroutineDispatcherProviderModule.kt @@ -12,5 +12,7 @@ import dagger.hilt.components.SingletonComponent interface CoroutineDispatcherProviderModule { @Binds - fun bindCoroutineDispatcherProvider(coroutineDispatcherProvider: AppCoroutineDispatcherProvider): CoroutineDispatcherProvider + fun bindCoroutineDispatcherProvider( + coroutineDispatcherProvider: AppCoroutineDispatcherProvider, + ): CoroutineDispatcherProvider } \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/DomainLayer.kt b/domain/src/main/java/com/tangem/domain/DomainLayer.kt index ef083fdc96..a8a52c2d4e 100644 --- a/domain/src/main/java/com/tangem/domain/DomainLayer.kt +++ b/domain/src/main/java/com/tangem/domain/DomainLayer.kt @@ -12,7 +12,7 @@ object DomainLayer { var onInitComplete: ((DomainModuleError?) -> Unit)? = null - fun init(data: Map? = null) { + fun init() { initActionStateLogger() onInitComplete?.invoke(null) diff --git a/domain/src/main/java/com/tangem/domain/common/CardDTO.kt b/domain/src/main/java/com/tangem/domain/common/CardDTO.kt index 58df56c687..1d20c068a4 100644 --- a/domain/src/main/java/com/tangem/domain/common/CardDTO.kt +++ b/domain/src/main/java/com/tangem/domain/common/CardDTO.kt @@ -48,6 +48,7 @@ data class CardDTO( backupStatus = BackupStatus.fromSdkStatus(card.backupStatus), ) + @Suppress("ComplexMethod") override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is CardDTO) return false @@ -120,13 +121,6 @@ data class CardDTO( val patch: Int, val type: SdkFirmwareVersion.FirmwareType, ) : Comparable { - constructor(firmwareVersion: SdkFirmwareVersion) : this( - major = firmwareVersion.major, - minor = firmwareVersion.minor, - patch = firmwareVersion.patch, - type = firmwareVersion.type, - ) - val stringValue: String get() = StringBuilder() .append("$major.$minor") @@ -134,6 +128,13 @@ data class CardDTO( .append(type.rawValue ?: "") .toString() + constructor(firmwareVersion: SdkFirmwareVersion) : this( + major = firmwareVersion.major, + minor = firmwareVersion.minor, + patch = firmwareVersion.patch, + type = firmwareVersion.type, + ) + override fun compareTo(other: SdkFirmwareVersion): Int = when { major != other.major -> major.compareTo(other.major) minor != other.minor -> minor.compareTo(other.minor) diff --git a/domain/src/main/java/com/tangem/domain/common/LogConfig.kt b/domain/src/main/java/com/tangem/domain/common/LogConfig.kt index ebe3cef306..f0626bf715 100644 --- a/domain/src/main/java/com/tangem/domain/common/LogConfig.kt +++ b/domain/src/main/java/com/tangem/domain/common/LogConfig.kt @@ -3,22 +3,21 @@ package com.tangem.domain.common import com.tangem.domain.features.BuildConfig object LogConfig { - val imageLoader: Boolean = false + const val imageLoader: Boolean = false val storeAction: Boolean = BuildConfig.DEBUG - val zendesk: Boolean = false + const val zendesk: Boolean = false val network: NetworkLogConfig = NetworkLogConfig val analyticsHandlers: AnalyticsHandlersLogConfig = AnalyticsHandlersLogConfig } object NetworkLogConfig { - val mercuryoService: Boolean = false - val moonPayService: Boolean = false - val paymentologyApiService: Boolean = BuildConfig.DEBUG + const val mercuryoService: Boolean = false + const val moonPayService: Boolean = false val blockchainSdkNetwork: Boolean = BuildConfig.DEBUG } object AnalyticsHandlersLogConfig { - val firebase: Boolean = false - val appsFlyer: Boolean = false - val amplitude: Boolean = false + const val firebase: Boolean = false + const val appsFlyer: Boolean = false + const val amplitude: Boolean = false } \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt b/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt index 1328643534..58c03985a6 100644 --- a/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt +++ b/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt @@ -6,35 +6,8 @@ import com.tangem.common.CardIdRange import com.tangem.common.contains object SaltPayWorkaround { - fun tokenFrom(blockchain: Blockchain): Token { - return when (blockchain) { - Blockchain.SaltPay -> Token( - name = "WXDAI", - symbol = "wxDAI", - contractAddress = "0x4200000000000000000000000000000000000006", - decimals = 18, - id = "wrapped-xdai", - ) - else -> throw IllegalArgumentException() - } - } - - fun isVisaBatchId(batchId: String): Boolean = visaBatches.contains(batchId) - - fun isWalletCardId(cardId: String): Boolean { - return if (walletCardIds.contains(cardId)) true else walletCardIdRanges.contains(cardId) - } - - val visaBatches = listOf( - "AE02", - "AE03", - ) + attachTestVisaBatches() - - private fun attachTestVisaBatches(): List { - return listOf( - "FF03", - ) - } + @Suppress("MagicNumber") + val visaBatches = listOf("AE02", "AE03") + attachTestVisaBatches() val walletCardIds = listOf( "AC01000000033503", @@ -67,19 +40,38 @@ object SaltPayWorkaround { "AC03000000076229", ) + attachTestWalletCardIds() - private fun attachTestWalletCardIds(): List { - return listOf( - "FF04000000000232", - ) - } - val walletCardIdRanges = listOf( CardIdRange("AC05000000000003", "AC05000000023997")!!, ) + attachTestWalletCardIdRanges() + fun tokenFrom(blockchain: Blockchain): Token { + return when (blockchain) { + Blockchain.SaltPay -> Token( + name = "WXDAI", + symbol = "wxDAI", + contractAddress = "0x4200000000000000000000000000000000000006", + decimals = 18, + id = "wrapped-xdai", + ) + else -> error("It is not SaltPay") + } + } + + fun isVisaBatchId(batchId: String): Boolean = visaBatches.contains(batchId) + + fun isWalletCardId(cardId: String): Boolean { + return if (walletCardIds.contains(cardId)) true else walletCardIdRanges.contains(cardId) + } + + private fun attachTestVisaBatches(): List { + return listOf("FF03") + } + + private fun attachTestWalletCardIds(): List { + return listOf("FF04000000000232") + } + private fun attachTestWalletCardIdRanges(): List { - return listOf( - CardIdRange("FF04000000000000", "FF04999999999999")!!, - ) + return listOf(CardIdRange("FF04000000000000", "FF04999999999999")!!) } } \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt b/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt index 21fa7e169f..cf9c502268 100644 --- a/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt +++ b/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt @@ -13,10 +13,6 @@ object TapWorkarounds { private const val TEST_CARD_BATCH = "99FF" private const val TEST_CARD_ID_STARTS_WITH = "FF99" - fun isStart2CoinIssuer(cardIssuer: String?): Boolean { - return cardIssuer?.lowercase(Locale.US) == START_2_COIN_ISSUER - } - val CardDTO.isTangemTwins: Boolean get() = TwinsHelper.getTwinCardNumber(cardId) != null @@ -41,6 +37,7 @@ object TapWorkarounds { val CardDTO.useOldStyleDerivation: Boolean get() = batchId == "AC01" || batchId == "AC02" || batchId == "CB95" + val CardDTO.derivationStyle: DerivationStyle? get() = if (!settings.isHDWalletAllowed) { null @@ -49,7 +46,6 @@ object TapWorkarounds { } else { DerivationStyle.NEW } - val CardDTO.isExcluded: Boolean get() { val excludedBatch = excludedBatches.contains(batchId) @@ -60,9 +56,6 @@ object TapWorkarounds { val CardDTO.isNotSupportedInThatRelease: Boolean get() = false - fun CardDTO.getTangemNoteBlockchain(): Blockchain? = - tangemNoteBatches[batchId] ?: if (isSaltPay) Blockchain.Gnosis else null - private val tangemNoteBatches = mapOf( "AB01" to Blockchain.Bitcoin, "AB02" to Blockchain.Ethereum, @@ -78,16 +71,16 @@ object TapWorkarounds { "AB12" to Blockchain.Ethereum, ) + private val excludedBatches = listOf("0027", "0030", "0031", "0035") + + private val excludedIssuers = listOf("TTM BANK") + + fun CardDTO.getTangemNoteBlockchain(): Blockchain? = + tangemNoteBatches[batchId] ?: if (isSaltPay) Blockchain.Gnosis else null + + fun isStart2CoinIssuer(cardIssuer: String?): Boolean { + return cardIssuer?.lowercase(Locale.US) == START_2_COIN_ISSUER + } + fun Card.getTangemNoteBlockchain(): Blockchain? = tangemNoteBatches[batchId] ?: null - - private val excludedBatches = listOf( - "0027", - "0030", - "0031", - "0035", - ) - - private val excludedIssuers = listOf( - "TTM BANK", - ) } \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/TwinsHelper.kt b/domain/src/main/java/com/tangem/domain/common/TwinsHelper.kt index a1ded44422..7e801695fb 100644 --- a/domain/src/main/java/com/tangem/domain/common/TwinsHelper.kt +++ b/domain/src/main/java/com/tangem/domain/common/TwinsHelper.kt @@ -2,47 +2,32 @@ package com.tangem.domain.common import com.tangem.crypto.CryptoUtils -class TwinsHelper { - companion object { - const val TWIN_FILE_NAME = "TwinPublicKey" +object TwinsHelper { + private val firstCardSeries = listOf("CB61", "CB64") + private val secondCardSeries = listOf("CB62", "CB65") - fun verifyTwinPublicKey(issuerData: ByteArray, cardWalletPublicKey: ByteArray?): Boolean { - if (issuerData.size < 65 || cardWalletPublicKey == null) return false + @Suppress("MagicNumber") + fun verifyTwinPublicKey(issuerData: ByteArray, cardWalletPublicKey: ByteArray?): Boolean { + if (issuerData.size < 65 || cardWalletPublicKey == null) return false - val publicKey = issuerData.sliceArray(0 until 65) - val signedKey = issuerData.sliceArray(65 until issuerData.size) - return CryptoUtils.verify(cardWalletPublicKey, publicKey, signedKey) - } + val publicKey = issuerData.sliceArray(0 until 65) + val signedKey = issuerData.sliceArray(65 until issuerData.size) + return CryptoUtils.verify(cardWalletPublicKey, publicKey, signedKey) + } - fun getTwinCardNumber(cardId: String): TwinCardNumber? = when { - firstCardSeries.any(cardId::startsWith) -> TwinCardNumber.First - secondCardSeries.any(cardId::startsWith) -> TwinCardNumber.Second - else -> null - } + fun getTwinCardNumber(cardId: String): TwinCardNumber? = when { + firstCardSeries.any(cardId::startsWith) -> TwinCardNumber.First + secondCardSeries.any(cardId::startsWith) -> TwinCardNumber.Second + else -> null + } - fun getPairCardSeries(cardId: String): String? { - return when (getTwinCardNumber(cardId) ?: return null) { - TwinCardNumber.First -> { - val index = firstCardSeries.indexOf(cardId.take(4)) - secondCardSeries[index] - } - TwinCardNumber.Second -> { - val index = secondCardSeries.indexOf(cardId.take(4)) - firstCardSeries[index] - } - } - } + @Suppress("MagicNumber") + fun getTwinCardIdForUser(cardId: String): String { + if (cardId.length < 16) return cardId - fun getTwinCardIdForUser(cardId: String): String { - if (cardId.length < 16) return cardId - - val twinCardId = cardId.substring(11..14) - val twinCardNumber = getTwinCardNumber(cardId)?.number ?: 1 - return "$twinCardId #$twinCardNumber" - } - - private val firstCardSeries = listOf("CB61", "CB64") - private val secondCardSeries = listOf("CB62", "CB65") + val twinCardId = cardId.substring(11..14) + val twinCardNumber = getTwinCardNumber(cardId)?.number ?: 1 + return "$twinCardId #$twinCardNumber" } } 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 c684dbe3f5..f8bb58f3f1 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 @@ -2,6 +2,7 @@ package com.tangem.domain.common.extensions import com.tangem.blockchain.common.Blockchain +@Suppress("ComplexMethod") fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { return when (networkId) { "arbitrum-one" -> Blockchain.Arbitrum @@ -52,6 +53,7 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { } } +@Suppress("ComplexMethod") fun Blockchain.toNetworkId(): String { return when (this) { Blockchain.Unknown -> "unknown" @@ -103,6 +105,7 @@ fun Blockchain.toNetworkId(): String { } } +@Suppress("ComplexMethod") fun Blockchain.toCoinId(): String { return when (this) { Blockchain.Binance, Blockchain.BinanceTestnet, Blockchain.BSC, Blockchain.BSCTestnet -> "binancecoin" diff --git a/domain/src/main/java/com/tangem/domain/common/form/FieldDataConverters.kt b/domain/src/main/java/com/tangem/domain/common/form/FieldDataConverters.kt index a0cd94a04f..f4800c7223 100644 --- a/domain/src/main/java/com/tangem/domain/common/form/FieldDataConverters.kt +++ b/domain/src/main/java/com/tangem/domain/common/form/FieldDataConverters.kt @@ -12,7 +12,7 @@ interface DataConverterVisitor { interface FieldDataConverter : DataConverterVisitor -abstract class BaseFieldDataConverter() : FieldDataConverter { +abstract class BaseFieldDataConverter : FieldDataConverter { protected val collectIds: List get() = getIdToCollect() @@ -31,7 +31,7 @@ abstract class BaseFieldDataConverter() : FieldDataConverter { class FieldToJsonConverter( private val fieldsToConvert: List = listOf(), - protected val jsonConverter: MoshiJsonConverter + protected val jsonConverter: MoshiJsonConverter, ) : BaseFieldDataConverter() { override fun getConvertedData(): String = jsonConverter.toJson(collectedData, " ") 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 53147c46b8..8f5e7dfd00 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 @@ -13,26 +13,34 @@ import timber.log.Timber /** [REDACTED_AUTHOR] */ -abstract class CustomTokenValidator : Validator +interface CustomTokenValidator : Validator -class StringIsEmptyValidator : CustomTokenValidator() { +class StringIsEmptyValidator : CustomTokenValidator { override fun validate(data: String?): AddCustomTokenError? = when { data == null || data.isEmpty() -> null else -> AddCustomTokenError.FieldIsNotEmpty } } -class StringIsNotEmptyValidator : CustomTokenValidator() { +class StringIsNotEmptyValidator : CustomTokenValidator { override fun validate(data: String?): AddCustomTokenError? = when { data == null || data.isEmpty() -> AddCustomTokenError.FieldIsEmpty else -> null } } -class TokenContractAddressValidator : CustomTokenValidator() { +class TokenContractAddressValidator : CustomTokenValidator { private var blockchain: Blockchain = Blockchain.Unknown + private val successAddressValidator = object : AddressService() { + override fun makeAddress(walletPublicKey: ByteArray, curve: EllipticCurve?): String { + throw UnsupportedOperationException() + } + + override fun validate(address: String): Boolean = true + } + fun nextValidationFor(blockchain: Blockchain) { this.blockchain = blockchain } @@ -63,32 +71,25 @@ class TokenContractAddressValidator : CustomTokenValidator() { } } } - - private val successAddressValidator = object : AddressService() { - override fun makeAddress(walletPublicKey: ByteArray, curve: EllipticCurve?): String { - throw UnsupportedOperationException() - } - - override fun validate(address: String): Boolean = true - } } -class TokenNetworkValidator : CustomTokenValidator() { +class TokenNetworkValidator : CustomTokenValidator { override fun validate(data: Blockchain?): AddCustomTokenError? = when (data) { null, Blockchain.Unknown -> AddCustomTokenError.NetworkIsNotSelected else -> null } } -class TokenNameValidator : CustomTokenValidator() { +class TokenNameValidator : CustomTokenValidator { override fun validate(data: String?): AddCustomTokenError? = StringIsNotEmptyValidator().validate(data) } -class TokenSymbolValidator : CustomTokenValidator() { +class TokenSymbolValidator : CustomTokenValidator { override fun validate(data: String?): AddCustomTokenError? = StringIsNotEmptyValidator().validate(data) } -class TokenDecimalsValidator : CustomTokenValidator() { +@Suppress("MagicNumber") +class TokenDecimalsValidator : CustomTokenValidator { override fun validate(data: String?): AddCustomTokenError? { val decimal = data?.toIntOrNull() ?: return AddCustomTokenError.FieldIsEmpty diff --git a/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt b/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt index abf878da69..17c22839fd 100644 --- a/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt +++ b/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt @@ -25,6 +25,7 @@ class UserWalletId( return stringValue.hashCode() } + @Suppress("MagicNumber") override fun toString(): String { return with(stringValue) { "UserWalletId(${take(3)}...${takeLast(3)})" diff --git a/domain/src/main/java/com/tangem/domain/common/util/ValueDebouncer.kt b/domain/src/main/java/com/tangem/domain/common/util/ValueDebouncer.kt index 919157a26c..4296bf4c3f 100644 --- a/domain/src/main/java/com/tangem/domain/common/util/ValueDebouncer.kt +++ b/domain/src/main/java/com/tangem/domain/common/util/ValueDebouncer.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.onEach /** [REDACTED_AUTHOR] */ +@Suppress("MagicNumber") class ValueDebouncer( private val initialValue: T, private val debounceDuration: Long = 400, diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt index 8015e7fc84..2296da1914 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt @@ -48,7 +48,7 @@ class AddCustomTokenService( return@withContext emptyList() } - throw IllegalStateException("Unreachable code because runCatching must return result") + error("Unreachable code because runCatching must return result") } private fun selectNetworksForSearch(networkId: String?): String { diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/CustomCurrency.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/CustomCurrency.kt index a09d2f3907..fd1a47b728 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/CustomCurrency.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/CustomCurrency.kt @@ -18,11 +18,11 @@ sealed class CustomCurrency( class CustomBlockchain( network: Blockchain, - derivationPath: DerivationPath? + derivationPath: DerivationPath?, ) : CustomCurrency(network, derivationPath) { class Converter( - private val derivationStyle: DerivationStyle? + private val derivationStyle: DerivationStyle?, ) : BaseFieldDataConverter() { override fun getConvertedData(): CustomBlockchain { val mainNetwork = collectedData[CustomTokenFieldId.Network] as Blockchain @@ -30,24 +30,25 @@ sealed class CustomCurrency( val derivationPath = AddCustomTokenState.getDerivationPath( mainNetwork, derivationPathNetwork, - derivationStyle + derivationStyle, ) return CustomBlockchain(mainNetwork, derivationPath) } - override fun getIdToCollect(): List = listOf(CustomTokenFieldId.Network, CustomTokenFieldId.DerivationPath) + override fun getIdToCollect(): List = + listOf(CustomTokenFieldId.Network, CustomTokenFieldId.DerivationPath) } } class CustomToken( val token: Token, network: Blockchain, - derivationPath: DerivationPath? + derivationPath: DerivationPath?, ) : CustomCurrency(network, derivationPath) { class Converter( private val tokenId: String?, - private val derivationStyle: DerivationStyle? + private val derivationStyle: DerivationStyle?, ) : BaseFieldDataConverter() { override fun getConvertedData(): CustomToken { @@ -56,7 +57,7 @@ sealed class CustomCurrency( val derivationPath = AddCustomTokenState.getDerivationPath( mainNetwork, derivationPathNetwork, - derivationStyle + derivationStyle, ) val token = Token( 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 cc41cdb3d4..c2ae9ff018 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 @@ -31,7 +31,10 @@ sealed class AddCustomTokenAction : Action { data class OnTokenNetworkChanged(val blockchainNetwork: Field.Data) : AddCustomTokenAction() data class OnTokenNameChanged(val tokenName: Field.Data) : AddCustomTokenAction() data class OnTokenSymbolChanged(val tokenSymbol: Field.Data) : AddCustomTokenAction() - data class OnTokenDerivationPathChanged(val blockchainDerivationPath: Field.Data) : AddCustomTokenAction() + data class OnTokenDerivationPathChanged( + val blockchainDerivationPath: Field.Data, + ) : AddCustomTokenAction() + data class OnTokenDecimalsChanged(val tokenDecimals: Field.Data) : AddCustomTokenAction() object OnAddCustomTokenClicked : AddCustomTokenAction() @@ -51,7 +54,7 @@ sealed class AddCustomTokenAction : Action { data class Remove(val warnings: Set) : Warning() data class Replace( val remove: Set, - val add: Set + val add: Set, ) : Warning() } 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 b0ffab36b3..080b0b2601 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 @@ -66,6 +66,7 @@ import timber.log.Timber /** [REDACTED_AUTHOR] */ +@Suppress("LargeClass") internal class AddCustomTokenHub : BaseStoreHub("AddCustomTokenHub") { private val hubState: AddCustomTokenState @@ -79,10 +80,11 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT return storeState.copy(addCustomTokensState = newHubState) } + @Suppress("ComplexMethod") override suspend fun handleAction( action: Action, storeState: DomainState, - cancel: ValueCallback + cancel: ValueCallback, ) { if (action !is AddCustomTokenAction) return @@ -225,7 +227,7 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT val derivationField = DerivationPath.getField() derivationField.data = derivationField.data.copy( value = Blockchain.Unknown, - isUserInput = false + isUserInput = false, ) state.setField(derivationField) dispatchOnMain(UpdateForm(hubState)) @@ -344,6 +346,7 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT } } + @Suppress("MagicNumber") private suspend fun requestInfoAboutToken(contractAddress: String): List { val tangemTechServiceManager = requireNotNull(hubState.tangemTechServiceManager) dispatchOnMain(Screen.UpdateTokenFields(listOf(ContractAddress to ViewStates.TokenField(isLoading = true)))) @@ -375,7 +378,7 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT tokenId: String? = hubState.foundToken?.id, tokenContractAddress: String = ContractAddress.getFieldValue(), tokenNetworkId: String = Network.getFieldValue().toNetworkId(), - selectedDerivation: Blockchain = DerivationPath.getFieldValue() + selectedDerivation: Blockchain = DerivationPath.getFieldValue(), ): Boolean { val savedCurrencies = hubState.appSavedCurrencies ?: return false @@ -388,6 +391,7 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT val sameAddress = tokenContractAddress == wrappedCurrency.token.contractAddress val sameBlockchain = Blockchain.fromNetworkId(tokenNetworkId) == wrappedCurrency.blockchain val sameDerivationPath = derivationPath?.rawPath == wrappedCurrency.derivationPath + @Suppress("ComplexCondition") if (sameId && sameAddress && sameBlockchain && sameDerivationPath) { return true } @@ -399,7 +403,7 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT private fun isBlockchainPersistIntoAppSavedTokensList( selectedNetwork: Blockchain = Network.getFieldValue(), - selectedDerivation: Blockchain = DerivationPath.getFieldValue() + selectedDerivation: Blockchain = DerivationPath.getFieldValue(), ): Boolean { val state = hubState val savedCurrencies = state.appSavedCurrencies ?: return false @@ -419,11 +423,11 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT } private fun getDerivationPathFromSelectedBlockchain( - selectedDerivationBlockchain: Blockchain + selectedDerivationBlockchain: Blockchain, ): com.tangem.common.hdWallet.DerivationPath? = AddCustomTokenState.getDerivationPath( mainNetwork = Network.getFieldValue(), derivationNetwork = selectedDerivationBlockchain, - derivationStyle = hubState.cardDerivationStyle + derivationStyle = hubState.cardDerivationStyle, ) private suspend fun fillTokenFields(token: CoinsResponse.Coin, coinNetwork: CoinsResponse.Coin.Network) { @@ -482,7 +486,7 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT @Throws private fun throwUnAppropriateInitialization(objName: String) { throw AddCustomTokenError.UnAppropriateInitialization( - "AddCustomTokenHub", "$objName must be not NULL" + "AddCustomTokenHub", "$objName must be not NULL", ) } @@ -567,16 +571,14 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT private suspend fun AddCustomTokenError.Warning.remove() { dispatchOnMain(Warning.Remove(setOf(this))) } - - private suspend fun AddCustomTokenError.Warning.replaceBy(to: AddCustomTokenError.Warning) { - dispatchOnMain(Warning.Replace(setOf(this), setOf(to))) - } } +@Suppress("ComplexMethod") private class AddCustomTokenReducer( private val globalState: DomainGlobalState, ) : ReStoreReducer { + @Suppress("LongMethod") override fun reduceAction(action: Action, state: AddCustomTokenState): AddCustomTokenState { return when (action) { is Init.SetAddedCurrencies -> { 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 ad0be88966..8a722f7e52 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 @@ -265,7 +265,7 @@ data class AddCustomTokenState( private var builder: StringBuilder = StringBuilder() override fun convert(action: Action, stateHolder: DomainState): String? { - val action = (action as? AddCustomTokenAction) ?: return null + val action = action as? AddCustomTokenAction ?: return null val state = stateHolder.addCustomTokensState val fieldConverter = diff --git a/domain/src/main/java/com/tangem/domain/redux/DomainStore.kt b/domain/src/main/java/com/tangem/domain/redux/DomainStore.kt index 703ca18a88..8d849a2e82 100644 --- a/domain/src/main/java/com/tangem/domain/redux/DomainStore.kt +++ b/domain/src/main/java/com/tangem/domain/redux/DomainStore.kt @@ -9,8 +9,6 @@ import org.rekotlin.Store /** [REDACTED_AUTHOR] */ -private class DomainStore // for simple search - private val RE_STORE_HUBS: List> = listOf( DomainGlobalHub(), AddCustomTokenHub(), @@ -19,7 +17,7 @@ private val RE_STORE_HUBS: List> = listOf( val domainStore = Store( state = DomainState(), middleware = RE_STORE_HUBS.map { it.getMiddleware() }, - reducer = { action, state -> reduce(action, state) } + reducer = { action, state -> reduce(action, state) }, ) private fun reduce(action: Action, domainState: DomainState?): DomainState { diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt index 5f483b473a..5d32a2adc4 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt @@ -29,6 +29,7 @@ import com.tangem.core.ui.components.SmallInfoCard import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.referral.presentation.R +@Suppress("LongParameterList") @OptIn(ExperimentalComposeUiApi::class) @Composable internal fun ParticipateBottomBlock( diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt index cd1f8795b7..2432d8c1e0 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt @@ -123,6 +123,7 @@ internal class ReferralViewModel @Inject constructor( private fun ReferralData.getNetworkName(): String = getToken().networkId.replaceFirstChar(Char::uppercase) + @Suppress("MagicNumber") private fun ReferralInfo.getAddressValue(): String { check(address.length > 5) { "Invalid address" } return address.substring(startIndex = 0, endIndex = 4) + "..." + diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt index cdcc527c0e..f7ec74b40b 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt @@ -20,6 +20,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext import javax.inject.Inject +@Suppress("LongParameterList") internal class SwapRepositoryImpl @Inject constructor( private val tangemTechApi: TangemTechApi, private val oneInchApiFactory: OneInchApiFactory, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index f49cafea71..0bfe04ae49 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -25,6 +25,7 @@ import com.tangem.utils.toFormattedString import java.math.BigDecimal import javax.inject.Inject +@Suppress("LargeClass") internal class SwapInteractorImpl @Inject constructor( private val transactionManager: TransactionManager, private val userWalletManager: UserWalletManager, @@ -120,8 +121,9 @@ internal class SwapInteractorImpl @Inject constructor( "estimatedGas not found call findBestQuote" }, ) - val transactionData = - requireNotNull(cache.getApproveTransactionData()) { "getApproveTransactionData not found, call findQuotes" } + val transactionData = requireNotNull(cache.getApproveTransactionData()) { + "getApproveTransactionData not found, call findQuotes" + } val gasPrice = transactionData.gasPrice.toBigDecimalOrNull() ?: error("cannot parse gasPrice") val fee = transactionManager.calculateFee(networkId, gasPrice.toPlainString(), estimatedGas) val result = transactionManager.sendTransaction( @@ -272,6 +274,7 @@ internal class SwapInteractorImpl @Inject constructor( /** * Load quote data calls only if spend is not allowed for token contract address */ + @Suppress("LongParameterList") private suspend fun loadQuoteData( networkId: String, fromTokenAddress: String, @@ -321,6 +324,7 @@ internal class SwapInteractorImpl @Inject constructor( /** * Load swap data calls only if spend is allowed for token contract address */ + @Suppress("LongParameterList") private suspend fun loadSwapData( networkId: String, fromTokenAddress: String, @@ -362,6 +366,7 @@ internal class SwapInteractorImpl @Inject constructor( } } + @Suppress("LongParameterList") private suspend fun updateBalances( networkId: String, fromToken: Currency, @@ -448,6 +453,7 @@ internal class SwapInteractorImpl @Inject constructor( } } + @Suppress("MagicNumber") private fun increaseByPercents(percents: Int, value: Int): Int { return value * (percents / 100 + 1) } diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt index f7a2c78934..1f882537f6 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt @@ -52,6 +52,7 @@ interface SwapRepository { walletAddress: String, ): AggregatedSwapDataModel + @Suppress("LongParameterList") suspend fun prepareSwapTransaction( networkId: String, fromTokenAddress: String, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index a75d723ff7..9dad0f4277 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -49,6 +49,7 @@ import com.tangem.feature.swap.models.SwapWarning import com.tangem.feature.swap.models.TransactionCardType import com.tangem.feature.swap.presentation.R +@Suppress("LongMethod") @Composable internal fun SwapScreenContent( modifier: Modifier = Modifier, @@ -170,7 +171,11 @@ private fun MainInfo(state: SwapStateHolder) { tokenIconUrl = state.receiveCardData.tokenIconUrl, tokenCurrency = state.receiveCardData.tokenCurrency, networkIconRes = state.receiveCardData.networkIconRes, - onChangeTokenClick = if (state.receiveCardData.canSelectAnotherToken) state.onSelectTokenClick else null, + onChangeTokenClick = if (state.receiveCardData.canSelectAnotherToken) { + state.onSelectTokenClick + } else { + null + }, ) } Card( diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt index 2b2ad1c321..19d8aa0a0a 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt @@ -74,19 +74,18 @@ private fun ListOfTokens(state: SwapSelectTokenStateHolder, modifier: Modifier = itemsIndexed(items = state.tokens) { index, item -> TokenItem(token = item, onTokenClick = { state.onTokenSelected(item.id) }) - if (index != state.tokens.lastIndex) Divider( - color = TangemTheme.colors.stroke.primary, - startIndent = TangemTheme.dimens.spacing54, - ) + if (index != state.tokens.lastIndex) { + Divider( + color = TangemTheme.colors.stroke.primary, + startIndent = TangemTheme.dimens.spacing54, + ) + } } } } @Composable -private fun TokenItem( - token: TokenToSelect, - onTokenClick: (String) -> Unit, -) { +private fun TokenItem(token: TokenToSelect, onTokenClick: (String) -> Unit) { Row( modifier = Modifier .fillMaxWidth() @@ -95,18 +94,12 @@ private fun TokenItem( bottom = TangemTheme.dimens.spacing14, end = TangemTheme.dimens.spacing16, ) - .clickable( - onClick = { onTokenClick(token.id) }, - ), + .clickable { onTokenClick(token.id) }, verticalAlignment = Alignment.CenterVertically, ) { - TokenIcon(token = token) - Column( - modifier = Modifier - .align(Alignment.CenterVertically), - ) { + Column(modifier = Modifier.align(Alignment.CenterVertically)) { Text( text = token.name, style = TangemTheme.typography.subtitle1, @@ -126,7 +119,6 @@ private fun TokenItem( Spacer(modifier = Modifier.weight(1f)) - if (!token.available) { Text( text = stringResource(id = R.string.swapping_token_not_available), @@ -137,8 +129,7 @@ private fun TokenItem( Column( horizontalAlignment = Alignment.End, verticalArrangement = Arrangement.Center, - modifier = Modifier - .padding(start = TangemTheme.dimens.spacing8), + modifier = Modifier.padding(start = TangemTheme.dimens.spacing8), ) { Text( text = token.addedTokenBalanceData.amount ?: "", @@ -153,10 +144,10 @@ private fun TokenItem( ) } } - } } +@Suppress("MagicNumber") @Composable private fun TokenIcon(token: TokenToSelect) { Box( diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt index c5a0edeca4..daaa05fc22 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt @@ -60,6 +60,7 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.swap.models.TransactionCardType import com.valentinilk.shimmer.shimmer +@Suppress("LongParameterList") @Composable fun TransactionCard( modifier: Modifier = Modifier, @@ -159,6 +160,7 @@ private fun Header( } } +@Suppress("LongMethod") @Composable private fun Content( type: TransactionCardType, @@ -243,6 +245,7 @@ private fun Content( } } +@Suppress("MagicNumber") @Composable private fun AutoSizeTextField( amount: String, @@ -302,6 +305,7 @@ private fun AutoSizeTextField( } } +@Suppress("MagicNumber") @Composable fun Token( tokenIconUrl: String, @@ -320,9 +324,7 @@ fun Token( Box( modifier = Modifier - .padding( - end = TangemTheme.dimens.spacing16, - ) + .padding(end = TangemTheme.dimens.spacing16) .size(TangemTheme.dimens.size42), ) { val tokenImageModifier = Modifier @@ -345,14 +347,11 @@ fun Token( modifier = Modifier .align(Alignment.TopEnd) .size(TangemTheme.dimens.size18) - .background( - color = Color.White, - shape = CircleShape, - ), + .background(color = Color.White, shape = CircleShape), contentAlignment = Alignment.Center, ) { Image( - modifier = Modifier.padding(all = (0.5).dp), + modifier = Modifier.padding(all = 0.5.dp), painter = painterResource(id = networkIconRes), contentDescription = null, ) @@ -368,8 +367,7 @@ fun Token( maxLines = 1, style = TangemTheme.typography.subtitle2, textAlign = TextAlign.Center, - modifier = Modifier - .defaultMinSize(minWidth = TangemTheme.dimens.size80), + modifier = Modifier.defaultMinSize(minWidth = TangemTheme.dimens.size80), ) } } diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt index ca92f3faf6..712634f1fb 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt @@ -7,6 +7,7 @@ import java.math.BigDecimal interface TransactionManager { + @Suppress("LongParameterList") @Throws(IllegalStateException::class) suspend fun sendTransaction( networkId: String, From 43ee7319ee6f8a1633e491d64eec2a2d291dd299 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 18 Jan 2023 10:42:54 +0300 Subject: [PATCH 084/145] Updated on 2026-08-14 --- .../userWalletList/UserWalletsListManager.kt | 66 +++++++++++++++++++ .../WalletCurrenciesManager.kt | 45 +++++++++++++ .../walletStores/WalletStoresManager.kt | 52 +++++++++++++++ 3 files changed, 163 insertions(+) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt index fd85a066ba..91d3b8dd91 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt @@ -6,16 +6,62 @@ import com.tangem.tap.domain.model.UserWallet import kotlinx.coroutines.flow.Flow interface UserWalletsListManager { + /** + * [Flow] with all saved [UserWallet]s updates + * */ val userWallets: Flow> + + /** + * [Flow] with selected [UserWallet] updates + * */ val selectedUserWallet: Flow + + /** + * Selected [UserWallet] + * */ val selectedUserWalletSync: UserWallet? + + /** + * Indicates that all [UserWallet]s is unlocked + * + * @see [isLockedSync] + * @see [UserWallet.isLocked] + * */ val isLocked: Flow + + /** + * Indicates that all [UserWallet]s is unlocked + * + * Sync version + * + * @see [isLocked] + * @see [UserWallet.isLocked] + * */ val isLockedSync: Boolean + val hasSavedUserWallets: Boolean + /** + * Receive saved [UserWallet]s, populate [userWallets] flow with it and set [isLocked] as false. + * Require biometric user authorization + * + * @return [CompletionResult] of operation, with selected [UserWallet] + * or null if there is no selected [UserWallet] + * */ suspend fun unlockWithBiometry(): CompletionResult + + /** + * Remove [UserWallet]s from [userWallets] and set [isLocked] as true + * */ fun lock() + /** + * Set [UserWallet] with provided [UserWalletId] as selected + * + * @param userWalletId [UserWalletId] of [UserWallet] which must be selected + * + * @return [CompletionResult] of operation with selected [UserWallet] + * */ suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult /** @@ -38,10 +84,30 @@ interface UserWalletsListManager { * */ suspend fun update(userWalletId: UserWalletId, update: (UserWallet) -> UserWallet): CompletionResult + /** + * Delete saved [UserWallet]s with provided [UserWalletId]s + * + * @param userWalletIds [UserWalletId]s of [UserWallet]s which must be deleted + * + * @return [CompletionResult] of operation + * */ suspend fun delete(userWalletIds: List): CompletionResult + + /** + * Clear all saved [UserWallet]s and set [isLocked] as true + * + * @return [CompletionResult] of operation + * */ suspend fun clear(): CompletionResult + /** + * Get [UserWallet] with provided [UserWalletId] + * May terminate with [NoSuchElementException] if [UserWallet] is not found + * + * @return [CompletionResult] of operation with found [UserWallet] + * */ suspend fun get(userWalletId: UserWalletId): CompletionResult + // For provider companion object } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt index fb77a29584..2738438ac1 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt @@ -5,25 +5,70 @@ import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.features.wallet.models.Currency interface WalletCurrenciesManager { + /** + * Update [UserWallet] currencies with same blockchain as provided [Currency] blockchain + * [UserWallet] currencies updates can be observed + * with [com.tangem.tap.domain.walletStores.WalletStoresManager.getAll] + * or [com.tangem.tap.domain.walletStores.WalletStoresManager.get] + * + * @param userWallet [UserWallet] which currencies will be updated + * @param currency [Currency] to find other currencies to update + * + * @return [CompletionResult] of operation + * */ suspend fun update( userWallet: UserWallet, currency: Currency, ): CompletionResult + /** + * Add list of [Currency] to [UserWallet]. + * [UserWallet] currencies updates can be observed + * with [com.tangem.tap.domain.walletStores.WalletStoresManager.getAll] + * or [com.tangem.tap.domain.walletStores.WalletStoresManager.get] + * + * @param userWallet [UserWallet] to add currencies + * @param currenciesToAdd list of [Currency] to add [UserWallet] + * + * @return [CompletionResult] of operation + * */ suspend fun addCurrencies( userWallet: UserWallet, currenciesToAdd: List, ): CompletionResult + /** + * Remove [Currency] from [UserWallet] + * [UserWallet] currencies updates can be observed + * with [com.tangem.tap.domain.walletStores.WalletStoresManager.getAll] + * or [com.tangem.tap.domain.walletStores.WalletStoresManager.get] + * + * @param userWallet [UserWallet] which currency will be removed + * @param currencyToRemove [Currency] to remove from [UserWallet] + * + * @return [CompletionResult] of operation + * */ suspend fun removeCurrency( userWallet: UserWallet, currencyToRemove: Currency, ): CompletionResult + /** + * Remove list of [Currency] from [UserWallet] + * [UserWallet] currencies updates can be observed + * with [com.tangem.tap.domain.walletStores.WalletStoresManager.getAll] + * or [com.tangem.tap.domain.walletStores.WalletStoresManager.get] + * + * @param userWallet [UserWallet] which currency will be removed + * @param currenciesToRemove list of [Currency] to remove from [UserWallet] + * + * @return [CompletionResult] of operation + * */ suspend fun removeCurrencies( userWallet: UserWallet, currenciesToRemove: List, ): CompletionResult + // For provider companion object } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt index e834cf0541..ed773b522d 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt @@ -7,25 +7,77 @@ import com.tangem.tap.domain.model.WalletStoreModel import kotlinx.coroutines.flow.Flow interface WalletStoresManager { + /** + * Get all [WalletStoreModel]s updates + * + * @return [Flow] with map of [WalletStoreModel] list assigned by [UserWalletId] + * */ fun getAll(): Flow>> + + /** + * Get [WalletStoreModel]s updates which associated with provided [UserWalletId] + * + * @param userWalletId [UserWalletId] of user wallet + * + * @return [Flow] with [WalletStoreModel] list + * */ fun get(userWalletId: UserWalletId): Flow> + /** + * Delete [WalletStoreModel]s associated with provided [UserWalletId]s + * + * @param userWalletsIds [UserWalletId] list + * + * @return [CompletionResult] of operation + * */ suspend fun delete(userWalletsIds: List): CompletionResult + + /** + * Clear all [WalletStoreModel]s + * + * @return [CompletionResult] of operation + * */ suspend fun clear(): CompletionResult + /** + * Fetch wallet stores associated with provided [UserWallet]. Fetched [WalletStoreModel]s updates can be observed + * with [get] and [getAll] methods + * + * @param userWallet [UserWallet] to fetch [WalletStoreModel]s + + * + * @return [CompletionResult] of operation + * */ suspend fun fetch( userWallet: UserWallet, refresh: Boolean = false, ): CompletionResult + /** + * Fetch wallet stores associated with provided [UserWallet]s. Fetched [WalletStoreModel]s updates can be observed + * with [get] and [getAll] methods + * + * @param userWallets [UserWallet]s list to fetch [WalletStoreModel]s + + * + * @return [CompletionResult] of operation + * */ suspend fun fetch( userWallets: List, refresh: Boolean = false, ): CompletionResult + /** + * Update [WalletStoreModel]s amounts associated with provided [UserWallet]s + * + * @param userWallets [UserWallet]s list to update [WalletStoreModel]s amounts + * + * @return [CompletionResult] of operation + * */ suspend fun updateAmounts( userWallets: List, ): CompletionResult + // For provider companion object } \ No newline at end of file From 5f299485ba9c887262ce0a816c8f1dca344c4d7f Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 16 Jan 2023 20:39:12 +0300 Subject: [PATCH 085/145] Updated on 2026-08-14 --- app/src/main/res/color/btn_tap_text_color.xml | 7 -- .../main/res/color/selector_btn_primary.xml | 5 ++ .../main/res/color/selector_btn_secondary.xml | 5 ++ .../res/color/selector_btn_text_primary.xml | 5 ++ .../res/color/selector_btn_text_secondary.xml | 5 ++ app/src/main/res/values/dimens.xml | 1 + app/src/main/res/values/styles.xml | 82 +++++++------------ 7 files changed, 49 insertions(+), 61 deletions(-) delete mode 100644 app/src/main/res/color/btn_tap_text_color.xml create mode 100644 app/src/main/res/color/selector_btn_primary.xml create mode 100644 app/src/main/res/color/selector_btn_secondary.xml create mode 100644 app/src/main/res/color/selector_btn_text_primary.xml create mode 100644 app/src/main/res/color/selector_btn_text_secondary.xml diff --git a/app/src/main/res/color/btn_tap_text_color.xml b/app/src/main/res/color/btn_tap_text_color.xml deleted file mode 100644 index 01e0752f01..0000000000 --- a/app/src/main/res/color/btn_tap_text_color.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/color/selector_btn_primary.xml b/app/src/main/res/color/selector_btn_primary.xml new file mode 100644 index 0000000000..6e8aab4c79 --- /dev/null +++ b/app/src/main/res/color/selector_btn_primary.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/color/selector_btn_secondary.xml b/app/src/main/res/color/selector_btn_secondary.xml new file mode 100644 index 0000000000..47a88e3645 --- /dev/null +++ b/app/src/main/res/color/selector_btn_secondary.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/color/selector_btn_text_primary.xml b/app/src/main/res/color/selector_btn_text_primary.xml new file mode 100644 index 0000000000..70f6aded83 --- /dev/null +++ b/app/src/main/res/color/selector_btn_text_primary.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/color/selector_btn_text_secondary.xml b/app/src/main/res/color/selector_btn_text_secondary.xml new file mode 100644 index 0000000000..41aa7c843c --- /dev/null +++ b/app/src/main/res/color/selector_btn_text_secondary.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 63924e74c0..fdeae808a2 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -31,6 +31,7 @@ 4dp 8dp + 12dp 44dp 32sp diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 2bd13d5482..a5774803a5 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -41,69 +41,43 @@ @color/accent - + + + + + + - - - - - - - - - - - - - - + +